Code Monkey home page Code Monkey logo

scout's People

Contributors

abridoux avatar damien-rivet avatar homebysix avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar

scout's Issues

Should return non-zero error code on failure

unix tools should return a non-zero error code on failure. For example, when trying to read a non-existent key, scout should return non-zero.

This allows you to do:

if value=$(scout read 'invalidKey' -I file.plist); then
   # key exists and value is something useful
else
   # key did not exist and value is empty
fi

General negative indexes for an array

Describe your idea briefly.
Allow to target the last nth element of an array, rather than only the last one.

Describe the solution you'd like
Something like scout read array[-2] would allow to read the element at index 3 if the array count is 5.

Remarks

  • The negative index can no longer be used to set a value to a last index in an array. As this feature is already offered with the add command, it will be removed.
  • To append a value rather than insert it at the last valid index, the add command should use the count | [#] element to specify that the value should be added and not inserted.

Enable auto-detection whether `scout` runs interactively or not

scout --no-color should auto-activate whenever scout is ran in an interactive shell session.

Steps to reproduce:

# Without removing ANSI control characters down the pipework:

λ cat Bookmarks.plist | scout | plist-to-json /dev/stdin | jq '.[].Children | .[]'

parse error: Invalid numeric literal at line 1, column 11

Manually, everything works as expected:

λ cat Bookmarks.plist| scout --no-color | plist-to-json /dev/stdin | jq '.[].Children | .[]' | head -n 5

{
  "Title": "History",
  "WebBookmarkIdentifier": "History",
  "WebBookmarkType": "WebBookmarkTypeProxy",
  "WebBookmarkUUID": "6DC2987D-AD0D-420E-B9AF-01EE94322C4B"

Get dictionary keys

Get a dictionary's keys in an array.
Add a new PathElement to transform a dictionary into an array holding its keys

Array slicing improvements

  • Specify several slices like [lower1:upper1;lower2:upper2]
  • Specify single indexes among the slice like [lower1:upper1;singleIndex;lower2:upper2]

How to duplicate an item

I'd like to be able to add an new entry which duplicates all the properties of an existing entry. For example, using people.json, I'd like to create Tom's evil twin, evilTom.

I can't pass a reference to an existing entry:
scout add -i /tmp/people.json "people.evilTom=people.Tom"
merely adds the string "people.Tom" to evilTom.

Capturing the output of Tom to a variable first:
tom=$(scout read -i /tmp/people.json "people.Tom")
is fine, but then adding it to a new entry
scout add -i /tmp/people.json "people.evilTom=$tom"
simply adds it as a text string, meaning it can no longer be parsed.

It seems like the only option is to read each individual property (which requires knowledge of the datatype of that property) and then write that individual property to the new location. I feel like this is a task which ought to be much easier in Scout, but would actually be easier with a text parser. Am I missing something?

Dictionary keys filter

Let the user filter a dictionary keys with a regular expression by enclosing it with sharp signs. The read and delete commands could then be applied. The read subcommand should output the result as a list.

Examples

scout "dict.#[0-9]*#" to find all the keys in dict which begin by a number
scout set "dict.#*id*#=123ABC" to set all the keys which contain "id" in dict to "123ABC"

Won't build on 10.14.6

I downloaded the source tarball for scout 1.3.1, unzipped it, cd'd into it, and typed make.

After a while, it fails with oodles of errors:

/Users/chdiza/Downloads/scout-1.3.1/Sources/ScoutCLT/Read/ReadCommand.swift:28:19: error: type 'Any' has no member 'long'
    @Flag(name: [.long], inversion: .prefixedNo, help: "Colorise the ouput")
                 ~^~~~
/Users/chdiza/Downloads/scout-1.3.1/Sources/ScoutCLT/Set/SetCommand.swift:16:6: error: incorrect argument label in call (have 'wrappedValue:help:', expected 'default:help:')
    @Argument(help: PathAndValue.help)
     ^
/Users/chdiza/Downloads/scout-1.3.1/Sources/ScoutCLT/Set/SetCommand.swift:16:6: error: initializer 'init(default:help:)' requires that '[PathAndValue]' conform to 'ExpressibleByArgument'
    @Argument(help: PathAndValue.help)
     ^
ArgumentParser.Argument<τ_0_0>:4:12: note: where 'T' = '[PathAndValue]'
    public init<T>(default initial: T?, help: ArgumentParser.ArgumentHelp? = nil) where Value == T?, T : ArgumentParser.ExpressibleByArgument
           ^
/Users/chdiza/Downloads/scout-1.3.1/Sources/ScoutCLT/Set/SetCommand.swift:28:19: error: type 'Any' has no member 'short'
    @Flag(name: [.short, .long], inversion: .prefixedNo, help: "Output the modified data")
                 ~^~~~~
/Users/chdiza/Downloads/scout-1.3.1/Sources/ScoutCLT/Set/SetCommand.swift:28:27: error: type 'Any' has no member 'long'
    @Flag(name: [.short, .long], inversion: .prefixedNo, help: "Output the modified data")
                         ~^~~~
/Users/chdiza/Downloads/scout-1.3.1/Sources/ScoutCLT/Set/SetCommand.swift:31:19: error: type 'Any' has no member 'long'
    @Flag(name: [.long], inversion: .prefixedNo, help: "Colorise the ouput")
                 ~^~~~
/Users/chdiza/Downloads/scout-1.3.1/Sources/ScoutCLT/Delete/DeleteCommand.swift:21:6: error: incorrect argument label in call (have 'wrappedValue:help:', expected 'default:help:')
    @Argument(help: "Paths to indicate the keys to be deleted")
     ^
/Users/chdiza/Downloads/scout-1.3.1/Sources/ScoutCLT/Delete/DeleteCommand.swift:21:6: error: initializer 'init(default:help:)' requires that '[Path]' conform to 'ExpressibleByArgument'
    @Argument(help: "Paths to indicate the keys to be deleted")
     ^
ArgumentParser.Argument<τ_0_0>:4:12: note: where 'T' = '[Path]'
    public init<T>(default initial: T?, help: ArgumentParser.ArgumentHelp? = nil) where Value == T?, T : ArgumentParser.ExpressibleByArgument
           ^
/Users/chdiza/Downloads/scout-1.3.1/Sources/ScoutCLT/Delete/DeleteCommand.swift:33:19: error: type 'Any' has no member 'short'
    @Flag(name: [.short, .long], inversion: .prefixedNo, help: "Output the modified data")
                 ~^~~~~
/Users/chdiza/Downloads/scout-1.3.1/Sources/ScoutCLT/Delete/DeleteCommand.swift:33:27: error: type 'Any' has no member 'long'
    @Flag(name: [.short, .long], inversion: .prefixedNo, help: "Output the modified data")
                         ~^~~~
/Users/chdiza/Downloads/scout-1.3.1/Sources/ScoutCLT/Delete/DeleteCommand.swift:36:19: error: type 'Any' has no member 'long'
    @Flag(name: [.long], inversion: .prefixedNo, help: "Colorise the ouput")
                 ~^~~~
/Users/chdiza/Downloads/scout-1.3.1/Sources/ScoutCLT/Add/AddCommand.swift:16:6: error: incorrect argument label in call (have 'wrappedValue:help:', expected 'default:help:')
    @Argument(help: PathAndValue.help)
     ^
/Users/chdiza/Downloads/scout-1.3.1/Sources/ScoutCLT/Add/AddCommand.swift:16:6: error: initializer 'init(default:help:)' requires that '[PathAndValue]' conform to 'ExpressibleByArgument'
    @Argument(help: PathAndValue.help)
     ^
ArgumentParser.Argument<τ_0_0>:4:12: note: where 'T' = '[PathAndValue]'
    public init<T>(default initial: T?, help: ArgumentParser.ArgumentHelp? = nil) where Value == T?, T : ArgumentParser.ExpressibleByArgument
           ^
/Users/chdiza/Downloads/scout-1.3.1/Sources/ScoutCLT/Add/AddCommand.swift:28:19: error: type 'Any' has no member 'short'
    @Flag(name: [.short, .long], inversion: .prefixedNo, help: "Output the modified data")
                 ~^~~~~
/Users/chdiza/Downloads/scout-1.3.1/Sources/ScoutCLT/Add/AddCommand.swift:28:27: error: type 'Any' has no member 'long'
    @Flag(name: [.short, .long], inversion: .prefixedNo, help: "Output the modified data")
                         ~^~~~
/Users/chdiza/Downloads/scout-1.3.1/Sources/ScoutCLT/Add/AddCommand.swift:31:19: error: type 'Any' has no member 'long'
    @Flag(name: [.long], inversion: .prefixedNo, help: "Colorise the ouput")
                 ~^~~~
/Users/chdiza/Downloads/scout-1.3.1/Sources/ScoutCLT/Read/ReadCommand.swift:11:8: error: type 'ReadCommand' does not conform to protocol 'Decodable'
struct ReadCommand: ParsableCommand {
       ^
Swift.Decodable:2:5: note: protocol requires initializer 'init(from:)' with type 'Decodable'
    init(from decoder: Decoder) throws
    ^
/Users/chdiza/Downloads/scout-1.3.1/Sources/ScoutCLT/Read/ReadCommand.swift:23:9: note: cannot automatically synthesize 'Decodable' because 'Path?' does not conform to 'Decodable'
    var readingPath: Path?
        ^
/Users/chdiza/Downloads/scout-1.3.1/Sources/ScoutCLT/Read/ReadCommand.swift:29:9: note: cannot automatically synthesize 'Decodable' because '<<error type>>' does not conform to 'Decodable'
    var color = true
        ^
/Users/chdiza/Downloads/scout-1.3.1/Sources/ScoutCLT/Set/SetCommand.swift:10:8: error: type 'SetCommand' does not conform to protocol 'Decodable'
struct SetCommand: ParsableCommand {
       ^
Swift.Decodable:2:5: note: protocol requires initializer 'init(from:)' with type 'Decodable'
    init(from decoder: Decoder) throws
    ^
/Users/chdiza/Downloads/scout-1.3.1/Sources/ScoutCLT/Set/SetCommand.swift:17:9: note: cannot automatically synthesize 'Decodable' because '<<error type>>' does not conform to 'Decodable'
    var pathsAndValues = [PathAndValue]()
        ^
/Users/chdiza/Downloads/scout-1.3.1/Sources/ScoutCLT/Set/SetCommand.swift:29:9: note: cannot automatically synthesize 'Decodable' because '<<error type>>' does not conform to 'Decodable'
    var verbose = false
        ^
/Users/chdiza/Downloads/scout-1.3.1/Sources/ScoutCLT/Set/SetCommand.swift:32:9: note: cannot automatically synthesize 'Decodable' because '<<error type>>' does not conform to 'Decodable'
    var color = true
        ^
/Users/chdiza/Downloads/scout-1.3.1/Sources/ScoutCLT/Delete/DeleteCommand.swift:10:8: error: type 'DeleteCommand' does not conform to protocol 'Decodable'
struct DeleteCommand: ParsableCommand {
       ^
Swift.Decodable:2:5: note: protocol requires initializer 'init(from:)' with type 'Decodable'
    init(from decoder: Decoder) throws
    ^
/Users/chdiza/Downloads/scout-1.3.1/Sources/ScoutCLT/Delete/DeleteCommand.swift:22:9: note: cannot automatically synthesize 'Decodable' because '<<error type>>' does not conform to 'Decodable'
    var readingPaths = [Path]()
        ^
/Users/chdiza/Downloads/scout-1.3.1/Sources/ScoutCLT/Delete/DeleteCommand.swift:34:9: note: cannot automatically synthesize 'Decodable' because '<<error type>>' does not conform to 'Decodable'
    var verbose = false
        ^
/Users/chdiza/Downloads/scout-1.3.1/Sources/ScoutCLT/Delete/DeleteCommand.swift:37:9: note: cannot automatically synthesize 'Decodable' because '<<error type>>' does not conform to 'Decodable'
    var color = true
        ^
/Users/chdiza/Downloads/scout-1.3.1/Sources/ScoutCLT/Add/AddCommand.swift:10:8: error: type 'AddCommand' does not conform to protocol 'Decodable'
struct AddCommand: ParsableCommand {
       ^
Swift.Decodable:2:5: note: protocol requires initializer 'init(from:)' with type 'Decodable'
    init(from decoder: Decoder) throws
    ^
/Users/chdiza/Downloads/scout-1.3.1/Sources/ScoutCLT/Add/AddCommand.swift:17:9: note: cannot automatically synthesize 'Decodable' because '<<error type>>' does not conform to 'Decodable'
    var pathsAndValues = [PathAndValue]()
        ^
/Users/chdiza/Downloads/scout-1.3.1/Sources/ScoutCLT/Add/AddCommand.swift:29:9: note: cannot automatically synthesize 'Decodable' because '<<error type>>' does not conform to 'Decodable'
    var verbose = false
        ^
/Users/chdiza/Downloads/scout-1.3.1/Sources/ScoutCLT/Add/AddCommand.swift:32:9: note: cannot automatically synthesize 'Decodable' because '<<error type>>' does not conform to 'Decodable'
    var color = true
        ^
/Users/chdiza/Downloads/scout-1.3.1/Sources/ScoutCLT/Main/DocCommand.swift:109:8: error: type 'DocCommand' does not conform to protocol 'Decodable'
struct DocCommand: ParsableCommand {
       ^
Swift.Decodable:2:5: note: protocol requires initializer 'init(from:)' with type 'Decodable'
    init(from decoder: Decoder) throws
    ^
/Users/chdiza/Downloads/scout-1.3.1/Sources/ScoutCLT/Main/DocCommand.swift:115:9: note: cannot automatically synthesize 'Decodable' because 'Command?' does not conform to 'Decodable'
    var command: Command?
        ^

make: *** [install] Error 1

Array slicing. Specify lower and upper indexes to slice an array.

Let the user specify a lower and upper bounds of the elements to display.
Possible syntax: array[lower:upper], array[lower:] to target all indexes from min and array[:upper] to target all index up to max, and array[:] to target all the elements in the array. The CRUD subcommand could then be applied. The read subcommand should output a list.

Examples

Given the following JSON

{
  "names": [
    "Thomas",
    "Jim",
    "Enzo",
    "Duke"
  ],
  "devices": [
    {
      "Id": "ABC",
      "Registered": true
    },
    {
      "Id": "123",
      "Registered": false
    },
    {
      "Id": "456",
      "Registered": false
    }
  ]
}

scout names[1:2] should output Jim\nEnzo
scout delete names[1:]should delete the names Jim, Enzo and Duke

[CLT] Add a flag or path marker to return the number of elements in an array

Given the Sample Json it would be nice if there was a special key path to get the number of items in an array. For example:

scout "people[#]" should output 2
scout "people.Arnaud.hobbies[#] should output 3

Alternative syntax could be people.@count

This would allow to write shell scripts that loop over elements in a plist or json array:

mountedImageCount=$(hdiutil info -plist | scout "images[#]")
if [[ $mountedImageCount -gt 0 ]]; then
  for ( index=0, index<$mountedImageCount, index++ ); do
    hdiutil info -plist | scout "images[$index].image-path"
  done
fi

Filter data depending on the value

Allow to filter a group of values depending on the value of a certain key or if a certain key regardless of its value.

For example, with the Json

{
  "people" : {
    "Tom" : {
      "age" : 68,
      "hobbies" : [
        "cooking",
        "guitar"
      ],
      "height" : 175
    },
    "Suzanne" : {
      "job" : "actress",
      "movies" : [
        {
          "title" : "Tomorrow is so far",
          "awards" : "Best speech for a silent movie"
        },
        {
          "title" : "Yesterday will never go",
          "awards" : "Best title"
        },
        {
          "title" : "What about today?"
        }
      ]
    },
    "Robert" : {
      "age" : 23,
      "hobbies" : [
        "video games",
        "party",
        "tennis"
      ],
      "running_records" : [
        [
          10,
          12,
          9,
          10
        ],
        [
          9,
          12,
          11
        ]
      ],
      "height" : 181
    }
  }
}

scout filter "people.&.age<70" should output

{
  "people" : {
    "Tom" : {
      "age" : 68,
      "hobbies" : [
        "cooking",
        "guitar"
      ],
      "height" : 175
    },
    "Robert" : {
      "age" : 23,
      "hobbies" : [
        "video games",
        "party",
        "tennis"
      ],
      "running_records" : [
        [
          10,
          12,
          9,
          10
        ],
        [
          9,
          12,
          11
        ]
      ],
      "height" : 181
    }
  }
}

[CLT] Auto-complete paths

When a file path is already present in the given arguments, it's is possible to parse the file to offer the best key matching the last one in the current path.

One thing though is that this completion will add a space after the path which might be painful if the path is not complete as the user will have to erase the added space to keep adding elements in the path.

Feature request: "flatten" data and output readable dot-notation data

JSON"dot notation" "associative array"

  • As a reference, have a look at gron over at tomnomnom/gron.
  • I have a feature request over there here, but I realize my example might be a little confusing.
  • I'll try to make a shorter and more concise example of the use case.

Given the following input JSON

Exerpt snapshot of my Home Assistant instance, via hass-cli:

[
  {
    "entity_id": "person.storm",
    "state": "home",
    "attributes": {
      "editable": true,
      "id": "0a71abd732aa4c37aaa4c77ec4a0e592",
      "latitude": 10.65601134689769,
      "longitude": 10.65601134689769,
      "gps_accuracy": 65,
      "source": "device_tracker.sfonx",
      "user_id": "6077e1d60aaaa9103d88b9885ea3a",
      "friendly_name": "STORM",
      "entity_picture": "/api/image/serve/4750daaaa6c6587d6e751bc49bd/512x512"
    },
    "last_changed": "2020-11-15T00:28:41.896545+00:00",
    "last_updated": "2020-11-16T12:31:30.635576+00:00",
    "context": {
      "id": "362a2fd22e4e415054af37b908c75fc4",
      "parent_id": null,
      "user_id": null
    }
  },
  {
    "entity_id": "sun.sun",
    "state": "below_horizon",
    "attributes": {
      "next_dawn": "2020-11-17T06:28:15+00:00",
      "next_dusk": "2020-11-17T15:36:21+00:00",
      "next_midnight": "2020-11-16T23:02:25+00:00",
      "next_noon": "2020-11-17T11:02:18+00:00",
      "next_rising": "2020-11-17T07:16:58+00:00",
      "next_setting": "2020-11-17T14:47:38+00:00",
      "elevation": -20.86,
      "azimuth": 267.49,
      "rising": false,
      "friendly_name": "SUN"
    },
    "last_changed": "2020-11-16T14:49:40.025488+00:00",
    "last_updated": "2020-11-16T17:37:44.031768+00:00",
    "context": {
      "id": "6298c72b78183a753678e76825d6375e",
      "parent_id": null,
      "user_id": null
    }
  },
  {
    "entity_id": "entity_controller.motion_kitchen",
    "state": "blocked",
    "attributes": {
      "sensor_type": "duration",
      "mode": "night",
      "delay": "600s",
      "last_triggered_by": "binary_sensor.pr_kitchen",
      "blocked_at": "2020-11-16T18:50:21.038561",
      "blocked_by": "light.stove",
      "friendly_name": "motion_kitchen",
      "icon": "mdi:close-circle"
    }
  },
  {
    "entity_id": "light.bedroom_mini",
    "state": "off",
    "attributes": {
      "is_deconz_group": false,
      "friendly_name": "Mini sov",
      "supported_features": 41
    },
    "last_changed": "2020-11-16T12:00:46.878946+00:00",
    "last_updated": "2020-11-16T12:00:46.878946+00:00",
    "context": {
      "id": "a14e69bf8b96e4ae847d114c2c7c3384",
      "parent_id": null,
      "user_id": null
    }
  }
]

As you see, the structure of the data is completely deterministic, as expected.

Stuffing it through gron, since it is an array, gron yields:

json = [];
json[0] = {};
json[0].attributes = {};
json[0].attributes.editable = true;
json[0].attributes.entity_picture = "/api/image/serve/4750d81a3c4b566c6587d6e751bc49bd/512x512";
json[0].attributes.friendly_name = "STORM";
json[0].attributes.gps_accuracy = 65;
json[0].attributes.id = "0a71abd732aa4c37aaa4c77ec4a0e592";
json[0].attributes.latitude = 10.65601134689769;
json[0].attributes.longitude = 10.65601134689769;
json[0].attributes.source = "device_tracker.sfonx";
json[0].attributes.user_id = "6077e1d6074c4c8a9103d88b9885ea3a";
json[0].context = {};
json[0].context.id = "362a2fd22e4e415054af37b908c75fc4";
json[0].context.parent_id = null;
json[0].context.user_id = null;
json[0].entity_id = "person.storm";
json[0].last_changed = "2020-11-15T00:28:41.896545+00:00";
json[0].last_updated = "2020-11-16T12:31:30.635576+00:00";
json[0].state = "home";
json[1] = {};
json[1].attributes = {};
json[1].attributes.azimuth = 267.49;
json[1].attributes.elevation = -20.86;
json[1].attributes.friendly_name = "SUN";
json[1].attributes.next_dawn = "2020-11-17T06:28:15+00:00";
json[1].attributes.next_dusk = "2020-11-17T15:36:21+00:00";
json[1].attributes.next_midnight = "2020-11-16T23:02:25+00:00";
json[1].attributes.next_noon = "2020-11-17T11:02:18+00:00";
json[1].attributes.next_rising = "2020-11-17T07:16:58+00:00";
json[1].attributes.next_setting = "2020-11-17T14:47:38+00:00";
json[1].attributes.rising = false;
json[1].context = {};
json[1].context.id = "6298c72b78183a753678e76825d6375e";
json[1].context.parent_id = null;
json[1].context.user_id = null;
json[1].entity_id = "sun.sun";
json[1].last_changed = "2020-11-16T14:49:40.025488+00:00";
json[1].last_updated = "2020-11-16T17:37:44.031768+00:00";
json[1].state = "below_horizon";
json[2] = {};
json[2].attributes = {};
json[2].attributes.blocked_at = "2020-11-16T18:50:21.038561";
json[2].attributes.blocked_by = "light.stove";
json[2].attributes.delay = "600s";
json[2].attributes.friendly_name = "motion_kitchen";
json[2].attributes.icon = "mdi:close-circle";
json[2].attributes.last_triggered_by = "binary_sensor.pr_kitchen";
json[2].attributes.mode = "night";
json[2].attributes.sensor_type = "duration";
json[2].entity_id = "entity_controller.motion_kitchen";
json[2].state = "blocked";
json[3] = {};
json[3].attributes = {};
json[3].attributes.friendly_name = "Mini sov";
json[3].attributes.is_deconz_group = false;
json[3].attributes.supported_features = 41;
json[3].context = {};
json[3].context.id = "a14e69bf8b96e4ae847d114c2c7c3384";
json[3].context.parent_id = null;
json[3].context.user_id = null;
json[3].entity_id = "light.bedroom_mini";
json[3].last_changed = "2020-11-16T12:00:46.878946+00:00";
json[3].last_updated = "2020-11-16T12:00:46.878946+00:00";
json[3].state = "off";

The json[0] and json[1] unfortunately makes this useless in shell scripting!

Essentially, ultimately, what we're looking for is output like this:

person.storm = "home";
person.storm.attributes.editable = true;
person.storm.attributes.entity_picture = "/api/image/serve/4750d81a3c4b566c6587d6e751bc49bd/512x512";
person.storm.attributes.friendly_name = "STORM";
person.storm.attributes.gps_accuracy = 65;
person.storm.attributes.id = "0a71abd732aa4c37aaa4c77ec4a0e592";
person.storm.attributes.latitude = 10.65601134689769;
person.storm.attributes.longitude = 10.65601134689769;
person.storm.attributes.source = "device_tracker.sfonx";
person.storm.attributes.user_id = "6077e1d6074c4c8a9103d88b9885ea3a";
person.storm.context.id = "362a2fd22e4e415054af37b908c75fc4";
person.storm.context.parent_id = null;
person.storm.context.user_id = null;
person.storm.last_changed = "2020-11-15T00:28:41.896545+00:00";
person.storm.last_updated = "2020-11-16T12:31:30.635576+00:00";
sun.sun = "below_horizon";
sun.sun.attributes.azimuth = 267.49;
sun.sun.attributes.elevation = -20.86;
sun.sun.attributes.friendly_name = "SUN";
sun.sun.attributes.next_dawn = "2020-11-17T06:28:15+00:00";
sun.sun.attributes.next_dusk = "2020-11-17T15:36:21+00:00";
sun.sun.attributes.next_midnight = "2020-11-16T23:02:25+00:00";
sun.sun.attributes.next_noon = "2020-11-17T11:02:18+00:00";
sun.sun.attributes.next_rising = "2020-11-17T07:16:58+00:00";
sun.sun.attributes.next_setting = "2020-11-17T14:47:38+00:00";
sun.sun.attributes.rising = false;
sun.sun.context.id = "6298c72b78183a753678e76825d6375e";
sun.sun.context.parent_id = null;
sun.sun.context.user_id = null;
sun.sun.last_changed = "2020-11-16T14:49:40.025488+00:00";
sun.sun.last_updated = "2020-11-16T17:37:44.031768+00:00";
sun.sun.state = "below_horizon";
entity_controller.motion_kitchen = "blocked";
entity_controller.motion_kitchen.attributes.blocked_at = "2020-11-16T18:50:21.038561";
entity_controller.motion_kitchen.attributes.blocked_by = "light.stove";
entity_controller.motion_kitchen.attributes.delay = "600s";
entity_controller.motion_kitchen.attributes.friendly_name = "motion_kitchen";
entity_controller.motion_kitchen.attributes.icon = "mdi:close-circle";
entity_controller.motion_kitchen.attributes.last_triggered_by = "binary_sensor.pr_kitchen";
entity_controller.motion_kitchen.attributes.mode = "night";
entity_controller.motion_kitchen.attributes.sensor_type = "duration";
entity_controller.motion_kitchen.state = "blocked";
light.bedroom_mini = "off";
light.bedroom_mini.attributes.friendly_name = "Mini sov";
light.bedroom_mini.attributes.is_deconz_group = false;
light.bedroom_mini.attributes.supported_features = 41;
light.bedroom_mini.context.id = "a14e69bf8b96e4ae847d114c2c7c3384";
light.bedroom_mini.context.parent_id = null;
light.bedroom_mini.context.user_id = null;
light.bedroom_mini.entity_id = "light.bedroom_mini";
light.bedroom_mini.last_changed = "2020-11-16T12:00:46.878946+00:00";
light.bedroom_mini.last_updated = "2020-11-16T12:00:46.878946+00:00";

The important change is renaming the key json[n] to the value of json[n].entity_id, which (in this case) is guaranteed to be unique as it is, well, the "entity_id" itself.

If the lone json[n] object itself can get the value of json[n].state, 99% of the job is done.

If an attempt can be made to flatten the nested arrays, 100% of the job is done. This is the exact same operation, just done recursively! So, going from this... :

json[0] = {};
(...)
json[0].state = "home";
json[0].entity_id = "person.storm";
json[0].last_changed = "2020-11-15T00:28:41.896545+00:00";

... to this:

person.storm = "home"
(...)
# perhaps keep this: person.storm.state = "home";
person.storm.last_changed = "2020-11-15T00:28:41.896545+00:00";

The tool somehow needs to know it is getting a (nested) array, and where to get the name of the index.

E.g.:

scout --dotnotationableizewith="entity_id" --toplevelvaluesfrom="state" myjson.txt

The part with recursion

Can be solved by just running scout twice, which is more than good enough for my use case and relieves us of the problem of how to make scout understand which values are to be used when recursively flattening nested arrays.

Command -line tool force type

Add a feature to the command line tool to try to force the type of a value when setting or adding it (now only the string type can be enforced with //):
scout set « people.Tom.height »=int(165.0)
scout add « people.Robert.value »=str(true)

When listing paths, filter value with a predicate

Describe your idea briefly.
The library could offer the possibility to filter listed paths with the paths command based on the value.

Describe the solution you'd like
A predicate could be used to specify how to filter. To throw a relevant error and not crash, the NSPredicate cannot be used. The library could rather use BooleanExpressionEvaluation.

Possible syntax with the -v|--value option.

scout paths -v "value > 10" # keep the number values greater than 10
scout paths -v "value <= 10" # keep the number values lesser than or equal to 10
scout paths -v "10 <= value && value <= 15" # keep the number values between 10 and 15
scout paths -v "value hasPrefix 'hobb'" # keep the values that start with "hobb"
scout paths -v "value matches '[0-9]{3}'" # keep the values composed of exactly 3 digits.

Additional context
The -v|--value option should work coherently with the remaining arguments:

  • initial path
  • filter on the keys -k|--key option

Batch read/set/delete/add command

Describe your idea briefly.
Offer the possibility to batch read, set, delete or add a value in the data.

Describe the solution you'd like
It's possible with Scout 3.0.0 to list the paths in the data. Thus, to read, set, delete or add value on those paths. But this is not efficient when the same value has to be set on all paths, since the program will be called repeatedly.

Additional context
The command could look like scout batch set movies[:].title="New title"

CSV export

Add a CSV export function to be able to export an array to a CSV format.

  • The export should work on single values array and on dictionary values array. With dictionary values, each discovered key should add a new header.
  • When a key does not exist in a dictionary, the value in the CSV will be empty.
  • When dealing with sub-dictionaries, the header name should be the key name of the sub-dictionary and the name of the key in the sub-dictionary separated with by a dot.

Update program documentation for 3.0.0

Program documentation

  • Conversion between formats
  • paths command

Change examples with the people file

Remove the people first key to only show a dictionary

  • Playground

Improve subscript errors

For now the subscript errors only output « Dictionary » or « Array ». Improve that to also display the key name.

PathElement change to an enum

The PathElement protocol allows other types than String or Int, which forces to typecast and to assert a failure in case of the type is not String or Int.
The PathElement should be moved to an enum like:

public enum PathElement: Equatable {
    case key(String)
    case index(Int)
}

This will also allow to add new cases in the future, like arrayCount to return an array count.

Array slicing last slice index

Change the lastNegativeIndex to a lastSliceIndex to allow mixing of positive and negative range like [1:-1] to exclude the first and last one elements

PathExplorer Refactoring

Add typealiases for the dictionaries [String: Any] and arrays [Any] in PahExplorerSerialization

Before release 2.0.0_Alpha01

  • Update in-line doc
  • Update Readme with instructions to install completion script
  • Update to ArgumentParser 0.3.0 to get the empty arguments array behavior
  • Add license in the CLT
  • Remove the VersionCommand to use the version parameter for the Scout command
  • Add a short name to the --no-color flag: -nc

Updated slicing indexes

With the last feature to subscript an array with a negative index, the slicing does not make sense now.

array[-2:] will target the last three elements, where it would be more intuitive if it targeted the last two elements. Especially now as the index '-1' targets the last element in the array.
To target the last two elements, the new syntax should be
array[-2:-1] or array[-2:]

Completion script installation

Add examples in the Readme to explain how to install the completion script. Also, add this installation step when installing with Homebrew.

Array slicing getting value

Sub issue of #66

Allow to continue the exploration after an array slicing.

Examples

Given the following JSON

{
  "names": [
    "Thomas",
    "Jim",
    "Enzo",
    "Duke"
  ],
  "devices": [
    {
      "Id": "ABC",
      "Registered": true
    },
    {
      "Id": "123",
      "Registered": false
    },
    {
      "Id": "456",
      "Registered": false
    }
  ]
}

scout devices[:1].id should output `ABC\n123

List all the paths in data

Describe your idea briefly.
It would be great to get a list of the paths in the data. A regular expression could be used to filter keys rather than returning all of them

Describe the solution you'd like
The command scout paths would output a list of paths that can then be used to parse the data. If a regular expression is specified, only the paths containing a key element matching the expression would be returned.

Also, an initial path could be provided to target only the data starting from this path. With the support of array slicing and dictionary filtering.

Additional context
This could offer nice compositions to parse the data. For example to inspect and set or add new data.
scout paths "people.Suzanne.movies[:].title" will output all Suzanne's movies title paths:

people.Suzanne.movies[0].title
people.Suzanne.movies[1].title
people.Suzanne.movies[2].title

scout paths "people.#Tom|Robert#.hobbies" will output all the paths leading to the hobbies key in Robert and Tom dictionary

people.Robert.hobbies
people.Robert.hobbies[0]
people.Robert.hobbies[1]
people.Robert.hobbies[2]
people.Tom.hobbies
people.Tom.hobbies[0]
people.Tom.hobbies[1]

Also, the same command with the --single filter to target single values only.

people.Robert.hobbies[0]
people.Robert.hobbies[1]
people.Robert.hobbies[2]
people.Tom.hobbies[0]
people.Tom.hobbies[1]

scout paths -k ".*ies" would output all the paths finishing with a key ending with "ies"

people.Robert.hobbies
people.Robert.hobbies[0]
people.Robert.hobbies[1]
people.Robert.hobbies[2]
people.Suzanne.movies
people.Tom.hobbies
people.Tom.hobbies[0]
people.Tom.hobbies[1]

Finally, this would make data parsing easy in bash/zsh. The following would print all the hobbies values right after their path.

paths=(`scout paths "people.#Tom|Robert#.hobbies" --single`)
for path in $paths; do
	echo -n "$path: "
	$scout read -i $file $path;
done

will print

people.Robert.hobbies[0]: video games
people.Robert.hobbies[1]: party
people.Robert.hobbies[2]: tennis
people.Tom.hobbies[0]: cooking
people.Tom.hobbies[1]: guitar

Possibility to delete all the keys with a specified name

Describe your idea briefly.
The program could offer the possibility to delete all the keys matching a string or maybe a regular expression in the data.

Describe the solution you'd like
Something like scout delete -i File.json -k "name" would delete all the (key, value) pairs where the key is "name" in the data.

Option to delete object if empty

When deleting a value in an array or dictionary, offer an option to delete the object if it's its last value which is deleted. For example, with the JSON

{
  "Suzanne" : {
      "movies" : [
        {
          "title" : "Tomorrow is so far"
        },
        {
          "title" : "Yesterday will never go"
        },
        {
          "title" : "What about today?"
        }
      ]
  }
}

the command
scout delete Suzanne.movies[2].title --delete-empty should output:

{
  "Suzanne" : {
      "movies" : [
        {
          "title" : "Tomorrow is so far"
        },
        {
          "title" : "Yesterday will never go"
        }
      ]
  }
}

Update Wiki 3.0.0

Wiki

  • Breaking changes with negative indexes
  • Conversion between formats
  • paths command
  • Change examples with the people file

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.