Skip to content
Permalink
Browse files

Add edit-in-place feature

  • Loading branch information
antonmedv committed Dec 9, 2019
1 parent 9713a05 commit f60d7aa89c2e44943631c3022a501fa58be2a477
Showing with 103 additions and 104 deletions.
  1. +47 βˆ’65 DOCS.md
  2. +7 βˆ’7 bang.js
  3. +11 βˆ’5 fx.js
  4. +3 βˆ’11 index.js
  5. +0 βˆ’10 reduce.js
  6. +34 βˆ’0 std.js
  7. +1 βˆ’6 test.js
112 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,37 +168,51 @@ $ 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

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:
14 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)
16 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
@@ -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
@@ -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`)
34 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
@@ -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')
})

0 comments on commit f60d7aa

Please sign in to comment.
You can’t perform that action at this time.