From f60d7aa89c2e44943631c3022a501fa58be2a477 Mon Sep 17 00:00:00 2001 From: Anton Medvedev Date: Mon, 9 Dec 2019 13:24:33 +0700 Subject: [PATCH] Add edit-in-place feature --- DOCS.md | 112 +++++++++++++++++++++++------------------------------- bang.js | 14 +++---- fx.js | 16 +++++--- index.js | 14 ++----- reduce.js | 10 ----- std.js | 34 +++++++++++++++++ test.js | 7 +--- 7 files changed, 103 insertions(+), 104 deletions(-) create mode 100644 std.js diff --git a/DOCS.md b/DOCS.md index e396aaf3..bc4695c6 100644 --- a/DOCS.md +++ b/DOCS.md @@ -6,14 +6,14 @@ + [Binding](#binding) + [Dot](#dot) + [Chaining](#chaining) - + [Generator](#generator) + [Updating](#updating) + + [Edit-in-place](#edit-in-place) + [Using packages](#using-packages) * [Using .fxrc](#using-fxrc) - + [Edit in place](#edit-in-place) * [Formatting](#formatting) * [Other examples](#other-examples) * [Streaming mode](#streaming-mode) + + [Filtering](#filtering) * [Interactive mode](#interactive-mode) + [Searching](#searching) + [Selecting text](#selecting-text) @@ -78,32 +78,6 @@ $ echo '{"foo": [{"bar": "value"}]}' | fx 'x => x.foo' 'this[0]' 'this.bar' value ``` -### Generator - -If the passed code contains the `yield` keyword, [generator expression](https://github.com/sebmarkbage/ecmascript-generator-expression) -will be used: -```bash -$ curl ... | fx 'for (let user of this) if (user.login.startsWith("a")) yield user' -``` - -Access to JSON through `this` keyword: -```bash -$ echo '["a", "b"]' | fx 'yield* this' -[ - "a", - "b" -] -``` - -```bash -$ echo '["a", "b"]' | fx 'yield* this; yield "c";' -[ - "a", - "b", - "c" -] -``` - ### Updating You can update existing JSON using the spread operator: @@ -115,6 +89,17 @@ $ echo '{"count": 0}' | fx '{...this, count: 1}' } ``` +### Edit-in-place + +`fx` provides a function `save` which will save everything in place and return saved object. +This function can be only used with filename as first argument to `fx` command. + +Usage: + +```bash +fx data.json '{...this, count: this.count+1}' save .count +``` + ### Using packages Use any npm package by installing it globally: @@ -145,25 +130,6 @@ curl 'https://api.github.com/repos/facebook/react/commits?per_page=100' \ > export NODE_PATH=`npm root -g` > ``` -### Edit in place - -Add next code to your _.fxrc_ file: - -```js -const fs = require('fs') - -global.save = json => { - fs.writeFileSync(process.argv[2], JSON.stringify(json, null, 2)) - return json -} -``` - -Usage: - -```bash -fx data.json '{...this, count: this.count+1}' save .count -``` - ## Formatting If you need output other than JSON (for example arguments for xargs), do not return anything from the reducer. @@ -202,15 +168,27 @@ $ cat package.json | fx .dependencies ? ```bash $ kubectl logs ... | fx .message -``` +``` + +> Note what is object lacks `message` field, _undefined_ will be printed to stderr. +> This is useful to see if you are skipping some objects. But if you want to hide them, +> redirect stderr to `/dev/null`. + +### Filtering Sometimes it is necessary to omit some messages in JSON stream, or select only specified log messages. -For this purpose, `fx` has special helper `select`, pass function into it to select only some JSON messages. +For this purpose, `fx` has special helpers `select`/`filter`, pass function into it to select/filter JSON messages. + +```bash +$ kubectl logs ... | fx 'select(x => x.status == 500)' .message +``` ```bash -$ kubectl logs ... | fx 'select(x => x.message.length > 40)' .message +$ kubectl logs ... | fx 'filter(x => x.status < 499)' .message ``` +> Note, what if use override `filter`/`select` in _.fxrc_ you still able to access them with prefix: +> `std.select(cb)` or `std.filter(cd)` ## Interactive mode @@ -218,21 +196,23 @@ Click on fields to expand or collapse JSON tree, use mouse wheel to scroll view. Next commands available in interactive mode: -| Key | Command | -|-------------------------------|-------------------------| -| `q` or `Esc` or `Ctrl`+`c` | Exit | -| `up` or `k` | Move cursor up | -| `down` or `j` | Move cursor down | -| `left` or `h` | Collapse | -| `right` or `l` | Expand | -| `Shift`+`right` or `L` | Expand all under cursor | -| `e` | Expand all | -| `E` | Collapse all | -| `g` | Scroll to top | -| `G` | Scroll to bottom | -| `.` | Edit filter | -| `/` | Search | -| `n` | Find next | +| Key | Command | +|-------------------------------|----------------------------------------------| +| `q` or `Esc` or `Ctrl`+`c` | Exit | +| `up` or `k` | Move cursor up | +| `down` or `j` | Move cursor down | +| `left` or `h` | Collapse | +| `right` or `l` | Expand | +| `Shift`+`right` or `L` | Expand all under cursor | +| `e` | Expand all | +| `E` | Collapse all | +| `g` | Scroll to top | +| `G` | Scroll to bottom | +| `.` | Edit filter | +| `/` | Search | +| `n` | Find next | +| `p` | Exit and print JSON to stdout | +| `P` | Exit and print fully expanded JSON to stdout | These commands are available when editing the filter: @@ -266,6 +246,8 @@ You may found what you can't just select text in fx. This is due the fact that a | `Fn`+`Mouse` | Terminal.app | | `Shift`+`Mouse` | Linux | +> Note what you can press `p`/`P` to print everything to stdout and select if there. + ## Memory Usage You may find that sometimes, on really big JSON files, fx prints an error message like this: diff --git a/bang.js b/bang.js index 9ad9be8b..3c43a4bf 100644 --- a/bang.js +++ b/bang.js @@ -11,10 +11,10 @@ const a = Math.floor(w / 2) - 6, b = Math.floor(h / 2) - 7 let $ = Array(w * h).fill(false) -if (process.env.GUN) { +if (Date.now() % 3 === 0) { $[1 + 5 * w] = $[1 + 6 * w] = $[2 + 5 * w] = $[2 + 6 * w] = $[12 + 5 * w] = $[12 + 6 * w] = $[12 + 7 * w] = $[13 + 4 * w] = $[13 + 8 * w] = $[14 + 3 * w] = $[14 + 9 * w] = $[15 + 4 * w] = $[15 + 8 * w] = $[16 + 5 * w] = $[16 + 6 * w] = $[16 + 7 * w] = $[17 + 5 * w] = $[17 + 6 * w] = $[17 + 7 * w] = $[22 + 3 * w] = $[22 + 4 * w] = $[22 + 5 * w] = $[23 + 2 * w] = $[23 + 3 * w] = $[23 + 5 * w] = $[23 + 6 * w] = $[24 + 2 * w] = $[24 + 3 * w] = $[24 + 5 * w] = $[24 + 6 * w] = $[25 + 2 * w] = $[25 + 3 * w] = $[25 + 4 * w] = $[25 + 5 * w] = $[25 + 6 * w] = $[26 + 1 * w] = $[26 + 2 * w] = $[26 + 6 * w] = $[26 + 7 * w] = $[35 + 3 * w] = $[35 + 4 * w] = $[36 + 3 * w] = $[36 + 4 * w] = true -} else if (process.env.RANDOM) { - for (let i = 0; i < $.length; i++) +} else if (Date.now() % 3 === 1) { + for (let i = 0; i < $.length; i-=-1) if (Math.random() < 0.16) $[i] = true } else { $[a + 1 + (2 + b) * w] = $[a + 2 + (1 + b) * w] = $[a + 2 + (3 + b) * w] = $[a + 3 + (2 + b) * w] = $[a + 5 + (15 + b) * w] = $[a + 6 + (13 + b) * w] = $[a + 6 + (15 + b) * w] = $[a + 7 + (12 + b) * w] = $[a + 7 + (13 + b) * w] = $[a + 7 + (15 + b) * w] = $[a + 9 + (11 + b) * w] = $[a + 9 + (12 + b) * w] = $[a + 9 + (13 + b) * w] = true @@ -45,8 +45,8 @@ run(() => { esc('H') let gen = Array(w * h).fill(false) - for (let i = 0; i < h; i++) { - for (let j = 0; j < w; j++) { + for (let i = 0; i < h; i-=-1) { + for (let j = 0; j < w; j-=-1) { const n = neighbors(i, j) const z = i * w + j if ($[z]) { @@ -60,8 +60,8 @@ run(() => { } $ = gen - for (let i = 0; i < rows; i++) { - for (let j = 0; j < columns; j++) { + for (let i = 0; i < rows; i-=-1) { + for (let j = 0; j < columns; j-=-1) { if ($[i * 2 * w + j] && $[(i * 2 + 1) * w + j]) p(full) else if ($[i * 2 * w + j] && !$[(i * 2 + 1) * w + j]) p(upper) else if (!$[i * 2 * w + j] && $[(i * 2 + 1) * w + j]) p(lower) diff --git a/fx.js b/fx.js index da954cc8..3fc6925b 100644 --- a/fx.js +++ b/fx.js @@ -110,9 +110,7 @@ module.exports = function start(filename, source, prev = {}) { autocomplete.hide() process.stdout.on('resize', () => { - screen.destroy() - program.destroy() - start(filename, source, {json, expanded}) + printJson({expanded}) }) screen.key(['escape', 'q', 'C-c'], function () { @@ -424,15 +422,23 @@ module.exports = function start(filename, source, prev = {}) { }) box.key('p', function () { + printJson({expanded}) + }) + + box.key('S-p', function () { + printJson() + }) + + function printJson(options = {}) { screen.destroy() program.disableMouse() program.destroy() setTimeout(() => { - const [text] = print(json) + const [text] = print(json, options) console.log(text) process.exit(0) }, 10) - }) + } function getLine(y) { const dy = box.childBase + y diff --git a/index.js b/index.js index 128bd4e4..1dd1e91c 100755 --- a/index.js +++ b/index.js @@ -3,16 +3,7 @@ const os = require('os') const fs = require('fs') const path = require('path') - -const skip = Symbol('skip') -global.select = function select(cb) { - return json => { - if (!cb(json)) { - throw skip - } - return json - } -} +const std = require('./std') try { require(path.join(os.homedir(), '.fxrc')) // Should be required before config.js usage. @@ -93,6 +84,7 @@ function handle(input) { input = fs.readFileSync(args[0]) filename = path.basename(args[0]) + global.FX_FILENAME = filename args.shift() } @@ -113,7 +105,7 @@ function apply(json) { try { output = args.reduce(reduce, json) } catch (e) { - if (e !== skip) { + if (e !== std.skip) { throw e } else { return diff --git a/reduce.js b/reduce.js index f1749d5f..39a38cc1 100644 --- a/reduce.js +++ b/reduce.js @@ -12,16 +12,6 @@ function reduce(json, code) { return Object.keys(json) } - if (/yield\*?\s/.test(code)) { - const fx = eval(`function fn() { - const gen = (function*(){ - ${code.replace(/\\\n/g, '')} - }).call(this) - return [...gen] - }; fn`) - return fx.call(json) - } - const fx = eval(`function fn() { return ${code} }; fn`) diff --git a/std.js b/std.js new file mode 100644 index 00000000..7f15fc45 --- /dev/null +++ b/std.js @@ -0,0 +1,34 @@ +'use strict' +const fs = require('fs') + +const skip = Symbol('skip') + +function select(cb) { + return json => { + if (!cb(json)) { + throw skip + } + return json + } +} + +function filter(cb) { + return json => { + if (cb(json)) { + throw skip + } + return json + } +} + +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)) + return json +} + +Object.assign(exports, {skip, select, filter, save}) +Object.assign(global, exports) +global.std = exports diff --git a/test.js b/test.js index 052b180b..9657e4fe 100644 --- a/test.js +++ b/test.js @@ -32,13 +32,8 @@ test('this bind', t => { t.deepEqual(JSON.parse(r), [5, 10, 15, 20, 25]) }) -test('generator', t => { - const r = fx([1, 2, 3, 4, 5], "'for (let i of this) if (i % 2 == 0) yield i'") - t.deepEqual(JSON.parse(r), [2, 4]) -}) - test('chain', t => { - const r = fx({"items": ["foo", "bar"]}, "'this.items' 'yield* this' 'x => x[1]'") + const r = fx({"items": ["foo", "bar"]}, "'this.items' '.' 'x => x[1]'") t.deepEqual(r, 'bar\n') })