├── .carve_ignore ├── .circleci ├── config.yml └── script │ ├── deploy │ ├── docker │ ├── install-clojure │ ├── install-leiningen │ ├── lein │ ├── performance │ ├── release │ └── tools.deps ├── .cirrus.yml ├── .clj-kondo └── config.edn ├── .github ├── FUNDING.yml └── pull_request_template.md ├── .gitignore ├── CHANGELOG.md ├── LICENSE ├── README.md ├── appveyor.yml ├── assets └── jeti.png ├── bb.edn ├── deps.edn ├── doc ├── cljdoc.edn └── query.md ├── install ├── logo ├── icon.png ├── icon.svg ├── logo-300dpi.png └── logo.svg ├── pom.xml ├── project.clj ├── resources ├── JET_RELEASED_VERSION ├── JET_VERSION └── META-INF │ └── native-image │ └── borkdude │ └── jet │ ├── native-image.properties │ └── reflect-config.json ├── script ├── babashka │ └── release_artifact.clj ├── bump-version.clj ├── changelog.clj ├── check_glibc.sh ├── compile ├── compile.bat ├── install-clojure ├── install-graalvm ├── install-leiningen ├── setup-musl └── test ├── src-java └── org │ └── babashka │ └── CLibrary.java ├── src ├── data_readers.clj └── jet │ ├── base64.clj │ ├── data_readers.clj │ ├── formats.clj │ ├── jeti.clj │ ├── main.clj │ ├── patches.clj │ ├── query.clj │ ├── specter.clj │ └── utils.clj ├── test-resources ├── fn.clj └── specter-test.clj └── test ├── data └── commits.json └── jet ├── main_test.clj ├── parse_opts_test.clj ├── query_test.clj └── test_utils.clj /.carve_ignore: -------------------------------------------------------------------------------- 1 | jet.data-readers/lit 2 | -------------------------------------------------------------------------------- /.circleci/config.yml: -------------------------------------------------------------------------------- 1 | # Clojure CircleCI 2.0 configuration file 2 | # 3 | # Check https://circleci.com/docs/2.0/language-clojure/ for more details 4 | # 5 | version: 2.1 6 | jobs: 7 | testjvm: 8 | docker: 9 | # specify the version you desire here 10 | - image: circleci/clojure:lein-2.9.1 11 | working_directory: ~/repo 12 | environment: 13 | LEIN_ROOT: "true" 14 | steps: 15 | - checkout 16 | - restore_cache: 17 | keys: 18 | - v1-dependencies-{{ checksum "project.clj" }} 19 | # fallback to using the latest cache if no exact match is found 20 | - v1-dependencies- 21 | - run: 22 | name: Install Clojure 23 | command: | 24 | wget -nc https://download.clojure.org/install/linux-install-1.11.1.1347.sh 25 | chmod +x linux-install-1.11.1.1347.sh 26 | sudo ./linux-install-1.11.1.1347.sh 27 | - run: 28 | name: Run JVM tests 29 | command: | 30 | script/test 31 | # - run: 32 | # name: Run as tools.deps dependency 33 | # command: | 34 | # .circleci/script/tools.deps 35 | - run: 36 | name: Run as lein command 37 | command: | 38 | .circleci/script/lein 39 | - save_cache: 40 | paths: 41 | - ~/.m2 42 | key: v1-dependencies-{{ checksum "project.clj" }} 43 | buildlinux: 44 | docker: 45 | - image: cimg/clojure@sha256:8298383d7753a2305663403871c31f238c3c5f0200e13f2394e32d4598a9fdf2 46 | working_directory: ~/repo 47 | environment: 48 | LEIN_ROOT: "true" 49 | GRAALVM_HOME: /home/circleci/graalvm-ce-java11-22.3.0 50 | JET_PLATFORM: linux # used in release script 51 | JET_TEST_ENV: native 52 | BABASHKA_STATIC: "true" 53 | BABASHKA_MUSL: "true" 54 | 55 | steps: 56 | - checkout 57 | - restore_cache: 58 | keys: 59 | - buildlinux-{{ checksum "project.clj" }}-{{ checksum ".circleci/config.yml" }} 60 | - run: 61 | name: Install Clojure 62 | command: | 63 | wget https://download.clojure.org/install/linux-install-1.11.1.1347.sh 64 | chmod +x linux-install-1.11.1.1347.sh 65 | sudo ./linux-install-1.11.1.1347.sh 66 | - run: 67 | name: Install native dev tools 68 | command: | 69 | sudo apt-get update 70 | sudo apt-get -y install build-essential zlib1g-dev 71 | sudo -E script/setup-musl 72 | - run: 73 | name: Download GraalVM 74 | command: | 75 | cd ~ 76 | if ! [ -d graalvm-ce-java11-22.3.0 ]; then 77 | curl -O -sL https://github.com/graalvm/graalvm-ce-builds/releases/download/vm-22.3.0/graalvm-ce-java11-linux-amd64-22.3.0.tar.gz 78 | tar xzf graalvm-ce-java11-linux-amd64-22.3.0.tar.gz 79 | fi 80 | - run: 81 | name: Build binary 82 | command: | 83 | script/compile 84 | no_output_timeout: 30m 85 | - run: 86 | name: Run tests 87 | command: | 88 | script/test 89 | # - run: 90 | # name: Performance report 91 | # command: | 92 | # .circleci/script/performance 93 | - run: 94 | name: Install bb 95 | command: | 96 | sudo bash < <(curl -s https://raw.githubusercontent.com/babashka/babashka/master/install) 97 | - run: 98 | name: Release 99 | command: | 100 | .circleci/script/release 101 | - save_cache: 102 | paths: 103 | - ~/.m2 104 | - ~/graalvm-ce-java11-22.3.0 105 | key: buildlinux-{{ checksum "project.clj" }}-{{ checksum ".circleci/config.yml" }} 106 | - store_artifacts: 107 | path: /tmp/release 108 | destination: release 109 | buildmac: 110 | macos: 111 | xcode: "14.0.0" 112 | environment: 113 | GRAALVM_HOME: /Users/distiller/graalvm-ce-java11-22.3.0/Contents/Home 114 | JET_PLATFORM: macos # used in release script 115 | JET_TEST_ENV: native 116 | steps: 117 | - checkout 118 | - restore_cache: 119 | keys: 120 | - buildmac-{{ checksum "project.clj" }}-{{ checksum ".circleci/config.yml" }} 121 | - run: 122 | name: Install Clojure 123 | command: | 124 | .circleci/script/install-clojure /usr/local 125 | - run: 126 | name: Install Leiningen 127 | command: | 128 | .circleci/script/install-leiningen 129 | 130 | - run: 131 | name: Download GraalVM 132 | command: | 133 | cd ~ 134 | ls -la 135 | if ! [ -d graalvm-ce-java11-22.3.0 ]; then 136 | curl -O -sL https://github.com/graalvm/graalvm-ce-builds/releases/download/vm-22.3.0/graalvm-ce-java11-darwin-amd64-22.3.0.tar.gz 137 | tar xzf graalvm-ce-java11-darwin-amd64-22.3.0.tar.gz 138 | fi 139 | - run: 140 | name: Build binary 141 | command: | 142 | script/compile 143 | no_output_timeout: 30m 144 | - run: 145 | name: Run tests 146 | command: | 147 | script/test 148 | # - run: 149 | # name: Performance report 150 | # command: | 151 | # .circleci/script/performance 152 | - run: 153 | name: Install bb 154 | command: | 155 | sudo bash < <(curl -s https://raw.githubusercontent.com/babashka/babashka/master/install) 156 | - run: 157 | name: Release 158 | command: | 159 | sudo bash < <(curl -s https://raw.githubusercontent.com/babashka/babashka/master/install) 160 | .circleci/script/release 161 | 162 | - save_cache: 163 | paths: 164 | - ~/.m2 165 | - ~/graalvm-ce-java11-22.3.0 166 | key: buildmac-{{ checksum "project.clj" }}-{{ checksum ".circleci/config.yml" }} 167 | - store_artifacts: 168 | path: /tmp/release 169 | destination: release 170 | buildlinux-aarch64: 171 | machine: 172 | enabled: true 173 | image: ubuntu-2004:current 174 | resource_class: arm.medium 175 | working_directory: ~/repo 176 | environment: 177 | LEIN_ROOT: "true" 178 | GRAALVM_HOME: /home/circleci/graalvm-ce-java11-22.3.0 179 | JET_PLATFORM: linux # used in release script 180 | JET_ARCH: aarch64 181 | JET_TEST_ENV: native 182 | BABASHKA_STATIC: "true" 183 | 184 | steps: 185 | - checkout 186 | - run: 187 | name: Check max glibc version 188 | command: script/check_glibc.sh 189 | - restore_cache: 190 | keys: 191 | - buildlinux-aarch64-{{ checksum "project.clj" }}-{{ checksum ".circleci/config.yml" }} 192 | - run: 193 | name: Install Clojure 194 | command: | 195 | wget https://download.clojure.org/install/linux-install-1.11.1.1347.sh 196 | chmod +x linux-install-1.11.1.1347.sh 197 | sudo ./linux-install-1.11.1.1347.sh 198 | - run: 199 | name: Install native dev tools 200 | command: | 201 | sudo apt-get update 202 | sudo apt-get -y install build-essential zlib1g-dev 203 | - run: 204 | name: Download GraalVM 205 | command: | 206 | cd ~ 207 | if ! [ -d graalvm-ce-java11-22.3.0 ]; then 208 | curl -O -sL https://github.com/graalvm/graalvm-ce-builds/releases/download/vm-22.3.0/graalvm-ce-java11-linux-aarch64-22.3.0.tar.gz 209 | tar xzf graalvm-ce-java11-linux-aarch64-22.3.0.tar.gz 210 | fi 211 | - run: 212 | name: Build binary 213 | command: | 214 | script/compile 215 | no_output_timeout: 30m 216 | - run: 217 | name: Run tests 218 | command: | 219 | script/test 220 | # - run: 221 | # name: Performance report 222 | # command: | 223 | # .circleci/script/performance 224 | - run: 225 | name: Install bb 226 | command: | 227 | sudo bash < <(curl -s https://raw.githubusercontent.com/babashka/babashka/master/install) 228 | - run: 229 | name: Release 230 | command: | 231 | .circleci/script/release 232 | - save_cache: 233 | paths: 234 | - ~/.m2 235 | - ~/graalvm-ce-java11-22.3.0 236 | key: buildlinux-aarch64-{{ checksum "project.clj" }}-{{ checksum ".circleci/config.yml" }} 237 | - store_artifacts: 238 | path: /tmp/release 239 | destination: release 240 | deploy: 241 | docker: 242 | - image: circleci/clojure:lein-2.9.1 243 | working_directory: ~/repo 244 | environment: 245 | LEIN_ROOT: "true" 246 | steps: 247 | - checkout 248 | - restore_cache: 249 | keys: 250 | - v1-dependencies-{{ checksum "project.clj" }} 251 | # fallback to using the latest cache if no exact match is found 252 | - v1-dependencies- 253 | - run: .circleci/script/deploy 254 | - save_cache: 255 | paths: 256 | - ~/.m2 257 | key: v1-dependencies-{{ checksum "project.clj" }} 258 | # docker: 259 | # docker: 260 | # - image: circleci/buildpack-deps:stretch 261 | # steps: 262 | # - checkout 263 | # - setup_remote_docker: 264 | # docker_layer_caching: true 265 | # - run: 266 | # name: Build Docker image 267 | # command: .circleci/script/docker 268 | 269 | workflows: 270 | version: 2 271 | ci: 272 | jobs: 273 | - testjvm 274 | - buildlinux 275 | - buildlinux-aarch64 276 | - buildmac 277 | - deploy: 278 | filters: 279 | branches: 280 | only: master 281 | requires: 282 | - testjvm 283 | - buildlinux 284 | - buildlinux-aarch64 285 | - buildmac 286 | # - docker: 287 | # filters: 288 | # branches: 289 | # only: master 290 | # requires: 291 | # - testjvm 292 | # - buildlinux 293 | # - buildmac 294 | -------------------------------------------------------------------------------- /.circleci/script/deploy: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | if [ -z "$CIRCLE_PULL_REQUEST" ] && [ "$CIRCLE_BRANCH" = "master" ] 4 | then 5 | lein deploy clojars 6 | fi 7 | 8 | exit 0; 9 | -------------------------------------------------------------------------------- /.circleci/script/docker: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | image_name="borkdude/clj-kondo" 4 | image_tag=$(cat resources/CLJ_KONDO_VERSION) 5 | latest_tag="latest" 6 | 7 | if [[ $image_tag =~ SNAPSHOT$ ]]; then 8 | echo "This is a snapshot version" 9 | snapshot="true" 10 | else 11 | echo "This is a non-snapshot version" 12 | snapshot="false" 13 | fi 14 | 15 | if [ -z "$CIRCLE_PULL_REQUEST" ] && [ "$CIRCLE_BRANCH" = "master" ]; then 16 | echo "Building Docker image $image_name:$image_tag" 17 | echo "$DOCKERHUB_PASS" | docker login -u "$DOCKERHUB_USER" --password-stdin 18 | docker build -t "$image_name" . 19 | docker tag "$image_name:$latest_tag" "$image_name:$image_tag" 20 | # we only update latest when it's not a SNAPSHOT version 21 | if [ "false" = "$snapshot" ]; then 22 | echo "Pushing image $image_name:$latest_tag" 23 | docker push "$image_name:$latest_tag" 24 | fi 25 | # we update the version tag, even if it's a SNAPSHOT version 26 | echo "Pushing image $image_name:$image_tag" 27 | docker push "$image_name:$image_tag" 28 | else 29 | echo "Not publishing Docker image" 30 | fi 31 | 32 | exit 0; 33 | -------------------------------------------------------------------------------- /.circleci/script/install-clojure: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | install_dir=${1:-/tmp/clojure} 4 | mkdir -p "$install_dir" 5 | cd /tmp 6 | curl -O -sL https://download.clojure.org/install/clojure-tools-1.11.1.1347.tar.gz 7 | tar xzf clojure-tools-1.11.1.1347.tar.gz 8 | cd clojure-tools 9 | clojure_lib_dir="$install_dir/lib/clojure" 10 | mkdir -p "$clojure_lib_dir/libexec" 11 | cp ./*.jar "$clojure_lib_dir/libexec" 12 | cp deps.edn "$clojure_lib_dir" 13 | cp example-deps.edn "$clojure_lib_dir" 14 | 15 | sed -i -e 's@PREFIX@'"$clojure_lib_dir"'@g' clojure 16 | mkdir -p "$install_dir/bin" 17 | cp clojure "$install_dir/bin" 18 | cp clj "$install_dir/bin" 19 | 20 | cd /tmp 21 | rm -rf clojure-tools-1.11.1.1347.tar.gz 22 | rm -rf clojure-tools 23 | echo "Installed clojure to $install_dir/bin" 24 | -------------------------------------------------------------------------------- /.circleci/script/install-leiningen: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | curl https://raw.githubusercontent.com/technomancy/leiningen/2.9.1/bin/lein > lein 4 | sudo mkdir -p /usr/local/bin/ 5 | sudo mv lein /usr/local/bin/lein 6 | sudo chmod a+x /usr/local/bin/lein 7 | -------------------------------------------------------------------------------- /.circleci/script/lein: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | echo '{:a 1}' | lein jet --from edn --to json 4 | -------------------------------------------------------------------------------- /.circleci/script/performance: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | err=0 4 | function _trap_error() { 5 | local exit_code="$1" 6 | if [ "$exit_code" -ne 2 ] && [ "$exit_code" -ne 3 ]; then 7 | echo "EXIT CODE :( $exit_code" 8 | (( err |= "$exit_code" )) 9 | fi 10 | } 11 | 12 | trap '_trap_error $?' ERR 13 | trap 'exit $err' SIGINT SIGTERM 14 | 15 | 16 | rm -rf performance.txt 17 | echo -e "==== Build initial cache" | tee -a performance.txt 18 | cp="$(clojure -R:cljs -Spath)" 19 | read -r -d '' config <<'EOF' || true 20 | {:linters 21 | {:not-a-function 22 | {:skip-args [clojure.pprint/defdirectives 23 | cljs.pprint/defdirectives 24 | clojure.data.json/codepoint-case]}}} 25 | EOF 26 | 27 | (time ./clj-kondo --lint "$cp" --cache --config "$config") 2>&1 | tee -a performance.txt 28 | 29 | echo -e "\n==== Lint a single file (emulate in-editor usage)" | tee -a performance.txt 30 | (time ./clj-kondo --lint src/clj_kondo/impl/core.clj --cache) 2>&1 | tee -a performance.txt 31 | 32 | count=$(find . -name "*.clj*" -type f | wc -l | tr -d ' ') 33 | echo -e "\n==== Launch clj-kondo for each file in project ($count)" | tee -a performance.txt 34 | (time find src -name "*.clj*" -type f -exec ./clj-kondo --lint {} --cache \; ) 2>&1 | tee -a performance.txt 35 | 36 | exit "$err" 37 | -------------------------------------------------------------------------------- /.circleci/script/release: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | set -eo pipefail 4 | 5 | rm -rf /tmp/release 6 | mkdir -p /tmp/release 7 | cp jet /tmp/release 8 | VERSION=$(cat resources/JET_VERSION) 9 | 10 | # release jar, adapted from https://github.com/clj-kondo/clj-kondo/blob/master/.circleci/script/release 11 | 12 | if [[ "$JET_PLATFORM" = "linux" ]]; then 13 | lein with-profiles +clojure-1.10.3 do clean, uberjar 14 | jar="target/jet-$VERSION-standalone.jar" 15 | cp "$jar" /tmp/release 16 | fi 17 | 18 | pushd /tmp/release 19 | 20 | ## release binary as zip archive 21 | 22 | arch=${JET_ARCH:-amd64} 23 | 24 | archive="jet-$VERSION-$JET_PLATFORM-$arch.tar.gz" 25 | 26 | tar zcvf "$archive" jet 27 | ## cleanup 28 | 29 | rm jet 30 | 31 | popd 32 | 33 | bb release-artifact "/tmp/release/$archive" || true 34 | -------------------------------------------------------------------------------- /.circleci/script/tools.deps: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | err=0 4 | function _trap_error() { 5 | local exit_code="$1" 6 | if [ "$exit_code" -ne 2 ] && [ "$exit_code" -ne 3 ]; then 7 | (( err |= "$exit_code" )) 8 | fi 9 | } 10 | 11 | trap '_trap_error $?' ERR 12 | trap 'exit $err' SIGINT SIGTERM 13 | 14 | 15 | # Run as local root dependency 16 | rm -rf /tmp/proj 17 | mkdir -p /tmp/proj 18 | cd /tmp/proj 19 | clojure -Sdeps '{:deps {clj-kondo {:local/root "/home/circleci/repo"}}}' \ 20 | -m clj-kondo.main --lint /home/circleci/repo/src /home/circleci/repo/test 21 | 22 | # Run as git dependency 23 | rm -rf /tmp/proj 24 | mkdir -p /tmp/proj 25 | cd /tmp/proj 26 | 27 | github_user=${CIRCLE_PR_USERNAME:-borkdude} 28 | clojure -Sdeps "{:deps {clj-kondo {:git/url \"https://github.com/$github_user/clj-kondo\" :sha \"$CIRCLE_SHA1\"}}}" \ 29 | -m clj-kondo.main --lint /home/circleci/repo/src /home/circleci/repo/test 30 | 31 | exit "$err" 32 | -------------------------------------------------------------------------------- /.cirrus.yml: -------------------------------------------------------------------------------- 1 | macos_instance: 2 | image: ghcr.io/cirruslabs/macos-monterey-base:latest 3 | 4 | task: 5 | env: 6 | LEIN_ROOT: "true" 7 | GRAALVM_VERSION: "22.3.0" 8 | GRAALVM_HOME: ${HOME}/graalvm-ce-java11-22.3.0/Contents/Home 9 | JET_PLATFORM: macos # used in release script 10 | JET_ARCH: aarch64 11 | JET_TEST_ENV: native 12 | GITHUB_TOKEN: ENCRYPTED[!2542f8eaef6c92f10eae1d5113838d473d5b8ef8affc17e7257cc5b137fae3f12b5d91ab9f8f97a9dc258fd47291e952!] 13 | 14 | script: | 15 | ls script 16 | sudo script/install-clojure 17 | sudo script/install-leiningen 18 | script/install-graalvm 19 | export PATH=$GRAALVM_HOME/bin:$PATH 20 | java -version 21 | 22 | script/compile 23 | script/test 24 | 25 | VERSION=$(cat resources/JET_VERSION) 26 | arch=${JET_ARCH:-amd64} 27 | archive="jet-$VERSION-$JET_PLATFORM-$arch.tar.gz" 28 | tar zcvf "$archive" jet 29 | 30 | sudo bash < <(curl -s https://raw.githubusercontent.com/babashka/babashka/master/install) 31 | bb release-artifact "$archive" || true 32 | binaries_artifacts: 33 | path: "jet-*.tar.gz" 34 | -------------------------------------------------------------------------------- /.clj-kondo/config.edn: -------------------------------------------------------------------------------- 1 | {:lint-as {me.raynes.conch/let-programs clojure.core/let} 2 | :linters {:unsorted-required-namespaces {:level :warning}}} 3 | -------------------------------------------------------------------------------- /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | # These are supported funding model platforms 2 | 3 | github: borkdude # Replace with up to 4 GitHub Sponsors-enabled usernames e.g., [user1, user2] 4 | patreon: borkdude # Replace with a single Patreon username 5 | open_collective: # Replace with a single Open Collective username 6 | ko_fi: borkdude 7 | tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel 8 | community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry 9 | liberapay: # Replace with a single Liberapay username 10 | issuehunt: # Replace with a single IssueHunt username 11 | otechie: # Replace with a single Otechie username 12 | custom: # Replace with up to 4 custom sponsorship URLs e.g., ['link1', 'link2'] 13 | -------------------------------------------------------------------------------- /.github/pull_request_template.md: -------------------------------------------------------------------------------- 1 | Please answer the following questions and leave the below in as part of your PR. 2 | 3 | - [ ] This PR corresponds to an [issue with a clear problem statement](https://github.com/babashka/babashka/blob/master/doc/dev.md#start-with-an-issue-before-writing-code). 4 | 5 | - [ ] This PR contains a [test](https://github.com/babashka/babashka/blob/master/doc/dev.md#tests) to prevent against future regressions 6 | 7 | - [ ] I have updated the [CHANGELOG.md](https://github.com/borkdude/deps.clj/blob/master/CHANGELOG.md) file with a description of the addressed issue. 8 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | /classes 3 | /checkouts 4 | profiles.clj 5 | pom.xml.asc 6 | *.jar 7 | *.class 8 | /.lein-* 9 | /.nrepl-port 10 | .hgignore 11 | .hg/ 12 | /jet 13 | .cache 14 | jet.build_artifacts.txt 15 | .clj-kondo/babashka 16 | .cpcache 17 | *.tar.gz 18 | jet.iml 19 | .idea/ 20 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | [jet](https://github.com/borkdude/jet): CLI to transform between JSON, EDN, YAML and Transit using Clojure. 4 | 5 | ## 0.7.27 (2023-08-02) 6 | 7 | - [#137](https://github.com/borkdude/jet/issues/137): Added missing functions from clojure v1.11: 8 | `update-keys`, `update-vals`, `parse-long`, `parse-double`, `parse-boolean` and `parse-uuid` 9 | - [#148](https://github.com/borkdude/jet/issues/148): restore default EDN `tagged-literal` data reader 10 | - [#143](https://github.com/borkdude/jet/issues/143): Add named cases for convenience to the --keywordize / -k switch: 11 | `key-fn | :kebab-case | :snake_case | :PascalCase | :camelCase | :Camel_Snake_Case | :SCREAMING_SNAKE_CASE | :HTTP-Header-Case` 12 | 13 | ## 0.6.26 (2023-07-03) 14 | 15 | - [#134](https://github.com/borkdude/jet/issues/134): From YAML, 16 | `--keywordize []` now uses the provided `key-fn`. 17 | 18 | ## 0.5.25 (2023-04-26) 19 | 20 | - [#130](https://github.com/borkdude/jet/issues/130): `--no-commas` option strips commas from EDN. Only works with uncolored output. 21 | 22 | ## 0.4.24 (2023-04-09) 23 | 24 | - [#127](https://github.com/borkdude/jet/issues/127): support linux aarch64 binary 25 | 26 | ## 0.4.23 (2023-01-27) 27 | 28 | - [#123](https://github.com/borkdude/jet/issues/123): Add `base64/encode` and `base64/decode` 29 | - Add `jet/paths` and `jet/when-pred` 30 | - Deprecate interactive mode 31 | - Deprecate `--query` in favor of `--thread-last`, `--thread-first` or `--func` 32 | 33 | ## 0.3.21 (2022-10-28) 34 | 35 | - [#74](https://github.com/borkdude/jet/issues/74): Add yaml support ([@qdzo](https://github.com/qdzo)) 36 | 37 | ## 0.2.20 (2022-06-17) 38 | 39 | - Make suitable as clj tool 40 | 41 | ## 0.2.19 (2022-06-17) 42 | 43 | - Migrate CLI to [babashka CLI](https://github.com/babashka/cli) 44 | - Introduce `jet.main/exec` which can be called as clojure CLI exec fn 45 | 46 | ## 0.2.18 (2022-06-16) 47 | 48 | - Provide macOS `aarch64` binary 49 | - Reverse `--pretty` to `--no-pretty`: pretty printing is now done by default 50 | - Introduce colored output when pretty-printing. Defaults to `--colors auto` 51 | which only prints colors when connected to terminal. Toggle manually with 52 | `--colors true` or `--colors false`. 53 | - Integrate [specter](https://github.com/redplanetlabs/specter) 54 | - Introduce `--thread-last` and `--thread-first` 55 | 56 | ## 0.1.1 57 | 58 | - Compile linux binary as static with musl 59 | 60 | ## 0.1.0 61 | 62 | - Allow keywordize fn to access all available conversion functions from camel-snake-kebab lib. e.g. `csk/->PascalCase` 63 | 64 | ## 0.0.15 65 | 66 | - Add short options: `-q` for `--query`, `-c` for `--collect` ([@dotemacs](https://github.com/dotemacs)) 67 | - Add tests for option parsing ([@dotemacs](https://github.com/dotemacs)) 68 | - The `-f` / `--func` option can now read from a file ([@dotemacs](https://github.com/dotemacs)) 69 | 70 | ## 0.0.14 71 | 72 | - Add short options: `-i` (input) for `--from`, `-o` (output) for `--to` 73 | - Add `--func` / `-f` option for executing function over input 74 | - Use `{:default tagged-literal}` as default data reader options when none is provided 75 | - Upgrade GraalVM to 21.0.0 76 | - Upgrade sci to 0.2.4 77 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Eclipse Public License - v 1.0 2 | 3 | THE ACCOMPANYING PROGRAM IS PROVIDED UNDER THE TERMS OF THIS ECLIPSE PUBLIC 4 | LICENSE ("AGREEMENT"). ANY USE, REPRODUCTION OR DISTRIBUTION OF THE PROGRAM 5 | CONSTITUTES RECIPIENT'S ACCEPTANCE OF THIS AGREEMENT. 6 | 7 | 1. DEFINITIONS 8 | 9 | "Contribution" means: 10 | 11 | a) in the case of the initial Contributor, the initial code and documentation 12 | distributed under this Agreement, and 13 | b) in the case of each subsequent Contributor: 14 | i) changes to the Program, and 15 | ii) additions to the Program; 16 | 17 | where such changes and/or additions to the Program originate from and are 18 | distributed by that particular Contributor. A Contribution 'originates' from 19 | a Contributor if it was added to the Program by such Contributor itself or 20 | anyone acting on such Contributor's behalf. Contributions do not include 21 | additions to the Program which: (i) are separate modules of software 22 | distributed in conjunction with the Program under their own license 23 | agreement, and (ii) are not derivative works of the Program. 24 | 25 | "Contributor" means any person or entity that distributes the Program. 26 | 27 | "Licensed Patents" mean patent claims licensable by a Contributor which are 28 | necessarily infringed by the use or sale of its Contribution alone or when 29 | combined with the Program. 30 | 31 | "Program" means the Contributions distributed in accordance with this Agreement. 32 | 33 | "Recipient" means anyone who receives the Program under this Agreement, 34 | including all Contributors. 35 | 36 | 2. GRANT OF RIGHTS 37 | a) Subject to the terms of this Agreement, each Contributor hereby grants 38 | Recipient a non-exclusive, worldwide, royalty-free copyright license to 39 | reproduce, prepare derivative works of, publicly display, publicly perform, 40 | distribute and sublicense the Contribution of such Contributor, if any, and 41 | such derivative works, in source code and object code form. 42 | b) Subject to the terms of this Agreement, each Contributor hereby grants 43 | Recipient a non-exclusive, worldwide, royalty-free patent license under 44 | Licensed Patents to make, use, sell, offer to sell, import and otherwise 45 | transfer the Contribution of such Contributor, if any, in source code and 46 | object code form. This patent license shall apply to the combination of the 47 | Contribution and the Program if, at the time the Contribution is added by 48 | the Contributor, such addition of the Contribution causes such combination 49 | to be covered by the Licensed Patents. The patent license shall not apply 50 | to any other combinations which include the Contribution. No hardware per 51 | se is licensed hereunder. 52 | c) Recipient understands that although each Contributor grants the licenses to 53 | its Contributions set forth herein, no assurances are provided by any 54 | Contributor that the Program does not infringe the patent or other 55 | intellectual property rights of any other entity. Each Contributor 56 | disclaims any liability to Recipient for claims brought by any other entity 57 | based on infringement of intellectual property rights or otherwise. As a 58 | condition to exercising the rights and licenses granted hereunder, each 59 | Recipient hereby assumes sole responsibility to secure any other 60 | intellectual property rights needed, if any. For example, if a third party 61 | patent license is required to allow Recipient to distribute the Program, it 62 | is Recipient's responsibility to acquire that license before distributing 63 | the Program. 64 | d) Each Contributor represents that to its knowledge it has sufficient 65 | copyright rights in its Contribution, if any, to grant the copyright 66 | license set forth in this Agreement. 67 | 68 | 3. REQUIREMENTS 69 | 70 | A Contributor may choose to distribute the Program in object code form under its 71 | own license agreement, provided that: 72 | 73 | a) it complies with the terms and conditions of this Agreement; and 74 | b) its license agreement: 75 | i) effectively disclaims on behalf of all Contributors all warranties and 76 | conditions, express and implied, including warranties or conditions of 77 | title and non-infringement, and implied warranties or conditions of 78 | merchantability and fitness for a particular purpose; 79 | ii) effectively excludes on behalf of all Contributors all liability for 80 | damages, including direct, indirect, special, incidental and 81 | consequential damages, such as lost profits; 82 | iii) states that any provisions which differ from this Agreement are offered 83 | by that Contributor alone and not by any other party; and 84 | iv) states that source code for the Program is available from such 85 | Contributor, and informs licensees how to obtain it in a reasonable 86 | manner on or through a medium customarily used for software exchange. 87 | 88 | When the Program is made available in source code form: 89 | 90 | a) it must be made available under this Agreement; and 91 | b) a copy of this Agreement must be included with each copy of the Program. 92 | Contributors may not remove or alter any copyright notices contained within 93 | the Program. 94 | 95 | Each Contributor must identify itself as the originator of its Contribution, if 96 | any, in a manner that reasonably allows subsequent Recipients to identify the 97 | originator of the Contribution. 98 | 99 | 4. COMMERCIAL DISTRIBUTION 100 | 101 | Commercial distributors of software may accept certain responsibilities with 102 | respect to end users, business partners and the like. While this license is 103 | intended to facilitate the commercial use of the Program, the Contributor who 104 | includes the Program in a commercial product offering should do so in a manner 105 | which does not create potential liability for other Contributors. Therefore, if 106 | a Contributor includes the Program in a commercial product offering, such 107 | Contributor ("Commercial Contributor") hereby agrees to defend and indemnify 108 | every other Contributor ("Indemnified Contributor") against any losses, damages 109 | and costs (collectively "Losses") arising from claims, lawsuits and other legal 110 | actions brought by a third party against the Indemnified Contributor to the 111 | extent caused by the acts or omissions of such Commercial Contributor in 112 | connection with its distribution of the Program in a commercial product 113 | offering. The obligations in this section do not apply to any claims or Losses 114 | relating to any actual or alleged intellectual property infringement. In order 115 | to qualify, an Indemnified Contributor must: a) promptly notify the Commercial 116 | Contributor in writing of such claim, and b) allow the Commercial Contributor to 117 | control, and cooperate with the Commercial Contributor in, the defense and any 118 | related settlement negotiations. The Indemnified Contributor may participate in 119 | any such claim at its own expense. 120 | 121 | For example, a Contributor might include the Program in a commercial product 122 | offering, Product X. That Contributor is then a Commercial Contributor. If that 123 | Commercial Contributor then makes performance claims, or offers warranties 124 | related to Product X, those performance claims and warranties are such 125 | Commercial Contributor's responsibility alone. Under this section, the 126 | Commercial Contributor would have to defend claims against the other 127 | Contributors related to those performance claims and warranties, and if a court 128 | requires any other Contributor to pay any damages as a result, the Commercial 129 | Contributor must pay those damages. 130 | 131 | 5. NO WARRANTY 132 | 133 | EXCEPT AS EXPRESSLY SET FORTH IN THIS AGREEMENT, THE PROGRAM IS PROVIDED ON AN 134 | "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, EITHER EXPRESS OR 135 | IMPLIED INCLUDING, WITHOUT LIMITATION, ANY WARRANTIES OR CONDITIONS OF TITLE, 136 | NON-INFRINGEMENT, MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Each 137 | Recipient is solely responsible for determining the appropriateness of using and 138 | distributing the Program and assumes all risks associated with its exercise of 139 | rights under this Agreement , including but not limited to the risks and costs 140 | of program errors, compliance with applicable laws, damage to or loss of data, 141 | programs or equipment, and unavailability or interruption of operations. 142 | 143 | 6. DISCLAIMER OF LIABILITY 144 | 145 | EXCEPT AS EXPRESSLY SET FORTH IN THIS AGREEMENT, NEITHER RECIPIENT NOR ANY 146 | CONTRIBUTORS SHALL HAVE ANY LIABILITY FOR ANY DIRECT, INDIRECT, INCIDENTAL, 147 | SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING WITHOUT LIMITATION LOST 148 | PROFITS), HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, 149 | STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY 150 | OUT OF THE USE OR DISTRIBUTION OF THE PROGRAM OR THE EXERCISE OF ANY RIGHTS 151 | GRANTED HEREUNDER, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. 152 | 153 | 7. GENERAL 154 | 155 | If any provision of this Agreement is invalid or unenforceable under applicable 156 | law, it shall not affect the validity or enforceability of the remainder of the 157 | terms of this Agreement, and without further action by the parties hereto, such 158 | provision shall be reformed to the minimum extent necessary to make such 159 | provision valid and enforceable. 160 | 161 | If Recipient institutes patent litigation against any entity (including a 162 | cross-claim or counterclaim in a lawsuit) alleging that the Program itself 163 | (excluding combinations of the Program with other software or hardware) 164 | infringes such Recipient's patent(s), then such Recipient's rights granted under 165 | Section 2(b) shall terminate as of the date such litigation is filed. 166 | 167 | All Recipient's rights under this Agreement shall terminate if it fails to 168 | comply with any of the material terms or conditions of this Agreement and does 169 | not cure such failure in a reasonable period of time after becoming aware of 170 | such noncompliance. If all Recipient's rights under this Agreement terminate, 171 | Recipient agrees to cease use and distribution of the Program as soon as 172 | reasonably practicable. However, Recipient's obligations under this Agreement 173 | and any licenses granted by Recipient relating to the Program shall continue and 174 | survive. 175 | 176 | Everyone is permitted to copy and distribute copies of this Agreement, but in 177 | order to avoid inconsistency the Agreement is copyrighted and may only be 178 | modified in the following manner. The Agreement Steward reserves the right to 179 | publish new versions (including revisions) of this Agreement from time to time. 180 | No one other than the Agreement Steward has the right to modify this Agreement. 181 | The Eclipse Foundation is the initial Agreement Steward. The Eclipse Foundation 182 | may assign the responsibility to serve as the Agreement Steward to a suitable 183 | separate entity. Each new version of the Agreement will be given a 184 | distinguishing version number. The Program (including Contributions) may always 185 | be distributed subject to the version of the Agreement under which it was 186 | received. In addition, after a new version of the Agreement is published, 187 | Contributor may elect to distribute the Program (including its Contributions) 188 | under the new version. Except as expressly stated in Sections 2(a) and 2(b) 189 | above, Recipient receives no rights or licenses to the intellectual property of 190 | any Contributor under this Agreement, whether expressly, by implication, 191 | estoppel or otherwise. All rights in the Program not expressly granted under 192 | this Agreement are reserved. 193 | 194 | This Agreement is governed by the laws of the State of New York and the 195 | intellectual property laws of the United States of America. No party to this 196 | Agreement will bring a legal action under this Agreement more than one year 197 | after the cause of action arose. Each party waives its rights to a jury trial in 198 | any resulting litigation. 199 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | [![CircleCI](https://circleci.com/gh/borkdude/jet/tree/master.svg?style=shield)](https://circleci.com/gh/borkdude/jet/tree/master) 4 | [![Clojars Project](https://img.shields.io/clojars/v/borkdude/jet.svg)](https://clojars.org/borkdude/jet) 5 | [![cljdoc badge](https://cljdoc.org/badge/borkdude/jet)](https://cljdoc.org/d/borkdude/jet/CURRENT) 6 | 7 | CLI to transform between [JSON](https://www.json.org/), [EDN](https://github.com/edn-format/edn), [YAML](https://yaml.org/) and [Transit](https://github.com/cognitect/transit-format) using Clojure. 8 | 9 | ## Quickstart 10 | 11 | ``` shellsession 12 | $ bash < <(curl -s https://raw.githubusercontent.com/borkdude/jet/master/install) 13 | $ echo '{:a 1}' | jet --to json 14 | {"a":1} 15 | ``` 16 | 17 | ## Rationale 18 | 19 | This is a command line tool to transform between JSON, EDN and Transit using 20 | Clojure. It runs as a GraalVM binary with fast startup time which makes it 21 | suited for shell scripting. It may seem familiar to users of `jq`. 22 | 23 | ## Installation 24 | 25 | ### Brew 26 | 27 | Linux and macOS binaries are provided via brew. 28 | 29 | Install: 30 | 31 | brew install borkdude/brew/jet 32 | 33 | Upgrade: 34 | 35 | brew upgrade jet 36 | 37 | ### Windows 38 | 39 | On Windows you can install using [scoop](https://scoop.sh/) and the 40 | [scoop-clojure](https://github.com/littleli/scoop-clojure) bucket. 41 | 42 | ### ASDF 43 | 44 | jet can be installed with [asdf version manager](https://asdf-vm.com). 45 | More information on the [plugin page](https://github.com/rynkowsg/asdf-jet). 46 | 47 | ### Installer script 48 | 49 | Install via the installer script: 50 | 51 | ``` shellsession 52 | $ bash <(curl -s https://raw.githubusercontent.com/borkdude/jet/master/install) 53 | ``` 54 | 55 | By default this will install into `/usr/local/bin`. To change this, provide the directory name: 56 | 57 | ``` shellsession 58 | $ bash <(curl -s https://raw.githubusercontent.com/borkdude/jet/master/install) /tmp 59 | ``` 60 | 61 | ### Download 62 | 63 | You may also download a binary from [Github](https://github.com/borkdude/jet/releases). 64 | 65 | ### JVM 66 | 67 | #### Leiningen 68 | 69 | This tool can also be used via the JVM. If you use leiningen, you can put the 70 | following in your `.lein/profiles`: 71 | 72 | ``` clojure 73 | {:user 74 | {:dependencies [[borkdude/jet "0.4.23"]] 75 | :aliases {"jet" ["run" "-m" "jet.main"]}}} 76 | ``` 77 | 78 | And then call `jet` like: 79 | 80 | ``` shellsession 81 | $ echo '["^ ","~:a",1]' | lein jet --from transit --to edn 82 | {:a 1} 83 | ``` 84 | 85 | #### Deps.edn 86 | 87 | In `deps.edn`: 88 | 89 | ``` clojure 90 | :jet {:deps {borkdude/jet {:mvn/version "0.4.23"}} 91 | :exec-fn jet.main/exec 92 | :main-opts ["-m" "jet.main"]} 93 | ``` 94 | 95 | You can use both the `-M` and `-X` style invocation, whichever you prefer: 96 | 97 | ``` clojure 98 | $ echo '[1 2 3]' | clj -M:jet --colors --func '#(-> % first inc)' 99 | 2 100 | 101 | $ echo '[1 2 3]' | clj -X:jet :colors true :thread-last '"(map inc)"' 102 | (2 3 4) 103 | ``` 104 | 105 | Or install jet as a clj tool: 106 | 107 | ``` clojure 108 | $ clojure -Ttools install-latest :lib io.github.borkdude/jet :as jet 109 | 110 | $ echo '[1 2 3]' | clj -Tjet exec :colors true :func '"#(-> % first inc)"' 111 | 2 112 | ``` 113 | 114 | ## Usage 115 | 116 | `jet` supports the following options: 117 | 118 | ``` shell 119 | -i, --from [ edn | transit | json | yaml ] defaults to edn. 120 | -o, --to [ edn | transit | json | yaml ] defaults to edn. 121 | -t, --thread-last implicit thread last 122 | -T, --thread-first implicit thread first 123 | -f, --func a single-arg Clojure function, or a path to a file that contains a function, that transforms input. 124 | --no-pretty disable pretty printing 125 | -k, --keywordize [ ] if present, keywordizes JSON/YAML keys. The default transformation function is keyword unless you provide your own. 126 | --colors [ auto | true | false] use colored output while pretty-printing. Defaults to auto. 127 | --edn-reader-opts options passed to the EDN reader. 128 | --no-commas remove commas from EDN 129 | -c, --collect given separate values, collects them in a vector. 130 | -h, --help print this help text. 131 | -v, --version print the current version of jet. 132 | -q, --query DEPRECATED, prefer -t, -T or -f. Given a jet-lang query, transforms input. 133 | ``` 134 | 135 | Transform EDN using `--thread-last`, `--thread-first` or `--func`. 136 | 137 | Examples: 138 | 139 | ``` shellsession 140 | $ echo '{"a": 1}' | jet --from json --to edn 141 | {"a" 1} 142 | 143 | $ echo '{"a": 1}' | jet -i json --keywordize -o edn 144 | {:a 1} 145 | 146 | $ echo '{"my key": 1}' | jet -i json -k '#(keyword (str/replace % " " "_"))' -o edn 147 | {:my_key 1} 148 | 149 | $ echo '{"anApple": 1}' | jet -i json -k '#(-> % csk/->kebab-case keyword)' -o edn 150 | {:an-apple 1} 151 | 152 | $ echo '{"a": 1}' | jet -i json -o yaml 153 | a: 1 154 | 155 | $ echo '{"a": 1}' | jet -i json -o transit 156 | ["^ ","a",1] 157 | 158 | $ echo '{:a {:b {:c 1}}}' | jet --thread-last ':a :b :c' 159 | 1 160 | 161 | $ echo '{:a {:b {:c 1}}}' | jet --func '#(-> % :a :b :c)' 162 | 1 163 | 164 | $ echo '{:a {:b {:c [1 2]}}}' | jet -t ':a :b :c (map inc)' 165 | (2 3) 166 | 167 | $ cat /tmp/fn.clj 168 | #(-> % :a :b :c) 169 | $ echo '{:a {:b {:c 1}}}' | jet --func /tmp/fn.clj 170 | 1 171 | 172 | $ echo '{:a {:a 1}}' | ./jet -t '(s/transform [s/MAP-VALS s/MAP-VALS] inc)' 173 | {:a {:a 2}} 174 | ``` 175 | 176 | ## Raw output 177 | 178 | Get raw output from query rather than wrapped in quotes: 179 | 180 | ```shellsession 181 | $ echo '{"a": "hello there"}' | jet --from json --keywordize -t ":a" --to edn 182 | "hello there" 183 | 184 | $ echo '{"a": "hello there"}' | jet --from json --keywordize -t ":a symbol" --to edn 185 | hello there 186 | ``` 187 | 188 | or simply use `println` to get rid of the quotes: 189 | 190 | ``` clojure 191 | $ echo '{"a": "hello there"}' | jet --from json --keywordize -t ":a println" --to edn 192 | hello there 193 | ``` 194 | 195 | ## Data readers 196 | 197 | You can enable data readers by passing options to `--edn-reader-opts`: 198 | 199 | ``` shell 200 | $ echo '#foo{:a 1}' | jet --edn-reader-opts '{:default tagged-literal}' 201 | #foo {:a 1} 202 | $ echo '#foo{:a 1}' | jet --edn-reader-opts "{:readers {'foo (fn [x] [:foo x])}}" 203 | [:foo {:a 1}] 204 | ``` 205 | 206 | See this [blog](https://insideclojure.org/2018/06/21/tagged-literal/) by Alex Miller for more information on the `tagged-literal` function. 207 | 208 | Since jet 0.0.14 `--edn-reader-opts` defaults to `{:default tagged-literal}`. 209 | 210 | ## Streaming 211 | 212 | Jet supports streaming over multiple values, without reading the entire input 213 | into memory: 214 | 215 | ``` shellsession 216 | $ echo '{"a": 1} {"a": 1}' | jet --from json --keywordize -t ':a' --to edn 217 | 1 218 | 1 219 | ``` 220 | 221 | When you want to collect multiple values into a vector, you can use `--collect`: 222 | 223 | ``` shellsession 224 | $ echo '{"a": 1} {"a": 1}' | lein jet --from json --keywordize --collect --to edn 225 | [{:a 1} {:a 1}] 226 | ``` 227 | 228 | ## Specter 229 | 230 | As of version `0.2.18` the [specter](https://github.com/redplanetlabs/specter) library is available in `--func`, `--thread-first` and `--thread-last`: 231 | 232 | ``` clojure 233 | $ echo '{:a {:a 1}}' | ./jet -t '(s/transform [s/MAP-VALS s/MAP-VALS] inc)' 234 | {:a {:a 2}} 235 | ``` 236 | 237 | ## Base64 238 | 239 | To encode and decode base64 you can use `base64/encode` and `base64/decode`. 240 | 241 | ## Jet utility functions 242 | 243 | In the `jet` namespace, the following utilities are available: 244 | 245 | ### `paths` 246 | 247 | Return all paths (and sub-paths) in maps and vectors. Each result is a map of `:path` and `:val` (via `get-in`). 248 | 249 | ``` clojure 250 | $ echo '{:a {:b [1 2 3 {:x 2}] :c {:d 3}}}' | jet -t '(jet/paths)' 251 | [{:path [:a], :val {:b [1 2 3 {:x 2}], :c {:d 3}}} 252 | {:path [:a :b], :val [1 2 3 {:x 2}]} 253 | {:path [:a :c], :val {:d 3}} 254 | {:path [:a :b 0], :val 1} 255 | {:path [:a :b 1], :val 2} 256 | {:path [:a :b 2], :val 3} 257 | {:path [:a :b 3], :val {:x 2}} 258 | {:path [:a :b 3 :x], :val 2} 259 | {:path [:a :c :d], :val 3}] 260 | ``` 261 | 262 | ### `when-pred` 263 | 264 | Given a predicate, return predicate that returns the given argument when 265 | predicate was truthy. In case of an exception during the predicate call, catches 266 | and returns `nil`. 267 | 268 | The following returns all paths for which the leafs are odd numbers: 269 | 270 | ``` clojure 271 | $ echo '{:a {:b [1 2 3 {:x 2}] :c {:d 3}}}' | jet -t '(jet/paths) (filter (comp (jet/when-pred odd?) :val)) (mapv :path)' 272 | [[:a :b 0] [:a :b 2] [:a :c :d]] 273 | ``` 274 | 275 | ## Emacs integration 276 | 277 | Sometimes it's useful to reformat REPL output in Emacs to make it more 278 | readable, copy to clipboard or just pretty-print to another buffer. 279 | All of that is avaiable in the [jet.el](https://github.com/ericdallo/jet.el) package. 280 | 281 | ## Vim integration 282 | 283 | To convert data in vim buffers, you can select the data you want to convert in visual mode, 284 | then invoke `jet` by typing for example `:'<,'>!jet -k --from json` (vim will insert the 285 | `'<,'>` for you as you type). 286 | 287 | ## Test 288 | 289 | Test the JVM version: 290 | 291 | script/test 292 | 293 | Test the native version: 294 | 295 | JET_TEST_ENV=native script/test 296 | 297 | ## Build 298 | 299 | You will need leiningen and GraalVM. 300 | 301 | script/compile 302 | 303 | ## License 304 | 305 | Copyright © 2019-2023 Michiel Borkent 306 | 307 | Distributed under the EPL License, same as Clojure. See LICENSE. 308 | -------------------------------------------------------------------------------- /appveyor.yml: -------------------------------------------------------------------------------- 1 | --- 2 | 3 | version: "v-{build}" 4 | 5 | image: Visual Studio 2017 6 | 7 | clone_folder: C:\projects\jet 8 | 9 | environment: 10 | GRAALVM_HOME: C:\projects\jet\graalvm\graalvm-ce-java11-22.3.0 11 | 12 | cache: 13 | - C:\ProgramData\chocolatey\lib -> project.clj, appveyor.yml 14 | - '%USERPROFILE%\.m2 -> project.clj' 15 | - 'graalvm -> appveyor.yml' 16 | 17 | clone_script: 18 | - ps: >- 19 | if(-not $env:APPVEYOR_PULL_REQUEST_NUMBER) { 20 | git clone -q --branch=$env:APPVEYOR_REPO_BRANCH https://github.com/$env:APPVEYOR_REPO_NAME.git $env:APPVEYOR_BUILD_FOLDER 21 | cd $env:APPVEYOR_BUILD_FOLDER 22 | git checkout -qf $env:APPVEYOR_REPO_COMMIT 23 | } else { 24 | git clone -q https://github.com/$env:APPVEYOR_REPO_NAME.git $env:APPVEYOR_BUILD_FOLDER 25 | cd $env:APPVEYOR_BUILD_FOLDER 26 | git fetch -q origin +refs/pull/$env:APPVEYOR_PULL_REQUEST_NUMBER/merge: 27 | git checkout -qf FETCH_HEAD 28 | } 29 | #- cmd: git submodule update --init --recursive 30 | 31 | build_script: 32 | - cmd: >- 33 | powershell -Command "(New-Object Net.WebClient).DownloadFile('https://raw.githubusercontent.com/technomancy/leiningen/stable/bin/lein.bat', 'lein.bat')" 34 | 35 | call lein self-install 36 | 37 | # set CLJ_KONDO_TEST_ENV=jvm 38 | 39 | # call script/test.bat 40 | 41 | - cmd: >- 42 | call "C:\Program Files (x86)\Microsoft Visual Studio\2017\Community\VC\Auxiliary\Build\vcvars64.bat" 43 | 44 | powershell -Command "if (Test-Path('graalvm')) { return } else { (New-Object Net.WebClient).DownloadFile('https://github.com/graalvm/graalvm-ce-builds/releases/download/vm-22.3.0/graalvm-ce-java11-windows-amd64-22.3.0.zip', 'graalvm.zip') }" 45 | 46 | powershell -Command "if (Test-Path('graalvm')) { return } else { Expand-Archive graalvm.zip graalvm }" 47 | 48 | call script/compile.bat 49 | 50 | echo {:a 1} | jet --to json 51 | 52 | # - cmd: >- 53 | # lein clean 54 | 55 | # set CLJ_KONDO_TEST_ENV=native 56 | 57 | # call script/test.bat 58 | 59 | artifacts: 60 | - path: jet-*-windows-amd64.zip 61 | name: jet 62 | -------------------------------------------------------------------------------- /assets/jeti.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/borkdude/jet/1b126c051fb858708f30eca6d57eaded172d79b2/assets/jeti.png -------------------------------------------------------------------------------- /bb.edn: -------------------------------------------------------------------------------- 1 | {:paths ["script"] 2 | :deps {borkdude/gh-release-artifact 3 | {:git/url "https://github.com/borkdude/gh-release-artifact" 4 | :sha "cf082df46a648178d1904e9cbcb787d8136a35c6"}} 5 | :tasks {release-artifact babashka.release-artifact/release}} 6 | -------------------------------------------------------------------------------- /deps.edn: -------------------------------------------------------------------------------- 1 | {:paths ["src" "resources"] 2 | :deps {org.clojure/clojure {:mvn/version "1.11.1"} 3 | com.cognitect/transit-clj {:mvn/version "1.0.333"} 4 | cheshire/cheshire {:mvn/version "5.11.0"} 5 | clj-commons/clj-yaml {:mvn/version "1.0.26"} 6 | mvxcvi/puget {:mvn/version "1.3.4"} 7 | commons-io/commons-io {:mvn/version "2.11.0"} 8 | org.babashka/sci {:mvn/version "0.8.41"} 9 | org.babashka/cli {:mvn/version "0.8.58"} 10 | camel-snake-kebab/camel-snake-kebab {:mvn/version "0.4.3"} 11 | com.rpl/specter {:mvn/version "1.1.4"} 12 | rewrite-clj/rewrite-clj {:mvn/version "1.1.47"}} 13 | :aliases 14 | {:test {:extra-paths ["test"] 15 | :extra-deps {clj-commons/conch {:mvn/version "0.9.2"}}}} 16 | :tools/usage {:ns-default jet.main}} 17 | -------------------------------------------------------------------------------- /doc/cljdoc.edn: -------------------------------------------------------------------------------- 1 | {:cljdoc.doc/tree 2 | [["Readme" {:file "README.md"}] 3 | ["Query" {:file "doc/query.md"}]]} 4 | -------------------------------------------------------------------------------- /doc/query.md: -------------------------------------------------------------------------------- 1 | # jet-lang 2 | 3 | An EDN query language. 4 | 5 | ## Caution 6 | 7 | This query language is work in progress. Consider it experimental, suited for 8 | exploratory programming, but not suited for production usage yet. The latest 9 | breaking change happened on (2019-08-05). 10 | 11 | ## Introduction 12 | 13 | The `--query` option allows to select or remove specific parts of the output. A 14 | query is written in jet-lang which uses EDN syntax. 15 | 16 | These Clojure-like functions are supported in jet-lang: 17 | 18 | - for maps: `assoc`, `assoc-in`, `update`, `update-in`, `keys`, `vals`, 19 | `select-keys`, `dissoc`, `map-vals`, `juxt`, `count`, `into`, 20 | `set/rename-keys`, `set/join`. 21 | - for lists: `first`, `last`, `take`, `drop`, `nth`, `map`, `zipmap`, `filter`, 22 | `remove`, `juxt`, `count`, `distinct`, `dedupe`, `conj`, `into`. 23 | - working with strings: `str`, `re-find`. 24 | - logic: `and`, `or`, `not`, `if`, `=`, `not=`, `>`, `>=`, `<`, `<=`. 25 | - literal values: `quote`/`#jet/lit`. 26 | - copy the entire input value: `identity` (for short `id`, thanks Haskell). 27 | - print the result of an intermediate query: `jet/debug`. 28 | - arithmetic: `+`, `-`, `*`, `/`, `inc`, `dec`. 29 | - integers, keywords, and strings lookup up a value in the input (that's whe we need `#jet/lit`) 30 | 31 | All these functions have an implicit _input_ argument. 32 | 33 | To pass the input through a sequence of queries, just put them inside a vector. 34 | Thus `[:user :name]` will get the name of the user inside the input. (You can omit 35 | the vector for the top-level query.) 36 | 37 | To learn more about how to use them, read the [tutorial](#tutorial) or go 38 | straight to the [gallery](#gallery). 39 | 40 | ## Tutorial 41 | 42 | Tip: to follow along, you can use the jet interactive shell. To start it, type 43 | `jet --interactive`. Using `rlwrap` is recommended. 44 | 45 | 46 | 47 | In this tutorial the word list also applies to arrays and vectors. 48 | 49 | Single values can be selected by using a key: 50 | 51 | ``` clojure 52 | echo '{:a 1}' | jet --query ':a' 53 | 1 54 | ``` 55 | 56 | or more explicity with `get`: 57 | 58 | ``` clojure 59 | echo '{:a 1}' | jet --query '(get :a)' 60 | 1 61 | ``` 62 | 63 | Numbers can be used for looking up by position in lists: 64 | ``` clojure 65 | $ echo '[1 2 3]' | lein jet --query '0' 66 | 1 67 | $ echo '[1 2 3]' | lein jet --query '100' 68 | nil 69 | ``` 70 | 71 | You can also use `nth` for this: 72 | 73 | ``` clojure 74 | $ echo '[1 2 3]' | lein jet --query '(nth #jet/lit 0)' 75 | 1 76 | ``` 77 | 78 | A subselection of a map can be made with `select-keys`: 79 | 80 | ``` clojure 81 | echo '{:a 1 :b 2 :c 3}' | jet --query '(select-keys [:a :b])' 82 | {:a 1, :b 2} 83 | ``` 84 | 85 | Removing keys can be achieved with `dissoc`: 86 | 87 | ``` clojure 88 | echo '{:a 1 :b 2 :c 3}' | jet --query '(dissoc :c)' 89 | {:a 1, :b 2} 90 | ``` 91 | 92 | A query can be applied to every element in a list using `map`: 93 | 94 | ``` clojure 95 | $ echo '[{:a 1 :b 2} {:a 2 :b 3}]' | jet --query '(map (select-keys [:a]))' 96 | [{:a 1} {:a 2}] 97 | ``` 98 | 99 | Associating a new key and value in a map is done with `assoc`: 100 | 101 | ``` clojure 102 | $ echo '{:a 1}' | jet --query '(assoc :b :a)' 103 | {:a 1, :b 1} 104 | ``` 105 | 106 | Updating an existing key and value can be done with `update`: 107 | 108 | ``` clojure 109 | $ echo '{:a {:b 1}}' | jet --query '(update :a :b)' 110 | {:a 1} 111 | ``` 112 | 113 | 114 | 115 | ``` clojure 116 | echo '{:a [1 2 3]}' | jet --query '(update :a (take #jet/lit 2))' 117 | {:a [1 2]} 118 | ``` 119 | 120 | ``` clojure 121 | echo '{:a [1 2 3]}' | jet --query '(update :a (drop #jet/lit 2))' 122 | {:a [3]} 123 | ``` 124 | 125 | ``` clojure 126 | echo '{:a [1 2 3]}' | jet --query '(update :a (nth #jet/lit 2))' 127 | {:a 3} 128 | ``` 129 | 130 | The difference between `assoc` and `update` is that the query provided to the 131 | former begins at the root and the query provided to the latter begins at place 132 | to be updated. 133 | 134 | There are also `assoc-in` and `update-in` which behave in similar ways but allow 135 | changing nested values: 136 | 137 | ``` clojure 138 | $ echo '{:a 1}' | jet --query '(assoc-in [:b :c] :a)' 139 | {:a 1, :b {:c 1}} 140 | 141 | $ echo '{:a {:b [1 2 3]}}' | jet --query '(update-in [:a :b] last)' 142 | {:a {:b 3}} 143 | ``` 144 | 145 | Creating a new map from scratch is done with `hash-map`: 146 | 147 | ``` clojure 148 | $ echo '{:a 1 :b 2}' | jet --query '(hash-map :foo :a :bar :b)' 149 | {:foo 1, :bar 2} 150 | ``` 151 | 152 | or using a map literal: 153 | 154 | ``` clojure 155 | $ echo '{:a 1 :b 2}' | jet --query '{:foo :a :bar :b}' 156 | {:foo 1, :bar 2} 157 | ``` 158 | 159 | Inserting literal values can be done with done with `quote`: 160 | 161 | ``` clojure 162 | $ echo '{:a 1}' | jet --query '{:foo :a :bar (quote "hello")}' 163 | {:foo 1, :bar "hello"} 164 | ``` 165 | 166 | or prefixing it with the tag `#jet/lit`: 167 | 168 | ``` clojure 169 | $ echo '{:a 1}' | jet --query '{:foo :a :bar #jet/lit "hello"}' 170 | {:foo 1, :bar "hello"} 171 | ``` 172 | 173 | Applying multiple queries after one another can be achieved using vector 174 | notation. 175 | 176 | ``` clojure 177 | $ echo '{:a {:a/a 1 :a/b 2} :b 2}' | jet --query '[(select-keys [:a]) (update :a :a/a)]' 178 | {:a 1} 179 | ``` 180 | 181 | The outer query is implicitly wrapped, so you don't have to wrap it yourself: 182 | 183 | ``` clojure 184 | $ echo '{:a {:a/a 1 :a/b 2} :b 2}' | jet --query '(select-keys [:a]) (update :a :a/a)' 185 | {:a 1} 186 | ``` 187 | 188 | Copy the input value: 189 | 190 | ``` shellsession 191 | $ echo '{:a 1}' | jet --query '{:input (identity)}' 192 | {:input {:a 1}} 193 | ``` 194 | 195 | Or for short: 196 | 197 | ``` shellsession 198 | $ echo '{:a 1}' | jet --query '{:input (id)}' 199 | {:input {:a 1}} 200 | ``` 201 | 202 | When functions with only one (implicit input) argument are used, the wrapping 203 | parens may be left out, so even shorter: 204 | 205 | ``` shellsession 206 | $ echo '{:a 1}' | jet --query '{:input id}' 207 | {:input {:a 1}} 208 | ``` 209 | 210 | You can print the result of an intermediate query using `jet/debug`: 211 | 212 | ``` clojure 213 | $ echo '{:a {:a/a 1 :a/b 2} :b 2}' | jet --query '(select-keys [:a]) jet/debug (update :a :a/a)' 214 | {:a #:a{:a 1, :b 2}} 215 | {:a 1} 216 | ``` 217 | 218 | Keys and values: 219 | 220 | ``` clojure 221 | $ echo '{:a [1 2 3] :b [4 5 6]}' | jet --query '(keys)' 222 | [:a :b] 223 | ``` 224 | 225 | ``` clojure 226 | $ echo '{:a [1 2 3] :b [4 5 6]}' | jet --query '(vals)' 227 | [[1 2 3] [4 5 6]] 228 | ``` 229 | 230 | ``` clojure 231 | echo '{"foo bar": 1}' | jet --from json --to json --query '(set/rename-keys {"foo bar" "foo-bar"})' 232 | {"foo-bar":1} 233 | ``` 234 | 235 | To apply a function on all map values, use `map-vals`: 236 | 237 | ``` clojure 238 | $ echo '{:foo {:a 1 :b 2} :bar {:a 1 :b 2}}' | jet --query '(map-vals :a)' 239 | {:foo 1 :bar 2} 240 | ``` 241 | 242 | Miscellaneous list functions: 243 | 244 | ``` clojure 245 | echo '[1 2 3]' | jet --query '(first)' 246 | 1 247 | ``` 248 | 249 | ``` clojure 250 | echo '[1 2 3]' | jet --query '(last)' 251 | 3 252 | ``` 253 | 254 | 256 | 257 | ``` clojure 258 | echo '[[1 2 3] [4 5 6]]' | jet --query '(last)' 259 | [4 5 6] 260 | ``` 261 | 262 | ``` clojure 263 | echo '[[1 2 3] [4 5 6]]' | jet --query '(map last)' 264 | [3 6] 265 | ``` 266 | 267 | ``` clojure 268 | echo '[{:a 1} {:a 2}]' | jet --query '(count)' 269 | 2 270 | ``` 271 | 272 | ``` clojure 273 | echo '[{:a 1} {:a 2}]' | jet --query '(map count)' 274 | [1 1] 275 | ``` 276 | 277 | Use `juxt` to apply multiple queries to the same element. The result is a list 278 | of multiple results. 279 | 280 | ``` clojure 281 | $ echo '{:a [1 2 3] :b [4 5 6]}' | jet --query '(juxt :a :b)' 282 | [[1 2 3] [4 5 6]] 283 | ``` 284 | 285 | ``` clojure 286 | $ echo '{:a [1 2 3]}' | jet --query '(update :a (juxt first last))' 287 | {:a [1 3]} 288 | ``` 289 | 290 | ``` clojure 291 | $ echo '[1 2 3]' | jet --query '(juxt 0 1 2 3)' 292 | [1 2 3 nil] 293 | ``` 294 | 295 | An example with `zipmap`: 296 | 297 | ``` clojure 298 | $ echo '{:keys [:a :b :c] :vals [1 2 3]}' \ 299 | | jet --query '[(juxt :keys :vals) (zipmap)]' 300 | {:a 1, :b 2, :c 3} 301 | ``` 302 | 303 | Examples with `filter` and `remove`: 304 | 305 | ``` clojure 306 | $ curl -s https://jsonplaceholder.typicode.com/todos \ 307 | | jet --from json --keywordize --to edn --query '[(filter :completed) (count)]' 308 | 90 309 | ``` 310 | 311 | ``` clojure 312 | $ curl -s https://jsonplaceholder.typicode.com/todos \ 313 | | jet --from json --keywordize --to edn --query '[(remove :completed) (count)]' 314 | 110 315 | ``` 316 | 317 | Remove duplicate values with `distinct` and `dedupe`: 318 | 319 | ``` clojure 320 | $ echo '{:a [1 1 2 2 3 3 1 1]}' | jet --query '[:a (distinct)]' 321 | [1 2 3] 322 | 323 | $ echo '{:a [1 1 2 2 3 3 1 1]}' | jet --query '[:a (dedupe)]' 324 | [1 2 3 1] 325 | ``` 326 | 327 | Producing a string can be done with `str`: 328 | 329 | ``` shellsession 330 | echo '{:a 1 :b 2}' | jet --query '(str :a #jet/lit "/" :b)' 331 | "1/2" 332 | ``` 333 | 334 | Comparing values can be done with `=`, `not=`, `>`, `>=`, `<` and `<=`. 335 | 336 | ``` clojure 337 | $ echo '[{:a 1} {:a 2} {:a 3}]' | jet --query '(filter (>= :a #jet/lit 2))' 338 | [{:a 2} {:a 3}] 339 | ``` 340 | 341 | ``` clojure 342 | echo '[{:a {:b 1}} {:a {:b 2}}]' \ 343 | | jet --query '(filter (= [:a :b] #jet/lit 1))' 344 | [{:a {:b 1}}] 345 | ``` 346 | 347 | Examples with `take-while` and `drop-while`: 348 | 349 | ``` clojure 350 | $ echo '[1 2 3 4 5 1 2]' | jet --query '(take-while (< id #jet/lit 5))' 351 | [1 2 3 4] 352 | $ echo '[1 2 3 4 5 1 2]' | jet --query '(drop-while (< id #jet/lit 5))' 353 | [5 1 2] 354 | ``` 355 | 356 | Applying a regex can be done with `re-find`: 357 | 358 | ``` shellsession 359 | $ echo '{:a "foo bar" :b 2}' | lein jet --query '(re-find #jet/lit "foo" :a)' 360 | "foo" 361 | ``` 362 | 363 | Control flow with `if`: 364 | 365 | ``` shellsession 366 | $ echo '{:a "foo bar" :b 2}' | jet --query '(if (re-find #jet/lit "foo" :a) :a :b)' 367 | "foo bar" 368 | ``` 369 | There is also `while`, which repeats a query until the condition is not met. 370 | 371 | ``` shellsession 372 | $ echo '0' | jet --query '(while (< id #jet/lit 10) (inc id))' 373 | 10 374 | ``` 375 | 376 | The Fibonacci sequence using `while`: 377 | 378 | ``` shellsession 379 | $ echo '{:fib0 0 :fib1 1 :n 0 :fib []}' | lein jet --query ' 380 | (while (<= :n #jet/lit 10) 381 | {:fib0 :fib1 :fib1 (+ :fib0 :fib1) :n (inc :n) :fib (conj :fib :fib0)}) 382 | :fib' 383 | [0 1 1 2 3 5 8 13 21 34 55] 384 | ``` 385 | 386 | Boolean logic: 387 | 388 | ``` shellsession 389 | $ echo '{:a 1 :b 3}' | jet --query '(and :a :b)' 390 | 3 391 | ``` 392 | 393 | ``` shellsession 394 | $ echo '{:a 1 :b 3}' | jet --query '(or :a :b)' 395 | 1 396 | ``` 397 | 398 | ``` shellsession 399 | $ echo '{:a 1 :b 3}' | jet --query '(not :a)' 400 | false 401 | ``` 402 | 403 | Arithmetic: 404 | 405 | ``` shellsession 406 | $ echo '{:a 3 :b 2}]' | jet --query '(inc :a)' 407 | 4 408 | ``` 409 | 410 | ``` shellsession 411 | $ echo '{:a 3 :b 2}]' | jet --query '(* :a :b) (- id #jet/lit 2)' 412 | 4 413 | ``` 414 | 415 | Use `conj` for adding elements to a list: 416 | 417 | ``` shellsession 418 | $ echo '[1 2 3]' | jet --query '(conj id #jet/lit 4)' 419 | [1 2 3 4] 420 | ``` 421 | 422 | Because `conj` is varargs, it always takes an explicit input query. 423 | 424 | Concatenating two lists or merging two maps can be done with `into`: 425 | 426 | ``` shellsession 427 | $ echo '{:a [1 2 3] :b [4 5 6]}' | jet --query '(into :a :b)' 428 | [1 2 3 4 5 6] 429 | $ echo '{:a {:x 1} :b {:y 2}}' | jet --query '(into :a :b)' 430 | {:x 1, :y 2} 431 | ``` 432 | 433 | ## Gallery 434 | 435 | ### jq example 436 | 437 | The last example of the [jq](https://stedolan.github.io/jq/tutorial/) tutorial 438 | using jet: 439 | 440 | ``` shellsession 441 | $ curl -s 'https://api.github.com/repos/stedolan/jq/commits?per_page=5' | \ 442 | jet --from json --keywordize --to edn --pretty --query ' 443 | (map 444 | {:message [:commit :message] 445 | :name [:commit :committer :name] 446 | :parents [:parents (map :html_url)]})' 447 | 448 | ({:message "Merge pull request #1948 from eli-schwartz/no-pacman-sy\n\ndocs: fix seriously dangerous download instructions for Arch Linux", 449 | :name "GitHub", 450 | ... 451 | ``` 452 | 453 | ### Latest commit SHA 454 | 455 | Get the latest commit SHA and date for a project from Github: 456 | ``` clojure 457 | $ curl -s https://api.github.com/repos/borkdude/clj-kondo/commits \ 458 | | jet --from json --keywordize --to edn \ 459 | --query '[0 {:sha :sha :date [:commit :author :date]}]' 460 | {:sha "bde8b1cbacb2b44ad2cd57d5875338f0926c8c0b", :date "2019-08-05T21:11:56Z"} 461 | ``` 462 | 463 | ### Find unused private vars using clj-kondo analysis output 464 | 465 | ``` clojure 466 | cat << EOF > /tmp/test.clj 467 | (ns foo) 468 | (defn- foo []) ;; NOTE: unused 469 | (defn- bar []) ;; NOTE: unused 470 | (defn- baz []) 471 | 472 | (defn -main [] 473 | (baz)) 474 | EOF 475 | 476 | clj-kondo --lint /tmp/test.clj --config '{:output {:analysis true :format :edn}}' | \ 477 | jet --query ' 478 | ;; select the analysis part of the clj-kondo output 479 | :analysis 480 | 481 | ;; create a map with private vars and used vars 482 | {:private-vars [:var-definitions (filter :private) (map (select-keys [:name :ns]))] 483 | :used-vars [:var-usages (map [(select-keys [:name :to]) (set/rename-keys {:to :ns})])]} 484 | 485 | ;; private vars that are not used: 486 | (set/difference :private-vars :used-vars) 487 | ' 488 | #{{:name bar, :ns foo} {:name foo, :ns foo}} 489 | ``` 490 | -------------------------------------------------------------------------------- /install: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | set -euo pipefail 4 | 5 | default_install_dir="/usr/local/bin" 6 | install_dir=$default_install_dir 7 | install_dir_opt=${1:-} 8 | if [ "$install_dir_opt" ]; then 9 | install_dir="$install_dir_opt" 10 | fi 11 | 12 | download_dir=/tmp 13 | 14 | latest_release="$(curl -sL https://raw.githubusercontent.com/borkdude/jet/master/resources/JET_RELEASED_VERSION)" 15 | 16 | case "$(uname -m)" in 17 | aarch64) arch=aarch64;; 18 | arm64) arch="aarch64" 19 | ;; 20 | *) arch=amd64 21 | esac 22 | 23 | case "$(uname -s)" in 24 | Linux*) platform=linux;; 25 | Darwin*) platform=macos;; 26 | esac 27 | 28 | download_url="https://github.com/borkdude/jet/releases/download/v$latest_release/jet-$latest_release-$platform-$arch.tar.gz" 29 | download_file="jet-$latest_release-$platform-$arch.tar.gz" 30 | 31 | cd "$download_dir" 32 | echo -e "Downloading $download_url." 33 | curl -o "$download_file" -sL "$download_url" 34 | tar -xzf "$download_file" 35 | rm "$download_file" 36 | 37 | cd "$install_dir" 38 | if [ -f jet ]; then 39 | echo "Moving $install_dir/jet to $install_dir/jet.old" 40 | fi 41 | 42 | mv -f "$download_dir/jet" "$PWD/jet" 43 | 44 | echo "Successfully installed jet in $install_dir." 45 | -------------------------------------------------------------------------------- /logo/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/borkdude/jet/1b126c051fb858708f30eca6d57eaded172d79b2/logo/icon.png -------------------------------------------------------------------------------- /logo/icon.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 22 | 24 | 47 | 49 | 50 | 52 | image/svg+xml 53 | 55 | 56 | 57 | 58 | 59 | 64 | 72 | 80 | 88 | 89 | 90 | -------------------------------------------------------------------------------- /logo/logo-300dpi.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/borkdude/jet/1b126c051fb858708f30eca6d57eaded172d79b2/logo/logo-300dpi.png -------------------------------------------------------------------------------- /logo/logo.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 22 | 24 | 46 | 48 | 49 | 51 | image/svg+xml 52 | 54 | 55 | 56 | 57 | 58 | 63 | 68 | 73 | 78 | 86 | 94 | 102 | 103 | 104 | -------------------------------------------------------------------------------- /pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4.0.0 4 | borkdude 5 | jet 6 | jar 7 | 0.4.23 8 | jet 9 | jet 10 | https://github.com/borkdude/jet 11 | 12 | 13 | Eclipse Public License 1.0 14 | http://opensource.org/licenses/eclipse-1.0.php 15 | 16 | 17 | 18 | https://github.com/borkdude/jet 19 | scm:git:git://github.com/borkdude/jet.git 20 | scm:git:ssh://git@github.com/borkdude/jet.git 21 | 4c35500524f9045ec52c5724b4f97aa51208da23 22 | 23 | 24 | src 25 | test 26 | 27 | 28 | resources 29 | 30 | 31 | 32 | 33 | resources 34 | 35 | 36 | target 37 | target/classes 38 | 39 | 40 | 41 | 42 | central 43 | https://repo1.maven.org/maven2/ 44 | 45 | false 46 | 47 | 48 | true 49 | 50 | 51 | 52 | clojars 53 | https://repo.clojars.org/ 54 | 55 | true 56 | 57 | 58 | true 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | org.clojure 68 | clojure 69 | 1.11.2 70 | 71 | 72 | com.cognitect 73 | transit-clj 74 | 1.0.329 75 | 76 | 77 | cheshire 78 | cheshire 79 | 5.11.0 80 | 81 | 82 | clj-commons 83 | clj-yaml 84 | 1.0.26 85 | 86 | 87 | mvxcvi 88 | puget 89 | 1.3.2 90 | 91 | 92 | commons-io 93 | commons-io 94 | 2.11.0 95 | 96 | 97 | org.babashka 98 | sci 99 | 0.5.34 100 | 101 | 102 | org.babashka 103 | cli 104 | 0.6.45 105 | 106 | 107 | camel-snake-kebab 108 | camel-snake-kebab 109 | 0.4.3 110 | 111 | 112 | com.rpl 113 | specter 114 | 1.1.4 115 | 116 | 117 | clj-commons 118 | conch 119 | 0.9.2 120 | test 121 | 122 | 123 | 124 | 125 | 129 | -------------------------------------------------------------------------------- /project.clj: -------------------------------------------------------------------------------- 1 | (defproject borkdude/jet 2 | #=(clojure.string/trim 3 | #=(slurp "resources/JET_VERSION")) 4 | :description "jet" 5 | :url "https://github.com/borkdude/jet" 6 | :scm {:name "git" 7 | :url "https://github.com/borkdude/jet"} 8 | :license {:name "Eclipse Public License 1.0" 9 | :url "http://opensource.org/licenses/eclipse-1.0.php"} 10 | :source-paths ["src"] 11 | :dependencies [[org.clojure/clojure "1.11.1"] 12 | [com.cognitect/transit-clj "1.0.333"] 13 | [cheshire "5.11.0"] 14 | [clj-commons/clj-yaml "1.0.26"] 15 | [mvxcvi/puget "1.3.2"] 16 | [commons-io/commons-io "2.11.0"] 17 | [org.babashka/sci "0.8.41"] 18 | [org.babashka/cli "0.8.58"] 19 | [camel-snake-kebab "0.4.3"] 20 | [com.rpl/specter "1.1.4"] 21 | [rewrite-clj/rewrite-clj "1.1.47"]] 22 | :profiles {:test {:dependencies [[clj-commons/conch "0.9.2"]]} 23 | :uberjar {:dependencies [[com.github.clj-easy/graal-build-time "0.1.4"]] 24 | :global-vars {*assert* false} 25 | :jvm-opts [#_"-Dclojure.compiler.direct-linking=true" 26 | #_"-Dclojure.spec.skip-macros=true"] 27 | :aot [jet.main] 28 | :main jet.main} 29 | :native-image {:jvm-opts ["-Djet.native=true"] 30 | :java-source-paths ["src-java"]}} 31 | :aliases {"jet" ["run" "-m" "jet.main"]} 32 | :deploy-repositories [["clojars" {:url "https://clojars.org/repo" 33 | :username :env/clojars_user 34 | :password :env/clojars_pass 35 | :sign-releases false}]]) 36 | -------------------------------------------------------------------------------- /resources/JET_RELEASED_VERSION: -------------------------------------------------------------------------------- 1 | 0.7.27 2 | -------------------------------------------------------------------------------- /resources/JET_VERSION: -------------------------------------------------------------------------------- 1 | 0.7.28-SNAPSHOT -------------------------------------------------------------------------------- /resources/META-INF/native-image/borkdude/jet/native-image.properties: -------------------------------------------------------------------------------- 1 | ImageName=jet 2 | Args=-J-Dclojure.spec.skip-macros=true \ 3 | -J-Dclojure.compiler.direct-linking=true \ 4 | -H:IncludeResources=JET_VERSION \ 5 | --initialize-at-build-time=com.fasterxml.jackson \ 6 | --initialize-at-build-time=org.yaml.snakeyaml.DumperOptions\$FlowStyle \ 7 | -H:Log=registerResource: \ 8 | --no-server \ 9 | -------------------------------------------------------------------------------- /resources/META-INF/native-image/borkdude/jet/reflect-config.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "name": "java.lang.Class", 4 | "allDeclaredConstructors": true, 5 | "allPublicConstructors": true, 6 | "allDeclaredMethods": true, 7 | "allPublicMethods": true 8 | }, 9 | 10 | { 11 | "name" : "clojure.lang.Util", 12 | "methods": [{"name": "equiv"}] 13 | } 14 | 15 | ] 16 | -------------------------------------------------------------------------------- /script/babashka/release_artifact.clj: -------------------------------------------------------------------------------- 1 | (ns babashka.release-artifact 2 | (:require [borkdude.gh-release-artifact :as ghr] 3 | [clojure.java.shell :refer [sh]] 4 | [clojure.string :as str])) 5 | 6 | (defn current-branch [] 7 | (or (System/getenv "APPVEYOR_PULL_REQUEST_HEAD_REPO_BRANCH") 8 | (System/getenv "APPVEYOR_REPO_BRANCH") 9 | (System/getenv "CIRCLE_BRANCH") 10 | (System/getenv "GITHUB_REF_NAME") 11 | (System/getenv "CIRRUS_BRANCH") 12 | (-> (sh "git" "rev-parse" "--abbrev-ref" "HEAD") 13 | :out 14 | str/trim))) 15 | 16 | (defn release [& args] 17 | (let [ght (System/getenv "GITHUB_TOKEN") 18 | _ (println "Github token found") 19 | file (first args) 20 | _ (println "File" file) 21 | branch (current-branch) 22 | _ (println "On branch:" branch) 23 | current-version 24 | (-> (slurp "resources/JET_VERSION") 25 | str/trim)] 26 | (if (and ght (contains? #{"master" "main"} branch)) 27 | (do (assert file "File name must be provided") 28 | (println "On main branch. Publishing asset.") 29 | (ghr/overwrite-asset {:org "borkdude" 30 | :repo "jet" 31 | :file file 32 | :tag (str "v" current-version) 33 | :draft true 34 | :overwrite (str/ends-with? current-version "SNAPSHOT") 35 | :sha256 true})) 36 | (println "Skipping release artifact (no GITHUB_TOKEN or not on main branch)")) 37 | nil)) 38 | -------------------------------------------------------------------------------- /script/bump-version.clj: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bb 2 | 3 | (ns bump-version 4 | (:require [babashka.process :as p] 5 | [clojure.java.io :as io] 6 | [clojure.string :as str])) 7 | 8 | (def version-file (io/file "resources" "JET_VERSION")) 9 | (def released-version-file (io/file "resources" "JET_RELEASED_VERSION")) 10 | 11 | (case (first *command-line-args*) 12 | "release" (let [version-string (str/trim (slurp version-file)) 13 | [major minor patch] (str/split version-string #"\.") 14 | patch (str/replace patch "-SNAPSHOT" "") 15 | new-version (str/join "." [major minor patch])] 16 | (spit version-file new-version) 17 | (-> (p/process ["git" "commit" "-a" "-m" (str "v" new-version)] {:inherit true}) 18 | (p/check)) 19 | (-> (p/process ["git" "diff" "HEAD^" "HEAD"] {:inherit true}) 20 | (p/check)) 21 | nil) 22 | "post-release" (do 23 | (io/copy version-file released-version-file) 24 | (let [version-string (str/trim (slurp version-file)) 25 | [major minor patch] (str/split version-string #"\.") 26 | patch (Integer. patch) 27 | patch (str (inc patch) "-SNAPSHOT") 28 | new-version (str/join "." [major minor patch])] 29 | (spit version-file new-version) 30 | (-> (p/process ["git" "commit" "-a" "-m" "Version bump"] {:inherit true}) 31 | (p/check)) 32 | (-> (p/process ["git" "diff" "HEAD^" "HEAD"] {:inherit true}) 33 | (p/check)) 34 | nil)) 35 | (println "Expected: release | post-release.")) 36 | -------------------------------------------------------------------------------- /script/changelog.clj: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bb 2 | 3 | (ns changelog 4 | (:require [clojure.string :as str])) 5 | 6 | (let [changelog (slurp "CHANGELOG.md") 7 | replaced (str/replace changelog 8 | #" #(\d+)" 9 | (fn [[_ issue after]] 10 | (format " [#%s](https://github.com/borkdude/jet/issues/%s)%s" 11 | issue issue (str after)))) 12 | replaced (str/replace replaced 13 | #"@([a-zA-Z0-9-_]+)([, \.)])" 14 | (fn [[_ name after]] 15 | (format "[@%s](https://github.com/%s)%s" 16 | name name after)))] 17 | (spit "CHANGELOG.md" replaced)) 18 | -------------------------------------------------------------------------------- /script/check_glibc.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | function ver_lte() { 4 | printf '%s\n%s' "$1" "$2" | sort -C -V 5 | } 6 | 7 | max_glibc_version="2.31" 8 | current_glibc_version=$(ldd --version | head -1 | awk '{print $4}' | cut -d "-" -f 1) 9 | 10 | function bail() { 11 | echo "glibc greater than max version ${max_glibc_version}: ${current_glibc_version}" 12 | exit 1 13 | } 14 | 15 | ver_lte "${current_glibc_version}" "${max_glibc_version}" || bail 16 | -------------------------------------------------------------------------------- /script/compile: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | set -eo pipefail 4 | 5 | if [ -z "$GRAALVM_HOME" ]; then 6 | echo "Please set GRAALVM_HOME" 7 | exit 1 8 | fi 9 | 10 | "$GRAALVM_HOME/bin/gu" install native-image || true 11 | 12 | JET_VERSION=$(cat resources/JET_VERSION) 13 | 14 | export PATH=$GRAALVM_HOME/bin:$PATH 15 | export JAVA_HOME=$GRAALVM_HOME 16 | 17 | lein with-profiles +native-image "do" clean, uberjar 18 | 19 | BABASHKA_STATIC=${BABASHKA_STATIC:-} 20 | BABASHKA_MUSL=${BABASHKA_MUSL:-} 21 | 22 | args=("-jar" "target/jet-$JET_VERSION-standalone.jar" 23 | "-H:+ReportExceptionStackTraces" 24 | "--verbose" 25 | "--no-fallback" 26 | "--no-server" 27 | "-J-Xmx3g" 28 | "-J-Djet.native=true") 29 | 30 | if [ "$BABASHKA_STATIC" = "true" ]; then 31 | args+=("--static") 32 | if [ "$BABASHKA_MUSL" = "true" ]; then 33 | args+=("--libc=musl" 34 | # see https://github.com/oracle/graal/issues/3398 35 | "-H:CCompilerOption=-Wl,-z,stack-size=2097152") 36 | else 37 | # see https://github.com/oracle/graal/issues/3737 38 | args+=("-H:+StaticExecutableWithDynamicLibC") 39 | fi 40 | fi 41 | 42 | "$GRAALVM_HOME/bin/native-image" "${args[@]}" 43 | 44 | lein clean 45 | -------------------------------------------------------------------------------- /script/compile.bat: -------------------------------------------------------------------------------- 1 | @echo off 2 | 3 | Rem set GRAALVM_HOME=C:\Users\IEUser\Downloads\graalvm\graalvm-ce-19.2.1 4 | Rem set PATH=%PATH%;C:\Users\IEUser\bin 5 | 6 | if "%GRAALVM_HOME%"=="" ( 7 | echo Please set GRAALVM_HOME 8 | exit /b 9 | ) 10 | set JAVA_HOME=%GRAALVM_HOME%\bin 11 | set PATH=%GRAALVM_HOME%\bin;%PATH% 12 | 13 | set /P JET_VERSION=< resources\JET_VERSION 14 | echo Building jet %JET_VERSION% 15 | 16 | java -version 17 | call lein with-profiles +native-image do clean, uberjar 18 | if %errorlevel% neq 0 exit /b %errorlevel% 19 | 20 | call %GRAALVM_HOME%\bin\gu.cmd install native-image 21 | 22 | Rem the --no-server option is not supported in GraalVM Windows. 23 | call %GRAALVM_HOME%\bin\native-image.cmd ^ 24 | "-jar" "target/jet-%JET_VERSION%-standalone.jar" ^ 25 | "-H:+ReportExceptionStackTraces" ^ 26 | "-H:Log=registerResource:" ^ 27 | "--no-fallback" ^ 28 | "--verbose" ^ 29 | "-J-Xmx3g" 30 | 31 | if %errorlevel% neq 0 exit /b %errorlevel% 32 | 33 | echo Creating zip archive 34 | jar -cMf jet-%JET_VERSION%-windows-amd64.zip jet.exe 35 | -------------------------------------------------------------------------------- /script/install-clojure: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | set -euo pipefail 4 | 5 | CLOJURE_TOOLS_VERSION="1.10.3.1040" 6 | 7 | install_dir="${1:-/usr/local}" 8 | mkdir -p "$install_dir" 9 | cd /tmp 10 | curl -O -sL "https://download.clojure.org/install/clojure-tools-$CLOJURE_TOOLS_VERSION.tar.gz" 11 | tar xzf "clojure-tools-$CLOJURE_TOOLS_VERSION.tar.gz" 12 | cd clojure-tools 13 | clojure_lib_dir="$install_dir/lib/clojure" 14 | mkdir -p "$clojure_lib_dir/libexec" 15 | cp ./*.jar "$clojure_lib_dir/libexec" 16 | cp deps.edn "$clojure_lib_dir" 17 | cp example-deps.edn "$clojure_lib_dir" 18 | 19 | sed -i -e 's@PREFIX@'"$clojure_lib_dir"'@g' clojure 20 | mkdir -p "$install_dir/bin" 21 | cp clojure "$install_dir/bin" 22 | cp clj "$install_dir/bin" 23 | 24 | cd /tmp 25 | rm -rf "clojure-tools-$CLOJURE_TOOLS_VERSION.tar.gz" 26 | rm -rf "clojure-tools" 27 | echo "Installed clojure to $install_dir/bin" -------------------------------------------------------------------------------- /script/install-graalvm: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | set -euo pipefail 4 | 5 | INSTALL_DIR="${1:-$HOME}" 6 | 7 | GRAALVM_VERSION="${GRAALVM_VERSION:-21.2.0}" 8 | 9 | case "$JET_PLATFORM" in 10 | macos) 11 | GRAALVM_PLATFORM="darwin" 12 | ;; 13 | linux) 14 | GRAALVM_PLATFORM="linux" 15 | ;; 16 | esac 17 | 18 | case "${JET_ARCH:-}" in 19 | aarch64) 20 | GRAALVM_ARCH="aarch64" 21 | ;; 22 | *) 23 | GRAALVM_ARCH="amd64" 24 | ;; 25 | esac 26 | 27 | GRAALVM_FILENAME="graalvm-ce-java11-$GRAALVM_PLATFORM-$GRAALVM_ARCH-$GRAALVM_VERSION.tar.gz" 28 | 29 | pushd "$INSTALL_DIR" >/dev/null 30 | 31 | if ! [ -d "graalvm-ce-java11-$GRAALVM_VERSION" ]; then 32 | echo "Downloading GraalVM $GRAALVM_PLATFORM-$GRAALVM_ARCH-$GRAALVM_VERSION on '$PWD'..." 33 | curl -O -sL "https://github.com/graalvm/graalvm-ce-builds/releases/download/vm-$GRAALVM_VERSION/$GRAALVM_FILENAME" 34 | tar xzf "$GRAALVM_FILENAME" 35 | fi 36 | 37 | popd >/dev/null -------------------------------------------------------------------------------- /script/install-leiningen: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | curl https://raw.githubusercontent.com/technomancy/leiningen/2.9.1/bin/lein > lein 4 | mkdir -p /usr/local/bin/ 5 | mv lein /usr/local/bin/lein 6 | chmod a+x /usr/local/bin/lein 7 | lein self-install -------------------------------------------------------------------------------- /script/setup-musl: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | set -euo pipefail 4 | 5 | if [[ -z "${BABASHKA_STATIC:-}" ]]; then 6 | echo "BABASHKA_STATIC wasn't set, skipping musl installation." 7 | exit 0 8 | fi 9 | 10 | if [[ -z "${BABASHKA_MUSL:-}" ]]; then 11 | echo "BABASHKA_MUSL wasn't set, skipping musl installation." 12 | exit 0 13 | fi 14 | 15 | if [[ "${BABASHKA_ARCH:-"x86_64"}" != "x86_64" ]]; then 16 | echo "GraalVM only supports building static binaries on x86_64." 17 | exit 1 18 | fi 19 | 20 | apt-get update -y && apt-get install musl-tools -y 21 | 22 | ZLIB_VERSION="1.2.11" 23 | ZLIB_SHA256="c3e5e9fdd5004dcb542feda5ee4f0ff0744628baf8ed2dd5d66f8ca1197cb1a1" 24 | 25 | # stable archive path 26 | curl -O -sL --fail --show-error "https://zlib.net/fossils/zlib-${ZLIB_VERSION}.tar.gz" 27 | 28 | echo "${ZLIB_SHA256} zlib-${ZLIB_VERSION}.tar.gz" | sha256sum --check 29 | tar xf "zlib-${ZLIB_VERSION}.tar.gz" 30 | 31 | arch=${BABASHKA_ARCH:-"x86_64"} 32 | echo "ARCH: $arch" 33 | 34 | cd "zlib-${ZLIB_VERSION}" 35 | CC=musl-gcc ./configure --static --prefix="/usr/local" 36 | make CC=musl-gcc 37 | make install 38 | cd .. 39 | 40 | # Install libz.a in the correct place so ldd can find it 41 | install -Dm644 "/usr/local/lib/libz.a" "/usr/lib/$arch-linux-musl/libz.a" 42 | 43 | ln -s /usr/bin/musl-gcc /usr/bin/x86_64-linux-musl-gcc 44 | -------------------------------------------------------------------------------- /script/test: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | set -eo pipefail 4 | 5 | if [ "$JET_TEST_ENV" = "native" ]; then 6 | lein test 7 | else 8 | lein test 9 | fi 10 | -------------------------------------------------------------------------------- /src-java/org/babashka/CLibrary.java: -------------------------------------------------------------------------------- 1 | package org.babashka; 2 | 3 | import org.graalvm.nativeimage.c.function.CEntryPoint; 4 | import org.graalvm.nativeimage.c.function.CFunction; 5 | import org.graalvm.nativeimage.c.function.CFunction.Transition; 6 | import org.graalvm.nativeimage.c.CContext; 7 | import java.util.List; 8 | import java.util.Collections; 9 | 10 | import org.graalvm.word.Pointer; 11 | 12 | @CContext(CLibrary.Directives.class) 13 | public final class CLibrary { 14 | public static final class Directives implements CContext.Directives { 15 | @Override 16 | public List getHeaderFiles() { 17 | if ((System.getProperty("os.name").startsWith("Win"))) { 18 | return Collections.singletonList(""); 19 | } 20 | else { 21 | return Collections.singletonList(""); 22 | } 23 | } 24 | } 25 | @CFunction 26 | public static native int isatty(int fd); 27 | } 28 | -------------------------------------------------------------------------------- /src/data_readers.clj: -------------------------------------------------------------------------------- 1 | {jet/lit jet.data-readers/lit} 2 | -------------------------------------------------------------------------------- /src/jet/base64.clj: -------------------------------------------------------------------------------- 1 | (ns jet.base64 2 | (:import [java.util Base64])) 3 | 4 | (set! *warn-on-reflection* true) 5 | 6 | (defn decode [x] 7 | (let [bytes (.decode (Base64/getDecoder) (str x))] 8 | (String. bytes "utf-8"))) 9 | 10 | (defn encode [x] 11 | (.encodeToString (Base64/getEncoder) (.getBytes (str x)))) 12 | 13 | (def base64-namespace 14 | {'decode decode 15 | 'encode encode}) 16 | -------------------------------------------------------------------------------- /src/jet/data_readers.clj: -------------------------------------------------------------------------------- 1 | (ns jet.data-readers 2 | {:no-doc true}) 3 | 4 | (defn lit [x] 5 | (list 'quote x)) 6 | -------------------------------------------------------------------------------- /src/jet/formats.clj: -------------------------------------------------------------------------------- 1 | (ns jet.formats 2 | {:no-doc true} 3 | (:require 4 | [cheshire.core :as cheshire] 5 | [cheshire.factory :as factory] 6 | [cheshire.parse :as cheshire-parse] 7 | [clj-yaml.core :as yaml] 8 | [clojure.edn :as edn] 9 | [clojure.string :as str] 10 | [clojure.walk :as walk] 11 | [cognitect.transit :as transit] 12 | [fipp.edn :as fipp] 13 | [jet.data-readers] 14 | [puget.printer :as puget] 15 | [rewrite-clj.zip :as z]) 16 | (:import 17 | [com.fasterxml.jackson.core JsonFactory] 18 | [java.io Reader] 19 | [org.apache.commons.io.input ReaderInputStream])) 20 | 21 | (set! *warn-on-reflection* true) 22 | 23 | (def ^:private with-c-lib? 24 | (boolean (resolve 'org.babashka.CLibrary))) 25 | 26 | (def in-native-image? 27 | (= "true" 28 | (System/getProperty "com.oracle.graalvm.isaot") )) 29 | 30 | (defmacro ^:no-doc 31 | if-c-lib [then else] 32 | (if with-c-lib? 33 | then else)) 34 | 35 | (defn in-terminal? [] 36 | (if-c-lib 37 | (when in-native-image? 38 | (pos? (org.babashka.CLibrary/isatty 1))) 39 | false)) 40 | 41 | (defn colorize? [colors] 42 | (or (= true colors) 43 | (= :always colors) 44 | (and (= :auto colors) 45 | (in-terminal?)))) 46 | 47 | (defn pprint [x colors uncomma] 48 | (if colors 49 | (puget/cprint x {:map-delimiter (if uncomma "" ",")}) 50 | (fipp/pprint x))) 51 | 52 | (defn json-parser [] 53 | (.createParser ^JsonFactory (or factory/*json-factory* 54 | factory/json-factory) 55 | ^Reader *in*)) 56 | 57 | (defn parse-json [json-reader keywordize] 58 | (cheshire-parse/parse-strict json-reader keywordize ::EOF nil)) 59 | 60 | (defn generate-json [o pretty] 61 | (cheshire/generate-string o {:pretty pretty})) 62 | 63 | (defn parse-edn [opts *in*] 64 | (edn/read (assoc opts :eof ::EOF) *in*)) 65 | 66 | (defn uncomma-edn [edn-str] 67 | (-> (let [loc (z/of-string edn-str)] 68 | (loop [loc loc] 69 | (let [next (z/next* loc)] 70 | (if (z/end? loc) (z/root loc) 71 | (if (= :comma (z/tag loc)) 72 | (recur (z/remove* loc)) 73 | (recur next)))))) 74 | str)) 75 | 76 | (defn generate-edn [o pretty color uncomma] 77 | (let [edn-str (if pretty (str/trim (with-out-str (pprint o color uncomma))) 78 | (pr-str o))] 79 | (if (and uncomma (not color)) 80 | (uncomma-edn edn-str) 81 | edn-str))) 82 | 83 | (defn transit-reader [] 84 | (transit/reader (ReaderInputStream. *in*) :json)) 85 | 86 | (defn parse-transit [rdr] 87 | (try (transit/read rdr) 88 | (catch java.lang.RuntimeException e 89 | (if-let [cause (.getCause e)] 90 | (if (instance? java.io.EOFException cause) 91 | ::EOF 92 | (throw e)) 93 | (throw e))))) 94 | 95 | (defn generate-transit [o] 96 | (let [bos (java.io.ByteArrayOutputStream. 1024) 97 | writer (transit/writer bos :json)] 98 | (transit/write writer o) 99 | (String. (.toByteArray bos) "UTF-8"))) 100 | 101 | (defn parse-yaml [rdr keywordize] 102 | (try 103 | (or (some->> (if (fn? keywordize) 104 | [:key-fn #(-> % :key keywordize)] 105 | [:keywords (boolean keywordize)]) 106 | (concat [rdr]) 107 | (apply yaml/parse-stream) 108 | (walk/postwalk (fn [x] (if (seq? x) (vec x) x)))) 109 | ::EOF) 110 | (catch org.yaml.snakeyaml.parser.ParserException e 111 | (if (str/includes? (ex-message e) "but got ") 112 | ::EOF 113 | (throw e))))) 114 | 115 | (defn generate-yaml [o pretty] 116 | (yaml/generate-string o :dumper-options {:flow-style (if pretty :block :auto)})) 117 | -------------------------------------------------------------------------------- /src/jet/jeti.clj: -------------------------------------------------------------------------------- 1 | (ns jet.jeti 2 | {:no-doc true} 3 | (:require 4 | [clojure.edn :as edn] 5 | [clojure.java.io :as io] 6 | [clojure.string :as str] 7 | [jet.formats :as formats] 8 | [jet.query :refer [query]])) 9 | 10 | (set! *warn-on-reflection* true) 11 | 12 | (defn new-id [state] 13 | (let [uuid (subs (str (java.util.UUID/randomUUID)) 0 4)] 14 | (if (contains? state uuid) 15 | (recur state) 16 | uuid))) 17 | 18 | (defn print-help [sub-command] 19 | (if sub-command 20 | (case sub-command 21 | :jeti/slurp 22 | (println ":jeti/slurp [{:format ..., :keywordize ...}]: slurps a file into the shell from disk. :format is one of :json, :edn, :yaml or :transit (defaults to :edn) and :keywordize is a boolean that only applies to :json and :yaml.") 23 | :jeti/spit 24 | (println ":jeti/spit [{:format ..., :pretty ...}]: spits a file to disk. :format is one of :json, :edn, :yaml or :transit (defaults to :edn) and :pretty is a boolean that indicates if the output should be pretty printed.") 25 | (println "Help for" sub-command "not found.")) 26 | (do (println "Available commands:") 27 | (println ":jeti/set-val {:a 1} : set value.") 28 | (println ":jeti/jump \"34d4\" : jump to a previous state.") 29 | (println ":jeti/quit, :jeti/exit : exit this shell.") 30 | (println ":jeti/slurp : read a file from disk. Type :jeti/help :jeti/slurp for more details.") 31 | (println ":jeti/spit : writes file to disk. Type :jeti/help :jeti/spit for more details.") 32 | (println ":jeti/bookmark \"name\" : save a bookmark.") 33 | (println ":jeti/bookmarks : show bookmarks.") 34 | (println ":jeti/print-length : set *print-length*") 35 | (println ":jeti/print-level : set *print-level*") 36 | (println)))) 37 | 38 | (defn jeti-set! [state current-id init-val] 39 | (try (let [next-id (new-id state)] 40 | {:state (assoc state next-id init-val) 41 | :next-id next-id}) 42 | (catch Exception e 43 | (println "Could not read" format init-val e) 44 | {:state state 45 | :next-id current-id}))) 46 | 47 | (defn read-file [state current-id file {:keys [:format :keywordize] 48 | :or {format :edn}}] 49 | (try (let [next-id (new-id state) 50 | file-as-string (slurp (str file)) 51 | file-as-edn (case format 52 | :edn (with-in-str file-as-string (formats/parse-edn nil *in*)) 53 | :transit (with-in-str file-as-string (formats/parse-transit 54 | (formats/transit-reader))) 55 | :json (with-in-str file-as-string 56 | (formats/parse-json (formats/json-parser) keywordize)) 57 | :yaml (with-in-str file-as-string 58 | (formats/parse-yaml *in* keywordize)))] 59 | {:state (assoc state next-id file-as-edn) 60 | :next-id next-id}) 61 | (catch Exception e 62 | (println "Could not read" format file e) 63 | {:state state 64 | :next-id current-id}))) 65 | 66 | (defn write-file [value file {:keys [:format :pretty] 67 | :or {format :edn}}] 68 | (try (let [out-string (case format 69 | :edn (formats/generate-edn value pretty true) 70 | :transit (formats/generate-transit value) 71 | :json (formats/generate-json value pretty) 72 | :yaml (formats/generate-yaml value pretty))] 73 | (spit file out-string)) 74 | (catch Exception e 75 | (println "Could not write to" (str file ":") (.getMessage e))))) 76 | 77 | (defn start-jeti! [init-cmd colors] 78 | (println "Welcome to jeti. The answer is just a few queries away!") 79 | (println "Running jet" (str "v" (str/trim (slurp (io/resource "JET_VERSION"))) ".")) 80 | (println "Type :jeti/help to print help.") 81 | (println) 82 | (let [init-cmd (when-not (boolean? init-cmd) 83 | init-cmd) 84 | init-id (new-id nil)] 85 | (loop [{:keys [:bookmarks :print-level :print-length :init-cmd] :as state} 86 | {:init-cmd init-cmd 87 | init-id ::start 88 | :bookmarks [] 89 | :print-length 5 90 | :print-level 5} 91 | previous-id nil 92 | current-id init-id] 93 | (let [current-val (get state current-id) 94 | start? (identical? ::start current-val) 95 | same? (= current-id previous-id) 96 | _ (when (and (not start?) 97 | (not same?)) 98 | (println (binding [*print-length* print-length 99 | *print-level* print-level] 100 | (formats/generate-edn current-val true colors false))) 101 | (println)) 102 | proceed? (cond (and start? init-cmd) 103 | (do (println ">" init-cmd) 104 | true) 105 | same? false 106 | start? true 107 | :else 108 | (= "Y" (str/trim 109 | (do 110 | (print "Type Y to enter this state. ") 111 | (flush) 112 | (read-line))))) 113 | current-id (cond start? current-id 114 | proceed? current-id 115 | :else previous-id) 116 | current-val (get state current-id) 117 | start-val? (identical? ::start current-val) 118 | bookmark-name (some #(when (= current-id (:id %)) 119 | (:name %)) 120 | bookmarks)] 121 | (when-not (and init-cmd start?) 122 | (print (str (when-not start-val? current-id) 123 | (when bookmark-name 124 | (format "(%s)" bookmark-name)) 125 | "> "))) 126 | (flush) 127 | (when-let [q (if (and start? init-cmd) 128 | init-cmd 129 | (read-line))] 130 | (let [state (dissoc state :init-cmd) 131 | [fst :as q] (try (edn/read-string 132 | {:readers *data-readers*} 133 | (format "[%s]" q)) 134 | (catch Exception e 135 | (println "Invalid input:" (.getMessage e)) 136 | nil)) 137 | [cmd & opts] (when fst 138 | (when 139 | (and (keyword? fst) 140 | (= "jeti" (namespace fst))) 141 | q)) 142 | first-opts (first opts)] 143 | (cond (not q) 144 | (recur state current-id current-id) 145 | cmd 146 | (case cmd 147 | :jeti/jump 148 | (let [id first-opts] 149 | (cond (not (string? id)) 150 | (do 151 | (println ":jeti/jump expects a string") 152 | (recur state current-id current-id)) 153 | (contains? state id) 154 | (recur state current-id id) 155 | :else 156 | (do 157 | (println id "is not a valid state.") 158 | (recur state current-id current-id)))) 159 | :jeti/help 160 | (do (print-help first-opts) 161 | (recur state current-id current-id)) 162 | (:jeti/quit :jeti/exit) (println "Goodbye for now!") 163 | :jeti/set-val (let [{:keys [:state :next-id]} 164 | (jeti-set! state current-id first-opts)] 165 | (recur state current-id next-id)) 166 | :jeti/slurp 167 | (let [{:keys [:state :next-id]} 168 | (read-file state current-id first-opts (second opts))] 169 | (recur state current-id next-id)) 170 | :jeti/spit 171 | (do (write-file current-val first-opts (second opts)) 172 | (recur state current-id current-id)) 173 | :jeti/bookmark 174 | (let [bookmark-name first-opts 175 | bookmark {:id current-id 176 | :name bookmark-name} 177 | next-state (update state :bookmarks conj bookmark)] 178 | (recur next-state current-id current-id)) 179 | :jeti/bookmarks 180 | (do (doseq [{:keys [:name :id]} (:bookmarks state)] 181 | (println (str id ": " name))) 182 | (recur state current-id current-id)) 183 | :jeti/print-length 184 | (recur (assoc state :print-length 185 | (if (number? first-opts) first-opts 186 | (do (println "You didn't enter a number:" first-opts) 187 | print-length))) 188 | current-id current-id) 189 | :jeti/print-level 190 | (recur (assoc state :print-level 191 | (if (number? first-opts) first-opts 192 | (do (println "You didn't enter a number:" first-opts) 193 | print-level))) 194 | current-id current-id) 195 | :jeti/debug (do (prn state) 196 | (recur state current-id current-id)) 197 | (do 198 | (println "I did not understand your command.") 199 | (recur state current-id current-id))) 200 | :else 201 | (let [next-input (try (if start? 202 | (first q) 203 | (query current-val q)) 204 | (catch Exception e 205 | (println "Error while executing query:" 206 | (.getMessage e)) 207 | ::error)) 208 | [next-id next-input] (if (identical? ::error next-input) 209 | [current-id current-val] 210 | [(new-id state) next-input])] 211 | (recur (assoc state next-id next-input) 212 | current-id next-id))))))))) 213 | -------------------------------------------------------------------------------- /src/jet/main.clj: -------------------------------------------------------------------------------- 1 | (ns jet.main 2 | {:no-doc true} 3 | (:require 4 | [babashka.cli :as cli] 5 | [camel-snake-kebab.core :as csk] 6 | [clojure.edn :as edn] 7 | [clojure.java.io :as io] 8 | [clojure.string :as str] 9 | [jet.base64 :refer [base64-namespace]] 10 | [jet.data-readers] 11 | [jet.formats :as formats] 12 | [jet.jeti :refer [start-jeti!]] 13 | [jet.query :as q] 14 | [jet.specter :as specter :refer [config]] 15 | [sci.core :as sci] 16 | [sci.impl.utils :refer [clojure-core-ns]]) 17 | (:gen-class)) 18 | 19 | (set! *warn-on-reflection* true) 20 | 21 | (defn -paths [m opts] 22 | (cond 23 | (map? m) 24 | (vec 25 | (concat (map vector (keys m)) 26 | (mapcat (fn [[k v]] 27 | (let [sub (-paths v opts) 28 | nested (map #(into [k] %) (filter (comp not empty?) sub))] 29 | (when (seq nested) 30 | nested))) 31 | m))) 32 | (vector? m) 33 | (vec (concat (map vector (range (count m))) 34 | (mapcat (fn [idx] 35 | (let [sub (-paths (get m idx) opts) 36 | nested (map #(into [idx] %) (filter (comp not empty?) sub))] 37 | (when (seq nested) 38 | nested))) 39 | (range (count m))))) 40 | :else [])) 41 | 42 | (defn paths 43 | [m] 44 | (let [paths (-paths m nil) 45 | paths (mapv (fn [path] 46 | (let [v (get-in m path)] 47 | {:path path 48 | :val v})) 49 | paths)] 50 | paths)) 51 | 52 | (defn when-pred [pred] 53 | (fn [x] 54 | (try (when (pred x) 55 | x) 56 | (catch Exception _ nil)))) 57 | 58 | (def ctx 59 | (-> (sci/init {:namespaces {'clojure.core {'update-vals (sci/copy-var update-vals clojure-core-ns) 60 | 'update-keys (sci/copy-var update-keys clojure-core-ns) 61 | 'parse-boolean (sci/copy-var parse-boolean clojure-core-ns) 62 | 'parse-double (sci/copy-var parse-double clojure-core-ns) 63 | 'parse-long (sci/copy-var parse-long clojure-core-ns) 64 | 'parse-uuid (sci/copy-var parse-uuid clojure-core-ns)} 65 | 'camel-snake-kebab.core 66 | {'->PascalCase csk/->PascalCase 67 | '->camelCase csk/->camelCase 68 | '->SCREAMING_SNAKE_CASE csk/->SCREAMING_SNAKE_CASE 69 | '->snake_case csk/->snake_case 70 | '->kebab-case csk/->kebab-case 71 | '->Camel_Snake_Case csk/->Camel_Snake_Case 72 | '->HTTP-Header-Case csk/->HTTP-Header-Case} 73 | 'com.rpl.specter 74 | {} 75 | 'base64 base64-namespace 76 | 'jet {'paths paths 77 | 'when-pred when-pred}} 78 | :aliases '{str clojure.string 79 | s com.rpl.specter 80 | csk camel-snake-kebab.core}}) 81 | (sci/merge-opts config))) 82 | 83 | (defn coerce-file [s] 84 | (if (.exists (io/as-file s)) 85 | (slurp s) 86 | s)) 87 | 88 | (defn coerce-eval-string [x] 89 | (->> x coerce-file (sci/eval-string* ctx))) 90 | 91 | (defn coerce-keywordize [x] 92 | (cond (= :kebab-case x) (comp keyword csk/->kebab-case) 93 | (= :snake_case x) (comp keyword csk/->snake_case) 94 | (= :PascalCase x) (comp keyword csk/->PascalCase) 95 | (= :camelCase x) (comp keyword csk/->camelCase) 96 | (= :Camel_Snake_Case x) (comp keyword csk/->Camel_Snake_Case) 97 | (= :SCREAMING_SNAKE_CASE x) (comp keyword csk/->SCREAMING_SNAKE_CASE) 98 | (= :HTTP-Header-Case x) (comp keyword csk/->HTTP-Header-Case) 99 | :else (cli/coerce x (fn [x] 100 | (cond (= "true" x) true 101 | (= "false" x) false 102 | :else (coerce-eval-string x)))))) 103 | 104 | (defn coerce-query [query] 105 | (edn/read-string 106 | {:readers *data-readers*} 107 | (format "[%s]" query))) 108 | 109 | (defn coerce-thread-last [s] 110 | (->> s coerce-file 111 | (format "(fn [edn] (->> edn %s))") 112 | (sci/eval-string* ctx))) 113 | 114 | (defn coerce-thread-first [s] 115 | (->> s coerce-file 116 | (format "(fn [edn] (-> edn %s))") 117 | (sci/eval-string* ctx))) 118 | 119 | (defn coerce-interactive [interactive] 120 | (not-empty (str/join " " interactive))) 121 | 122 | (def cli-spec 123 | {:from {:coerce :keyword 124 | :alias :i 125 | :ref "[ edn | transit | json | yaml ]" 126 | :desc "defaults to edn."} 127 | :to {:coerce :keyword 128 | :alias :o 129 | :ref "[ edn | transit | json | yaml ]" 130 | :desc "defaults to edn."} 131 | :colors {:ref "[ auto | true | false]" 132 | :desc "use colored output while pretty-printing. Defaults to auto."} 133 | :thread-last {:alias :t 134 | :desc "implicit thread last"} 135 | :thread-first {:alias :T 136 | :desc "implicit thread first"} 137 | :func {:alias :f 138 | :desc "a single-arg Clojure function, or a path to a file that contains a function, that transforms input."} 139 | :query {:alias :q 140 | :desc "DEPRECATED, prefer -t, -T or -f. Given a jet-lang query, transforms input."} 141 | :collect {:alias :c 142 | :desc "given separate values, collects them in a vector."} 143 | :version {:alias :v 144 | :desc "print the current version of jet."} 145 | :help {:alias :h 146 | :desc "print this help text."} 147 | :keywordize {:alias :k 148 | :ref "[ | :kebab-case | :snake_case | :PascalCase | :camelCase | :Camel_Snake_Case | :SCREAMING_SNAKE_CASE | :HTTP-Header-Case ]" 149 | :desc "if present, keywordizes JSON/YAML keys. The default transformation function is keyword unless you provide your own."} 150 | :no-pretty {:coerce :boolean 151 | :desc "disable pretty printing"} 152 | :edn-reader-opts {:desc "options passed to the EDN reader." 153 | :default "{:default tagged-literal}"} 154 | :no-commas {:coerce :boolean 155 | :desc "remove commas from EDN"}}) 156 | 157 | (def cli-opts 158 | {:spec cli-spec 159 | :order (let [first-ks [:from :to 160 | :thread-last :thread-first :func]] 161 | (into first-ks (remove (set first-ks) (keys cli-spec)))) 162 | :no-keyword-opts true}) 163 | 164 | (defn parse-opts [args] 165 | (cli/parse-opts 166 | args 167 | cli-opts)) 168 | 169 | (defn get-version 170 | "Gets the current version of the tool" 171 | [] 172 | (str/trim (slurp (io/resource "JET_VERSION")))) 173 | 174 | (defn print-help 175 | "Prints the help text" 176 | [] 177 | (println (str "jet v" (get-version))) 178 | (println) 179 | (println "Options:") 180 | (println) 181 | (println (cli/format-opts cli-opts)) 182 | (println)) 183 | 184 | (defn exec [{:keys [from to keywordize 185 | no-pretty version query 186 | func thread-first thread-last interactive collect 187 | edn-reader-opts 188 | help colors no-commas] 189 | :or {from :edn 190 | to :edn 191 | colors :auto}}] 192 | (let [colors (formats/colorize? colors) 193 | keywordize (coerce-keywordize keywordize) 194 | [func thread-first thread-last edn-reader-opts query] 195 | [(cli/coerce func coerce-eval-string) (cli/coerce thread-first coerce-thread-first) 196 | (cli/coerce thread-last coerce-thread-last) 197 | (cli/coerce edn-reader-opts coerce-eval-string) 198 | (cli/coerce query coerce-query)]] 199 | (cond 200 | version (println (get-version)) 201 | interactive (start-jeti! interactive colors) 202 | help (print-help) 203 | :else 204 | (let [reader (case from 205 | :json (formats/json-parser) 206 | :transit (formats/transit-reader) 207 | :yaml nil 208 | :edn nil) 209 | next-val (case from 210 | :edn #(formats/parse-edn edn-reader-opts *in*) 211 | :json #(formats/parse-json reader keywordize) 212 | :transit #(formats/parse-transit reader) 213 | :yaml #(formats/parse-yaml *in* keywordize)) 214 | collected (when collect (vec (take-while #(not= % ::formats/EOF) 215 | (repeatedly next-val)))) 216 | func (or func thread-first thread-last)] 217 | (loop [] 218 | (let [input (if collect collected (next-val))] 219 | (when-not (identical? ::formats/EOF input) 220 | (let [input (if query (q/query input query) 221 | input) 222 | input (if func 223 | (func input) 224 | input)] 225 | (case to 226 | :edn (some-> 227 | input 228 | (formats/generate-edn (not no-pretty) colors no-commas) 229 | println) 230 | :json (some-> 231 | input 232 | (formats/generate-json (not no-pretty)) 233 | println) 234 | :transit (some-> 235 | (formats/generate-transit input) 236 | println) 237 | :yaml (some-> 238 | input 239 | (formats/generate-yaml (not no-pretty)) 240 | println))) 241 | (when-not collect (recur))))))))) 242 | 243 | (defn main [& args] 244 | (let [opts (parse-opts args)] 245 | (exec opts))) 246 | 247 | ;; enable println, prn etc. 248 | (sci/alter-var-root sci/out (constantly *out*)) 249 | (sci/alter-var-root sci/err (constantly *err*)) 250 | (vreset! specter/sci-ctx ctx) 251 | 252 | (when (System/getProperty "jet.native") 253 | (require 'jet.patches)) 254 | 255 | (def musl? 256 | "Captured at compile time, to know if we are running inside a 257 | statically compiled executable with musl." 258 | (and (= "true" (System/getenv "BABASHKA_STATIC")) 259 | (= "true" (System/getenv "BABASHKA_MUSL")))) 260 | 261 | (defmacro run [args] 262 | (if musl? 263 | ;; When running in musl-compiled static executable we lift execution of bb 264 | ;; inside a thread, so we have a larger than default stack size, set by an 265 | ;; argument to the linker. See https://github.com/oracle/graal/issues/3398 266 | `(let [v# (volatile! nil) 267 | f# (fn [] 268 | (vreset! v# (apply main ~args)))] 269 | (doto (Thread. nil f# "main") 270 | (.start) 271 | (.join)) 272 | @v#) 273 | `(apply main ~args))) 274 | 275 | (defn -main 276 | [& args] 277 | (run args)) 278 | -------------------------------------------------------------------------------- /src/jet/patches.clj: -------------------------------------------------------------------------------- 1 | (ns jet.patches 2 | "Don't load this namespace unless you're building a native image." 3 | (:require [com.rpl.specter.impl] 4 | [jet.specter :as js])) 5 | 6 | ;; (alter-var-root #'eval (constantly nil)) 7 | ;; (alter-var-root #'require (constantly nil)) 8 | ;; (alter-var-root #'resolve (constantly nil)) 9 | ;; (alter-var-root #'requiring-resolve (constantly nil)) 10 | 11 | ;; Note, when using direct linking we need to patch more vars than this 12 | (alter-var-root #'com.rpl.specter.impl/closed-code (constantly js/closed-code)) 13 | (println "Patches applied") 14 | -------------------------------------------------------------------------------- /src/jet/query.clj: -------------------------------------------------------------------------------- 1 | (ns jet.query 2 | {:no-doc true} 3 | (:refer-clojure :exclude [comparator]) 4 | (:require [clojure.set :as set] 5 | [jet.utils :as utils])) 6 | 7 | (declare query) 8 | 9 | (defn safe-nth [x n] 10 | (try (nth x n) 11 | (catch Exception _e 12 | nil))) 13 | 14 | (defn var-lookup [sym] 15 | (case sym 16 | id identity 17 | identity identity 18 | conj conj 19 | count count 20 | first first 21 | last last 22 | vals vals 23 | keys keys 24 | take take 25 | drop drop 26 | type type 27 | inc inc 28 | dec dec 29 | nth safe-nth 30 | = = 31 | < < 32 | <= <= 33 | > > 34 | >= >= 35 | not= not= 36 | map map 37 | filter filter 38 | remove remove 39 | distinct distinct 40 | dedupe dedupe 41 | + + 42 | - - 43 | * * 44 | / / 45 | symbol symbol 46 | drop-while drop-while 47 | take-while take-while 48 | prn prn 49 | println println 50 | nil)) 51 | 52 | (defn promote-query* [q] 53 | (if (symbol? q) 54 | (list q) 55 | q)) 56 | 57 | (defn create-map [x args] 58 | (let [keys (take-nth 2 args) 59 | vals (take-nth 2 (rest args)) 60 | vals (map #(query x %) vals)] 61 | (zipmap keys vals))) 62 | 63 | (defn query* [x q] 64 | (let [[op & args] q 65 | f (var-lookup op) 66 | [arg1 arg2 arg3] args 67 | res (case op 68 | ;; special case 69 | quote arg1 70 | ;; spy 71 | jet/debug (do (utils/prn (if arg1 72 | (query x arg1) 73 | x)) 74 | x) 75 | jet/describe (do (let [v (if arg1 76 | (query x arg1) 77 | x)] 78 | (println "Type: " (type v)) 79 | (when (counted? v) 80 | (println "Count:" (count v))) 81 | (when (map? v) 82 | (utils/println "Keys:" (keys v)))) 83 | x) 84 | ;; accessor, arg is not a query 85 | get (get x arg1) 86 | ;; control flow 87 | if (if (query x arg1) (query x arg2) (query x arg3)) 88 | while (if (query x arg1) 89 | (query* (query x arg2) 90 | (list 'while arg1 arg2)) 91 | x) 92 | ;; functions with 1 arg 93 | (first last keys vals inc dec 94 | identity id count distinct dedupe 95 | type) 96 | (if arg1 97 | (f (query x arg1)) 98 | (f x)) 99 | ;; macros with 1 arg 100 | not (if arg1 101 | (not (query x arg1)) 102 | (not x)) 103 | 104 | ;; functions with 2 args 105 | ;; index first 106 | (take drop) 107 | (if arg2 108 | (f (query x arg1) (query x arg2)) 109 | ;; index first 110 | (f (query x arg1) x)) 111 | nth (if arg2 112 | (f (query x arg1) (query x arg2)) 113 | ;; index last 114 | (f x (query x arg1))) 115 | map-vals (zipmap (keys x) 116 | (map #(query % arg1) (vals x))) 117 | zipmap (zipmap (first x) (second x)) 118 | (map filter remove take-while drop-while) 119 | (f #(query % (promote-query* arg1)) x) 120 | select-keys (select-keys x arg1) 121 | set/join (if arg2 122 | (if arg3 123 | (set/join (set (query x arg1)) (set (query x arg2)) arg3) 124 | (set/join (set (query x arg1)) (set (query x arg2)))) 125 | (set/join (set x) (set (query x arg1)))) 126 | set/rename-keys (if arg2 127 | (set/rename-keys (query x arg1) arg2) 128 | (set/rename-keys x arg1)) 129 | set/difference (if arg2 130 | (set/difference (set (query x arg1)) (set (query x arg2))) 131 | (set/difference (set x) (set (query x arg1)))) 132 | update (let [[k update-query] args 133 | update-query (promote-query* update-query) 134 | v (get x k)] 135 | (assoc x k (query v update-query))) 136 | assoc-in (let [[path assoc-in-query] args 137 | v (query x assoc-in-query)] 138 | (assoc-in x path v)) 139 | update-in (let [[path update-in-query] args 140 | update-in-query (promote-query* update-in-query) 141 | v (get-in x path) 142 | v (query v update-in-query)] 143 | (assoc-in x path v)) 144 | 145 | ;; functions/macros with varargs 146 | ;; juxt may be preferred over vector 147 | ;; vector (into [] (map #(query x %) args)) 148 | into (if-not arg2 149 | (into (query x arg1) x) 150 | (into (query x arg1) (query x arg2))) 151 | conj (apply conj (query x arg1) (map #(query x %) (rest args))) 152 | juxt (vec (for [q args 153 | :let [q (promote-query* q)]] 154 | (query x q))) 155 | and (reduce (fn [_ q] 156 | (let [v (query x q)] 157 | (if v v (reduced v)))) 158 | nil args) 159 | or (first (for [q args 160 | :let [v (query x q)] 161 | :when v] 162 | v)) 163 | dissoc (apply dissoc x (rest q)) 164 | hash-map (create-map x args) 165 | assoc (let [args args 166 | keys (take-nth 2 args) 167 | vals (take-nth 2 (rest args)) 168 | vals (map #(query x %) vals)] 169 | (merge x (zipmap keys vals))) 170 | (= < <= > >= not= + - * /) 171 | (apply f (map #(query x %) args)) 172 | 173 | ;; special cases 174 | str (apply str (map #(query x %) args)) 175 | re-find (re-find (re-pattern (query x arg1)) (query x arg2)) 176 | ;; fallback 177 | (if f 178 | (f x) 179 | (get x q)))] 180 | (if (and (vector? x) (sequential? res)) 181 | (vec res) 182 | res))) 183 | 184 | (defn query 185 | [x q] 186 | (if-let [[_ v] 187 | (when (map? x) 188 | (find x q))] 189 | v 190 | (cond 191 | (not q) nil 192 | (vector? q) 193 | (if-let [next-op (first q)] 194 | (recur (query x next-op) (vec (rest q))) 195 | x) 196 | (list? q) (query* x q) 197 | (symbol? q) (query* x [q]) 198 | (map? q) (create-map x (apply concat (seq q))) 199 | (number? q) (safe-nth x q)))) 200 | 201 | ;;;; Scratch 202 | 203 | (comment 204 | ) 205 | -------------------------------------------------------------------------------- /src/jet/specter.clj: -------------------------------------------------------------------------------- 1 | (ns jet.specter 2 | (:require 3 | [com.rpl.specter :as specter] 4 | [com.rpl.specter.impl :as i] 5 | [sci.core :as sci])) 6 | 7 | ;; copied from specter cli, might wanna move this to sci.configs 8 | 9 | (def sns (sci/create-ns 'com.rpl.specter nil)) 10 | (def ins (sci/create-ns 'com.rpl.specter.impl nil)) 11 | 12 | (def sci-ctx (volatile! nil)) 13 | (def tmp-closure (sci/new-dynamic-var '*tmp-closure* ins)) 14 | 15 | (defn closed-code 16 | "Patch for closed-code which uses clojure.core/eval which isn't possible in native-image." 17 | [closure body] 18 | (let [lv (mapcat #(vector % `(i/*tmp-closure* '~%)) 19 | (keys closure))] 20 | (sci/binding [tmp-closure closure] 21 | (sci/eval-form @sci-ctx `(let [~@lv] ~body))))) 22 | 23 | (when (System/getProperty "jet.native") 24 | (require 'jet.patches)) 25 | 26 | ;; Private vars, used from path macro 27 | (def ic-prepare-path #'specter/ic-prepare-path) 28 | (def ic-possible-params #'specter/ic-possible-params) 29 | 30 | (defmacro path 31 | "Patch for path macro which uses clojure.core/intern as a side effect, 32 | which is replaced by sci.core/intern here." 33 | [& path] 34 | (let [local-syms (-> &env keys set) 35 | used-locals (i/used-locals local-syms path) 36 | 37 | ;; note: very important to use riddley's macroexpand-all here, so that 38 | ;; &env is preserved in any potential nested calls to select (like via 39 | ;; a view function) 40 | expanded (i/clj-macroexpand-all (vec path)) 41 | prepared-path (ic-prepare-path local-syms expanded) 42 | possible-params (vec (ic-possible-params expanded)) 43 | 44 | cache-sym (vary-meta 45 | (gensym "pathcache") 46 | merge {:cljs.analyzer/no-resolve true :no-doc true :private true}) 47 | 48 | info-sym (gensym "info") 49 | 50 | get-cache-code `(try (i/get-cell ~cache-sym) 51 | (catch ClassCastException e# 52 | ;; With AOT compilation it's possible for: 53 | ;; Thread 1: unbound, so throw exception 54 | ;; Thread 2: unbound, so throw exception 55 | ;; Thread 1: do alter-var-root 56 | ;; Thread 2: it's bound, so retrieve the current value 57 | (if (bound? (var ~cache-sym)) 58 | (i/get-cell ~cache-sym) 59 | (do 60 | (alter-var-root 61 | (var ~cache-sym) 62 | (fn [_#] (i/mutable-cell))) 63 | nil)))) 64 | add-cache-code `(i/set-cell! ~cache-sym ~info-sym) 65 | precompiled-sym (gensym "precompiled") 66 | handle-params-code `(~precompiled-sym ~@used-locals)] 67 | ;; this is the actual patch 68 | (sci/intern @sci-ctx @sci/ns cache-sym (i/mutable-cell)) 69 | ;; end patch 70 | `(let [info# ~get-cache-code 71 | 72 | info# 73 | (if (nil? info#) 74 | (let [~info-sym (i/magic-precompilation 75 | ~prepared-path 76 | ~(str *ns*) 77 | (quote ~used-locals) 78 | (quote ~possible-params))] 79 | ~add-cache-code 80 | ~info-sym) 81 | info#) 82 | 83 | ~precompiled-sym (i/cached-path-info-precompiled info#) 84 | dynamic?# (i/cached-path-info-dynamic? info#)] 85 | (if dynamic?# 86 | ~handle-params-code 87 | ~precompiled-sym)))) 88 | 89 | (def impl-ns (update-vals (ns-publics 'com.rpl.specter.impl) #(sci/copy-var* % ins))) 90 | (def specter-ns (update-vals (ns-publics 'com.rpl.specter) #(sci/copy-var* % sns))) 91 | 92 | (def config {:namespaces 93 | {'com.rpl.specter.impl 94 | (assoc impl-ns 95 | '*tmp-closure* tmp-closure) 96 | 'com.rpl.specter 97 | (assoc specter-ns 98 | ;; the patched path macro 99 | 'path (sci/copy-var path sns))} 100 | :classes {'java.lang.ClassCastException ClassCastException 101 | 'clojure.lang.Util clojure.lang.Util}}) 102 | -------------------------------------------------------------------------------- /src/jet/utils.clj: -------------------------------------------------------------------------------- 1 | (ns jet.utils 2 | {:no-doc true} 3 | (:refer-clojure :exclude [println prn])) 4 | 5 | (defn println [& args] 6 | (binding [*print-length* 5 7 | *print-level* 5] 8 | (apply clojure.core/println args))) 9 | 10 | (defn prn [& args] 11 | (binding [*print-length* 5 12 | *print-level* 5] 13 | (apply clojure.core/prn args))) 14 | -------------------------------------------------------------------------------- /test-resources/fn.clj: -------------------------------------------------------------------------------- 1 | #(-> % :a :b :c) 2 | -------------------------------------------------------------------------------- /test-resources/specter-test.clj: -------------------------------------------------------------------------------- 1 | (do 2 | (use 'com.rpl.specter) 3 | (def res 4 | [(transform [MAP-VALS MAP-VALS] 5 | inc 6 | {:a {:aa 1} :b {:ba -1 :bb 2}}) 7 | (select [ALL ALL #(= 0 (mod % 3))] 8 | [[1 2 3 4] [] [5 3 2 18] [2 4 6] [12]]) 9 | 10 | (transform [(filterer odd?) LAST] 11 | inc 12 | [2 1 3 6 9 4 8]) 13 | 14 | (setval [:a ALL nil?] NONE {:a [1 2 nil 3 nil]}) 15 | 16 | (setval [:a :b :c] NONE {:a {:b {:c 1}}}) 17 | 18 | (setval [:a (compact :b :c)] NONE {:a {:b {:c 1}}}) 19 | 20 | (transform [(srange 1 4) ALL odd?] inc [0 1 2 3 4 5 6 7]) 21 | 22 | (setval (srange 2 4) [:a :b :c :d :e] [0 1 2 3 4 5 6 7 8 9]) 23 | 24 | (setval [ALL END] [:a :b] [[1] '(1 2) [:c]]) 25 | 26 | (select (walker number?) 27 | {2 [1 2 [6 7]] :a 4 :c {:a 1 :d [2 nil]}}) 28 | 29 | (select ["a" "b"] 30 | {"a" {"b" 10}}) 31 | 32 | (transform [(srange 4 11) (filterer even?)] 33 | reverse 34 | [0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15]) 35 | 36 | (setval [ALL 37 | (selected? (filterer even?) (view count) (pred>= 2)) 38 | END] 39 | [:c :d] 40 | [[1 2 3 4 5 6] [7 0 -1] [8 8] []]) 41 | 42 | (transform [ALL (collect-one :b) :a even?] 43 | + 44 | [{:a 1 :b 3} {:a 2 :b -10} {:a 4 :b 10} {:a 3}]) 45 | 46 | (transform [:a (putval 10)] 47 | + 48 | {:a 1 :b 3}) 49 | 50 | (transform [ALL (if-path [:a even?] [:c ALL] :d)] 51 | inc 52 | [{:a 2 :c [1 2] :d 4} {:a 4 :c [0 10 -1]} {:a -1 :c [1 1 1] :d 1}])]) 53 | 54 | (constantly res)) 55 | 56 | -------------------------------------------------------------------------------- /test/jet/main_test.clj: -------------------------------------------------------------------------------- 1 | (ns jet.main-test 2 | (:require 3 | [clojure.edn :as edn] 4 | [clojure.string :as str] 5 | [clojure.test :as test :refer [deftest is testing]] 6 | [jet.test-utils :refer [jet]])) 7 | 8 | (def named-keywordize-json "{\"onceMoreUntoTheBreach\": \"dear friends\"}") 9 | (deftest named-camel-keywordize-test 10 | (is (= "{:onceMoreUntoTheBreach \"dear friends\"}\n" 11 | (jet named-keywordize-json 12 | "--from" "json" 13 | "--keywordize" ":camelCase") 14 | ))) 15 | (deftest named-pascal-keywordize-test 16 | (is (= "{:OnceMoreUntoTheBreach \"dear friends\"}\n" 17 | (jet named-keywordize-json 18 | "--from" "json" 19 | "--keywordize" ":PascalCase")))) 20 | 21 | (deftest named-snake-keywordize-test 22 | (is (= "{:once_more_unto_the_breach \"dear friends\"}\n" 23 | (jet named-keywordize-json 24 | "--from" "json" 25 | "--keywordize" ":snake_case")))) 26 | 27 | (deftest named-kebab-keywordize-test 28 | (is (= "{:once-more-unto-the-breach \"dear friends\"}\n" 29 | (jet named-keywordize-json 30 | "--from" "json" 31 | "--keywordize" ":kebab-case")))) 32 | 33 | (deftest named-screaming-snake-keywordize-test 34 | (is (= "{:ONCE_MORE_UNTO_THE_BREACH \"dear friends\"}\n" 35 | (jet named-keywordize-json 36 | "--from" "json" 37 | "--keywordize" ":SCREAMING_SNAKE_CASE")))) 38 | 39 | (deftest named-http-header-keywordize-test 40 | (is (= "{:Once-More-Unto-The-Breach \"dear friends\"}\n" 41 | (jet named-keywordize-json 42 | "--from" "json" 43 | "--keywordize" ":HTTP-Header-Case")))) 44 | 45 | (deftest named-camel-snake-keywordize-test 46 | (is (= "{:Once_More_Unto_The_Breach \"dear friends\"}\n" 47 | (jet named-keywordize-json 48 | "--from" "json" 49 | "--keywordize" ":Camel_Snake_Case")))) 50 | 51 | (deftest main-test 52 | (is (= "{\"a\" 1}\n" 53 | (jet "{\"a\": 1}" 54 | "--from" "json" 55 | "--to" "edn"))) 56 | (is (= "{:a 1}\n" 57 | (jet "{\"a\": 1}" 58 | "--from" "json" 59 | "--keywordize" 60 | "--to" "edn"))) 61 | (is (= "[\"^ \",\"~:a\",1]\n" 62 | (jet "{\"a\": 1}" 63 | "--from" "json" 64 | "--keywordize" "true" 65 | "--to" "transit"))) 66 | (is (= "{\"a\":1}\n" 67 | (jet "{:a 1}" 68 | "--from" "edn" 69 | "--to" "json" "--no-pretty"))) 70 | (is (= "[\"^ \",\"~:a\",1]\n" 71 | (jet "{:a 1}" 72 | "--from" "edn" 73 | "--to" "transit"))) 74 | (is (= "{:a 1}\n" 75 | (jet "[\"^ \",\"~:a\",1]\n" 76 | "--from" "transit" 77 | "--to" "edn"))) 78 | (is (= "{\"a\":1}\n" 79 | (jet "[\"^ \",\"~:a\",1]\n" 80 | "--from" "transit" 81 | "--to" "json" "--no-pretty"))) 82 | 83 | (is (= "{\"a\" 1}\n" 84 | (jet "{a: 1}" 85 | "--from" "yaml" 86 | "--to" "edn"))) 87 | ;; YAML coll should convert to EDN vector 88 | (is (= "[1 2]\n" 89 | (jet "- 1\n- 2\n\n" 90 | "--from" "yaml" 91 | "--to" "edn"))) 92 | (is (= "{:a 1}\n" 93 | (jet "a: 1" 94 | "--from" "yaml" 95 | "--keywordize" 96 | "--to" "edn"))) 97 | (is (= "{:a-b 1}\n" 98 | (jet "a_b: 1" 99 | "--from" "yaml" 100 | "--keywordize" 101 | "#(keyword (str/replace % \"_\" \"-\"))" 102 | "--to" "edn"))) 103 | (is (= "{:x/a 1}\n" 104 | (jet "a: 1" 105 | "--from" "yaml" 106 | "--keywordize" 107 | "#(keyword \"x\" %)" 108 | "--to" "edn"))) 109 | (is (= "[\"^ \",\"~:a\",1]\n" 110 | (jet "{a: 1}" 111 | "--from" "yaml" 112 | "--keywordize" "true" 113 | "--to" "transit"))) 114 | (is (= "{\"a\":1}\n" 115 | (jet "{a: 1}" 116 | "--from" "yaml" 117 | "--to" "json" "--no-pretty"))) 118 | (is (= "[\"^ \",\"a\",1]\n" 119 | (jet "{a: 1}" 120 | "--from" "yaml" 121 | "--to" "transit"))) 122 | (is (= "a: 1\n\n" 123 | (jet "[\"^ \",\"~:a\",1]\n" 124 | "--from" "transit" 125 | "--to" "yaml"))) 126 | (is (= "{a: 1}\n\n" 127 | (jet "[\"^ \",\"~:a\",1]\n" 128 | "--from" "transit" 129 | "--to" "yaml" "--no-pretty"))) 130 | (is (= "a: 1\n\n" 131 | (jet "{:a 1}\n" 132 | "--from" "edn" 133 | "--to" "yaml"))) 134 | (is (= "a: 1\n\n" 135 | (jet "{\"a\":1}\n" 136 | "--from" "json" 137 | "--to" "yaml"))) 138 | 139 | (testing "pretty printing" 140 | (is (= "{\n \"a\" : [ {\n \"b\" : {\n \"c\" : \"d\"\n }\n } ]\n}\n" 141 | (jet "{:a [{:b {:c :d}}]}" "--from" "edn" "--to" "json" "--pretty"))) 142 | (is (= "{:a [{:b {:c :d}}\n {:b {:c :d}}\n {:b {:c :d}}\n {:b {:c :d}}\n {:b {:c :d}}\n {:b {:c :d}}\n {:b {:c :d}}]}\n" 143 | (jet "{:a [{:b {:c :d}} {:b {:c :d}} {:b {:c :d}} {:b {:c :d}} {:b {:c :d}} {:b {:c :d}} {:b {:c :d}}]}" "--no-colors" "--from" "edn" "--to" "edn" "--pretty")))) 144 | (testing "query" 145 | (is (= "1\n" (jet "{:a 1 :b 2}" "--from" "edn" "--to" "edn" "--query" ":a")))) 146 | (testing "from and to default to edn" 147 | (is (= "1\n" (jet "{:a 1 :b 2}" "--query" ":a")))) 148 | (testing "implicity wrapping multiple queries" 149 | (is (= "1\n" (jet "{:a {:b 1}}" "--query" ":a :b"))))) 150 | 151 | (deftest interactive-test 152 | (testing "passing correct query will please jeti" 153 | (is (re-find #"[0-9a-f]{4}> 1" 154 | (jet (str/join "\n" [{:a 1} "Y" :a "Y"]) "--interactive" "--no-colors")))) 155 | (testing "passing --interactive arg as edn" 156 | (is (re-find #"[0-9a-f]{4}> 1" 157 | (jet (str/join "\n" ["Y" :a "Y"]) "--no-colors" "--interactive" "{:a 1}")))) 158 | (testing "passing --interactive arg as edn" 159 | (is (re-find #"[0-9a-f]{4}> 1" 160 | (jet (str/join "\n" ["Y" :a "Y"]) "--no-colors" "--interactive" ":jeti/set-val {:a 1}")))) 161 | (testing "slurping json file" 162 | (is (re-find #"[0-9a-f]{4}> 30" 163 | (jet (str/join "\n" ["Y" "count" "Y"]) "--no-colors" "--interactive" ":jeti/slurp test/data/commits.json {:format :json}")))) 164 | (testing "jeti doesn't get stuck in a loop and executes the command only once" 165 | (is (re-find #"Available commands" 166 | (jet "" "--no-colors" "--interactive" ":jeti/help"))))) 167 | 168 | (deftest stream-test 169 | (is (= "2\n3\n4\n" (jet "2 3 4" "--from" "edn" "--to" "edn"))) 170 | (is (= "2\n3\n4\n" (jet "{:x 2} {:x 3} {:x 4}" "--query" ":x"))) 171 | (is (= "2\n3\n4\n" (jet "{\"a\":2} {\"a\":3} {\"a\":4}" "--from" "json" "--keywordize" "--query" ":a"))) 172 | (is (= "2\n3\n4\n" (jet "[\"^ \",\"~:a\",2] [\"^ \",\"~:a\",3] [\"^ \",\"~:a\",4]" "--from" "transit" "--query" ":a")))) 173 | 174 | (deftest collect-test 175 | (is (= "[{:x 2} {:x 3} {:x 4}]\n" (jet "{:x 2} {:x 3} {:x 4}" "--collect")))) 176 | 177 | (deftest key-fn-test 178 | (is (= "[{:x 2} {:x 3} {:x 4}]\n" 179 | (jet "{\" x \": 2} {\" x \": 3} {\" x \": 4}" "--collect" "--from" "json" "--keywordize" "(comp keyword str/trim)"))) 180 | (let [casing-samples "{\"PascalCase\":1} 181 | {\"camelCase\":2} 182 | {\"SCREAMING_SNAKE_CASE\":3} 183 | {\"snake_case\":4} 184 | {\"kebab-case\":5} 185 | {\"Camel_Snake_Case\":6} 186 | {\"HTTP-Header-Case\":7}"] 187 | (is (= "{:pascal-case 1}\n{:camel-case 2}\n{:screaming-snake-case 3}\n{:snake-case 4}\n{:kebab-case 5}\n{:camel-snake-case 6}\n{:http-header-case 7}\n" 188 | (jet casing-samples "--from" "json" "--keywordize" "#(-> % csk/->kebab-case keyword)"))) 189 | (is (= "{:pascal_case 1}\n{:camel_case 2}\n{:screaming_snake_case 3}\n{:snake_case 4}\n{:kebab_case 5}\n{:camel_snake_case 6}\n{:http_header_case 7}\n" 190 | (jet casing-samples "--from" "json" "--keywordize" "#(-> % csk/->snake_case keyword)"))))) 191 | 192 | (deftest edn-reader-opts-test 193 | (is (= "#foo {:a 1}\n" (jet "#foo{:a 1}" "--no-pretty"))) 194 | (is (= "#foo {:a 1}\n" (jet "#foo{:a 1}" "--no-pretty" "--edn-reader-opts" "{:default tagged-literal}"))) 195 | (is (= "[:foo {:a 1}]\n" (jet "#foo{:a 1}" "--no-pretty" "--edn-reader-opts" "{:readers {'foo (fn [x] [:foo x])}}")))) 196 | 197 | (deftest func-test 198 | (is (= "1\n" (jet "{:a {:b {:c 1}}}" "--func" "#(-> % :a :b :c)"))) 199 | (testing "when function is in a file" 200 | (is (= "1\n" (jet "{:a {:b {:c 1}}}" "--func" "test-resources/fn.clj"))))) 201 | 202 | (deftest thread-first-test 203 | (is (= "3\n" 204 | (jet "1" "-T" "inc inc")))) 205 | 206 | (deftest thread-last-test 207 | (is (= "{:a {:a 2}}\n" 208 | (jet "{:a {:a 1}}" "-t" "(s/transform [s/MAP-VALS s/MAP-VALS] inc)")))) 209 | 210 | (deftest specter-test 211 | (is (= "[{:a {:aa 2}, :b {:ba 0, :bb 3}} [3 3 18 6 12] [2 1 3 6 10 4 8] {:a [1 2 3]} {:a {:b {}}} {} [0 2 2 4 4 5 6 7] [0 1 :a :b :c :d :e 4 5 6 7 8 9] [[1 :a :b] (1 2 :a :b) [:c :a :b]] [2 1 2 6 7 4 1 2] [10] [0 1 2 3 10 5 8 7 6 9 4 11 12 13 14 15] [[1 2 3 4 5 6 :c :d] [7 0 -1] [8 8 :c :d] []] [{:a 1, :b 3} {:a -8, :b -10} {:a 14, :b 10} {:a 3}] {:a 11, :b 3} [{:a 2, :c [2 3], :d 4} {:a 4, :c [1 11 0]} {:a -1, :c [1 1 1], :d 2}]]\n" 212 | (jet "{:a {:a 1}}" "--no-pretty" "-f" "test-resources/specter-test.clj")))) 213 | 214 | (deftest base64-test 215 | (is (= "\"Zm9v\"\n" (jet "{:a \"foo\"}" "-t" ":a base64/encode"))) 216 | (is (= "\"foo\"\n" (jet "{:a \"foo\"}" "-t" ":a base64/encode base64/decode")))) 217 | 218 | (deftest paths-test 219 | (is (= [[:a :b 0] [:a :b 2] [:a :c :d]] 220 | (edn/read-string (jet "{:a {:b [1 2 3 {:x 2}] :c {:d 3}}}" "-t" "(jet/paths) (filter (comp (jet/when-pred odd?) :val)) (mapv :path)"))))) 221 | 222 | (deftest no-commas-test 223 | (is (= "[{:a 1 :b 2}]\n" 224 | (jet "[{:a 1 :b 2}]" "--no-commas")))) 225 | 226 | (deftest update-keys-and-vals-test 227 | (is (= "{:a 1}\n" 228 | (jet "{\"a\" \"1\"}" "-T" "(update-vals parse-long) (update-keys keyword)")))) 229 | 230 | (deftest parse-functions-test 231 | (is (= "1\n" (jet "\"1\"" "-t" "parse-long"))) 232 | (is (= "true\n" (jet "\"true\"" "-t" "parse-boolean"))) 233 | (is (= "0.5\n" (jet "\"0.5\"" "-t" "parse-double"))) 234 | (is (= "#uuid \"00000000-0000-0000-0000-000000000000\"\n" (jet "\"00000000-0000-0000-0000-000000000000\"" "-t" "parse-uuid")))) -------------------------------------------------------------------------------- /test/jet/parse_opts_test.clj: -------------------------------------------------------------------------------- 1 | (ns jet.parse-opts-test 2 | (:require 3 | [clojure.test :as test :refer [are deftest is testing]] 4 | [jet.main :refer [parse-opts]])) 5 | 6 | (defn opts-match? 7 | "Compare `short` & `long` options." 8 | [short long] 9 | (= (parse-opts short) 10 | (parse-opts long))) 11 | 12 | (defmacro opt-test 13 | "Helper macro to test command line options in a terser fashion." 14 | [{:keys [opt short-opt long-opt outcome]}] 15 | `(testing ~(name opt) 16 | (are [result opts] (= result (~opt (parse-opts opts))) 17 | ~outcome ~short-opt 18 | ~outcome ~long-opt) 19 | (is (true? (opts-match? ~short-opt ~long-opt))))) 20 | 21 | (deftest parse-opts-test 22 | (testing "collect" 23 | (is (true? (:collect (parse-opts '("--collect")))))) 24 | (testing "interactive" 25 | (is (true? (:interactive (parse-opts '("--interactive")))))) 26 | (testing "long opt + =" 27 | (is (= {:from :edn, :to :json :edn-reader-opts "{:default tagged-literal}"} 28 | (parse-opts '("--from=edn" "--to=json"))))) 29 | (opt-test {:opt :collect 30 | :short-opt '("-c") 31 | :long-opt '("--collect") 32 | :outcome true}) 33 | (opt-test {:opt :from 34 | :short-opt '("-i" "json") 35 | :long-opt '("--from" "json") 36 | :outcome :json}) 37 | (opt-test {:opt :to 38 | :short-opt '("-o" "json") 39 | :long-opt '("--to" "json") 40 | :outcome :json}) 41 | (opt-test {:opt :version 42 | :short-opt '("-v") 43 | :long-opt '("--version") 44 | :outcome true}) 45 | (opt-test {:opt :help 46 | :short-opt '("-h") 47 | :long-opt '("--help") 48 | :outcome true}) 49 | (opt-test {:opt :keywordize 50 | :short-opt '("-k") 51 | :long-opt '("--keywordize") 52 | :outcome true})) 53 | -------------------------------------------------------------------------------- /test/jet/query_test.clj: -------------------------------------------------------------------------------- 1 | (ns jet.query-test 2 | (:require 3 | [clojure.test :as test :refer [deftest is]] 4 | [jet.data-readers] 5 | [jet.query :refer [query]] 6 | [clojure.set :as set])) 7 | 8 | (deftest query-test 9 | (is (= nil (query [] false))) 10 | (is (= '1 (query {:a 1 :b 2} :a))) 11 | (is (= '1 (query {1 1} 1))) 12 | (is (= '1 (query {"1" 1} "1"))) 13 | (is (= '{:a 1} (query {:a 1 :b 2} '(select-keys [:a])))) 14 | (is (= {:a #:a{:a 1}} (query {:a {:a/a 1 :a/b 2} :b 2} '[(select-keys [:a]) (update :a (select-keys [:a/a]))]))) 15 | (is (= {:a [#:a{:a 1}]} (query {:a [{:a/a 1 :a/b 2}] :b 2} '[(select-keys [:a]) (update :a (map (select-keys [:a/a])))]))) 16 | (is (= {:a 1, :c 3} (query {:a 1 :b 2 :c 3} '(dissoc :b)))) 17 | (is (= [1] (query [1 2 3] '(take #jet/lit 1)))) 18 | (is (= [2 3] (query [1 2 3] '(drop #jet/lit 1)))) 19 | (is (= {:a [1]} (query {:a [1 2 3]} '(update :a (take #jet/lit 1))))) 20 | (is (= {:a 2} (query {:a [1 2 3]} '(update :a (nth #jet/lit 1))))) 21 | (is (= {:a 1} (query {:a [1 2 3]} '(update :a first)))) 22 | (is (= 3 (query {:a [1 2 3]} '(last :a)))) 23 | (is (= {:a 3} (query {:a [1 2 3]} '(update :a last)))) 24 | (is (= {:foo [:bar]} (query {:foo {:bar 2}} '(update :foo keys)))) 25 | (is (= {:a [:a :b]} (query {:b {:a 1 :b 2}} '{:a (keys :b)}))) 26 | (is (= {:foo [2]} (query {:foo {:bar 2}} '(update :foo vals)))) 27 | (is (= [3 6] (query [[1 2 3] [4 5 6]] '(map last)))) 28 | (is (= [1 2] (query [{:a 1} {:a 2}] '(map :a)))) 29 | (is (= [1 2] (query [{:a 1} {:a 2}] '(map :a)))) 30 | (is (= [1 2] (query {:a 1 :b 2 :c 3} '[(dissoc :c) (vals)]))) 31 | (is (= {:foo 1 :bar 1} 32 | (query {:foo {:a 1 :b 2} :bar {:a 1 :b 2}} '(map-vals :a)))) 33 | (is (= {:a 1 :b 2 :c 3} 34 | (query {:keys [:a :b :c] :vals [1 2 3]} '[(juxt :keys :vals) (zipmap)]))) 35 | (is (= '[{:name foo :private true}] 36 | (query '[{:name foo :private true} 37 | {:name bar :private false}] '(filter :private)))) 38 | (is (= 1 (query '[{:name foo :private true} 39 | {:name bar :private false}] '[(filter :private) (count)]))) 40 | (is (= '[{:name foo, :private true}] 41 | (query '[{:name foo :private true} 42 | {:name bar :private false}] '(filter (= :name (quote foo)))))) 43 | (is (= '[{:a 2} {:a 3}] 44 | (query '[{:a 1} {:a 2} {:a 3}] '(filter (>= :a (quote 2)))))) 45 | (is (= '[{:a 1} {:a 2}] 46 | (query '[{:a 1} {:a 2} {:a 3}] '(filter (<= :a (quote 2)))))) 47 | (is (= '[{:a 1} {:a 2}] 48 | (query '[{:a 1} {:a 2} {:a 3}] '(filter (not= :a (quote 3)))))) 49 | (is (= '[{:a 1} {:a 2}] 50 | (query '[{:a 1} {:a 2} {:a 3}] '(remove (= :a (quote 3)))))) 51 | (is (= '[{:a 1}] 52 | (query '[{:a 1} [] []] '(filter first)))) 53 | (is (= '[{:a 1, :b 3}] 54 | (query '[{:a 1 :b 2} {:a 1 :b 3}] '(filter (> :b #jet/lit 2))))) 55 | (is (= false (query {:a 1 :b 1 :c 1} '(not= :a :b :c)))) 56 | (is (= '{:a 1 :b 2} 57 | (query '{:a 1 :b 2 :c 3} '(select-keys [:a :b])))) 58 | (is (= '{:c 3} 59 | (query '{:a 1 :b 2 :c 3} '(dissoc :a :b)))) 60 | (is (= '{:b 1} 61 | (query '{:a 1} '(set/rename-keys {:a :b})))) 62 | (is (= '{:foo 1 :bar 2} 63 | (query '{:a 1 :b 2} '(hash-map :foo :a :bar :b)))) 64 | (is (= '{:a 1 :b 3} 65 | (query '{:a 1 :b 2} '(assoc :b (quote 3))))) 66 | (is (= '{:a 1 :b 3} 67 | (query '{:a 1 :b 2} '(assoc :b #jet/lit 3)))) 68 | (is (= '{:a 1} 69 | (query nil '(hash-map :a (quote 1))))) 70 | (is (= '{:a 1} 71 | (query nil '{:a (quote 1)}))) 72 | (is (= '{:a 1 :b 1} 73 | (query {:a 1} '(assoc :b :a)))) 74 | (is (= '{:a 1} 75 | (query {:a {:b 1}} '(update :a :b)))) 76 | (is (= '{:a 1} 77 | (query {:a [1 2 3]} '(update :a first)))) 78 | (is (= '{:a {:a 2} :b 2} 79 | (query {:a {:a 1} :b 2} '(assoc-in [:a :a] :b)))) 80 | (is (= '{:a {:a 1}} 81 | (query {:a {:a {:b 1}}} '(update-in [:a :a] :b)))) 82 | (is (= '{:a {:a 1}} 83 | (query {:a {:a [1 2 3]}} '(update-in [:a :a] first)))) 84 | (is (= 1 (query {:a 1} '(get :a)))) 85 | (is (= 100 (query [100 1 2] '(get 0)))) 86 | (is (= 1 (query (list 1 2 3) '0))) 87 | (is (= [1 2 3] (query {:a [1 1 2 2 3 3 1 1]} '[:a (distinct)]))) 88 | (is (= [1 2 3 1] (query {:a [1 1 2 2 3 3 1 1]} '[:a (dedupe)]))) 89 | (is (= "foo bar" (query {:a "foo bar" :b 2} '(if (re-find #jet/lit "foo" :a) :a :b)))) 90 | (is (= 2 (query {:a "foo bar" :b 2} '(if (re-find #jet/lit "baz" :a) :a :b)))) 91 | (is (= "1/2" (query {:a 1 :b 2} '(str :a #jet/lit "/" :b)))) 92 | (is (= {:input {:a 3, :b 2}, :product 6} 93 | (query {:a 3 :b 2} '{:input (identity) :product (* :a :b)}))) 94 | (is (= 2 (query {:a 3 :b 2} '(identity :b)))) 95 | (is (= nil (query {:b 3} '(and :a :b)))) 96 | (is (= false (query {:a false} '(and :a :b)))) 97 | (is (= 2 (query {:a 1 :b 2} '(and :a :b)))) 98 | (is (= 3 (query {:b 3} '(or :a :b)))) 99 | (is (= true (query {:b 3} '(not :a)))) 100 | (is (= 4 (query {:b 3} '(inc :b)))) 101 | (is (= 2 (query {:b 3} '(dec :b)))) 102 | (is (= 3 (query [1 2 3] 'last))) 103 | (is (= 4 (query '{(inc :a) 4} '(inc :a)))) 104 | (is (= {:a 1 :b 2} (query {:a 1 :b 2 :c 3 :d 4} '(select-keys [:a :b])))) 105 | (is (= 10 (query 0 '(while (< id #jet/lit 10) (inc id))))) 106 | (is (= [0 1 1 2 3 5 8 13 21 34 55] 107 | (query {:fib0 0 :fib1 1 :n 0 :fib []} 108 | '[(while (<= :n #jet/lit 10) 109 | {:fib0 :fib1 :fib1 (+ :fib0 :fib1) :n (inc :n) :fib (conj :fib :fib0)}) 110 | :fib]))) 111 | (is (= [1 2 3 4 5 6] (query {:a [1 2 3] :b [4 5 6]} '(into :a :b)))) 112 | (is (= {:x 1 :y 2} (query {:a {:x 1} :b {:y 2}} '(into :a :b)))) 113 | (is (= "{:b {:c 10}}\n{:c 10}\n" 114 | (with-out-str (query {:a {:b {:c 10}}} '[:a jet/debug :b jet/debug :c])))) 115 | (is (= #{3} (query nil '(set/difference #jet/lit #{1 2 3} #jet/lit #{1 2})))) 116 | (is (= #{3} (query #{1 2 3} '(set/difference #jet/lit #{1 2})))) 117 | (is (= #{3} (query #{1 2 3} '(set/difference #jet/lit #{1 2})))) 118 | (is (= (symbol "1.10") (query {:version "1.10"} '[:version symbol]))) 119 | (is (= [1 2 3 4] (query [1 2 3 4 5 1 2] '(take-while (< id #jet/lit 5))))) 120 | (is (= [5 1 2] (query [1 2 3 4 5 1 2] '(drop-while (< id #jet/lit 5)))))) 121 | -------------------------------------------------------------------------------- /test/jet/test_utils.clj: -------------------------------------------------------------------------------- 1 | (ns jet.test-utils 2 | (:require 3 | [clojure.string :as str] 4 | [jet.main :as main] 5 | [me.raynes.conch :refer [let-programs] :as sh])) 6 | 7 | (set! *warn-on-reflection* true) 8 | 9 | (defn jet-jvm [input & args] 10 | (str/replace 11 | (with-out-str 12 | (with-in-str input 13 | (apply main/-main args))) 14 | "\r\n" 15 | "\n")) 16 | 17 | (defn jet-native [input & args] 18 | (let-programs [jet "./jet"] 19 | (binding [sh/*throw* false] 20 | (apply jet (conj (vec args) 21 | {:in input}))))) 22 | 23 | (def jet 24 | (case (System/getenv "JET_TEST_ENV") 25 | "jvm" #'jet-jvm 26 | "native" #'jet-native 27 | #'jet-jvm)) 28 | 29 | (if (= jet #'jet-jvm) 30 | (println "==== Testing JVM version") 31 | (println "==== Testing native version")) 32 | 33 | 34 | --------------------------------------------------------------------------------