Giter Site home page Giter Site logo

borkdude / jet Goto Github PK

View Code? Open in Web Editor NEW
654.0 16.0 40.0 359 KB

CLI to transform between JSON, EDN, YAML and Transit using Clojure

License: Eclipse Public License 1.0

Clojure 83.73% Shell 13.87% Batchfile 1.24% Java 1.16%
json edn transit cli converter clojure graalvm yaml

jet's Introduction

CircleCI Clojars Project cljdoc badge

CLI to transform between JSON, EDN, YAML and Transit using Clojure.

Quickstart

$ bash < <(curl -s https://raw.githubusercontent.com/borkdude/jet/master/install)
$ echo '{:a 1}' | jet --to json
{"a":1}

Rationale

This is a command line tool to transform between JSON, EDN and Transit using Clojure. It runs as a GraalVM binary with fast startup time which makes it suited for shell scripting. It may seem familiar to users of jq.

Installation

Brew

Linux and macOS binaries are provided via brew.

Install:

brew install borkdude/brew/jet

Upgrade:

brew upgrade jet

Windows

On Windows you can install using scoop and the scoop-clojure bucket.

Installer script

Install via the installer script:

$ bash <(curl -s https://raw.githubusercontent.com/borkdude/jet/master/install)

By default this will install into /usr/local/bin. To change this, provide the directory name:

$ bash <(curl -s https://raw.githubusercontent.com/borkdude/jet/master/install) /tmp

Download

You may also download a binary from Github.

JVM

Leiningen

This tool can also be used via the JVM. If you use leiningen, you can put the following in your .lein/profiles:

{:user
 {:dependencies [[borkdude/jet "0.4.23"]]
  :aliases {"jet" ["run" "-m" "jet.main"]}}}

And then call jet like:

$ echo '["^ ","~:a",1]' | lein jet --from transit --to edn
{:a 1}

Deps.edn

In deps.edn:

:jet {:deps {borkdude/jet {:mvn/version "0.4.23"}}
      :exec-fn jet.main/exec
      :main-opts ["-m" "jet.main"]}

You can use both the -M and -X style invocation, whichever you prefer:

$ echo '[1 2 3]' | clj -M:jet --colors --func '#(-> % first inc)'
2

$ echo '[1 2 3]' | clj -X:jet :colors true :thread-last '"(map inc)"'
(2 3 4)

Or install jet as a clj tool:

$ clojure -Ttools install-latest :lib io.github.borkdude/jet :as jet

$ echo '[1 2 3]' | clj -Tjet exec :colors true :func '"#(-> % first inc)"'
2

Usage

jet supports the following options:

  -i, --from            [ edn | transit | json | yaml ] defaults to edn.
  -o, --to              [ edn | transit | json | yaml ] defaults to edn.
  -t, --thread-last                                     implicit thread last
  -T, --thread-first                                    implicit thread first
  -f, --func                                            a single-arg Clojure function, or a path to a file that contains a function, that transforms input.
      --no-pretty                                       disable pretty printing
  -k, --keywordize      [ <key-fn> ]                    if present, keywordizes JSON/YAML keys. The default transformation function is keyword unless you provide your own.
      --colors          [ auto | true | false]          use colored output while pretty-printing. Defaults to auto.
      --edn-reader-opts                                 options passed to the EDN reader.
      --no-commas                                       remove commas from EDN
  -c, --collect                                         given separate values, collects them in a vector.
  -h, --help                                            print this help text.
  -v, --version                                         print the current version of jet.
  -q, --query                                           DEPRECATED, prefer -t, -T or -f. Given a jet-lang query, transforms input.

Transform EDN using --thread-last, --thread-first or --func.

Examples:

$ echo '{"a": 1}' | jet --from json --to edn
{"a" 1}

$ echo '{"a": 1}' | jet -i json --keywordize -o edn
{:a 1}

$ echo '{"my key": 1}' | jet -i json -k '#(keyword (str/replace % " " "_"))' -o edn
{:my_key 1}

$ echo '{"anApple": 1}' | jet -i json -k '#(-> % csk/->kebab-case keyword)' -o edn
{:an-apple 1}

$ echo '{"a": 1}' | jet -i json -o yaml
a: 1

$ echo '{"a": 1}' | jet -i json -o transit
["^ ","a",1]

$ echo '{:a {:b {:c 1}}}' | jet --thread-last ':a :b :c'
1

$ echo '{:a {:b {:c 1}}}' | jet --func '#(-> % :a :b :c)'
1

$ echo '{:a {:b {:c [1 2]}}}' | jet -t ':a :b :c (map inc)'
(2 3)

$ cat /tmp/fn.clj
#(-> % :a :b :c)
$ echo '{:a {:b {:c 1}}}' | jet --func /tmp/fn.clj
1

$ echo '{:a {:a 1}}' | ./jet -t '(s/transform [s/MAP-VALS s/MAP-VALS] inc)'
{:a {:a 2}}

Raw output

Get raw output from query rather than wrapped in quotes:

$ echo '{"a": "hello there"}' | jet --from json --keywordize -t ":a" --to edn
"hello there"

$ echo '{"a": "hello there"}' | jet --from json --keywordize -t ":a symbol" --to edn
hello there

or simply use println to get rid of the quotes:

$ echo '{"a": "hello there"}' | jet --from json --keywordize -t ":a println" --to edn
hello there

Data readers

You can enable data readers by passing options to --edn-reader-opts:

$ echo '#foo{:a 1}' | jet --edn-reader-opts '{:default tagged-literal}'
#foo {:a 1}
$ echo '#foo{:a 1}' | jet --edn-reader-opts "{:readers {'foo (fn [x] [:foo x])}}"
[:foo {:a 1}]

See this blog by Alex Miller for more information on the tagged-literal function.

Since jet 0.0.14 --edn-reader-opts defaults to {:default tagged-literal}.

Streaming

Jet supports streaming over multiple values, without reading the entire input into memory:

$ echo '{"a": 1} {"a": 1}' | jet --from json --keywordize -t ':a' --to edn
1
1

When you want to collect multiple values into a vector, you can use --collect:

$ echo '{"a": 1} {"a": 1}' | lein jet --from json --keywordize --collect --to edn
[{:a 1} {:a 1}]

Specter

As of version 0.2.18 the specter library is available in --func, --thread-first and --thread-last:

$ echo '{:a {:a 1}}' | ./jet -t '(s/transform [s/MAP-VALS s/MAP-VALS] inc)'
{:a {:a 2}}

Base64

To encode and decode base64 you can use base64/encode and base64/decode.

Jet utility functions

In the jet namespace, the following utilities are available:

paths

Return all paths (and sub-paths) in maps and vectors. Each result is a map of :path and :val (via get-in).

$ echo '{:a {:b [1 2 3 {:x 2}] :c {:d 3}}}' | jet -t '(jet/paths)'
[{:path [:a], :val {:b [1 2 3 {:x 2}], :c {:d 3}}}
 {:path [:a :b], :val [1 2 3 {:x 2}]}
 {:path [:a :c], :val {:d 3}}
 {:path [:a :b 0], :val 1}
 {:path [:a :b 1], :val 2}
 {:path [:a :b 2], :val 3}
 {:path [:a :b 3], :val {:x 2}}
 {:path [:a :b 3 :x], :val 2}
 {:path [:a :c :d], :val 3}]

when-pred

Given a predicate, return predicate that returns the given argument when predicate was truthy. In case of an exception during the predicate call, catches and returns nil.

The following returns all paths for which the leafs are odd numbers:

$ echo '{:a {:b [1 2 3 {:x 2}] :c {:d 3}}}' | jet -t '(jet/paths) (filter (comp (jet/when-pred odd?) :val)) (mapv :path)'
[[:a :b 0] [:a :b 2] [:a :c :d]]

Emacs integration

Sometimes it's useful to reformat REPL output in Emacs to make it more readable, copy to clipboard or just pretty-print to another buffer. All of that is avaiable in the jet.el package.

Vim integration

To convert data in vim buffers, you can select the data you want to convert in visual mode, then invoke jet by typing for example :'<,'>!jet -k --from json (vim will insert the '<,'> for you as you type).

Test

Test the JVM version:

script/test

Test the native version:

JET_TEST_ENV=native script/test

Build

You will need leiningen and GraalVM.

script/compile

License

Copyright ยฉ 2019-2023 Michiel Borkent

Distributed under the EPL License, same as Clojure. See LICENSE.

jet's People

Contributors

aschofie avatar borkdude avatar cesarolea avatar dependabot[bot] avatar dotemacs avatar ericdallo avatar evanlouie avatar holyjak avatar iarenaza avatar jeremyf avatar kannangce avatar kolharsam avatar kovasap avatar lread avatar oddsor avatar pieterbreed avatar qdzo avatar reedho avatar reutsharabani avatar rlhk avatar sdledesma avatar sogaiu avatar stathissideris avatar stelcodes avatar victorb avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

jet's Issues

Add --help Option

Great stuff.

If possible, can you add a --help option that will output the "Usage" section from the README?

For background, I'd installed via homebrew a while ago, but didn't actually use it until yesterday. I first tried to see what would happen if I just typed jet or jet --help, but it seems that it dropped it into interactive mode, so had to visit the website to check how to use. Would be nice to have the options handy right at the command line.

Thanks!

--pretty doesn't preserve the order of the keys

It seems when using jet --pretty on a larger (than just a few items) maps results with keys getting out of order.

e.g.: use this trivial map with some "lorem ipsum" key/vals

{:aam       :euismod
 :bellus    :id
 :crat      :donec
 :ditae     :dolor.
 :ehasellus :at
 :fui       :in
 :gigula    :mollis
 :hltricies :mauris
 :iollis    :tincidunt}

Notice the keys are sorted. But after running jet --pretty:

{:ehasellus :at,
 :crat :donec,
 :ditae :dolor.,
 :fui :in,
 :bellus :id,
 :hltricies :mauris,
 :iollis :tincidunt,
 :aam :euismod,
 :gigula :mollis}

The keys are no longer sorted. Optionally, it would be nice if --pretty by default did not add the commas

add --collect option

The --collection option will read as many EDN, JSON or Transit values as it can from stdin and collect them into a vector of values.

Add CSV input/output

CSV is still a very common format, and it would be great to be able to easily feed a CSV file into bb for instance.

Unfortunately it's not terribly well standardized when it comes to quoting, and line and cell separators, and there might have to be some detection of a Byte Order Mark at the start.

clojure/data.csv would be the obvious choice, using its defaults for a csv format, and maybe also adding a tsv format with commas instead of tabs.

Thank you!

I just used Jet for the first time, and it was super handy. Thank you for the excellent tool!!!!

Implement assoc

echo '{:a {:a 1 :b 2}}' | jet --from edn --to edn --query '(assoc :b [:a :a])'
{:a ... :b 1}

document jq tutorial example

The last step from the jq tutorial:

$ curl -s 'https://api.github.com/repos/stedolan/jq/commits?per_page=5' | \
jet --from json --keywordize --to edn --pretty --query '
(map
  (hash-map
    :message [:commit :message]
    :name [:commit :committer :name]
    :parents [:parents (map :html_url)]))' 

JSON -> EDN with --keywordize produces invalid EDN when keys have spaces

If you pass the --keywordize option, converting json to edn can produce invalid EDN:

% echo '{"a b": "c"}' | jet --from json --keywordize --to edn --pretty
{:a b "c"}
% echo '{"a b": "c"}' | jet --from json --keywordize --to edn --pretty | jet --from edn --to json
Exception in thread "main" java.lang.RuntimeException: Map literal must contain an even number of forms
	at clojure.lang.Util.runtimeException(Util.java:221)
	at clojure.lang.EdnReader$MapReader.invoke(EdnReader.java:682)
	at clojure.lang.EdnReader.read(EdnReader.java:145)
	at clojure.lang.EdnReader.read(EdnReader.java:111)
	at clojure.lang.EdnReader.readString(EdnReader.java:67)
	at clojure.edn$read_string.invokeStatic(edn.clj:46)
	at clojure.edn$read_string.invokeStatic(edn.clj:37)
	at jet.main$_main.invokeStatic(main.clj:51)
	at jet.main$_main.doInvoke(main.clj:44)
	at clojure.lang.RestFn.applyTo(RestFn.java:137)
	at jet.main.main(Unknown Source)

I'm not sure what the right solution here is: the strategies that occur to me are preserving json keys as strings if they contain invalid characters for keywords or applying some kind of deterministic transformation to the json keys (e.g. " " -> "-" and "-" -> "--") to turn them into valid edn keywords.

Clarify EDN, Transit, and JSON

In reading the README, I was interested in learning more, especially encoding functions and evaluating them.

I know what JSON is, I do not have know what EDN and Transit formats are. Can you provide a URL or definition to reference.

Support multiline "streams" of data?

I have a use-case that I would like to use jet for. I have a file that has one edn form per line:

$ cat lines.edn 
{:bar something, :foo 63}
{:bar something, :foo 94}
{:bar something, :foo 19}

I would like to have a file with each line transformed into JSON, like this:

{"bar":"something","foo":63}
{"bar":"something","foo":94}
{"bar":"something","foo":19}

However, as of now, jet only handles the first form in the file:

$ cat lines.edn | jet --to json
{"bar":"something","foo":63}

What do you think of supporting multiple forms?

Extension to YAML

Hello,

I think babashka supports yaml now, do you think it would make sense to extend the format to YAML? It might be useful for everyone working with k8s or ansible.

Best regards,
David

Read function from file, via `-F`?

I went ahead and implemented a comparison of jq tutorial in jet:

https://gist.github.com/dotemacs/6c9185cca3cf59486b55471b1e965ed5

If you look at the last example:

https://gist.github.com/dotemacs/6c9185cca3cf59486b55471b1e965ed5#add-the-parent-commit-urls-to-the-above

writing that query at the shell prompt, without paren completion/matching, is quickly going to become cumbersome/error prone.

What do you think of the idea to add the switch -F (and something appropriate in long format e.g. --fn-from-file), which would read a function from file?

Why would you want to write a function in a file?

Arguably, your $EDITOR has probably the best support for paren matching/completion. So that way you can write the function in a file and the pass it in as a command line option e.g.

cat foo.json | jet -i json -o json -F /tmp/fn.clj

Just an idea, what do you think?

Thanks

more consistent behavior of compare functions

(filter (not= ["Status" "Name"] "Active"))

should be written as:

(filter (not= ["Status" "Name"] #jet/lit "Active"))

Also you should be able to use compare functions outside of filter and remove, just for producing boolean values.

Request: Add uberjar to release artifacts

I'm trying to package Jet for NixOS and it's pretty tricky without an uberjar because NixOS doesn't allow the build process to have network access, so libraries cannot be downloaded when building. The prebuilt Linux executable also doesn't work because of missing dynamically linked libraries. Having an uberjar available makes it possible to build a GraalVM binary pretty straightforwardly. That's how Babashka is packaged and how I recently packaged zprint.

So my request is for a jet uberjar to be added to the releases. How does that sound? I'm not familiar with this codebase so I understand there may be complications. Cheers!

Support XML?

I know it's out of fashion, but maybe supporting XML would be interesting in coms contexts. There is of course the question of whether you'd convert it to hiccup-style structures or data.xml-style.

add identity function

This is useful for when you want to copy the previous value entirely, e.g. in an if/else

Conversion from json to edn without keywordize should produce string keys

Hi! Thanks for the nice useful command line utility!

I would like to propose a couple of improvements.

Example from the docs produces symbol keys which is really inconvenient. You can't even copy & paste it to use in code/repl without introducing quoting.

$ echo '{"a": 1}' | jet --from json --to edn --keywordize false
{a 1}

I'd say it should output string keys, i.e. {"a" 1}.

Also it would be nice if keywordize was true by default since edn maps usually use keywords as keys.

What do you think about these?

xml support?

Hi is there a plan to support xml apart from json, edn, transit?
Or should I use Babashka instead?

Thanks.

Long options with '=' are not parsed

It seems the long-form command-line options don't work in the --option=value way, only as --option value.

Thanks for this tool!

$ echo '{"a": 1}' | jet --from json
{"a" 1}
$ echo '{"a": 1}' | jet --from=json
Exception in thread "main" clojure.lang.EdnReader$ReaderException: java.lang.RuntimeException: Invalid token: :
        at clojure.lang.EdnReader.read(EdnReader.java:180)
        at clojure.lang.EdnReader.read(EdnReader.java:111)
        at clojure.edn$read.invokeStatic(edn.clj:35)
        at jet.formats$parse_edn.invokeStatic(formats.clj:29)
        at jet.main$_main$fn__10629.invoke(main.clj:114)
        at jet.main$_main.invokeStatic(main.clj:121)
        at jet.main$_main.doInvoke(main.clj:97)
        at clojure.lang.RestFn.applyTo(RestFn.java:137)
        at jet.main.main(Unknown Source)
Caused by: java.lang.RuntimeException: Invalid token: :
        at clojure.lang.Util.runtimeException(Util.java:221)
        at clojure.lang.EdnReader.interpretToken(EdnReader.java:285)
        at clojure.lang.EdnReader.read(EdnReader.java:171)
        at clojure.lang.EdnReader.readDelimitedList(EdnReader.java:766)
        at clojure.lang.EdnReader$MapReader.invoke(EdnReader.java:680)
        at clojure.lang.EdnReader.read(EdnReader.java:145)
        ... 8 more

Implement hash-map

Not sure about the name yet, but this would create a new map with the keys + query results attached.

echo '{:a {:a 1 :b 2} :b {:a 1}}' | jet --from edn --to edn --query '(hash-map :x [:a :a])'
{:x 1}

Sort keys in a map

I'm sorry, I can't figure this out. If you give me some pointers how to do this, I will send a PR for documentation update. I'm trying this and it's not working:

echo '{:b 2 :c 3 :a 1}' | jet --query '(into (sorted-map) #jet/lit)'

I'm sure at least a few people would find this functionality useful. Thanks!

Maybe cut a new tag?

Hey @borkdude

Thanks for adding the short switch options.

Can you please tag the latest master so that it can be picked up by brew upgrade jet?
Because it currently sees the v0.0.13 tag, from August 2020.

Thanks

What do you think of a config file?

How do you feel about jet having a config file where certain default options/preferences can be specified?

What's the benefit?

For example input and out formats by default are EDN. But what if you happen to work with JSON more as the default input format, could there be a way to specify that the default format should be JSON?
One way I figured that this could be implemented is by having a config file where you could specify the default input format.

It could be something like ~/.jet.edn the config could look like:

{:from :json
 :to :json}

So that instead of typing:

echo "some JSON payload" | jet -i json -f "#(foo bar %)" -t json

it could be typed as:

echo "some JSON payload" | jet -f "#(foo bar %)"

Where the :from & :to options would be read from ~/.jet.edn.

Fix >

Example:

$ echo '[{:a 1 :b 2} {:a 1 :b 3}]' | jet --query "(filter (> :b #jet/lit 2))"
[]

Make --interactive aware of stdin

Currently --interactive bypass processing of stdin. It would be nice if we can start interactive session from e.g. clipboard as initial value: pbpaste | jet --from json --interactive.

I would love to provide patch if You think this is good.

Thank you.

Can `-q` be added for `--query`?

Is there a reason that -q wasn't added for --query?

I'd be happy to add it, mostly because it's simpler to type -q on the command line instead of --query.

Let me know how you feel about it @borkdude

Thanks

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.