Skip to content

Commit 22719de

Browse files
authored
refactor: create a separate namespace (#20)
* refactor: cleanup lib and docs
1 parent 618f324 commit 22719de

File tree

13 files changed

+265
-33
lines changed

13 files changed

+265
-33
lines changed

Diff for: .github/workflows/release.yml

+2-2
Original file line numberDiff line numberDiff line change
@@ -67,7 +67,7 @@ jobs:
6767
- name: Install clojure tools-deps
6868
uses: DeLaGuardo/setup-clojure@master
6969
with:
70-
tools-deps: 1.10.3.956
70+
tools-deps: 1.10.3.967
7171

7272
- name: Compile uberjar
7373
run: |
@@ -201,7 +201,7 @@ jobs:
201201
- name: Install clojure tools-deps
202202
uses: DeLaGuardo/setup-clojure@master
203203
with:
204-
tools-deps: 1.10.3.956
204+
tools-deps: 1.10.3.967
205205

206206
- name: Publish lib to Clojars
207207
run: |

Diff for: .github/workflows/test.yml

+1-1
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,7 @@ jobs:
3434
- name: Install clojure tools-deps
3535
uses: DeLaGuardo/setup-clojure@master
3636
with:
37-
tools-deps: 1.10.3.956
37+
tools-deps: 1.10.3.967
3838

3939
- name: Unit Tests
4040
run: clojure -A:dev:test

Diff for: CHANGELOG.md

+3
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,9 @@ For a list of breaking changes, check [here](#breaking-changes).
66

77
## New
88

9+
- introduce `jq.api` namespace
10+
- deprecate `jq.core` namespace
11+
912
## Breaking changes
1013

1114
## Unreleased

Diff for: Makefile

+4-3
Original file line numberDiff line numberDiff line change
@@ -4,11 +4,11 @@ test:
44

55
.PHONY: lint
66
lint:
7-
clojure -M:clj-kondo
7+
clojure -M:clj-kondo --lint build cli src test
88

99
.PHONY: check-deps
1010
check-deps:
11-
clojure -Sdeps '{:deps {antq/antq {:mvn/version "RELEASE"}}}' -M -m antq.core
11+
clojure -Sdeps '{:deps {com.github.liquidz/antq {:mvn/version "RELEASE"} org.slf4j/slf4j-nop {:mvn/version "RELEASE"}}}' -M -m antq.core
1212

1313
.PHONY: uberjar
1414
uberjar:
@@ -26,4 +26,5 @@ native-image: uberjar
2626

2727
.PHONY: static-native-image
2828
static-native-image: uberjar
29-
CLJ_JQ_STATIC=true ./script/compile
29+
./script/setup-musl
30+
PATH=$$HOME/.musl/bin:$$PATH CLJ_JQ_STATIC=true CLJ_JQ_MUSL=true ./script/compile

Diff for: README.md

+32-13
Original file line numberDiff line numberDiff line change
@@ -4,15 +4,20 @@
44

55
# clj-jq
66

7-
A library to execute `jq` scripts on JSON data. It is a thin wrapper around [jackson-jq](https://github.com/eiiches/jackson-jq).
7+
A library to execute [`jq`](https://stedolan.github.io/jq/) scripts on JSON data within a Clojure application.
8+
It is a thin wrapper around [jackson-jq](https://github.com/eiiches/jackson-jq):
9+
a pure Java `jq` Implementation for Jackson JSON Processor.
810

911
Available `jq` functions can be found [here](https://github.com/eiiches/jackson-jq#implementation-status-and-current-limitations).
1012

13+
This library is compatible with the GraalVM `native-image`.
14+
1115
## Alternatives
1216

1317
There is another `jq` library for Clojure: [clj-jq](https://github.com/BrianMWest/clj-jq).
14-
This library works by shelling-out to an embedded `jq`. The problem is that it has costs for
15-
every invocation. Also, it creates difficulties to use this library with the GraalVM native-image.
18+
This library works by shelling-out to an embedded `jq` instance.
19+
The problem with this approach is that it has fixed costs for every invocation.
20+
Also, it creates difficulties to use this library with the GraalVM native-image.
1621

1722
## Use cases
1823

@@ -21,7 +26,7 @@ The library intends to be used for stream processing.
2126
### Compiling a script to execute it multiple times
2227

2328
```clojure
24-
(require '[jq.core :as jq])
29+
(require '[jq.api :as jq])
2530

2631
(let [data "[1,2,3]"
2732
query "map(.+1)"
@@ -37,7 +42,7 @@ Or inline:
3742
=> "[2,3,4]"
3843
```
3944

40-
### One of script execution
45+
### One-off script execution
4146

4247
```clojure
4348
(let [data "[1,2,3]"
@@ -46,20 +51,34 @@ Or inline:
4651
=> "[2,3,4]"
4752
```
4853

54+
## How to join multiple scripts together
55+
56+
Joining `jq` scripts is as simple as "piping" output of one script to another:
57+
join `jq` script strings with `|` character.
58+
59+
```clojure
60+
(let [data "[1,2,3]"
61+
script-inc "map(.+1)"
62+
script-reverse "reverse"
63+
query (clojure.string/join " | " [script-inc script-reverse])]
64+
(jq/execute data query))
65+
=> "[4,3,2]"
66+
```
67+
4968
## Performance
5069

5170
```clojure
5271
(use 'criterium.core)
53-
(let [jq-script (time (jq.core/processor ".a[] |= sqrt"))]
72+
(let [jq-script (time (jq/processor ".a[] |= sqrt"))]
5473
(quick-bench (jq-script "{\"a\":[10,2,3,4,5],\"b\":\"hello\"}")))
5574
=>
56-
"Elapsed time: 0.098731 msecs"
57-
Evaluation count : 203586 in 6 samples of 33931 calls.
58-
Execution time mean : 3.729388 µs
59-
Execution time std-deviation : 490.874949 ns
60-
Execution time lower quantile : 2.960691 µs ( 2.5%)
61-
Execution time upper quantile : 4.082989 µs (97.5%)
62-
Overhead used : 1.977144 ns
75+
"Elapsed time: 0.063264 msecs"
76+
Evaluation count : 198870 in 6 samples of 33145 calls.
77+
Execution time mean : 3.687955 µs
78+
Execution time std-deviation : 668.209042 ns
79+
Execution time lower quantile : 3.041275 µs ( 2.5%)
80+
Execution time upper quantile : 4.280444 µs (97.5%)
81+
Overhead used : 1.766661 ns
6382
```
6483

6584
## Future work

Diff for: cli/jq/cli.clj

+2-2
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
(:require [clojure.java.io :as io]
44
[clojure.string :as str]
55
[clojure.tools.cli :as cli]
6-
[jq.core :as jq])
6+
[jq.api :as jq])
77
(:import (java.io Reader BufferedReader)))
88

99
(def cli-options
@@ -23,7 +23,7 @@
2323
(println "Supported options:")
2424
(println summary))
2525

26-
(defn execute [jq-filter files options]
26+
(defn execute [jq-filter files _]
2727
(let [jq-processor (jq/processor jq-filter)]
2828
(if (seq files)
2929
(doseq [f files]

Diff for: deps.edn

+5-4
Original file line numberDiff line numberDiff line change
@@ -6,15 +6,15 @@
66
:aliases
77
{:dev
88
{:extra-paths ["dev" "classes" "test" "test/resources"]
9-
:extra-deps {org.clojure/tools.deps.alpha {:mvn/version "0.12.1030"
9+
:extra-deps {org.clojure/tools.deps.alpha {:mvn/version "0.12.1036"
1010
:exclusions [org.slf4j/slf4j-log4j12
1111
org.slf4j/slf4j-api
1212
org.slf4j/slf4j-nop]}
1313
criterium/criterium {:mvn/version "0.4.6"}}}
1414
:test
1515
{:extra-paths ["test" "test/resources"]
1616
:extra-deps {com.cognitect/test-runner {:git/url "https://github.com/cognitect-labs/test-runner.git"
17-
:sha "e7660458ce25bc4acb4ccc3e2415aae0a4907198"}}
17+
:sha "dd6da11611eeb87f08780a30ac8ea6012d4c05ce"}}
1818
:main-opts ["-m" "cognitect.test-runner"]}
1919
:clj-kondo
2020
{:main-opts ["-m" "clj-kondo.main" "--lint" "src" "test"]
@@ -24,9 +24,10 @@
2424
{:extra-paths ["cli" "classes" "resources"]
2525
:extra-deps {org.clojure/tools.cli {:mvn/version "1.0.206"}}}
2626
:build
27-
{:deps {io.github.clojure/tools.build {:git/tag "v0.1.9" :git/sha "6736c83"}}
27+
{:deps {io.github.clojure/tools.build {:git/tag "v0.2.2" :git/sha "3049217"}}
2828
:extra-paths ["build"]
29-
:ns-default build}
29+
:ns-default build
30+
:jvm-opts ["-Dclojure.compiler.direct-linking=true"]}
3031
:deploy
3132
{:replace-deps {slipset/deps-deploy {:mvn/version "0.1.5"}}
3233
:exec-fn deps-deploy.deps-deploy/deploy

Diff for: doc/cli.md

+44
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
# CLI utility
2+
3+
`clj-jq`: `jackson-jq` based command-line JSON processor.
4+
5+
## Installation
6+
7+
For MacOS and linux use `brew`:
8+
```shell
9+
brew install dainiusjocas/brew/clj_jq
10+
```
11+
12+
Upgrade:
13+
14+
```shell
15+
brew upgrade clj-jq
16+
```
17+
18+
Or download the latest binaries yourself from [Github release page](https://github.com/dainiusjocas/clj-jq/releases).
19+
20+
In case you're running MacOS then give run permissions for the executable binary:
21+
```shell
22+
sudo xattr -r -d com.apple.quarantine clj-jq
23+
```
24+
25+
## Options
26+
27+
```text
28+
clj-jq 1.1.0
29+
jackson-jq based command-line JSON processor
30+
Usage: clj-jq [options] jq-filter [file...]
31+
Supported options:
32+
-h, --help
33+
```
34+
35+
## Examples
36+
37+
```shell
38+
echo "[1,2,3,4]" | ./clj-jq '
39+
.
40+
| map(.+1)
41+
| reverse
42+
'
43+
#=> [5,4,3,2]
44+
```

Diff for: src/jq/api.clj

+55
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
(ns jq.api
2+
(:require [jq.api.api-impl :as impl])
3+
(:import (net.thisptr.jackson.jq JsonQuery)))
4+
5+
(set! *warn-on-reflection* true)
6+
7+
; jq docs http://manpages.ubuntu.com/manpages/hirsute/man1/jq.1.html
8+
(defn execute
9+
"Given a JSON data string (1) and a JQ query string (2)
10+
returns a JSON string result of (2) applied on (1)."
11+
^String [^String data ^String query]
12+
(impl/apply-json-query-on-string-data data (impl/compile-query query)))
13+
14+
(defn processor
15+
"Given a JQ query string (1) compiles it and returns a function that given
16+
a JSON string (2) will return a JSON string with (1) applied on (2)."
17+
[^String query]
18+
(let [^JsonQuery json-query (impl/compile-query query)]
19+
(fn ^String [^String data]
20+
(impl/apply-json-query-on-string-data data json-query))))
21+
22+
(defn flexible-processor
23+
"Given a JQ query string (1) compiles it and returns a function that given
24+
a JsonNode object or a String (2) will return
25+
either a JSON string or json node with (1) applied on (2)."
26+
([^String query] (flexible-processor query {}))
27+
([^String query opts]
28+
(let [^JsonQuery query (impl/compile-query query)
29+
output-format (get opts :output :string)]
30+
(fn [json-data]
31+
(cond
32+
; string => string
33+
(and (string? json-data) (= :string output-format))
34+
(impl/apply-json-query-on-string-data json-data query)
35+
36+
; string => json-node
37+
(and (string? json-data) (not= :string output-format))
38+
(impl/apply-json-query-on-json-node (impl/string->json-node json-data) query)
39+
40+
; json-node => string
41+
(and (not (string? json-data)) (= :string output-format))
42+
(impl/apply-json-query-on-json-node-data json-data query)
43+
44+
; json-node => json-node
45+
(and (not (string? json-data)) (not= :string output-format))
46+
(impl/apply-json-query-on-json-node json-data query))))))
47+
48+
(comment
49+
(jq.api/execute "{\"a\":[1,2,3,4,5],\"b\":\"hello\"}" ".")
50+
51+
((jq.api/processor ".") "{\"a\":[1,2,3,4,5],\"b\":\"hello\"}")
52+
53+
((jq.api/flexible-processor ".") "{\"a\":[1,2,3,4,5],\"b\":\"hello\"}")
54+
55+
((jq.api/flexible-processor "." {:output :json-node}) "{\"a\":[1,2,3,4,5],\"b\":\"hello\"}"))

Diff for: src/jq/api/api_impl.clj

+60
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
(ns ^{:doc "Internal implementation details."
2+
:no-doc true}
3+
jq.api.api-impl
4+
(:import (net.thisptr.jackson.jq JsonQuery Versions Scope BuiltinFunctionLoader Output)
5+
(com.fasterxml.jackson.databind ObjectMapper JsonNode)))
6+
7+
(set! *warn-on-reflection* true)
8+
9+
(def ^ObjectMapper mapper (ObjectMapper.))
10+
11+
(def jq-version Versions/JQ_1_6)
12+
13+
(def ^Scope root-scope
14+
"Scope that contains all the available Builtin functions."
15+
(let [scope (Scope/newEmptyScope)]
16+
; Load all the functions available for the JQ-1.6
17+
(.loadFunctions (BuiltinFunctionLoader/getInstance) jq-version scope)
18+
scope))
19+
20+
; Helper interface that specifies a method to get a string value.
21+
(definterface IGetter
22+
(^com.fasterxml.jackson.databind.JsonNode getValue []))
23+
24+
; Container class helper that implements the net.thisptr.jackson.jq.Output
25+
; interface that enables the class to be used as a callback for JQ and exposes the
26+
; unsynchronized-mutable container field for the result of the JQ transformation.
27+
(deftype OutputContainer [^:unsynchronized-mutable ^JsonNode container]
28+
Output
29+
(emit [_ json-node] (set! container json-node))
30+
IGetter
31+
(getValue [_] container))
32+
33+
(defn apply-json-query-on-json-node
34+
"Given a JSON data string and a JsonQuery object applies the query
35+
on the JSON data string and return JsonNode."
36+
^JsonNode [^JsonNode json-node ^JsonQuery json-query]
37+
(let [output-container (OutputContainer. nil)]
38+
(.apply json-query (Scope/newChildScope root-scope) json-node output-container)
39+
(.getValue output-container)))
40+
41+
(defn string->json-node ^JsonNode [^String data]
42+
(.readTree mapper data))
43+
44+
(defn json-node->string ^String [^JsonNode data]
45+
(.writeValueAsString mapper data))
46+
47+
(defn ^String apply-json-query-on-string-data
48+
"Reads data JSON string into a JsonNode and passes to the query executor."
49+
[^String data ^JsonQuery query]
50+
(json-node->string (apply-json-query-on-json-node (string->json-node data) query)))
51+
52+
(defn ^String apply-json-query-on-json-node-data
53+
"Reads data JSON string into a JsonNode and passes to the query executor."
54+
^String [^JsonNode data ^JsonQuery query]
55+
(json-node->string (apply-json-query-on-json-node data query)))
56+
57+
(defn compile-query
58+
"Compiles a JQ query string into a JsonQuery object."
59+
^JsonQuery [^String query]
60+
(JsonQuery/compile query jq-version))

0 commit comments

Comments
 (0)