From 776645e6c5a74d1c9891ec7f113138d893fe5ddb Mon Sep 17 00:00:00 2001 From: Anton Medvedev Date: Fri, 26 Jan 2018 00:30:05 +0700 Subject: [PATCH] first commit --- .gitignore | 2 ++ .travis.yml | 3 ++ README.md | 94 ++++++++++++++++++++++++++++++++++++++++++++++++++++ index.js | 68 +++++++++++++++++++++++++++++++++++++ package.json | 29 ++++++++++++++++ test.sh | 8 +++++ 6 files changed, 204 insertions(+) create mode 100644 .gitignore create mode 100644 .travis.yml create mode 100644 README.md create mode 100755 index.js create mode 100644 package.json create mode 100755 test.sh diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..4ef133f --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +/node_modules/ +package-lock.json diff --git a/.travis.yml b/.travis.yml new file mode 100644 index 0000000..0fe294a --- /dev/null +++ b/.travis.yml @@ -0,0 +1,3 @@ +language: node_js +node_js: + - "7" diff --git a/README.md b/README.md new file mode 100644 index 0000000..df2acde --- /dev/null +++ b/README.md @@ -0,0 +1,94 @@ +# fx [![Build Status](https://travis-ci.org/antonmedv/fx.svg?branch=master)](https://travis-ci.org/antonmedv/fx) + +Command-line JSON processing tool + +## Features + +* Don't need to learn new syntax +* Plain JavaScript +* Formatting and highlighting + +## Install + +``` +$ npm install -g fx +``` + +## Usage + +Pipe into `fx` any JSON and anonymous function for reducing it. + +``` +$ fx [code ...] +``` + +Pretty print JSON: +``` +$ echo '{"key":"value"}' | fx +{ + "key": "value" +} +``` + +Use anonymous function: +``` +$ echo '{"foo": [{"bar": "value"}]}' | fx "x => x.foo[0].bar" +"value" +``` + +If you don't pass anonymous function `param => ...`, code will be automatically transformed into anonymous function. +And you can get access to JSON by `this` keyword: +``` +$ echo '{"foo": [{"bar": "value"}]}' | fx "this.foo[0].bar" +"value" +``` + +You can pass any number of anonymous functions for reducing JSON: +``` +$ echo '{"foo": [{"bar": "value"}]}' | fx "x => x.foo" "this[0]" "this.bar" +"value" +``` + +If passed code contains `yield` keyword, [generator expression](https://github.com/sebmarkbage/ecmascript-generator-expression) +will be used: +``` +$ curl ... | fx "for (let user of this) if (user.login.startsWith('a')) yield user" +``` + +Access to JSON through `this` keyword: +``` +$ echo '["a", "b"]' | fx "yield* this" +[ + "a", + "b" +] +``` + +``` +$ echo '["a", "b"]' | fx "yield* this; yield 'c';" +[ + "a", + "b", + "c" +] +``` + +You can update existing JSON using spread operator: + +``` +$ echo '{"count": 0}' | fx "{...this, count: 1}" +{ + "count": 1 +} +``` + + +## Related + +* [jq](https://github.com/stedolan/jq) – cli JSON processor on C +* [jsawk](https://github.com/micha/jsawk) – like awk, but for JSON +* [json](https://github.com/trentm/json) – another JSON manipulating cli library + +## License + +MIT diff --git a/index.js b/index.js new file mode 100755 index 0000000..b134725 --- /dev/null +++ b/index.js @@ -0,0 +1,68 @@ +#!/usr/bin/env node +'use strict'; +const meow = require('meow') +const stdin = require('get-stdin') +const cardinal = require('cardinal') +const theme = require('cardinal/themes/tomorrow-night') + +const cli = meow(` + Usage + $ fx [code ...] + + Examples + $ echo '{"key": "value"}' | fx "x => x.key" + "value" + + $ echo '[1,2,3]' | fx "this.map(x => x * 2)" + [2, 4, 6] + + $ echo '{"items": ["one", "two"]}' | fx "this.items" "this[1]" + "two" + + $ echo '{"count": 0}' | fx "{...this, count: 1}" + {"count": 1} +`) + + + +const highlight = process.stdout.isTTY ? cardinal.highlight : x => x + +async function main() { + const text = await stdin() + + if (text === '') { + cli.showHelp() + } + + const json = JSON.parse(text) + const result = cli.input.reduce(reduce, json) + + if (typeof result === 'undefined') { + console.log(undefined) + } else { + const text = JSON.stringify(result, null, 4) + console.log(highlight(text, {theme})) + } +} + +function reduce(json, code) { + if (/^\w+\s*=>/.test(code)) { + const fx = eval(code) + return fx(json) + } else if (/yield/.test(code)) { + const fx = eval(` + function fn() { + const gen = (function*(){ + ${code.replace(/\\\n/g, '')} + }).call(this) + return [...gen] + }; fn + `) + return fx.call(json) + } else { + const fx = eval(`function fn() { return ${code} }; fn`) + return fx.call(json) + } +} + +main() diff --git a/package.json b/package.json new file mode 100644 index 0000000..df6c298 --- /dev/null +++ b/package.json @@ -0,0 +1,29 @@ +{ + "name": "fx", + "version": "0.0.0", + "description": "Command-line JSON processing tool", + "repository": "antonmedv/fx", + "author": "Anton Medvedev ", + "license": "MIT", + "bin": { + "fx": "index.js" + }, + "files": [ + "index.js" + ], + "scripts": { + "test": "bash test.sh" + }, + "keywords": [ + "json", + "cli" + ], + "engines": { + "node": ">=9" + }, + "dependencies": { + "cardinal": "^1.0.0", + "get-stdin": "^5.0.1", + "meow": "^4.0.0" + } +} diff --git a/test.sh b/test.sh new file mode 100755 index 0000000..7e3cf45 --- /dev/null +++ b/test.sh @@ -0,0 +1,8 @@ +#!/usr/bin/env bash +set -euxo pipefail +alias fx='node index.js' +echo '[{"greeting": "hello world"}]' | fx +echo '{"key": "value"}' | fx "x => x.key" +echo '[1, 2, 3, 4, 5]' | fx "this.map(x => x * this.length)" +echo '[1, 2, 3, 4, 5]' | fx "for (let i of this) if (i % 2 == 0) yield i" +echo '{"items": ["foo", "bar"]}' | fx "this.items" "yield* this; yield 'baz'"