├── .gitignore ├── bin ├── jq-paths └── jq-repl ├── jq.plugin.zsh ├── LICENSE └── README.md /.gitignore: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /bin/jq-paths: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | # path logic inspired by https://github.com/stedolan/jq/issues/243 3 | JQ_REPL_JQ="${JQ_REPL_JQ:-jq}" 4 | $JQ_REPL_JQ -r ' 5 | [ 6 | path(..) | 7 | map( 8 | # use generic object index syntax if key contains non-alphanumeric characters or starts with a digit 9 | select(type == "string" and (test("[^a-zA-Z0-9_]") or test("^[0-9]"))) |= "[\"" + . + "\"]" 10 | ) | 11 | map( 12 | # numbers are assumed to be array indexes only 13 | select(type == "number") |= "[]" 14 | ) | join(".") 15 | ] | sort | unique | .[] | split(".[") | join("[") | "." + . 16 | ' 17 | -------------------------------------------------------------------------------- /jq.plugin.zsh: -------------------------------------------------------------------------------- 1 | if [[ -o zle ]]; then 2 | 3 | __get_query() { 4 | if [ "${JQ_ZSH_PLUGIN_EXPAND_ALIASES:-1}" -eq 1 ]; then 5 | unset 'functions[_jq-plugin-expand]' 6 | functions[_jq-plugin-expand]=${LBUFFER} 7 | (($+functions[_jq-plugin-expand])) && COMMAND=${functions[_jq-plugin-expand]#$'\t'} 8 | jq-repl -- ${COMMAND} 9 | return $? 10 | else 11 | jq-repl -- ${LBUFFER} 12 | return $? 13 | fi 14 | } 15 | 16 | jq-complete() { 17 | local query="$(__get_query)" 18 | local ret=$? 19 | if [ -n "$query" ]; then 20 | LBUFFER="${LBUFFER} | ${JQ_REPL_JQ:-jq}" 21 | [[ -z "$JQ_REPL_ARGS" ]] || LBUFFER="${LBUFFER} ${JQ_REPL_ARGS}" 22 | LBUFFER="${LBUFFER} '$query'" 23 | fi 24 | zle reset-prompt 25 | return $ret 26 | } 27 | 28 | zle -N jq-complete 29 | # bind `alt + j` to jq-complete 30 | bindkey '\ej' jq-complete 31 | fi 32 | 33 | export PATH=$PATH:${0:A:h}/bin 34 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020 Zoltán Reegn 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /bin/jq-repl: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | # 3 | # if 1st arg is '-' read from stdin 4 | # if 1st arg is '--' take the command after it verbatim and execute it to get the input 5 | # if 1st arg is anything else, treat it as a file 6 | set -eu 7 | 8 | JQ_REPL_JQ="${JQ_REPL_JQ:-jq}" 9 | 10 | if [ -n "${1:-}" ] && [ "$1" != "-" ] && [ "$1" != "--" ]; then 11 | input="$1" 12 | else 13 | input=$(mktemp) 14 | trap 'rm -f "$input"' EXIT 15 | fi 16 | 17 | if [ -z "${1:-}" ] || [ "$1" = "-" ]; then 18 | cat /dev/stdin >"$input" 19 | fi 20 | if [ "${1:-}" = "--" ]; then 21 | shift 22 | export FZF_JQ_REPL_COMMAND="$* > $input; jq-paths <$input" 23 | else 24 | export FZF_JQ_REPL_COMMAND="jq-paths < \"$input\"" 25 | fi 26 | 27 | fzf_notation_types="~!@#$%^&*;/|" 28 | skip_notation="$(echo "$FZF_JQ_REPL_COMMAND" | sed -E "s,[^$fzf_notation_types],,g")" 29 | fzf_notation="$(echo "$fzf_notation_types" | sed -E "s,[$skip_notation],,g" | head -c 1)" 30 | if [ -z "$fzf_notation" ]; then 31 | # fall back to default and hope for the best 32 | fzf_notation="#" 33 | fi 34 | 35 | eval "$FZF_JQ_REPL_COMMAND" | 36 | fzf \ 37 | --preview "$JQ_REPL_JQ --color-output ${JQ_REPL_ARGS:-} {q} \"$input\"" \ 38 | --preview-window="down:90%" \ 39 | --height="99%" \ 40 | --query="." \ 41 | --bind "tab:replace-query,return:print-query" \ 42 | --bind "alt-up:preview-page-up,alt-down:preview-page-down" \ 43 | --bind "ctrl-r:reload${fzf_notation}${FZF_JQ_REPL_COMMAND}${fzf_notation}+refresh-preview" 44 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # jq zsh plugin 2 | 3 | Interactively build [jq](https://stedolan.github.io/jq/) expressions 4 | ([gojq](https://github.com/itchyny/gojq) is also supported). 5 | 6 | This zsh plugin gives you jq superpowers! 7 | 8 | ## Table of contents 9 | 10 | - [Demos](#demos) 11 | - [Installation](#installation) 12 | - [Usage](#usage) 13 | - [Key bindings](#key-bindings) 14 | 15 | ## Demos 16 | 17 | ### Interactive jq query construction 18 | 19 | [![asciicast](https://asciinema.org/a/IqAqzPS0ZgeaduQ3qs1B5ZgRI.svg)](https://asciinema.org/a/IqAqzPS0ZgeaduQ3qs1B5ZgRI) 20 | 21 | ### Insert jq query in the middle of a pipeline 22 | 23 | [![asciicast](https://asciinema.org/a/9Q4Va21OzD2VTbHwntmLWGvm6.svg)](https://asciinema.org/a/9Q4Va21OzD2VTbHwntmLWGvm6) 24 | 25 | ## Installation 26 | 27 | Besides [jq](https://stedolan.github.io/jq/), this plugin also requires 28 | [fzf](https://github.com/junegunn/fzf#installation) ([a recent version](https://github.com/reegnz/jq-zsh-plugin/issues/19)) to be installed and available on your 29 | PATH. 30 | 31 | The following installation methods are proven to work: 32 | 33 | * [Oh My Zsh](#oh-my-zsh) 34 | * [zplug](#zplug) 35 | * [Antigen](#antigen) 36 | * [Zgen](#zgen) 37 | 38 | ### [Oh My Zsh](https://ohmyz.sh) 39 | 40 | 1. Clone the repository: 41 | 42 | ```sh 43 | git clone https://github.com/reegnz/jq-zsh-plugin.git ${ZSH_CUSTOM:-~/.oh-my-zsh/custom}/plugins/jq 44 | ``` 45 | 46 | 2. Add the plugin to the list of plugins for Oh My Zsh to load (inside `~/.zshrc`): 47 | 48 | ```sh 49 | plugins=( 50 | # other plugins... 51 | jq 52 | ) 53 | ``` 54 | 55 | 3. Start a new terminal session. 56 | 57 | ### [zplug](https://github.com/zplug/zplug) 58 | 59 | ```sh 60 | zplug reegnz/jq-zsh-plugin 61 | ``` 62 | 63 | ### [Antigen](https://github.com/zsh-users/antigen) 64 | 65 | ```sh 66 | antigen bundle reegnz/jq-zsh-plugin 67 | ``` 68 | 69 | ### [Zgen](https://github.com/tarjoilija/zgen) 70 | 71 | ```sh 72 | zgen load reegnz/jq-zsh-plugin 73 | ``` 74 | 75 | ## Usage 76 | 77 | - type out a command that you expect to produce json on its standard output 78 | - press alt + j 79 | - start typing jq expression and watch it being evaluated in real time (like a true [REPL](https://en.wikipedia.org/wiki/Read%E2%80%93eval%E2%80%93print_loop)!) 80 | - use up/down and hit tab to select one of the suggestions 81 | - or type out a jq query on your own 82 | - press enter, and the jq expression is appended to your initial command! 83 | 84 | ## Key bindings 85 | 86 | Bringing up the jq query builder for a shell command: `alt + j` 87 | 88 | During interactive querying, the following shortcuts can be used: 89 | 90 | | Shortcut | Effect | 91 | | ------ | -------- | 92 | | `up` | Navigate path queries | 93 | | `down` | Navigate path queries | 94 | | `tab` | Select path query | 95 | | `shift + up` | Scroll up | 96 | | `shift + down` | Scroll down | 97 | | `alt + up` | Scroll up full page | 98 | | `alt + down` | Scroll down full page | 99 | | `ctrl+r` | Reload input | 100 | 101 | ## gojq support 102 | 103 | If you want to use an alternative `jq` implementation, like 104 | [gojq](https://github.com/itchyny/gojq) then you can override the default jq 105 | command used by the plugin. Set the following environment variable: 106 | 107 | ```sh 108 | JQ_REPL_JQ=gojq 109 | ``` 110 | 111 | ## Internals 112 | 113 | The project consists of the following components: 114 | 115 | - a `jq.plugin.zsh` providing a [user-defined zsh line-editor 116 | widget](https://zsh.sourceforge.io/Doc/Release/Zsh-Line-Editor.html), 117 | utilizing the `jq-repl` command 118 | - a `jq-repl` command to interactively build jq expressions, utilizing fzf for 119 | its UI 120 | - a `jq-paths` command to get all valid jq paths in the provided JSON document, 121 | used for suggesting paths. 122 | 123 | ## Troubleshooting 124 | 125 | ### MacOS: Pressing alt-j creates a `∆` symbol in iTerm2 126 | 127 | You need to remap your alt-key to `Esc+` in iTerm2: 128 | 129 | - `Cmd + ,` to enter preferences 130 | - Go to Profiles 131 | - select your profile from the pane on the left hand side 132 | - go to the keys tab 133 | - Set Left Option (⌥ ) Key to `Esc+` 134 | 135 | See other suggestions on stackoverflow if the above one doesn't help you: 136 | https://stackoverflow.com/q/196357/205318 137 | 138 | Another option is to map to `ctrl+j` instead by putting this in your `.zshrc`: 139 | 140 | ```sh 141 | bindkey `^j` jq-complete 142 | ``` 143 | 144 | ### Disable expanding shell aliases 145 | 146 | The plugin automatically expands shell aliases in a command before passing it 147 | to `jq-repl`. To disable, put the following line into your `.zshrc`: 148 | 149 | ```sh 150 | JQ_ZSH_PLUGIN_EXPAND_ALIASES=0 151 | ``` 152 | --------------------------------------------------------------------------------