Compare commits

..

1 Commits

Author SHA1 Message Date
Anton Medvedev 483dd9a783 Disable sleep on startup 2 years ago

@ -83,32 +83,8 @@ jobs:
brew:
needs: [commit]
runs-on: macos-latest
runs-on: ubuntu-latest
steps:
- name: Set up Homebrew
id: set-up-homebrew
uses: Homebrew/actions/setup-homebrew@master
with:
test-bot: false
- name: Cache Homebrew Bundler RubyGems
id: cache
uses: actions/cache@v2
with:
path: ${{ steps.set-up-homebrew.outputs.gems-path }}
key: ${{ runner.os }}-rubygems-${{ steps.set-up-homebrew.outputs.gems-hash }}
restore-keys: ${{ runner.os }}-rubygems-
- name: Install Homebrew Bundler RubyGems
if: steps.cache.outputs.cache-hit != 'true'
run: brew install-bundler-gems
- name: Configure Git user
uses: Homebrew/actions/git-user-config@master
- name: Update brew
run: brew update
- name: Bump formulae
uses: Homebrew/actions/bump-formulae@master
with:

@ -24,9 +24,6 @@ scoop install fx
pacman -S fx
```
```bash
pkg install fx
```
```bash
go install github.com/antonmedv/fx@latest
```
@ -56,24 +53,27 @@ curl ... | fx .
### Reducers
Write reducers in your favorite language: [JavaScript](doc/js.md) (default),
[Python](doc/python.md), or [Ruby](doc/ruby.md).
Write reducers in your favorite language: [JavaScript](docs/reducers.md#node) (default),
[Python](docs/reducers.md#python), or [Ruby](docs/reducers.md#ruby).
```bash
export FX_LANG=node
fx data.json '.filter(x => x.startsWith("a"))'
```
```bash
export FX_LANG=python
fx data.json '[x["age"] + i for i in range(10)]'
```
```bash
export FX_LANG=ruby
fx data.json 'x.to_a.map {|x| x[1]}'
```
## Documentation
See full [documentation](doc/doc.md).
See full [documentation](https://github.com/antonmedv/fx/blob/master/DOCS.md).
## Themes
@ -84,10 +84,8 @@ to `9`:
export FX_THEME=9
```
<img width="1214" alt="themes" src="doc/images/themes.png">
Add your own themes in [theme.go](pkg/theme/theme.go) file.
<img width="1214" alt="themes" src="docs/images/themes.png">
## License
[MIT](LICENSE)
[MIT](https://github.com/antonmedv/fx/blob/master/LICENSE)

@ -1,66 +0,0 @@
# Documentation
The **fx** can work in two modes: as a reducer or an interactive viewer.
To start the interactive mode pipe a JSON into **fx**:
```sh
$ curl ... | fx
```
Or you can pass a filename as the first parameter:
```sh
$ fx data.json
```
## Reducers
Use [JavaScript](js.md), [Python](python.md), or [Ruby](ruby.md).
## Streaming mode
The **fx** supports line-delimited JSON streaming or concatenated JSON streaming.
```sh
$ echo '
> {"message": "hello"}
> {"message": "world!"}
> ' | fx .message
hello
world!
```
## Interactive mode
Type `?` to see the full list of available shortcuts while in the interactive mode.
### Search
Press `/` and type regexp pattern to search in the current JSON.
Search is performed on the internal representation of the JSON without newlines.
Type `n` to jump to the next result, and `N` to the previous
### Selecting text
You can't just select text in fx. This is due to the fact that all mouse events are
redirected to stdin. To be able to select again you need to instruct your terminal
not to do it. This can be done by holding special keys while selecting:
| Key | Terminal |
|------------------|---------------|
| `Option`+`Mouse` | iTerm2, Hyper |
| `Fn`+`Mouse` | Terminal.app |
| `Shift`+`Mouse` | Linux |
## Configs
Next configs available for **fx** via environment variables.
| Name | Values | Description |
|----------------|-----------------------------------------------------|-------------------------------------------------------|
| `FX_LANG` | `js` (default), `node`, `python`, `python3`, `ruby` | Reducer type. |
| `FX_THEME` | `0` (disable colors), `1` (default), `2..9` | Color theme. |
| `FX_SHOW_SIZE` | `true` or `false` (default) | Show size of arrays and object in collapsed previews. |

@ -1,120 +0,0 @@
# JavaScript Reducers
If any additional arguments were passed, fx converts them into a function which
takes the JSON as an argument named `x`.
By default, fx uses builtin JavaScript VM ([goja](https://github.com/dop251/goja)),
but also can be used with node.
```sh
export FX_LANG=js # Default
```
Or for usage with node:
```sh
export FX_LANG=node
```
An example of anonymous function used as a reducer:
```sh
$ echo '{"foo": [{"bar": "value"}]}' | fx 'x => x.foo[0].bar'
value
```
The same reducer function can be simplified to:
```sh
$ echo '{"foo": [{"bar": "value"}]}' | fx 'x.foo[0].bar'
value
```
Each argument treated as a reducer function.
```sh
$ echo '{"foo": [{"bar": "value"}]}' | fx 'x.foo' 'x[0]' 'x.bar'
value
```
Update JSON using the spread operator:
```sh
$ echo '{"name": "fx", "count": 0}' | fx '{...this, count: 1}'
{
"name": "fx",
"count": 1
}
```
## Dot
Fx supports simple JS-like syntax for accessing data, which can be used with any `FX_LANG`.
```sh
$ echo '{"foo": [{"bar": "value"}]}' | fx .foo[0].bar
value
```
## .fxrc.js
Create _.fxrc.js_ file in `$HOME` directory, and define some useful functions.
```js
// .fxrc.js
function upper(s) {
return s.toUpperCase()
}
```
```sh
$ cat data.json | fx .name upper
ANTON
```
## Node
```sh
export FX_LANG=node
```
Use any npm package by installing it globally. Create _.fxrc.js_ file in `$HOME`
directory, and require any packages or define global functions. For example,
to access all lodash methods without `_` prefix, put next line into your
_.fxrc.js_ file:
```js
Object.assign(global, require('lodash/fp'))
```
And now you will be able to call all lodash methods. For example, see who's been
committing to react recently:
```sh
curl 'https://api.github.com/repos/facebook/react/commits?per_page=100' \
| fx 'groupBy("commit.author.name")' 'mapValues(size)' toPairs 'sortBy(1)' reverse 'take(10)' fromPairs
```
> To be able to require global modules make sure you have correct `NODE_PATH` env variable.
> ```sh
> export NODE_PATH=`npm root -g`
> ```
The _.fxrc.js_ file supports both: `import` and `require`.
```js
// .fxrc.js
import 'zx/globals'
const _ = require('lodash')
```
> If you want to use _.fxrc.js_ for both `FX_LANG=js` and `FX_LANG=node`,
> separate parts by `// nodejs:` comment:
> ```js
> // .fxrc.js
> function upper(s) {
> return s.toUpperCase()
> }
> // nodejs:
> import 'zx/globals'
> const _ = require('lodash')
> ```

@ -1,27 +0,0 @@
# Python Reducers
If any additional arguments was passed, **fx** converts it to a function which
takes the JSON as an argument named `x`.
```sh
export FX_LANG=python
```
Or
```sh
export FX_LANG=python3
```
Example:
```sh
fx data.json '[x["age"] + i for i in range(10)]'
```
## Dot
Fx supports simple syntax for accessing data, which can be used with any `FX_LANG`.
```sh
$ echo '{"foo": [{"bar": "value"}]}' | fx .foo[0].bar
value
```

@ -1,23 +0,0 @@
# Ruby Reducers
If any additional arguments was passed, **fx** converts it to a function which
takes the JSON as an argument named `x`.
```sh
export FX_LANG=ruby
```
Example:
```sh
fx data.json 'x.to_a.map {|x| x[1]}'
```
## Dot
Fx supports simple syntax for accessing data, which can be used with any `FX_LANG`.
```sh
$ echo '{"foo": [{"bar": "value"}]}' | fx .foo[0].bar
value
```

Before

Width:  |  Height:  |  Size: 411 KiB

After

Width:  |  Height:  |  Size: 411 KiB

Before

Width:  |  Height:  |  Size: 659 KiB

After

Width:  |  Height:  |  Size: 659 KiB

@ -0,0 +1,19 @@
# Reducers
Fx takes a few arguments after the file name, and converts them to a reducer.
## Node
Access all lodash (or ramda, etc) methods by using [.fxrc](#using-fxrc) file.
```bash
$ fx data.json 'groupBy("commit.committer.name")' 'mapValues(_.size)'
```
## Python
TODO
## Ruby
TODO

@ -26,7 +26,7 @@ require (
github.com/muesli/reflow v0.3.0 // indirect
github.com/pmezard/go-difflib v1.0.0 // indirect
github.com/rivo/uniseg v0.2.0 // indirect
golang.org/x/sys v0.0.0-20220503163025-988cb79eb6c6 // indirect
golang.org/x/sys v0.0.0-20220422013727-9388b58f7150 // indirect
golang.org/x/term v0.0.0-20220411215600-e5f449aeb171 // indirect
golang.org/x/text v0.3.7 // indirect
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c // indirect

@ -76,8 +76,8 @@ golang.org/x/sys v0.0.0-20210119212857-b64e53b001e4/go.mod h1:h1NjWce9XRLGQEsW7w
golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220503163025-988cb79eb6c6 h1:nonptSpoQ4vQjyraW20DXPAglgQfVnM9ZC6MmNLMR60=
golang.org/x/sys v0.0.0-20220503163025-988cb79eb6c6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220422013727-9388b58f7150 h1:xHms4gcpe1YE7A3yIllJXP16CMAGuqwO2lX1mTyyRRc=
golang.org/x/sys v0.0.0-20220422013727-9388b58f7150/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/term v0.0.0-20210422114643-f5beecf764ed/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/term v0.0.0-20220411215600-e5f449aeb171 h1:EH1Deb8WZJ0xc0WK//leUHXcX9aLE5SymusoTmMZye8=
golang.org/x/term v0.0.0-20220411215600-e5f449aeb171/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=

@ -2,41 +2,36 @@ package main
import (
"fmt"
"reflect"
"strings"
"github.com/charmbracelet/bubbles/key"
"github.com/charmbracelet/lipgloss"
"reflect"
"strings"
)
func usage(keyMap KeyMap) string {
title := lipgloss.NewStyle().Bold(true)
pad := lipgloss.NewStyle().PaddingLeft(4)
return fmt.Sprintf(`
%v
Terminal JSON viewer
%v
fx data.json
fx data.json .field
curl ... | fx
return fmt.Sprintf(`fx - terminal JSON viewer
%v
-h, --help print help
-v, --version print version
--print-code print code of the reducer
%v
fx data.json
fx data.json .field
curl ... | fx
%v
%v
%v
%v
[https://fx.wtf]
%v
[https://fx.wtf]
`,
title.Render("fx "+version),
title.Render("Usage"),
title.Render("Flags"),
title.Render("Key Bindings"),
strings.Join(keyMapInfo(keyMap, pad), "\n"),
strings.Join(
keyMapInfo(
keyMap,
lipgloss.NewStyle().PaddingLeft(2),
),
"\n",
),
title.Render("More info"),
)
}

@ -21,35 +21,12 @@ import (
"github.com/muesli/termenv"
)
var (
flagHelp bool
flagVersion bool
flagPrintCode bool
)
func main() {
var args []string
for _, arg := range os.Args[1:] {
switch arg {
case "-h", "--help":
flagHelp = true
case "-v", "-V", "--version":
flagVersion = true
case "--print-code":
flagPrintCode = true
default:
args = append(args, arg)
}
}
if flagHelp {
fmt.Println(usage(DefaultKeyMap()))
return
}
if flagVersion {
if len(os.Args) == 2 && (os.Args[1] == "-v" || os.Args[1] == "-V" || os.Args[1] == "--version") {
fmt.Println(version)
return
}
cpuProfile := os.Getenv("CPU_PROFILE")
if cpuProfile != "" {
f, err := os.Create(cpuProfile)
@ -61,6 +38,7 @@ func main() {
panic(err)
}
}
themeId, ok := os.LookupEnv("FX_THEME")
if !ok {
themeId = "1"
@ -72,23 +50,19 @@ func main() {
if termenv.ColorProfile() == termenv.Ascii {
theme = Themes["0"]
}
var showSize bool
if s, ok := os.LookupEnv("FX_SHOW_SIZE"); ok {
if s == "true" {
showSize = true
}
}
stdinIsTty := isatty.IsTerminal(os.Stdin.Fd())
stdoutIsTty := isatty.IsTerminal(os.Stdout.Fd())
filePath := ""
fileName := ""
var args []string
var dec *json.Decoder
if stdinIsTty {
// Nothing was piped, maybe file argument?
if len(args) >= 1 {
filePath = args[0]
f, err := os.Open(filePath)
if len(os.Args) >= 2 {
filePath = os.Args[1]
f, err := os.Open(os.Args[1])
if err != nil {
switch err.(type) {
case *fs.PathError:
@ -100,68 +74,44 @@ func main() {
}
fileName = path.Base(filePath)
dec = json.NewDecoder(f)
args = args[1:]
args = os.Args[2:]
}
} else {
dec = json.NewDecoder(os.Stdin)
args = os.Args[1:]
}
if dec == nil {
fmt.Println(usage(DefaultKeyMap()))
os.Exit(1)
}
dec.UseNumber()
object, err := Parse(dec)
jsonObject, err := Parse(dec)
if err != nil {
fmt.Println("JSON Parse Error:", err.Error())
os.Exit(1)
}
lang, ok := os.LookupEnv("FX_LANG")
if !ok {
lang = "js"
}
var fxrc string
if lang == "js" || lang == "node" {
home, err := os.UserHomeDir()
if err == nil {
b, err := os.ReadFile(path.Join(home, ".fxrc.js"))
if err == nil {
fxrc = "\n" + string(b)
if lang == "js" {
parts := strings.SplitN(fxrc, "// nodejs:", 2)
fxrc = parts[0]
}
}
}
}
if dec.More() {
os.Exit(stream(dec, object, lang, args, theme, fxrc))
exitCode := stream(dec, jsonObject, lang, args, theme)
os.Exit(exitCode)
}
if len(args) > 0 || !stdoutIsTty {
if len(args) > 0 && flagPrintCode {
fmt.Print(GenerateCode(lang, args, fxrc))
if len(args) > 0 && args[0] == "--print-code" {
fmt.Print(GenerateCode(lang, args[1:]))
return
}
if lang == "js" {
simplePath, ok := SplitSimplePath(args)
if ok {
output := GetBySimplePath(object, simplePath)
Echo(output, theme)
os.Exit(0)
}
vm, fn, err := CreateJS(args, fxrc)
if err != nil {
fmt.Println(err)
os.Exit(1)
}
os.Exit(ReduceJS(vm, fn, object, theme))
} else {
os.Exit(Reduce(object, lang, args, theme, fxrc))
}
exitCode := Reduce(jsonObject, lang, args, theme)
os.Exit(exitCode)
}
// Start interactive mode.
expand := map[string]bool{"": true}
if array, ok := object.(Array); ok {
if array, ok := jsonObject.(Array); ok {
for i := range array {
expand[accessor("", i)] = true
}
@ -169,7 +119,7 @@ func main() {
parents := map[string]string{}
children := map[string][]string{}
canBeExpanded := map[string]bool{}
Dfs(object, func(it Iterator) {
Dfs(jsonObject, func(it Iterator) {
parents[it.Path] = it.Parent
children[it.Parent] = append(children[it.Parent], it.Path)
switch it.Object.(type) {
@ -179,13 +129,14 @@ func main() {
canBeExpanded[it.Path] = len(it.Object.(Array)) > 0
}
})
input := textinput.New()
input.Prompt = ""
m := &model{
fileName: fileName,
theme: theme,
json: object,
showSize: showSize,
json: jsonObject,
width: 80,
height: 60,
mouseWheelDelta: 3,
@ -200,6 +151,11 @@ func main() {
searchInput: input,
}
m.collectSiblings(m.json, "")
// TODO: delete after tea can reopen stdin.
// https://github.com/charmbracelet/bubbletea/issues/302
// time.Sleep(100 * time.Millisecond)
p := tea.NewProgram(m, tea.WithAltScreen(), tea.WithMouseCellMotion())
if err := p.Start(); err != nil {
panic(err)
@ -217,7 +173,6 @@ type model struct {
footerHeight int
wrap bool
theme Theme
showSize bool // Show number of elements in preview
fileName string
json interface{}

@ -2,23 +2,22 @@ package reducer
import (
_ "embed"
"encoding/json"
"fmt"
"strings"
. "github.com/antonmedv/fx/pkg/json"
. "github.com/antonmedv/fx/pkg/theme"
"github.com/dop251/goja"
)
//go:embed js.js
var templateJs string
func js(args []string, fxrc string) string {
func js(args []string) string {
rs := "\n"
for i, a := range args {
rs += " try {"
switch {
case a == ".":
rs += `
x = function ()
{ return this }
.call(x)
`
case flatMapRegex.MatchString(a):
code := fold(strings.Split(a, "[]"))
rs += fmt.Sprintf(
@ -63,36 +62,16 @@ func js(args []string, fxrc string) string {
rs += " }\n"
}
return fmt.Sprintf(templateJs, fxrc, rs)
}
fn := `function reduce(input) {
let x = JSON.parse(input)
func CreateJS(args []string, fxrc string) (*goja.Runtime, goja.Callable, error) {
vm := goja.New()
_, err := vm.RunString(js(args, fxrc))
if err != nil {
return nil, nil, err
}
fn, ok := goja.AssertFunction(vm.Get("reduce"))
if !ok {
panic("Not a function")
}
return vm, fn, nil
// Reducers %v
if (typeof x === 'undefined') {
return 'null'
} else {
return JSON.stringify(x)
}
}
func ReduceJS(vm *goja.Runtime, reduce goja.Callable, input interface{}, theme Theme) int {
value, err := reduce(goja.Undefined(), vm.ToValue(Stringify(input)))
if err != nil {
fmt.Println(err)
return 1
}
output := value.String()
dec := json.NewDecoder(strings.NewReader(output))
dec.UseNumber()
object, err := Parse(dec)
if err != nil {
fmt.Print(output)
return 0
}
Echo(object, theme)
return 0
`
return fmt.Sprintf(fn, rs)
}

@ -1,12 +0,0 @@
// .fxrc.js %v
function reduce(input) {
let x = JSON.parse(input)
// Reducers %v
if (typeof x === 'undefined') {
return 'null'
} else {
return JSON.stringify(x)
}
}

@ -9,8 +9,8 @@ import (
"strings"
)
func CreateNodejs(args []string, fxrc string) *exec.Cmd {
cmd := exec.Command("node", "--input-type=module", "-e", nodejs(args, fxrc))
func CreateNodejs(args []string) *exec.Cmd {
cmd := exec.Command("node", "--input-type=module", "-e", nodejs(args))
nodePath, exist := os.LookupEnv("NODE_PATH")
if exist {
cmd.Dir = path.Dir(nodePath)
@ -24,14 +24,21 @@ func CreateNodejs(args []string, fxrc string) *exec.Cmd {
return cmd
}
//go:embed node.js
var templateNode string
//go:embed reduce.js
var templateJs string
func nodejs(args []string, fxrc string) string {
func nodejs(args []string) string {
rs := "\n"
for i, a := range args {
rs += " try {"
switch {
case a == ".":
rs += `
x = function ()
{ return this }
.call(x)
`
case flatMapRegex.MatchString(a):
code := fold(strings.Split(a, "[]"))
rs += fmt.Sprintf(
@ -79,5 +86,13 @@ func nodejs(args []string, fxrc string) string {
rs += " }\n"
}
return fmt.Sprintf(templateNode, fxrc, rs)
fxrc := ""
home, err := os.UserHomeDir()
if err == nil {
b, err := os.ReadFile(path.Join(home, ".fxrc.js"))
if err == nil {
fxrc = "\n" + string(b)
}
}
return fmt.Sprintf(templateJs, fxrc, rs)
}

@ -11,18 +11,26 @@ func CreatePython(bin string, args []string) *exec.Cmd {
return cmd
}
//go:embed python.py
//go:embed reduce.py
var templatePython string
func python(args []string) string {
rs := "\n"
for i, a := range args {
rs += fmt.Sprintf(
`try:
rs += "try:"
switch {
case a == ".":
rs += `
x = x
`
default:
rs += fmt.Sprintf(
`
f = (lambda x: (%v))(x)
x = f(x) if callable(f) else f
`, a)
}
// Generate a beautiful error message.
rs += "except Exception as e:\n"
pre, post, pointer := trace(args, i)

@ -6,18 +6,21 @@ import (
"encoding/json"
"fmt"
"os/exec"
"strconv"
"strings"
. "github.com/antonmedv/fx/pkg/dict"
. "github.com/antonmedv/fx/pkg/json"
. "github.com/antonmedv/fx/pkg/theme"
"github.com/dop251/goja"
)
func GenerateCode(lang string, args []string, fxrc string) string {
func GenerateCode(lang string, args []string) string {
switch lang {
case "js":
return js(args, fxrc)
return js(args)
case "node":
return nodejs(args, fxrc)
return nodejs(args)
case "python", "python3":
return python(args)
case "ruby":
@ -27,17 +30,76 @@ func GenerateCode(lang string, args []string, fxrc string) string {
}
}
func Reduce(input interface{}, lang string, args []string, theme Theme, fxrc string) int {
path, ok := SplitSimplePath(args)
func Reduce(input interface{}, lang string, args []string, theme Theme) int {
// TODO: Move to separate function.
path, ok := split(args)
if ok {
output := GetBySimplePath(input, path)
Echo(output, theme)
for _, get := range path {
switch get := get.(type) {
case string:
switch o := input.(type) {
case *Dict:
input = o.Values[get]
case string:
if get == "length" {
input = Number(strconv.Itoa(len([]rune(o))))
} else {
input = nil
}
case Array:
if get == "length" {
input = Number(strconv.Itoa(len(o)))
} else {
input = nil
}
default:
input = nil
}
case int:
switch o := input.(type) {
case Array:
input = o[get]
default:
input = nil
}
}
}
echo(input, theme)
return 0
}
// TODO: Remove switch and this Reduce function.
var cmd *exec.Cmd
switch lang {
case "js":
vm := goja.New()
_, err := vm.RunString(js(args))
if err != nil {
fmt.Println(err)
return 1
}
// TODO: Do not evaluate reduce function on every message in stream.
sum, ok := goja.AssertFunction(vm.Get("reduce"))
if !ok {
panic("Not a function")
}
res, err := sum(goja.Undefined(), vm.ToValue(Stringify(input)))
if err != nil {
fmt.Println(err)
return 1
}
output := res.String()
dec := json.NewDecoder(strings.NewReader(output))
dec.UseNumber()
jsonObject, err := Parse(dec)
if err != nil {
fmt.Print(output)
return 0
}
echo(jsonObject, theme)
return 0
case "node":
cmd = CreateNodejs(args, fxrc)
cmd = CreateNodejs(args)
case "python", "python3":
cmd = CreatePython(lang, args)
case "ruby":
@ -63,12 +125,12 @@ func Reduce(input interface{}, lang string, args []string, theme Theme, fxrc str
dec := json.NewDecoder(bytes.NewReader(output))
dec.UseNumber()
object, err := Parse(dec)
jsonObject, err := Parse(dec)
if err != nil {
fmt.Print(string(output))
return 0
}
Echo(object, theme)
echo(jsonObject, theme)
if dec.InputOffset() < int64(len(output)) {
fmt.Print(string(output[dec.InputOffset():]))
}

@ -11,16 +11,25 @@ func CreateRuby(args []string) *exec.Cmd {
return cmd
}
//go:embed ruby.rb
//go:embed reduce.rb
var templateRuby string
func ruby(args []string) string {
rs := "\n"
for i, a := range args {
rs += fmt.Sprintf(
`begin
rs += "begin"
switch {
case a == ".":
rs += `
x = x
`
default:
rs += fmt.Sprintf(
`
x = lambda {|x| %v }.call(x)
`, a)
}
// Generate a beautiful error message.
rs += "rescue Exception => e\n"
pre, post, pointer := trace(args, i)

@ -3,9 +3,6 @@ package reducer
import (
"strconv"
"unicode"
. "github.com/antonmedv/fx/pkg/dict"
. "github.com/antonmedv/fx/pkg/json"
)
type state int
@ -24,7 +21,7 @@ const (
singleQuoteEscape
)
func SplitSimplePath(args []string) ([]interface{}, bool) {
func split(args []string) ([]interface{}, bool) {
path := make([]interface{}, 0)
for _, arg := range args {
s := ""
@ -179,37 +176,3 @@ func SplitSimplePath(args []string) ([]interface{}, bool) {
func isProp(ch rune) bool {
return unicode.IsLetter(ch) || unicode.IsDigit(ch) || ch == '_' || ch == '$'
}
func GetBySimplePath(object interface{}, path []interface{}) interface{} {
for _, get := range path {
switch get := get.(type) {
case string:
switch o := object.(type) {
case *Dict:
object = o.Values[get]
case string:
if get == "length" {
object = Number(strconv.Itoa(len([]rune(o))))
} else {
object = nil
}
case Array:
if get == "length" {
object = Number(strconv.Itoa(len(o)))
} else {
object = nil
}
default:
object = nil
}
case int:
switch o := object.(type) {
case Array:
object = o[get]
default:
object = nil
}
}
}
return object
}

@ -7,7 +7,7 @@ import (
"github.com/stretchr/testify/require"
)
func Test_splitPath(t *testing.T) {
func Test_split(t *testing.T) {
tests := []struct {
args []string
want []interface{}
@ -83,14 +83,14 @@ func Test_splitPath(t *testing.T) {
}
for _, tt := range tests {
t.Run(strings.Join(tt.args, " "), func(t *testing.T) {
path, ok := SplitSimplePath(tt.args)
path, ok := split(tt.args)
require.Equal(t, tt.want, path)
require.True(t, ok)
})
}
}
func Test_splitPath_negative(t *testing.T) {
func Test_split_negative(t *testing.T) {
tests := []struct {
args []string
}{
@ -130,7 +130,7 @@ func Test_splitPath_negative(t *testing.T) {
}
for _, tt := range tests {
t.Run(strings.Join(tt.args, " "), func(t *testing.T) {
path, ok := SplitSimplePath(tt.args)
path, ok := split(tt.args)
require.False(t, ok, path)
})
}

@ -9,7 +9,7 @@ import (
. "github.com/antonmedv/fx/pkg/theme"
)
func Echo(object interface{}, theme Theme) {
func echo(object interface{}, theme Theme) {
if s, ok := object.(string); ok {
fmt.Println(s)
} else {

@ -2,11 +2,10 @@ package main
import (
"fmt"
"strings"
. "github.com/antonmedv/fx/pkg/dict"
. "github.com/antonmedv/fx/pkg/json"
"github.com/antonmedv/fx/pkg/theme"
"strings"
)
func (m *model) connect(path string, lineNumber int) {
@ -118,61 +117,46 @@ func (m *model) preview(v interface{}, path string, selectableValues bool) strin
if selectableValues && m.cursorPath() == path {
previewStyle = m.theme.Cursor
}
printValue := func(v interface{}) string {
switch v := v.(type) {
printValue := func(value interface{}) string {
switch value.(type) {
case nil, bool, Number:
return previewStyle(fmt.Sprintf("%v", v))
return previewStyle(fmt.Sprintf("%v", value))
case string:
return previewStyle(fmt.Sprintf("%q", v))
return previewStyle(fmt.Sprintf("%q", value))
case *Dict:
if m.showSize {
return previewStyle(toLowerNumber(fmt.Sprintf("{\u2026%v\u2026}", len(v.Keys))))
} else {
return previewStyle("{\u2026}")
}
return previewStyle("{\u2026}")
case Array:
if m.showSize {
return previewStyle(toLowerNumber(fmt.Sprintf("[\u2026%v\u2026]", len(v))))
} else {
return previewStyle("[\u2026]")
}
return previewStyle("[\u2026]")
}
return "..."
}
switch v := v.(type) {
switch v.(type) {
case *Dict:
output := m.printOpenBracket("{", searchResult, path, selectableValues)
keys := v.Keys
keys := v.(*Dict).Keys
for _, k := range keys {
key := fmt.Sprintf("%q", k)
output += previewStyle(key + ": ")
value, _ := v.Get(k)
value, _ := v.(*Dict).Get(k)
output += printValue(value)
break
}
if len(keys) > 1 {
if m.showSize {
output += previewStyle(toLowerNumber(fmt.Sprintf(", \u2026%v\u2026", len(v.Keys)-1)))
} else {
output += previewStyle(", \u2026")
}
output += previewStyle(", \u2026")
}
output += m.printCloseBracket("}", searchResult, path, selectableValues)
return output
case Array:
output := m.printOpenBracket("[", searchResult, path, selectableValues)
for _, value := range v {
slice := v.(Array)
for _, value := range slice {
output += printValue(value)
break
}
if len(v) > 1 {
if m.showSize {
output += previewStyle(toLowerNumber(fmt.Sprintf(", \u2026%v\u2026", len(v)-1)))
} else {
output += previewStyle(", \u2026")
}
if len(slice) > 1 {
output += previewStyle(", \u2026")
}
output += m.printCloseBracket("]", searchResult, path, selectableValues)
return output

@ -1,5 +1,5 @@
name: fx
version: 24.0.0
version: 23.0.1
summary: Terminal JSON viewer
description: Terminal JSON viewer
base: core18

@ -8,29 +8,15 @@ import (
. "github.com/antonmedv/fx/pkg/json"
. "github.com/antonmedv/fx/pkg/reducer"
. "github.com/antonmedv/fx/pkg/theme"
"github.com/dop251/goja"
)
func stream(dec *json.Decoder, object interface{}, lang string, args []string, theme Theme, fxrc string) int {
var vm *goja.Runtime
var fn goja.Callable
func stream(dec *json.Decoder, jsonObject interface{}, lang string, args []string, theme Theme) int {
var err error
if lang == "js" {
vm, fn, err = CreateJS(args, fxrc)
if err != nil {
fmt.Println(err)
return 1
}
}
for {
if object != nil {
if lang == "js" {
ReduceJS(vm, fn, object, theme)
} else {
Reduce(object, lang, args, theme, fxrc)
}
if jsonObject != nil {
Reduce(jsonObject, lang, args, theme)
}
object, err = Parse(dec)
jsonObject, err = Parse(dec)
if err == io.EOF {
return 0
}

@ -2,8 +2,6 @@ package main
import (
"fmt"
"strings"
"github.com/charmbracelet/lipgloss"
)
@ -35,16 +33,3 @@ func width(s string) int {
func accessor(path string, to interface{}) string {
return fmt.Sprintf("%v[%v]", path, to)
}
func toLowerNumber(s string) string {
var out strings.Builder
for _, r := range s {
switch {
case '0' <= r && r <= '9':
out.WriteRune('\u2080' + (r - '\u0030'))
default:
out.WriteRune(r)
}
}
return out.String()
}

@ -1,3 +1,3 @@
package main
const version = "24.0.0"
const version = "23.0.1"

Loading…
Cancel
Save