diff --git a/DOCS.md b/DOCS.md index 40f6883..5444d39 100644 --- a/DOCS.md +++ b/DOCS.md @@ -81,40 +81,35 @@ One of the frequent operations is mapping some function on an array. For example { "author": { "name": "antonmedv" - }, - ... + } }, {...}, - {...}, ... ] ``` -And we want to collect names of each object in array. We can do this by mapping anonymous function: +And we want to collect names of each object in the array. We can do this by mapping anonymous function: ```bash $ cat ... | fx '.map(x => x.author.name)' ``` -Or we can do the same by using `@` prefix: +Or we can do the same by using jq-like syntax: ```bash -$ cat ... | fx @.author.name +$ cat ... | fx .[].author.name [ "antonmedv", ... ] ``` -Expression followed by `@` symbol will be mapped to each element of array. - -> Note what `@` can be applied to map object values. +> Note what `[]` can be applied to map object values. > ```bash -> $ echo '{"foo": 1, "bar": 2}' | fx @+1 -> [2, 3] +> $ echo '{"foo": 1, "bar": 2}' | fx .[] +> [1, 2] > ``` -> -> Also note what symbol `@` alone is equivalent of `Object.values` function. + ### Chaining diff --git a/README.md b/README.md index 47660f4..8d5b002 100644 --- a/README.md +++ b/README.md @@ -27,7 +27,7 @@ brew install fx ``` Or download standalone binary from [releases](https://github.com/antonmedv/fx/releases) ```bash -bash <( curl -L https://fx.wtf ) +bash <( curl https://fx.wtf ) ``` ## Usage @@ -65,6 +65,11 @@ $ echo '{"count": 0}' | fx '{...this, count: 1}' } ``` +Extract values from maps. +```bash +$ fx commits.json | fx .[].author.name +``` + Print formatted JSON to stdout. ```bash $ curl ... | fx . diff --git a/install.sh b/install.sh deleted file mode 100644 index 5221f26..0000000 --- a/install.sh +++ /dev/null @@ -1,75 +0,0 @@ -#!/usr/bin/env bash - -set -e -set -u -set -o pipefail - -put() { - echo -e "\e[1;32m$1\e[0m" -} - -do_install() { - cat <<'EOM' - - ______ _____ _ _ _ - | ____| |_ _| | | | | | - | |____ __ | | _ __ ___| |_ __ _| | | ___ _ __ - | __\ \/ / | | | '_ \/ __| __/ _` | | |/ _ \ '__| - | | > < _| |_| | | \__ \ || (_| | | | __/ | - |_| /_/\_\ |_____|_| |_|___/\__\__,_|_|_|\___|_| - -EOM - - platform='' - if [[ "$OSTYPE" == "linux"* ]]; then - platform='linux' - elif [[ "$OSTYPE" == "darwin"* ]]; then - platform='macos' - elif [[ "$OSTYPE" == "win"* ]]; then - platform='win.exe' - fi - - if test "x$platform" = "x"; then - cat < "fx-$platform.zip" - - put "Extracting fx-$platform.zip" - unzip "fx-$platform.zip" - rm "fx-$platform.zip" - mv "fx-$platform" fx - - name='' - read -p $'\e[1;35mWhould you like to move fx binary to /usr/local/bin?\e[0m [Y/n] ' -n 1 -r - echo - if [[ $REPLY =~ ^[Yy]$ ]]; then - mv fx /usr/local/bin/fx - name='fx' - else - name='./fx' - fi - - cat < /dev/null -} - -do_install diff --git a/package.json b/package.json index c375e99..7ad4be3 100644 --- a/package.json +++ b/package.json @@ -46,8 +46,8 @@ "string-width": "^4.2.0" }, "devDependencies": { - "ava": "^3.9.0", - "pkg": "^4.4.8", - "release-it": "^13.6.3" + "ava": "^3.12.1", + "pkg": "^4.4.9", + "release-it": "^14.0.1" } } diff --git a/reduce.js b/reduce.js index f5b0419..625131f 100644 --- a/reduce.js +++ b/reduce.js @@ -13,10 +13,16 @@ function reduce(json, code) { return Object.keys(json) } - if (/^@/.test(code)) { - return eval(`function fn() { - return Object.values(this).map(x => x${code.substring(1)}) - }; fn`).call(json) + if (/^(\.\w*)+\[]/.test(code)) { + function fold(s) { + if (s.length === 1) { + return 'x => x' + s[0] + } + let obj = s.shift() + obj = obj === '.' ? 'x' : 'x' + obj + return `x => Object.values(${obj}).flatMap(${fold(s)})` + } + code = fold(code.split('[]')) } if (/^\.\[/.test(code)) { diff --git a/std.js b/std.js index 7f15fc4..cc096ce 100644 --- a/std.js +++ b/std.js @@ -1,5 +1,4 @@ 'use strict' -const fs = require('fs') const skip = Symbol('skip') @@ -25,7 +24,7 @@ function save(json) { if (!global.FX_FILENAME) { throw "No filename provided.\nTo edit-in-place, specify JSON file as first argument." } - fs.writeFileSync(global.FX_FILENAME, JSON.stringify(json, null, 2)) + require('fs').writeFileSync(global.FX_FILENAME, JSON.stringify(json, null, 2)) return json } diff --git a/test.js b/test.js index 88318d0..9fb9991 100644 --- a/test.js +++ b/test.js @@ -8,32 +8,32 @@ function fx(json, code = '') { } test('pass', t => { - const r = fx([{"greeting": "hello world"}]) - t.deepEqual(JSON.parse(r), [{"greeting": "hello world"}]) + const r = fx([{'greeting': 'hello world'}]) + t.deepEqual(JSON.parse(r), [{'greeting': 'hello world'}]) }) test('anon func', t => { - const r = fx({"key": "value"}, "'function (x) { return x.key }'") + const r = fx({'key': 'value'}, '\'function (x) { return x.key }\'') t.is(r, 'value\n') }) test('arrow func', t => { - const r = fx({"key": "value"}, "'x => x.key'") + const r = fx({'key': 'value'}, '\'x => x.key\'') t.is(r, 'value\n') }) test('arrow func ()', t => { - const r = fx({"key": "value"}, "'(x) => x.key'") + const r = fx({'key': 'value'}, '\'(x) => x.key\'') t.is(r, 'value\n') }) test('this bind', t => { - const r = fx([1, 2, 3, 4, 5], "'this.map(x => x * this.length)'") + const r = fx([1, 2, 3, 4, 5], '\'this.map(x => x * this.length)\'') t.deepEqual(JSON.parse(r), [5, 10, 15, 20, 25]) }) test('chain', t => { - const r = fx({"items": ["foo", "bar"]}, "'this.items' '.' 'x => x[1]'") + const r = fx({'items': ['foo', 'bar']}, '\'this.items\' \'.\' \'x => x[1]\'') t.is(r, 'bar\n') }) @@ -73,3 +73,13 @@ test('lossless number', t => { const r = execSync(`echo '{"long": 123456789012345678901}' | node index.js .long`).toString('utf8') t.is(r, '123456789012345678901\n') }) + +test('value iterator', t => { + const r = fx({master: {foo: [{bar: [{val: 1}]}]}}, '.master.foo[].bar[].val') + t.deepEqual(JSON.parse(r), [1]) +}) + +test('value iterator simple', t => { + const r = fx([{val:1},{val:2}], '.[].val') + t.deepEqual(JSON.parse(r), [1, 2]) +})