Compare commits
165
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
51fc9e02a5 | ||
|
|
0d31bdc4cf | ||
|
|
0960bfc06d | ||
|
|
22e6f9e966 | ||
|
|
df8346a935 | ||
|
|
e1fb525796 | ||
|
|
da170faafe | ||
|
|
03f746bf24 | ||
|
|
5989388c9c | ||
|
|
208c668818 | ||
|
|
9a0d5be790 | ||
|
|
130152a2ac | ||
|
|
730c730940 | ||
|
|
85caac4473 | ||
|
|
048acee39b | ||
|
|
8a02b2c9c1 | ||
|
|
4f6045a94e | ||
|
|
1ef90acb38 | ||
|
|
e843a4fdf9 | ||
|
|
b2b0bd37a1 | ||
|
|
450ade4818 | ||
|
|
c814c4e001 | ||
|
|
4f4494c770 | ||
|
|
ff014824bc | ||
|
|
8edf58a95f | ||
|
|
88e1f59ec1 | ||
|
|
80b064f97b | ||
|
|
0fc8200ab4 | ||
|
|
87594f81d6 | ||
|
|
def42a31ca | ||
|
|
605bfb3577 | ||
|
|
9fa2e51099 | ||
|
|
3f59bee3e0 | ||
|
|
88613f5469 | ||
|
|
2660b04a73 | ||
|
|
18ac789d6d | ||
|
|
956d40d9dd | ||
|
|
2fd725209e | ||
|
|
21be3857b5 | ||
|
|
d29bf5b4fc | ||
|
|
3c5b1419a2 | ||
|
|
012af2186b | ||
|
|
12a628abdc | ||
|
|
63ba2b1637 | ||
|
|
61cd6dbb3b | ||
|
|
4b4fefa254 | ||
|
|
0bc77083ba | ||
|
|
b10878fea7 | ||
|
|
6893284ee5 | ||
|
|
b436156f5b | ||
|
|
fb1c635af6 | ||
|
|
6d3d2750ec | ||
|
|
712330940b | ||
|
|
9c029f7077 | ||
|
|
2760fd365b | ||
|
|
8ea4a5584e | ||
|
|
9caa1ba146 | ||
|
|
bb87e6e30f | ||
|
|
a1f1231a73 | ||
|
|
0ab71031a6 | ||
|
|
2f2d05b087 | ||
|
|
2e01ff1fb6 | ||
|
|
3daecb246a | ||
|
|
cd4f61aee6 | ||
|
|
39a3e2c46f | ||
|
|
0c845aa291 | ||
|
|
341a5fcab3 | ||
|
|
dd4cec664c | ||
|
|
46d1ce2667 | ||
|
|
0673dee1b3 | ||
|
|
3dc5f4e948 | ||
|
|
90bc29c1b5 | ||
|
|
7fd9e86ea6 | ||
|
|
e113c67d51 | ||
|
|
bf88c73c20 | ||
|
|
ae11f111a5 | ||
|
|
2ce1e323df | ||
|
|
68d0048148 | ||
|
|
bafb43e589 | ||
|
|
c163e88af3 | ||
|
|
bd34b8dd2e | ||
|
|
61edf3fa93 | ||
|
|
8eff744eec | ||
|
|
b4742c1257 | ||
|
|
9a4576e756 | ||
|
|
b5560d8420 | ||
|
|
79ece35981 | ||
|
|
f06deb828a | ||
|
|
0c9eacedaa | ||
|
|
476b36770d | ||
|
|
239278fd3a | ||
|
|
98b36e74b8 | ||
|
|
c3cbbdd8a2 | ||
|
|
18879fb993 | ||
|
|
bdaddcf107 | ||
|
|
7b81a836c3 | ||
|
|
c538237f4e | ||
|
|
4a6241be06 | ||
|
|
1900c7bcac | ||
|
|
578d536233 | ||
|
|
b0d6d28310 | ||
|
|
10d66051ef | ||
|
|
cb3d5aff84 | ||
|
|
ca12bd9b5d | ||
|
|
6d89e297fe | ||
|
|
32d8f2000a | ||
|
|
0bd7614bd5 | ||
|
|
4b4cf789e0 | ||
|
|
76b1fc18f1 | ||
|
|
405071337d | ||
|
|
151b494382 | ||
|
|
12f74b226e | ||
|
|
18753cb7a7 | ||
|
|
71d4ff6ed8 | ||
|
|
7935d307f0 | ||
|
|
38bfec1594 | ||
|
|
2421dfb843 | ||
|
|
94035be3fa | ||
|
|
c8a2a0acc2 | ||
|
|
3a8c8f4747 | ||
|
|
b142d484d5 | ||
|
|
674e9fb7c2 | ||
|
|
6878123202 | ||
|
|
f7bf9a8e96 | ||
|
|
89809a2d0f | ||
|
|
9df19f53e4 | ||
|
|
65cbaac344 | ||
|
|
2fcb257168 | ||
|
|
63791b795a | ||
|
|
ea90b947b0 | ||
|
|
9a0d796298 | ||
|
|
0337027d54 | ||
|
|
8ea21a54ad | ||
|
|
6bac4ed059 | ||
|
|
a03ae02f44 | ||
|
|
45f126514c | ||
|
|
b279713e47 | ||
|
|
3504bd9a2a | ||
|
|
1a8a0ae0f5 | ||
|
|
441b066dd8 | ||
|
|
ef87010059 | ||
|
|
66fb962a66 | ||
|
|
e8abc0a2f6 | ||
|
|
7b1bd99de6 | ||
|
|
8619c6e060 | ||
|
|
2e1f2db8b5 | ||
|
|
ba32b33796 | ||
|
|
6e77b017c5 | ||
|
|
5d058fa416 | ||
|
|
12c60b2f32 | ||
|
|
9801de6979 | ||
|
|
c695f2cd9c | ||
|
|
27c29aeb7b | ||
|
|
06f20603f6 | ||
|
|
607a9e3912 | ||
|
|
d3b4ad04f5 | ||
|
|
e7caf68edd | ||
|
|
c6374b6a1f | ||
|
|
8cde328007 | ||
|
|
02bad4b298 | ||
|
|
9b2179089b | ||
|
|
1c806b74ea | ||
|
|
8001955fb7 | ||
|
|
e6fa039f3f | ||
|
|
e0b784ac6d |
No files matched your search
@@ -0,0 +1,29 @@
|
||||
---
|
||||
name: Bug report
|
||||
about: Create a report to help us improve
|
||||
|
||||
---
|
||||
|
||||
<!--
|
||||
READ THIS FIRST!
|
||||
|
||||
If you have a usage question, please ask us on either Stack Overflow (https://stackoverflow.com/questions/tagged/jq) or in the #jq channel (http://irc.lc/freenode/%23jq/) on Freenode (https://webchat.freenode.net/).
|
||||
|
||||
-->
|
||||
|
||||
**Describe the bug**
|
||||
A clear and concise description of what the bug is.
|
||||
|
||||
**To Reproduce**
|
||||
Provide a minimal test case to reproduce the behavior.
|
||||
If the input is large, either attach it as a file, or [create a gist](https://gist.github.com) and link to it here.
|
||||
|
||||
**Expected behavior**
|
||||
A clear and concise description of what you expected to happen.
|
||||
|
||||
**Environment (please complete the following information):**
|
||||
- OS and Version: [e.g. macOS, Windows, Linux (please specify distro)]
|
||||
- jq version [e.g. 1.5]
|
||||
|
||||
**Additional context**
|
||||
Add any other context about the problem here.
|
||||
@@ -0,0 +1,3 @@
|
||||
[submodule "modules/oniguruma"]
|
||||
path = modules/oniguruma
|
||||
url = https://github.com/kkos/oniguruma.git
|
||||
+189
-58
@@ -1,74 +1,205 @@
|
||||
os:
|
||||
- linux
|
||||
- osx
|
||||
|
||||
sudo: false
|
||||
|
||||
language: c
|
||||
|
||||
compiler:
|
||||
- gcc
|
||||
- clang
|
||||
stages:
|
||||
- name: test
|
||||
|
||||
matrix:
|
||||
- name: build
|
||||
# Don't run build stage for pull requests to save time and resources.
|
||||
if: type != pull_request
|
||||
|
||||
|
||||
jobs:
|
||||
include:
|
||||
- compiler: gcc
|
||||
env: COVERAGE="--disable-valgrind --enable-gcov"
|
||||
os: linux
|
||||
# Build with gcc and run tests on Ubuntu.
|
||||
- &test-ubuntu
|
||||
stage: test
|
||||
os: linux
|
||||
compiler: gcc
|
||||
python: 3.6
|
||||
|
||||
addons:
|
||||
apt:
|
||||
packages:
|
||||
- libonig-dev
|
||||
- valgrind
|
||||
- bison
|
||||
addons:
|
||||
apt:
|
||||
packages:
|
||||
- valgrind
|
||||
- bison
|
||||
- automake
|
||||
|
||||
before_install:
|
||||
- echo "$TRAVIS_OS_NAME"
|
||||
- uname -s
|
||||
- brew update || true;
|
||||
brew install flex || true;
|
||||
brew install bison || true;
|
||||
brew install oniguruma || true;
|
||||
- rm src/{lexer,parser}.{c,h}
|
||||
before_install:
|
||||
- uname -s
|
||||
- rm src/{lexer,parser}.{c,h}
|
||||
- sed -i.bak '/^AM_INIT_AUTOMAKE(\[-Wno-portability 1\.14\])$/s/14/11/' modules/oniguruma/configure.ac
|
||||
|
||||
install:
|
||||
- bundle install --gemfile=docs/Gemfile
|
||||
- wget http://ftp.debian.org/debian/pool/main/b/bison/bison_3.0.2.dfsg-2_amd64.deb
|
||||
- ar p bison_3.0.2.dfsg-2_amd64.deb data.tar.xz | tar xJ
|
||||
- if [ -n "$COVERAGE" ]; then pip install --user cpp-coveralls; fi
|
||||
install:
|
||||
- pyenv global 3.6
|
||||
- pip3 install pipenv
|
||||
- pushd docs && pipenv sync && popd
|
||||
- wget http://ftp.debian.org/debian/pool/main/b/bison/bison_3.0.2.dfsg-2_amd64.deb
|
||||
- ar p bison_3.0.2.dfsg-2_amd64.deb data.tar.xz | tar xJ
|
||||
- if [ -n "$COVERAGE" ]; then pip install --user cpp-coveralls; fi
|
||||
|
||||
before_script:
|
||||
# If this is OS X we'll get bison from brew, else we'll get bison
|
||||
# from the .deb unpacked above in the install section.
|
||||
- PATH=/usr/local/opt/bison/bin:$PWD/usr/bin:$PATH
|
||||
- echo SHELL=$SHELL
|
||||
- echo PATH=$PATH
|
||||
- which bison
|
||||
- bison --version
|
||||
- autoreconf -i
|
||||
- ./configure YACC="$(which bison) -y" $COVERAGE
|
||||
before_script:
|
||||
# If this is OS X we'll get bison from brew, else we'll get bison
|
||||
# from the .deb unpacked above in the install section.
|
||||
- PATH=/usr/local/opt/bison/bin:$PWD/usr/bin:$PATH
|
||||
- echo SHELL=$SHELL
|
||||
- echo PATH=$PATH
|
||||
- which bison
|
||||
- bison --version
|
||||
- autoreconf -if
|
||||
- ./configure --with-oniguruma=builtin YACC="$(which bison) -y" $COVERAGE
|
||||
|
||||
script:
|
||||
# When using the bison from Debian we need to tell that bison where
|
||||
# to find its data. Yay non-relocatable code. Not.
|
||||
- echo PATH=$PATH
|
||||
- which bison
|
||||
- make BISON_PKGDATADIR=$PWD/usr/share/bison src/parser.c || make src/parser.c
|
||||
- make -j4
|
||||
- make check -j4
|
||||
script:
|
||||
# When using the bison from Debian we need to tell that bison where
|
||||
# to find its data. Yay non-relocatable code. Not.
|
||||
- echo PATH=$PATH
|
||||
- which bison
|
||||
- make BISON_PKGDATADIR=$PWD/usr/share/bison src/parser.c || make src/parser.c
|
||||
# Make dist!
|
||||
#
|
||||
# Make it first to fail the build early, before we test with
|
||||
# valgrind.
|
||||
- make dist
|
||||
# Build and test the dist (without valgrind)
|
||||
- |
|
||||
(
|
||||
tar xvf jq-`scripts/version`.tar.gz &&
|
||||
cd jq-`scripts/version` &&
|
||||
pwd &&
|
||||
./configure --disable-valgrind --with-oniguruma=builtin YACC="$(which bison) -y" $COVERAGE &&
|
||||
make BISON_PKGDATADIR=$PWD/usr/share/bison src/parser.c || make src/parser.c &&
|
||||
make -j4 &&
|
||||
make check -j4 || true
|
||||
)
|
||||
# Build and test the HEAD
|
||||
- make -j4
|
||||
- make check -j4
|
||||
|
||||
after_script:
|
||||
- |
|
||||
if [ -n "$COVERAGE" ]; then
|
||||
rm -rf src/.libs # don't care about coverage for libjq
|
||||
coveralls --gcov-options '\-lp' \
|
||||
-e src/lexer.c -e src/parser.c -e src/jv_dtoa.c
|
||||
fi
|
||||
after_failure:
|
||||
- cat test-suite.log
|
||||
- cat tests/*.log
|
||||
|
||||
|
||||
# Build with clang and run tests on Ubuntu.
|
||||
- <<: *test-ubuntu
|
||||
compiler: clang
|
||||
|
||||
|
||||
# Build with gcc and run tests with gcov on Ubuntu.
|
||||
- <<: *test-ubuntu
|
||||
env: COVERAGE="--disable-valgrind --enable-gcov"
|
||||
|
||||
after_script:
|
||||
- rm -rf src/.libs # don't care about coverage for libjq
|
||||
- coveralls --gcov-options '\-lp'
|
||||
-e src/lexer.c -e src/parser.c -e src/jv_dtoa.c
|
||||
|
||||
|
||||
# Build with gcc and run tests on macOS.
|
||||
- &test-osx
|
||||
<<: *test-ubuntu
|
||||
os: osx
|
||||
|
||||
before_install:
|
||||
- uname -s
|
||||
- brew update
|
||||
- brew install flex bison
|
||||
- rm src/{lexer,parser}.{c,h}
|
||||
- sed -i.bak '/^AM_INIT_AUTOMAKE(\[-Wno-portability 1\.14\])$/s/14/11/' modules/oniguruma/configure.ac
|
||||
|
||||
install:
|
||||
- pip3 install pipenv
|
||||
- pushd docs && pipenv sync && popd
|
||||
- if [ -n "$COVERAGE" ]; then pip install --user cpp-coveralls; fi
|
||||
|
||||
|
||||
# Build with clang and run tests on macOS.
|
||||
- <<: *test-osx
|
||||
compiler: clang
|
||||
|
||||
|
||||
# Build with gcc and run tests on Alpine Linux v3.7 (inside chroot).
|
||||
# Note: Alpine uses musl libc.
|
||||
- &test-alpine
|
||||
stage: test
|
||||
os: linux
|
||||
language: minimal
|
||||
compiler: gcc
|
||||
sudo: true
|
||||
|
||||
before_install:
|
||||
- "wget 'https://raw.githubusercontent.com/alpinelinux/alpine-chroot-install/v0.7.0/alpine-chroot-install' \
|
||||
&& echo '090d323d887ef3a2fd4e752428553f22a52b87bb alpine-chroot-install' | sha1sum -c || travis_terminate 1"
|
||||
- alpine() { /alpine/enter-chroot -u "$USER" "$@"; }
|
||||
|
||||
install:
|
||||
- sudo sh alpine-chroot-install -b v3.7 -a "$ARCH"
|
||||
-p 'build-base automake autoconf bison git libtool oniguruma-dev python3 python3-dev libxml2-dev libxslt-dev'
|
||||
- /alpine/enter-chroot pip3 install pipenv
|
||||
- alpine sh -c 'cd docs && pipenv sync'
|
||||
|
||||
before_script:
|
||||
- autoreconf -if
|
||||
|
||||
script:
|
||||
- alpine ./configure --disable-docs
|
||||
- alpine make
|
||||
- alpine make check
|
||||
|
||||
after_failure:
|
||||
- cat test-suite.log
|
||||
- cat tests/*.log
|
||||
|
||||
# Build release binary statically linked with musl libc on Alpine Linux
|
||||
# (inside chroot). If building a tagged commit, then deploy release
|
||||
# tarball to GitHub Releases.
|
||||
- &build-alpine
|
||||
<<: *test-alpine
|
||||
stage: build
|
||||
env: ARCH=x86_64
|
||||
|
||||
script:
|
||||
- alpine ./configure --disable-docs --enable-all-static
|
||||
CFLAGS='-Os -static -no-pie' CXXFLAGS='-Os -static -no-pie'
|
||||
- alpine make
|
||||
- alpine strip jq
|
||||
|
||||
- jq -V
|
||||
- ls -lah jq
|
||||
- file jq
|
||||
# Ensure that the built executable is really statically linked.
|
||||
- file jq | grep -Fw 'statically linked'
|
||||
|
||||
before_deploy:
|
||||
- PKGNAME="jq-$TRAVIS_TAG-$ARCH-linux"
|
||||
- mkdir $PKGNAME && mv jq $PKGNAME/
|
||||
- tar -czf $PKGNAME.tar.gz $PKGNAME/
|
||||
- sha256sum $PKGNAME.tar.gz > $PKGNAME.tar.gz.sha256
|
||||
|
||||
deploy:
|
||||
provider: releases
|
||||
api_key:
|
||||
secure: # TODO: put encrypted GitHub token here!
|
||||
file: jq-$TRAVIS_TAG-*.tar.gz*
|
||||
file_glob: true
|
||||
skip_cleanup: true
|
||||
on:
|
||||
tags: true
|
||||
|
||||
# Build binaries for other architectures using QEMU user-mode emulation.
|
||||
- <<: *build-alpine
|
||||
env: ARCH=x86
|
||||
|
||||
- <<: *build-alpine
|
||||
env: ARCH=aarch64
|
||||
|
||||
- <<: *build-alpine
|
||||
env: ARCH=armhf
|
||||
|
||||
- <<: *build-alpine
|
||||
env: ARCH=ppc64le
|
||||
|
||||
after_failure:
|
||||
- cat test-suite.log
|
||||
- cat tests/*.log
|
||||
|
||||
notifications:
|
||||
email: false
|
||||
|
||||
@@ -19,11 +19,13 @@ cdnbacon <mike@busbud.com>
|
||||
Charles Merriam <charles.merriam@gmail.com>
|
||||
Colin von Heuring <colin@janrain.com>
|
||||
Damian Gryski <damian@gryski.com>
|
||||
David Fetter <david@fetter.org> - added --rawfile
|
||||
David Haguenauer <ml@kurokatta.org>
|
||||
David R. MacIver <david@drmaciver.com> - bug fixes
|
||||
David Tolnay <dtolnay@gmail.com> - destructuring, build improvements
|
||||
Doug Luce <doug@github.con.com> - build
|
||||
Eiichi Sato <sato.eiichi@gmail.com>
|
||||
Eric Bréchemier <eric@egull.co> - bug fix
|
||||
Filippo Giunchedi <fgiunchedi@gmail.com> - bug fixes
|
||||
Filippo Valsorda <filippo.valsorda@gmail.com> - recursive object merge (`*`)
|
||||
Hanfei Shen <qqshfox@gmail.com>
|
||||
@@ -58,6 +60,7 @@ Ryoichi KATO <ryo1kato@gmail.com> - doc fixes
|
||||
Rémy Léone <remy.leone@gmail.com> - add .travis.yml
|
||||
Santiago Lapresta <santiago.lapresta@gmail.com> - join, arrays, all, any, other filters
|
||||
Sebastian Freundt <freundt@ga-group.nl> - build
|
||||
Shaun Guth <shaun.guth@gmail.com> - base64d
|
||||
Shay Elkin <shay@everything.me>
|
||||
Simon Elsbrock <simon@iodev.org> - Debian
|
||||
Stefan Seemayer <stefan@seemayer.de>
|
||||
|
||||
+5
-7
@@ -13,9 +13,8 @@ RUN apt-get update && \
|
||||
git \
|
||||
bison \
|
||||
flex \
|
||||
ruby \
|
||||
wget \
|
||||
ruby-dev && \
|
||||
python3 \
|
||||
wget && \
|
||||
wget https://github.com/kkos/oniguruma/archive/v5.9.6.tar.gz -O onig-5.9.6.tar.gz && \
|
||||
sha512sum onig-5.9.6.tar.gz | grep 6b048d345e148c9da38af7a8df76d4a358eb3d4db91fa7834076e511f526a63544284f212b0d56badbbd33112c8b458a5fff02bfbbda012ecfe478bc436ea679 && \
|
||||
tar zxvf onig-5.9.6.tar.gz && \
|
||||
@@ -25,8 +24,8 @@ RUN apt-get update && \
|
||||
./configure --prefix=/usr/local && \
|
||||
make && \
|
||||
make install ) && \
|
||||
gem install bundler && \
|
||||
(cd /app/docs && bundle install) && \
|
||||
pip3 install pipenv && \
|
||||
(cd /app/docs && pipenv sync) && \
|
||||
(cd /app && \
|
||||
autoreconf -i && \
|
||||
./configure --disable-valgrind --enable-all-static --prefix=/usr/local && \
|
||||
@@ -43,8 +42,7 @@ RUN apt-get update && \
|
||||
bison \
|
||||
git \
|
||||
flex \
|
||||
ruby \
|
||||
ruby-dev && \
|
||||
python3 && \
|
||||
apt-get autoremove -y && \
|
||||
rm -rf oniguruma-5.9.6 && \
|
||||
rm -rf /var/lib/apt/lists/* /var/lib/gems
|
||||
|
||||
+38
-11
@@ -18,6 +18,10 @@ LIBJQ_SRC = src/builtin.c src/bytecode.c src/compile.c src/execute.c \
|
||||
AM_CFLAGS = -Wextra -Wall -Wno-missing-field-initializers \
|
||||
-Wno-unused-parameter -Wno-unused-function
|
||||
|
||||
if WIN32
|
||||
AM_CFLAGS += -municode
|
||||
endif
|
||||
|
||||
ACLOCAL_AMFLAGS = -I config/m4
|
||||
|
||||
### Generating the lexer and parser
|
||||
@@ -48,10 +52,11 @@ AM_YFLAGS = --warnings=all -d
|
||||
lib_LTLIBRARIES = libjq.la
|
||||
libjq_la_SOURCES = ${LIBJQ_SRC}
|
||||
libjq_la_LIBADD = -lm
|
||||
libjq_la_LDFLAGS = -export-symbols-regex '^j[qv]_' -version-info 1:4:0
|
||||
libjq_la_LDFLAGS = $(onig_LDFLAGS) -export-symbols-regex '^j[qv]_' -version-info 1:4:0
|
||||
|
||||
if WIN32
|
||||
libjq_la_LIBADD += -lshlwapi
|
||||
libjq_la_LDFLAGS += -no-undefined
|
||||
endif
|
||||
|
||||
include_HEADERS = src/jv.h src/jq.h
|
||||
@@ -60,6 +65,8 @@ if ENABLE_UBSAN
|
||||
AM_CFLAGS += -fsanitize=undefined
|
||||
endif
|
||||
|
||||
AM_CPPFLAGS = -I$(srcdir)/src
|
||||
|
||||
### Running tests under Valgrind
|
||||
|
||||
if ENABLE_ASAN
|
||||
@@ -97,11 +104,13 @@ generate_ver = ver="`{ $(srcdir)/scripts/version || echo '$(VERSION)' ; } | sed
|
||||
.remake-version-h: .FORCE
|
||||
@ $(generate_ver); test "x`cat src/version.h 2>/dev/null`" = "x$$ver" || touch .remake-version-h
|
||||
src/version.h: .remake-version-h
|
||||
mkdir -p src
|
||||
$(AM_V_GEN) $(generate_ver); echo "$$ver" > $@
|
||||
src/main.c: src/version.h
|
||||
|
||||
src/builtin.inc: src/builtin.jq
|
||||
$(AM_V_GEN) sed -e 's/\\/\\\\/g' -e 's/"/\\"/g' -e 's/^/"/' -e 's/$$/\\n"/' $^ > $@
|
||||
mkdir -p src
|
||||
$(AM_V_GEN) sed -e 's/\\/\\\\/g' -e 's/"/\\"/g' -e 's/^/"/' -e 's/$$/\\n"/' src/builtin.jq > $@
|
||||
src/builtin.o: src/builtin.inc
|
||||
|
||||
bin_PROGRAMS = jq
|
||||
@@ -109,13 +118,17 @@ jq_SOURCES = src/main.c src/version.h
|
||||
jq_LDFLAGS = -static-libtool-libs
|
||||
jq_LDADD = libjq.la -lm
|
||||
|
||||
if WIN32
|
||||
jq_LDADD += -lshlwapi
|
||||
endif
|
||||
|
||||
if ENABLE_ALL_STATIC
|
||||
jq_LDFLAGS += -all-static
|
||||
endif
|
||||
|
||||
### Tests (make check)
|
||||
|
||||
TESTS = tests/optionaltest tests/mantest tests/jqtest tests/onigtest tests/shtest tests/utf8test
|
||||
TESTS = tests/optionaltest tests/mantest tests/jqtest tests/onigtest tests/shtest tests/utf8test tests/base64test
|
||||
TESTS_ENVIRONMENT = NO_VALGRIND=$(NO_VALGRIND)
|
||||
|
||||
|
||||
@@ -123,16 +136,25 @@ TESTS_ENVIRONMENT = NO_VALGRIND=$(NO_VALGRIND)
|
||||
|
||||
man_MANS = jq.1
|
||||
if ENABLE_DOCS
|
||||
jq.1: $(srcdir)/docs/content/3.manual/manual.yml
|
||||
$(AM_V_GEN) ( cd ${abs_srcdir}/docs; '$(BUNDLER)' exec rake manpage ) > $@ || { rm -f $@; false; }
|
||||
jq.1: $(srcdir)/docs/content/manual/manual.yml
|
||||
$(AM_V_GEN) ( cd ${abs_srcdir}/docs; pipenv run python build_manpage.py ) > $@ || { rm -f $@; false; }
|
||||
jq.1.prebuilt: jq.1
|
||||
$(AM_V_GEN) cp $^ $@ || { rm -f $@; false; }
|
||||
$(AM_V_GEN) cp jq.1 $@ || { rm -f $@; false; }
|
||||
else
|
||||
jq.1: $(srcdir)/jq.1.prebuilt
|
||||
$(AM_V_GEN) cp $^ $@
|
||||
$(AM_V_GEN) cp $(srcdir)/jq.1.prebuilt $@
|
||||
endif
|
||||
|
||||
|
||||
### Build oniguruma
|
||||
|
||||
if BUILD_ONIGURUMA
|
||||
libjq_la_LIBADD += modules/oniguruma/src/.libs/libonig.la
|
||||
SUBDIRS = modules/oniguruma
|
||||
endif
|
||||
|
||||
AM_CFLAGS += $(onig_CFLAGS)
|
||||
|
||||
### Packaging
|
||||
|
||||
docs/site.yml: configure.ac
|
||||
@@ -143,12 +165,12 @@ install-binaries: $(BUILT_SOURCES)
|
||||
$(MAKE) $(AM_MAKEFLAGS) install-exec
|
||||
|
||||
DOC_FILES = docs/content docs/public docs/templates docs/site.yml \
|
||||
docs/Gemfile docs/Gemfile.lock docs/Rakefile docs/README.md \
|
||||
docs/default_manpage.md jq.1.default
|
||||
docs/Pipfile docs/Pipfile.lock docs/build_manpage.py \
|
||||
docs/build_manpage.py docs/README.md jq.1.prebuilt
|
||||
|
||||
EXTRA_DIST = $(DOC_FILES) $(man_MANS) $(TESTS) $(TEST_LOG_COMPILER) \
|
||||
jq.1.prebuilt jq.spec src/lexer.c src/lexer.h src/parser.c \
|
||||
src/parser.h src/version.h src/builtin.inc \
|
||||
src/parser.h src/version.h src/builtin.jq \
|
||||
scripts/version tests/jq.test tests/modules/.jq \
|
||||
tests/modules/a.jq tests/modules/b/b.jq tests/modules/c/c.jq \
|
||||
tests/modules/c/d.jq tests/modules/data.json \
|
||||
@@ -158,7 +180,12 @@ EXTRA_DIST = $(DOC_FILES) $(man_MANS) $(TESTS) $(TEST_LOG_COMPILER) \
|
||||
tests/modules/test_bind_order0.jq \
|
||||
tests/modules/test_bind_order1.jq \
|
||||
tests/modules/test_bind_order2.jq tests/onig.supp \
|
||||
tests/onig.test tests/setup tests/torture/input0.json
|
||||
tests/onig.test tests/setup tests/torture/input0.json \
|
||||
tests/optional.test tests/optionaltest \
|
||||
tests/utf8-truncate.jq tests/utf8test \
|
||||
tests/base64.test tests/base64test \
|
||||
tests/jq-f-test.sh tests/shtest
|
||||
|
||||
|
||||
|
||||
# README.md is expected in Github projects, good stuff in it, so we'll
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
|
||||
Release history
|
||||
|
||||
* jq version 1.6 was released on Fri Nov 2 2018
|
||||
* jq version 1.5 was released on Sat Aug 15 2015
|
||||
* jq version 1.4 was released on Mon Jun 9 2014
|
||||
* jq version 1.3 was released on Sun May 19 2013
|
||||
@@ -8,6 +9,29 @@ Release history
|
||||
* jq version 1.1 was released on Sun Oct 21 2012
|
||||
* jq version 1.0 was released on Sun Oct 21 2012
|
||||
|
||||
New features in 1.6 since 1.5:
|
||||
|
||||
- Destructuring Alternation
|
||||
|
||||
- New Builtins:
|
||||
- builtins/0
|
||||
- stderr/0
|
||||
- halt/0, halt_error/1
|
||||
- isempty/1
|
||||
- walk/1
|
||||
- utf8bytelength/1
|
||||
- localtime/0, strflocaltime/1
|
||||
- SQL-style builtins
|
||||
- and more!
|
||||
|
||||
- Add support for ASAN and UBSAN
|
||||
|
||||
- Make it easier to use jq with shebangs (8f6f28c)
|
||||
|
||||
- Add $ENV builtin variable to access environment
|
||||
|
||||
- Add JQ_COLORS env var for configuring the output colors
|
||||
|
||||
New features in 1.5 since 1.4:
|
||||
|
||||
- regular expressions (with Oniguruma)
|
||||
|
||||
@@ -23,19 +23,21 @@ Source tarball and built executable releases can be found on the
|
||||
homepage and on the github release page, https://github.com/stedolan/jq/releases
|
||||
|
||||
If you're building directly from the latest git, you'll need flex,
|
||||
bison (3.0 or newer), libtool, make, and autoconf installed. To get
|
||||
regexp support you'll also need to install Oniguruma (note that jq's
|
||||
tests require regexp support to pass). To build, run:
|
||||
bison (3.0 or newer), libtool, make, automake, and autoconf installed.
|
||||
To get regexp support you'll also need to install Oniguruma or clone it as a
|
||||
git submodule as per the instructions below.
|
||||
(note that jq's tests require regexp support to pass). To build, run:
|
||||
|
||||
autoreconf -i # if building from git
|
||||
./configure
|
||||
git submodule update --init # if building from git to get oniguruma
|
||||
autoreconf -fi # if building from git
|
||||
./configure --with-oniguruma=builtin
|
||||
make -j8
|
||||
make check
|
||||
|
||||
To build without bison or flex, add `--disable-maintainer-mode` to the
|
||||
./configure invocation:
|
||||
|
||||
./configure --disable-maintainer-mode
|
||||
./configure --with-oniguruma=builtin --disable-maintainer-mode
|
||||
|
||||
(Developers must not use `--disable-maintainer-mode`, not when making
|
||||
changes to the jq parser and/or lexer.)
|
||||
|
||||
+18
-12
@@ -1,23 +1,28 @@
|
||||
environment:
|
||||
matrix:
|
||||
- MSYSTEM: MINGW64
|
||||
PATH: C:\msys64\usr\bin;C:\msys64\mingw64\bin;C:\Windows\System32;C:\Windows;%PATH%
|
||||
PATH: C:\Python36;C:\Python36\Scripts;C:\msys64\usr\bin;C:\msys64\mingw64\bin;C:\Windows\System32;C:\Windows;%PATH%
|
||||
- MSYSTEM: MINGW32
|
||||
PATH: C:\msys64\usr\bin;C:\msys64\mingw32\bin;C:\Windows\System32;C:\Windows;%PATH%
|
||||
PATH: C:\Python36;C:\Python36\Scripts;C:\msys64\usr\bin;C:\msys64\mingw32\bin;C:\Windows\System32;C:\Windows;%PATH%
|
||||
|
||||
clone_script:
|
||||
- bash -lc "echo $APPVEYOR_PULL_REQUEST_HEAD_REPO_NAME - $APPVEYOR_PULL_REQUEST_HEAD_REPO_BRANCH"
|
||||
- bash -lc "git clone -q --branch=${APPVEYOR_PULL_REQUEST_HEAD_REPO_BRANCH:-$APPVEYOR_REPO_BRANCH} https://github.com/${APPVEYOR_PULL_REQUEST_HEAD_REPO_NAME:-$APPVEYOR_REPO_NAME}.git $APPVEYOR_BUILD_FOLDER"
|
||||
- bash -lc "cd $APPVEYOR_BUILD_FOLDER && git checkout -qf ${APPVEYOR_PULL_REQUEST_HEAD_REPO_BRANCH:APPVEYOR_REPO_BRANCH}"
|
||||
- bash -lc "cd $APPVEYOR_BUILD_FOLDER && git submodule update --init --recursive"
|
||||
|
||||
install:
|
||||
# update mysy2
|
||||
# update msys2
|
||||
- C:\msys64\usr\bin\bash -lc "pacman --needed --noconfirm -Sy pacman-mirrors"
|
||||
- C:\msys64\usr\bin\bash -lc "pacman --noconfirm -Sy"
|
||||
- C:\msys64\usr\bin\bash -lc "pacman --noconfirm -S autoconf automake bison flex"
|
||||
- C:\msys64\usr\bin\bash -lc "pacman --noconfirm -S mingw-w64-i686-oniguruma mingw-w64-x86_64-oniguruma"
|
||||
|
||||
- C:\msys64\usr\bin\bash -lc "pacman --noconfirm -Su"
|
||||
- C:\msys64\usr\bin\bash -lc "pacman --noconfirm -S autoconf automake bison flex git"
|
||||
- C:\msys64\usr\bin\bash -lc "pip3 install --upgrade pipenv"
|
||||
- C:\msys64\usr\bin\bash -lc "cd $APPVEYOR_BUILD_FOLDER/docs && ls && pipenv sync"
|
||||
|
||||
build_script:
|
||||
- bash -lc "exec 0</dev/null && cd $APPVEYOR_BUILD_FOLDER && autoreconf -i"
|
||||
- bash -lc "exec 0</dev/null && cd $APPVEYOR_BUILD_FOLDER && ./configure --disable-shared --enable-static --enable-all-static"
|
||||
- bash -lc "exec 0</dev/null && cd $APPVEYOR_BUILD_FOLDER && make -j8"
|
||||
- bash -lc "exec 0</dev/null && cd $APPVEYOR_BUILD_FOLDER && ./configure --with-oniguruma=builtin --disable-shared --enable-static --enable-all-static"
|
||||
- bash -lc "exec 0</dev/null && cd $APPVEYOR_BUILD_FOLDER && make"
|
||||
- bash -lc "exec 0</dev/null && cd $APPVEYOR_BUILD_FOLDER && cat jq.1 | groff -mandoc -Thtml > jq.html"
|
||||
- 7z a jq-package.zip jq.1 jq.html jq.exe
|
||||
- bash -lc "exec 0</dev/null && cd $APPVEYOR_BUILD_FOLDER && pwd && ls -la"
|
||||
@@ -26,9 +31,9 @@ build_script:
|
||||
test_script:
|
||||
# tests/optionaltest and tests/shtest fail on Windows; run them
|
||||
# anyways but ignore their failures. Also, trace shtest.
|
||||
- bash -lc "exec 0</dev/null && cd $APPVEYOR_BUILD_FOLDER && make -j3 'TESTS=tests/mantest tests/jqtest tests/onigtest' check"
|
||||
- bash -lc "exec 0</dev/null && cd $APPVEYOR_BUILD_FOLDER && make TESTS=tests/optionaltest check || cat test-suite.log"
|
||||
- bash -lc "exec 0</dev/null && cd $APPVEYOR_BUILD_FOLDER && make TRACE_TESTS=1 TESTS=tests/shtest check || cat test-suite.log"
|
||||
- bash -lc "exec 0</dev/null && cd $APPVEYOR_BUILD_FOLDER && make -j3 SUBDIRS= 'TESTS=tests/mantest tests/jqtest tests/onigtest tests/base64test' check"
|
||||
- bash -lc "exec 0</dev/null && cd $APPVEYOR_BUILD_FOLDER && make SUBDIRS= TESTS=tests/optionaltest check || true"
|
||||
- bash -lc "exec 0</dev/null && cd $APPVEYOR_BUILD_FOLDER && make SUBDIRS= TRACE_TESTS=1 TESTS=tests/shtest check || true"
|
||||
|
||||
artifacts:
|
||||
- path: jq-package.zip
|
||||
@@ -42,6 +47,7 @@ artifacts:
|
||||
|
||||
|
||||
on_failure:
|
||||
- cat config.log
|
||||
- cat test-suite.log
|
||||
- appveyor PushArtifact test-suite.log
|
||||
# also push the jq.exe so that someone can download it anyways...
|
||||
|
||||
+4
-4
@@ -10,10 +10,10 @@ NJOBS="-j`sysctl -n hw.ncpu || echo 1`"
|
||||
|
||||
|
||||
# Get oniguruma
|
||||
rm -rf $PWD/build/ios onig-5.9.5
|
||||
echo "Downloading oniguruma 5.9.5"
|
||||
rm -rf $PWD/build/ios oniguruma-5.9.6
|
||||
echo "Downloading oniguruma 5.9.6"
|
||||
curl -L https://github.com/kkos/oniguruma/archive/v5.9.6.tar.gz | tar xz
|
||||
cd oniguruma-5.9.5
|
||||
cd oniguruma-5.9.6
|
||||
|
||||
# So, we need to remake the configure scripts so that the arm64 architecture
|
||||
# exists in config.sub. In order to keep autoreconf from failing, create
|
||||
@@ -50,7 +50,7 @@ for arch in i386 x86_64 armv7 armv7s arm64; do
|
||||
|
||||
|
||||
# Build oniguruma for this architecture
|
||||
cd onig-5.9.5
|
||||
cd oniguruma-5.9.6
|
||||
CC=$CC CFLAGS=$CFLAGS LDFLAGS=$LDFLAGS ./configure --disable-shared --enable-static --host=$HOST --prefix=$ORIG_PWD/build/ios/$arch
|
||||
STATUS=$?
|
||||
if [ $STATUS -ne 0 ]
|
||||
|
||||
@@ -1,4 +1,9 @@
|
||||
dnl AC_FIND_FUNC(func, arguments)
|
||||
dnl AC_CHECK_MATH_FUNC(func)
|
||||
AC_DEFUN([AC_CHECK_MATH_FUNC], [
|
||||
AC_FIND_FUNC_NO_LIBS([$1], [m], [#include <math.h>], [$2])
|
||||
AC_LANG(C)
|
||||
AC_CHECK_LIB([m],[$1],[
|
||||
eval "ac_tr_func=HAVE_[]upcase($1)"
|
||||
AC_DEFINE_UNQUOTED($ac_tr_func)
|
||||
],[
|
||||
])
|
||||
])
|
||||
+125
-103
@@ -1,4 +1,7 @@
|
||||
m4_define([jq_version], m4_esyscmd_s([git describe --tags --dirty --match 'jq-*'|sed 's/^jq-//']))
|
||||
m4_define([jq_version],
|
||||
m4_esyscmd_s([(git rev-parse --verify -q jq-1.0 > /dev/null &&
|
||||
(git describe --tags --dirty --match 'jq-*'|sed 's/^jq-//')) ||
|
||||
echo `git rev-parse --abbrev-ref HEAD`-`git describe --always --dirty`])))
|
||||
|
||||
AC_INIT([jq], [jq_version], [https://github.com/stedolan/jq/issues],
|
||||
[jq], [https://stedolan.github.io/jq])
|
||||
@@ -38,41 +41,9 @@ if test "$USE_MAINTAINER_MODE" = yes; then
|
||||
[AC_MSG_ERROR([You need bison version 3.0 or greater, or use --disable-maintainer-mode.])])
|
||||
fi
|
||||
|
||||
AC_PROG_LEX
|
||||
if test "x$LEX" != xflex; then
|
||||
LEX="$SHELL $missing_dir/missing flex"
|
||||
fi
|
||||
AC_CHECK_PROGS(LEX, flex lex)
|
||||
fi
|
||||
|
||||
|
||||
##########################################################################
|
||||
# check for ONIGURUMA library
|
||||
##########################################################################
|
||||
|
||||
AC_ARG_WITH([oniguruma],
|
||||
[AS_HELP_STRING([--with-oniguruma=prefix],
|
||||
[try this for a non-standard install prefix of the oniguruma library])],
|
||||
[],
|
||||
[with_oniguruma=yes])
|
||||
|
||||
AS_IF([test "x$with_oniguruma" != xno], [
|
||||
AS_IF([test "x$with_oniguruma" != xyes], [
|
||||
CFLAGS="$CFLAGS -I${with_oniguruma}/include"
|
||||
LDFLAGS="$LDFLAGS -L${with_oniguruma}/lib"
|
||||
])
|
||||
# check for ONIGURUMA library
|
||||
have_oniguruma=0
|
||||
AC_CHECK_HEADER("oniguruma.h",
|
||||
AC_CHECK_LIB([onig],[onig_version],[LIBS="$LIBS -lonig"; have_oniguruma=1;]))
|
||||
# handle check results
|
||||
AS_IF([test $have_oniguruma = 1], [
|
||||
AC_DEFINE([HAVE_ONIGURUMA], 1, [Define to 1 if Oniguruma is installed])
|
||||
], [
|
||||
AC_MSG_NOTICE([Oniguruma was not found.])
|
||||
AC_MSG_NOTICE([Try setting the location using '--with-oniguruma=PREFIX'])
|
||||
])
|
||||
])
|
||||
|
||||
dnl Check for valgrind
|
||||
AC_CHECK_PROGS(valgrind_cmd, valgrind)
|
||||
if test "x$valgrind_cmd" = "x" ; then
|
||||
@@ -103,7 +74,7 @@ dnl Code coverage
|
||||
AC_ARG_ENABLE([gcov],
|
||||
AC_HELP_STRING([--enable-gcov], [enable gcov code coverage tool]))
|
||||
|
||||
dnl Don't attempt to build docs if there's no Ruby lying around
|
||||
dnl Don't attempt to build docs if python deps aren't installed
|
||||
AC_ARG_ENABLE([docs],
|
||||
AC_HELP_STRING([--disable-docs], [don't build docs]))
|
||||
|
||||
@@ -116,25 +87,25 @@ AC_ARG_ENABLE([all-static],
|
||||
AC_HELP_STRING([--enable-all-static], [link jq with static libraries only]))
|
||||
|
||||
AS_IF([test "x$enable_docs" != "xno"],[
|
||||
AC_CHECK_PROGS(bundle_cmd, bundle)
|
||||
AC_CHECK_PROGS(pipenv_cmd, pipenv)
|
||||
|
||||
AC_CACHE_CHECK([for Ruby dependencies], [jq_cv_ruby_deps],
|
||||
[jq_cv_ruby_deps=yes;
|
||||
AS_IF([test "x$bundle_cmd" = "x" || \
|
||||
! bmsg="`cd ${srcdir}/docs; "$bundle_cmd" check 2>/dev/null`"],[
|
||||
AC_MSG_WARN([$bmsg])
|
||||
AC_CACHE_CHECK([for Python dependencies], [jq_cv_python_deps],
|
||||
[jq_cv_python_deps=yes;
|
||||
AS_IF([test "x$pipenv_cmd" = "x" || \
|
||||
! bmsg="`cd ${srcdir}/docs; LC_ALL=$LANG "$pipenv_cmd" check`"],[
|
||||
AC_MSG_ERROR([$bmsg])
|
||||
cat <<EOF
|
||||
*****************************************************************
|
||||
* Ruby dependencies for building jq documentation not found. *
|
||||
* Python dependencies for building jq documentation not found. *
|
||||
* You can still build, install and hack on jq, but the manpage *
|
||||
* will not be rebuilt and some of the tests will not run. *
|
||||
* See docs/README.md for how to install the docs dependencies. *
|
||||
*****************************************************************
|
||||
EOF
|
||||
jq_cv_ruby_deps=no
|
||||
jq_cv_python_deps=no
|
||||
])])
|
||||
|
||||
if test "x$jq_cv_ruby_deps" != "xyes"; then
|
||||
if test "x$jq_cv_python_deps" != "xyes"; then
|
||||
enable_docs=no
|
||||
fi
|
||||
])
|
||||
@@ -153,9 +124,12 @@ AC_FIND_FUNC([isatty], [c], [#include <unistd.h>], [0])
|
||||
AC_FIND_FUNC([_isatty], [c], [#include <io.h>], [0])
|
||||
AC_FIND_FUNC([strptime], [c], [#include <time.h>], [0, 0, 0])
|
||||
AC_FIND_FUNC([strftime], [c], [#include <time.h>], [0, 0, 0, 0])
|
||||
AC_FIND_FUNC([setenv], [c], [#include <stdlib.h>], [0, 0, 0])
|
||||
AC_FIND_FUNC([timegm], [c], [#include <time.h>], [0])
|
||||
AC_FIND_FUNC([gmtime_r], [c], [#include <time.h>], [0, 0])
|
||||
AC_FIND_FUNC([gmtime], [c], [#include <time.h>], [0])
|
||||
AC_FIND_FUNC([localtime_r], [c], [#include <time.h>], [0, 0])
|
||||
AC_FIND_FUNC([localtime], [c], [#include <time.h>], [0])
|
||||
AC_FIND_FUNC([gettimeofday], [c], [#include <time.h>], [0, 0])
|
||||
AC_CHECK_MEMBER([struct tm.tm_gmtoff], [AC_DEFINE([HAVE_TM_TM_GMT_OFF],1,[Define to 1 if the system has the tm_gmt_off field in struct tm])],
|
||||
[], [[#include <time.h>]])
|
||||
@@ -175,65 +149,68 @@ if test $enable_pthread_tls = yes; then
|
||||
fi
|
||||
|
||||
dnl libm math.h functions
|
||||
AC_CHECK_MATH_FUNC(acos, [.5])
|
||||
AC_CHECK_MATH_FUNC(acosh, [.5])
|
||||
AC_CHECK_MATH_FUNC(asin, [.5])
|
||||
AC_CHECK_MATH_FUNC(asinh, [.5])
|
||||
AC_CHECK_MATH_FUNC(atan2, [.5,.5])
|
||||
AC_CHECK_MATH_FUNC(atan, [.5])
|
||||
AC_CHECK_MATH_FUNC(atanh, [.5])
|
||||
AC_CHECK_MATH_FUNC(cbrt, [.5])
|
||||
AC_CHECK_MATH_FUNC(ceil,[.5])
|
||||
AC_CHECK_MATH_FUNC(copysign,[.5,1.0])
|
||||
AC_CHECK_MATH_FUNC(cos, [.5])
|
||||
AC_CHECK_MATH_FUNC(cosh, [.5])
|
||||
AC_CHECK_MATH_FUNC(drem,[.5,1.0])
|
||||
AC_CHECK_MATH_FUNC(erf,[.5])
|
||||
AC_CHECK_MATH_FUNC(erfc,[.5])
|
||||
AC_CHECK_MATH_FUNC(exp10,[.5])
|
||||
AC_CHECK_MATH_FUNC(exp2, [.5])
|
||||
AC_CHECK_MATH_FUNC(exp, [.5])
|
||||
AC_CHECK_MATH_FUNC(expm1,[.5])
|
||||
AC_CHECK_MATH_FUNC(fabs,[.5])
|
||||
AC_CHECK_MATH_FUNC(fdim,[.5,1.0])
|
||||
AC_CHECK_MATH_FUNC(floor, [.5])
|
||||
AC_CHECK_MATH_FUNC(fma,[.5,1.0,1.5])
|
||||
AC_CHECK_MATH_FUNC(fmax,[.5,1.0])
|
||||
AC_CHECK_MATH_FUNC(fmin,[.5,1.0])
|
||||
AC_CHECK_MATH_FUNC(fmod,[.5,1.0])
|
||||
AC_CHECK_MATH_FUNC(frexp,[.5,1.0])
|
||||
AC_CHECK_MATH_FUNC(gamma,[.5])
|
||||
AC_CHECK_MATH_FUNC(hypot, [.5,.5])
|
||||
AC_CHECK_MATH_FUNC(j0, [.5])
|
||||
AC_CHECK_MATH_FUNC(j1, [.5])
|
||||
AC_CHECK_MATH_FUNC(ldexp,[.5,2])
|
||||
AC_CHECK_MATH_FUNC(lgamma,[.5])
|
||||
AC_CHECK_MATH_FUNC(log10, [.5])
|
||||
AC_CHECK_MATH_FUNC(log1p,[.5])
|
||||
AC_CHECK_MATH_FUNC(log2, [.5])
|
||||
AC_CHECK_MATH_FUNC(log, [.5])
|
||||
AC_CHECK_MATH_FUNC(logb,[.5])
|
||||
AC_CHECK_MATH_FUNC(modf,[.5,1.0])
|
||||
AC_CHECK_MATH_FUNC(nearbyint,[.5])
|
||||
AC_CHECK_MATH_FUNC(nextafter,[.5,1.0])
|
||||
AC_CHECK_MATH_FUNC(nexttoward,[.5,1.0])
|
||||
AC_CHECK_MATH_FUNC(pow10,[.5])
|
||||
AC_CHECK_MATH_FUNC(pow, [2,2])
|
||||
AC_CHECK_MATH_FUNC(remainder, [3,2])
|
||||
AC_CHECK_MATH_FUNC(rint,[.5])
|
||||
AC_CHECK_MATH_FUNC(round,[.5])
|
||||
AC_CHECK_MATH_FUNC(scalb,[.5,1.0])
|
||||
AC_CHECK_MATH_FUNC(scalbln,[.5,2])
|
||||
AC_CHECK_MATH_FUNC(significand,[.5])
|
||||
AC_CHECK_MATH_FUNC(sin, [.5])
|
||||
AC_CHECK_MATH_FUNC(sinh, [.5])
|
||||
AC_CHECK_MATH_FUNC(sqrt, [.5])
|
||||
AC_CHECK_MATH_FUNC(tan, [.5])
|
||||
AC_CHECK_MATH_FUNC(tanh, [.5])
|
||||
AC_CHECK_MATH_FUNC(tgamma, [.5])
|
||||
AC_CHECK_MATH_FUNC(trunc,[.5])
|
||||
AC_CHECK_MATH_FUNC(y0, [.5])
|
||||
AC_CHECK_MATH_FUNC(y1, [.5])
|
||||
AC_CHECK_MATH_FUNC(acos)
|
||||
AC_CHECK_MATH_FUNC(acosh)
|
||||
AC_CHECK_MATH_FUNC(asin)
|
||||
AC_CHECK_MATH_FUNC(asinh)
|
||||
AC_CHECK_MATH_FUNC(atan2)
|
||||
AC_CHECK_MATH_FUNC(atan)
|
||||
AC_CHECK_MATH_FUNC(atanh)
|
||||
AC_CHECK_MATH_FUNC(cbrt)
|
||||
AC_CHECK_MATH_FUNC(ceil)
|
||||
AC_CHECK_MATH_FUNC(copysign)
|
||||
AC_CHECK_MATH_FUNC(cos)
|
||||
AC_CHECK_MATH_FUNC(cosh)
|
||||
AC_CHECK_MATH_FUNC(drem)
|
||||
AC_CHECK_MATH_FUNC(erf)
|
||||
AC_CHECK_MATH_FUNC(erfc)
|
||||
AC_CHECK_MATH_FUNC(exp10)
|
||||
AC_CHECK_MATH_FUNC(exp2)
|
||||
AC_CHECK_MATH_FUNC(exp)
|
||||
AC_CHECK_MATH_FUNC(expm1)
|
||||
AC_CHECK_MATH_FUNC(fabs)
|
||||
AC_CHECK_MATH_FUNC(fdim)
|
||||
AC_CHECK_MATH_FUNC(floor)
|
||||
AC_CHECK_MATH_FUNC(fma)
|
||||
AC_CHECK_MATH_FUNC(fmax)
|
||||
AC_CHECK_MATH_FUNC(fmin)
|
||||
AC_CHECK_MATH_FUNC(fmod)
|
||||
AC_CHECK_MATH_FUNC(frexp)
|
||||
AC_CHECK_MATH_FUNC(gamma)
|
||||
AC_CHECK_MATH_FUNC(hypot)
|
||||
AC_CHECK_MATH_FUNC(j0)
|
||||
AC_CHECK_MATH_FUNC(j1)
|
||||
AC_CHECK_MATH_FUNC(jn)
|
||||
AC_CHECK_MATH_FUNC(ldexp)
|
||||
AC_CHECK_MATH_FUNC(lgamma)
|
||||
AC_CHECK_MATH_FUNC(log10)
|
||||
AC_CHECK_MATH_FUNC(log1p)
|
||||
AC_CHECK_MATH_FUNC(log2)
|
||||
AC_CHECK_MATH_FUNC(log)
|
||||
AC_CHECK_MATH_FUNC(logb)
|
||||
AC_CHECK_MATH_FUNC(modf)
|
||||
AC_CHECK_MATH_FUNC(lgamma_r)
|
||||
AC_CHECK_MATH_FUNC(nearbyint)
|
||||
AC_CHECK_MATH_FUNC(nextafter)
|
||||
AC_CHECK_MATH_FUNC(nexttoward)
|
||||
AC_CHECK_MATH_FUNC(pow10) # Not available with glibc version >= 2.27
|
||||
AC_CHECK_MATH_FUNC(pow)
|
||||
AC_CHECK_MATH_FUNC(remainder)
|
||||
AC_CHECK_MATH_FUNC(rint)
|
||||
AC_CHECK_MATH_FUNC(round)
|
||||
AC_CHECK_MATH_FUNC(scalb)
|
||||
AC_CHECK_MATH_FUNC(scalbln)
|
||||
AC_CHECK_MATH_FUNC(significand)
|
||||
AC_CHECK_MATH_FUNC(sin)
|
||||
AC_CHECK_MATH_FUNC(sinh)
|
||||
AC_CHECK_MATH_FUNC(sqrt)
|
||||
AC_CHECK_MATH_FUNC(tan)
|
||||
AC_CHECK_MATH_FUNC(tanh)
|
||||
AC_CHECK_MATH_FUNC(tgamma)
|
||||
AC_CHECK_MATH_FUNC(trunc)
|
||||
AC_CHECK_MATH_FUNC(y0)
|
||||
AC_CHECK_MATH_FUNC(y1)
|
||||
AC_CHECK_MATH_FUNC(yn)
|
||||
|
||||
dnl Thread local storage
|
||||
have___thread=no
|
||||
@@ -254,6 +231,51 @@ AC_C_BIGENDIAN(
|
||||
AC_MSG_ERROR(universial endianess not supported)
|
||||
)
|
||||
|
||||
dnl Oniguruma
|
||||
AC_ARG_WITH([oniguruma],
|
||||
[AS_HELP_STRING([--with-oniguruma=prefix],
|
||||
[try this for a non-standard install prefix of the oniguruma library])], ,
|
||||
[with_oniguruma=yes])
|
||||
|
||||
onig_CFLAGS=
|
||||
onig_LDFLAGS=
|
||||
build_oniguruma=no
|
||||
AS_IF([test "x$with_oniguruma" != xno], [
|
||||
save_CFLAGS="$CFLAGS"
|
||||
save_LDFLAGS="$LDFLAGS"
|
||||
AS_IF([test "x$with_oniguruma" != xyes], [
|
||||
AS_IF([test "x$with_oniguruma" = xbuiltin], [
|
||||
build_oniguruma=yes
|
||||
], [
|
||||
onig_CFLAGS="-I${with_oniguruma}/include"
|
||||
onig_LDFLAGS="-L${with_oniguruma}/lib"
|
||||
])
|
||||
])
|
||||
AS_IF([test "x$build_oniguruma" = xno], [
|
||||
# check for ONIGURUMA library, either in /usr or where requested
|
||||
CFLAGS="$CFLAGS $onig_CFLAGS"
|
||||
LDFLAGS="$LDFLAGS $onig_LDFLAGS"
|
||||
AC_CHECK_HEADER("oniguruma.h",
|
||||
AC_CHECK_LIB([onig],[onig_version]))
|
||||
# handle check results
|
||||
AS_IF([test "x$ac_cv_lib_onig_onig_version" != "xyes"], [
|
||||
build_oniguruma=yes
|
||||
AC_MSG_NOTICE([Oniguruma was not found. Will use the packaged oniguruma.])
|
||||
])
|
||||
])
|
||||
AS_IF([test "x$build_oniguruma" = xyes -a -f "${srcdir}/modules/oniguruma/configure.ac" ], [
|
||||
onig_CFLAGS="-I${srcdir}/modules/oniguruma/src"
|
||||
onig_LDFLAGS="-L${srcdir}/modules/oniguruma/src -Wl,-rpath,${libdir}"
|
||||
AC_CONFIG_SUBDIRS([modules/oniguruma])
|
||||
AC_DEFINE([HAVE_LIBONIG],1,[Define to 1 if the system includes libonig])
|
||||
])
|
||||
CFLAGS="$save_CFLAGS"
|
||||
LDFLAGS="$save_LDFLAGS"
|
||||
])
|
||||
AC_SUBST(onig_CFLAGS)
|
||||
AC_SUBST(onig_LDFLAGS)
|
||||
|
||||
AM_CONDITIONAL([BUILD_ONIGURUMA], [test "x$build_oniguruma" = xyes])
|
||||
AC_SUBST([BUNDLER], ["$bundle_cmd"])
|
||||
|
||||
AC_CONFIG_MACRO_DIR([config/m4])
|
||||
|
||||
@@ -1,6 +1,3 @@
|
||||
.DS_Store
|
||||
.sass-cache
|
||||
output/*
|
||||
|
||||
# Autogenerated from public/css/base.scss
|
||||
public/css/base.css
|
||||
@@ -1,7 +0,0 @@
|
||||
source "https://rubygems.org"
|
||||
|
||||
gem "rake"
|
||||
gem "bonsai"
|
||||
gem "maruku"
|
||||
gem "ronn"
|
||||
gem "mustache", "<1.0" # to support ruby <2.0
|
||||
@@ -1,63 +0,0 @@
|
||||
GEM
|
||||
remote: https://rubygems.org/
|
||||
specs:
|
||||
activesupport (4.2.3)
|
||||
i18n (~> 0.7)
|
||||
json (~> 1.7, >= 1.7.7)
|
||||
minitest (~> 5.1)
|
||||
thread_safe (~> 0.3, >= 0.3.4)
|
||||
tzinfo (~> 1.1)
|
||||
addressable (2.3.8)
|
||||
bonsai (1.4.9)
|
||||
activesupport (>= 3.0.3)
|
||||
builder (>= 3.0.0)
|
||||
i18n (>= 0.5.0)
|
||||
launchy (>= 0.3.7)
|
||||
liquid (>= 2.2.2)
|
||||
maruku (>= 0.6.0)
|
||||
rack
|
||||
sass
|
||||
sinatra (>= 1.0)
|
||||
tilt (>= 1.3)
|
||||
watch (>= 0.1.0)
|
||||
yui-compressor
|
||||
builder (3.2.2)
|
||||
hpricot (0.8.6)
|
||||
i18n (0.7.0)
|
||||
json (1.8.3)
|
||||
launchy (2.4.3)
|
||||
addressable (~> 2.3)
|
||||
liquid (3.0.6)
|
||||
maruku (0.7.2)
|
||||
minitest (5.8.0)
|
||||
mustache (0.99.8)
|
||||
rack (1.6.4)
|
||||
rack-protection (1.5.3)
|
||||
rack
|
||||
rake (10.4.2)
|
||||
rdiscount (2.1.8)
|
||||
ronn (0.7.3)
|
||||
hpricot (>= 0.8.2)
|
||||
mustache (>= 0.7.0)
|
||||
rdiscount (>= 1.5.8)
|
||||
sass (3.4.16)
|
||||
sinatra (1.4.6)
|
||||
rack (~> 1.4)
|
||||
rack-protection (~> 1.4)
|
||||
tilt (>= 1.3, < 3)
|
||||
thread_safe (0.3.5)
|
||||
tilt (2.0.1)
|
||||
tzinfo (1.2.2)
|
||||
thread_safe (~> 0.1)
|
||||
watch (0.1.0)
|
||||
yui-compressor (0.12.0)
|
||||
|
||||
PLATFORMS
|
||||
ruby
|
||||
|
||||
DEPENDENCIES
|
||||
bonsai
|
||||
maruku
|
||||
mustache (< 1.0)
|
||||
rake
|
||||
ronn
|
||||
@@ -0,0 +1,12 @@
|
||||
[[source]]
|
||||
name = "pypi"
|
||||
url = "https://pypi.org/simple"
|
||||
verify_ssl = true
|
||||
|
||||
[dev-packages]
|
||||
|
||||
[packages]
|
||||
jinja2 = "*"
|
||||
pyyaml = "*"
|
||||
markdown = "*"
|
||||
lxml = "*"
|
||||
Generated
+117
@@ -0,0 +1,117 @@
|
||||
{
|
||||
"_meta": {
|
||||
"hash": {
|
||||
"sha256": "16a9ef323592a417142c30be17e58c3cf36dc2bfdbf4757106a796b22262a1ce"
|
||||
},
|
||||
"pipfile-spec": 6,
|
||||
"requires": {},
|
||||
"sources": [
|
||||
{
|
||||
"name": "pypi",
|
||||
"url": "https://pypi.org/simple",
|
||||
"verify_ssl": true
|
||||
}
|
||||
]
|
||||
},
|
||||
"default": {
|
||||
"jinja2": {
|
||||
"hashes": [
|
||||
"sha256:74c935a1b8bb9a3947c50a54766a969d4846290e1e788ea44c1392163723c3bd",
|
||||
"sha256:f84be1bb0040caca4cea721fcbbbbd61f9be9464ca236387158b0feea01914a4"
|
||||
],
|
||||
"index": "pypi",
|
||||
"version": "==2.10"
|
||||
},
|
||||
"lxml": {
|
||||
"hashes": [
|
||||
"sha256:0537eee4902e8bf4f41bfee8133f7edf96533dd175930a12086d6a40d62376b2",
|
||||
"sha256:0562ec748abd230ab87d73384e08fa784f9b9cee89e28696087d2d22c052cc27",
|
||||
"sha256:09e91831e749fbf0f24608694e4573be0ef51430229450c39c83176cc2e2d353",
|
||||
"sha256:1ae4c0722fc70c0d4fba43ae33c2885f705e96dce1db41f75ae14a2d2749b428",
|
||||
"sha256:1c630c083d782cbaf1f7f37f6cac87bda9cff643cf2803a5f180f30d97955cef",
|
||||
"sha256:2fe74e3836bd8c0fa7467ffae05545233c7f37de1eb765cacfda15ad20c6574a",
|
||||
"sha256:37af783c2667ead34a811037bda56a0b142ac8438f7ed29ae93f82ddb812fbd6",
|
||||
"sha256:3f2d9eafbb0b24a33f56acd16f39fc935756524dcb3172892721c54713964c70",
|
||||
"sha256:47d8365a8ef14097aa4c65730689be51851b4ade677285a3b2daa03b37893e26",
|
||||
"sha256:510e904079bc56ea784677348e151e1156040dbfb736f1d8ea4b9e6d0ab2d9f4",
|
||||
"sha256:58d0851da422bba31c7f652a7e9335313cf94a641aa6d73b8f3c67602f75b593",
|
||||
"sha256:7940d5c2185ffb989203dacbb28e6ae88b4f1bb25d04e17f94b0edd82232bcbd",
|
||||
"sha256:7cf39bb3a905579836f7a8f3a45320d9eb22f16ab0c1e112efb940ced4d057a5",
|
||||
"sha256:9563a23c1456c0ab550c087833bc13fcc61013a66c6420921d5b70550ea312bf",
|
||||
"sha256:95b392952935947e0786a90b75cc33388549dcb19af716b525dae65b186138fc",
|
||||
"sha256:983129f3fd3cef5c3cf067adcca56e30a169656c00fcc6c648629dbb850b27fa",
|
||||
"sha256:a0b75b1f1854771844c647c464533def3e0a899dd094a85d1d4ed72ecaaee93d",
|
||||
"sha256:b5db89cc0ef624f3a81214b7961a99f443b8c91e88188376b6b322fd10d5b118",
|
||||
"sha256:c0a7751ba1a4bfbe7831920d98cee3ce748007eab8dfda74593d44079568219a",
|
||||
"sha256:c0c5a7d4aafcc30c9b6d8613a362567e32e5f5b708dc41bc3a81dac56f8af8bb",
|
||||
"sha256:d4d63d85eacc6cb37b459b16061e1f100d154bee89dc8d8f9a6128a5a538e92e",
|
||||
"sha256:da5e7e941d6e71c9c9a717c93725cda0708c2474f532e3680ac5e39ec57d224d",
|
||||
"sha256:dccad2b3c583f036f43f80ac99ee212c2fa9a45151358d55f13004d095e683b2",
|
||||
"sha256:df46307d39f2aeaafa1d25309b8a8d11738b73e9861f72d4d0a092528f498baa",
|
||||
"sha256:e70b5e1cb48828ddd2818f99b1662cb9226dc6f57d07fc75485405c77da17436",
|
||||
"sha256:ea825562b8cd057cbc9810d496b8b5dec37a1e2fc7b27bc7c1e72ce94462a09a"
|
||||
],
|
||||
"index": "pypi",
|
||||
"version": "==4.3.1"
|
||||
},
|
||||
"markdown": {
|
||||
"hashes": [
|
||||
"sha256:c00429bd503a47ec88d5e30a751e147dcb4c6889663cd3e2ba0afe858e009baa",
|
||||
"sha256:d02e0f9b04c500cde6637c11ad7c72671f359b87b9fe924b2383649d8841db7c"
|
||||
],
|
||||
"index": "pypi",
|
||||
"version": "==3.0.1"
|
||||
},
|
||||
"markupsafe": {
|
||||
"hashes": [
|
||||
"sha256:048ef924c1623740e70204aa7143ec592504045ae4429b59c30054cb31e3c432",
|
||||
"sha256:130f844e7f5bdd8e9f3f42e7102ef1d49b2e6fdf0d7526df3f87281a532d8c8b",
|
||||
"sha256:19f637c2ac5ae9da8bfd98cef74d64b7e1bb8a63038a3505cd182c3fac5eb4d9",
|
||||
"sha256:1b8a7a87ad1b92bd887568ce54b23565f3fd7018c4180136e1cf412b405a47af",
|
||||
"sha256:1c25694ca680b6919de53a4bb3bdd0602beafc63ff001fea2f2fc16ec3a11834",
|
||||
"sha256:1f19ef5d3908110e1e891deefb5586aae1b49a7440db952454b4e281b41620cd",
|
||||
"sha256:1fa6058938190ebe8290e5cae6c351e14e7bb44505c4a7624555ce57fbbeba0d",
|
||||
"sha256:31cbb1359e8c25f9f48e156e59e2eaad51cd5242c05ed18a8de6dbe85184e4b7",
|
||||
"sha256:3e835d8841ae7863f64e40e19477f7eb398674da6a47f09871673742531e6f4b",
|
||||
"sha256:4e97332c9ce444b0c2c38dd22ddc61c743eb208d916e4265a2a3b575bdccb1d3",
|
||||
"sha256:525396ee324ee2da82919f2ee9c9e73b012f23e7640131dd1b53a90206a0f09c",
|
||||
"sha256:52b07fbc32032c21ad4ab060fec137b76eb804c4b9a1c7c7dc562549306afad2",
|
||||
"sha256:52ccb45e77a1085ec5461cde794e1aa037df79f473cbc69b974e73940655c8d7",
|
||||
"sha256:5c3fbebd7de20ce93103cb3183b47671f2885307df4a17a0ad56a1dd51273d36",
|
||||
"sha256:5e5851969aea17660e55f6a3be00037a25b96a9b44d2083651812c99d53b14d1",
|
||||
"sha256:5edfa27b2d3eefa2210fb2f5d539fbed81722b49f083b2c6566455eb7422fd7e",
|
||||
"sha256:7d263e5770efddf465a9e31b78362d84d015cc894ca2c131901a4445eaa61ee1",
|
||||
"sha256:83381342bfc22b3c8c06f2dd93a505413888694302de25add756254beee8449c",
|
||||
"sha256:857eebb2c1dc60e4219ec8e98dfa19553dae33608237e107db9c6078b1167856",
|
||||
"sha256:98e439297f78fca3a6169fd330fbe88d78b3bb72f967ad9961bcac0d7fdd1550",
|
||||
"sha256:bf54103892a83c64db58125b3f2a43df6d2cb2d28889f14c78519394feb41492",
|
||||
"sha256:d9ac82be533394d341b41d78aca7ed0e0f4ba5a2231602e2f05aa87f25c51672",
|
||||
"sha256:e982fe07ede9fada6ff6705af70514a52beb1b2c3d25d4e873e82114cf3c5401",
|
||||
"sha256:edce2ea7f3dfc981c4ddc97add8a61381d9642dc3273737e756517cc03e84dd6",
|
||||
"sha256:efdc45ef1afc238db84cb4963aa689c0408912a0239b0721cb172b4016eb31d6",
|
||||
"sha256:f137c02498f8b935892d5c0172560d7ab54bc45039de8805075e19079c639a9c",
|
||||
"sha256:f82e347a72f955b7017a39708a3667f106e6ad4d10b25f237396a7115d8ed5fd",
|
||||
"sha256:fb7c206e01ad85ce57feeaaa0bf784b97fa3cad0d4a5737bc5295785f5c613a1"
|
||||
],
|
||||
"version": "==1.1.0"
|
||||
},
|
||||
"pyyaml": {
|
||||
"hashes": [
|
||||
"sha256:3d7da3009c0f3e783b2c873687652d83b1bbfd5c88e9813fb7e5b03c0dd3108b",
|
||||
"sha256:3ef3092145e9b70e3ddd2c7ad59bdd0252a94dfe3949721633e41344de00a6bf",
|
||||
"sha256:40c71b8e076d0550b2e6380bada1f1cd1017b882f7e16f09a65be98e017f211a",
|
||||
"sha256:558dd60b890ba8fd982e05941927a3911dc409a63dcb8b634feaa0cda69330d3",
|
||||
"sha256:a7c28b45d9f99102fa092bb213aa12e0aaf9a6a1f5e395d36166639c1f96c3a1",
|
||||
"sha256:aa7dd4a6a427aed7df6fb7f08a580d68d9b118d90310374716ae90b710280af1",
|
||||
"sha256:bc558586e6045763782014934bfaf39d48b8ae85a2713117d16c39864085c613",
|
||||
"sha256:d46d7982b62e0729ad0175a9bc7e10a566fc07b224d2c79fafb5e032727eaa04",
|
||||
"sha256:d5eef459e30b09f5a098b9cea68bebfeb268697f78d647bd255a085371ac7f3f",
|
||||
"sha256:e01d3203230e1786cd91ccfdc8f8454c8069c91bee3962ad93b87a4b2860f537",
|
||||
"sha256:e170a9e6fcfd19021dd29845af83bb79236068bf5fd4df3327c1be18182b2531"
|
||||
],
|
||||
"index": "pypi",
|
||||
"version": "==3.13"
|
||||
}
|
||||
},
|
||||
"develop": {}
|
||||
}
|
||||
+10
-15
@@ -2,24 +2,19 @@ Documentation
|
||||
=============
|
||||
|
||||
The jq website, manpages and some of the tests are generated from this
|
||||
directory. The directory holds a [Bonsai](http://tinytree.info)
|
||||
website, and the manual is a YAML file in `content/3.manual`.
|
||||
directory. The manual is a YAML file in `content/manual`.
|
||||
|
||||
To build the documentation (including building the jq manpage), you'll
|
||||
need a working Ruby setup. The easiest way to get one is to install
|
||||
RVM and Ruby 1.9.3 like so:
|
||||
need python3 and pipenv. You can install pipenv like so:
|
||||
|
||||
\curl -L https://get.rvm.io | bash -s stable --ruby=1.9.3
|
||||
pip install pipenv
|
||||
|
||||
After that finishes installing, you'll need to make sure RVM is on
|
||||
your path by doing `source $HOME/.rvm/scripts/rvm`, or just opening a
|
||||
new shell. See <https://rvm.io> for more info on RVM.
|
||||
Though, you may need to say pip3 instead, depending on your system. Once
|
||||
you have pipenv installed, you can install the dependencies by running
|
||||
`pipenv install` from the `docs` directory.
|
||||
|
||||
Once RVM is installed, you can install all the dependencies for jq's
|
||||
documentation build by running this from the `docs` directory:
|
||||
Once this is done, rerun `./configure` in the jq root directory and then
|
||||
the Makefile will be able to generate the jq manpage.
|
||||
|
||||
bundle install
|
||||
|
||||
When bundle manages to install the dependencies, rerun `./configure`
|
||||
in the jq root directory and then the Makefile will be able to
|
||||
generate the jq manpage.
|
||||
To build the website, run `pipenv run ./build_website.py` from inside
|
||||
the `docs` directory.
|
||||
@@ -1,24 +0,0 @@
|
||||
current_dir = File.dirname(__FILE__)
|
||||
rakefile_manual = File.expand_path(File.join(current_dir, "Rakefile.manual"))
|
||||
rakefile_website = File.expand_path(File.join(current_dir, "Rakefile.website"))
|
||||
|
||||
desc "Build the manpage from the bonsai source of the manual"
|
||||
task :manpage do
|
||||
system %(#{$0} -f #{rakefile_manual} manpage)
|
||||
end
|
||||
|
||||
desc "Collect jq unit test cases from the bonsai source of the manual"
|
||||
task :mantests do
|
||||
system %(#{$0} -f #{rakefile_manual} mantests)
|
||||
end
|
||||
|
||||
desc "Build the website from the bonsai sources"
|
||||
task :build do
|
||||
system %(#{$0} -f #{rakefile_website} build)
|
||||
end
|
||||
|
||||
desc "Serve a live view of the website on http://localhost:5000/jq/"
|
||||
task :serve do
|
||||
system %(#{$0} -f #{rakefile_website} serve)
|
||||
end
|
||||
|
||||
@@ -1,49 +0,0 @@
|
||||
require 'ronn'
|
||||
require 'tempfile'
|
||||
require 'yaml'
|
||||
|
||||
def load_manual
|
||||
YAML::load(File.open("content/3.manual/manual.yml"))
|
||||
end
|
||||
|
||||
task :manpage do
|
||||
Tempfile.open "manpage" do |f|
|
||||
manual = load_manual
|
||||
f.puts manual['manpage_intro']
|
||||
f.puts manual['body']
|
||||
manual['sections'].each do |section|
|
||||
|
||||
f.puts "## #{section['title'].upcase}\n"
|
||||
f.puts section['body']
|
||||
f.puts ""
|
||||
(section['entries'] || []).each do |entry|
|
||||
f.puts "### #{entry['title']}\n"
|
||||
f.puts entry['body']
|
||||
f.puts ""
|
||||
(entry['examples'] || []).each do |example|
|
||||
f.puts " jq '#{example['program']}'"
|
||||
f.puts " #{example['input']}"
|
||||
f.puts " => #{example['output'].join(", ")}"
|
||||
f.puts
|
||||
end
|
||||
end
|
||||
f.puts ""
|
||||
end
|
||||
f.puts manual['manpage_epilogue']
|
||||
f.close
|
||||
puts Ronn::Document.new(f.path).convert('roff').gsub(/<\/?code>/,"")
|
||||
end
|
||||
end
|
||||
|
||||
task :mantests do
|
||||
load_manual['sections'].each do |section|
|
||||
(section['entries'] || []).each do |entry|
|
||||
(entry['examples'] || []).each do |example|
|
||||
puts example['program'].gsub("\n", " ")
|
||||
puts example['input']
|
||||
example['output'].each do |s| puts s end
|
||||
puts
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -1,66 +0,0 @@
|
||||
require 'bonsai'
|
||||
require 'json'
|
||||
require 'liquid'
|
||||
require 'maruku'
|
||||
|
||||
module ExtraFilters
|
||||
def markdownify(input)
|
||||
Maruku.new(input).to_html
|
||||
end
|
||||
def search_id(input)
|
||||
input.gsub(/`/, '')
|
||||
end
|
||||
def section_id(input)
|
||||
input.gsub(/[^a-zA-Z0-9_]/, '')
|
||||
end
|
||||
def entry_id(input)
|
||||
input.gsub(/[ `]/, '')
|
||||
end
|
||||
def no_paragraph(input)
|
||||
input.gsub('<p>', '').gsub('</p>', '')
|
||||
end
|
||||
def json(input)
|
||||
input.to_json
|
||||
end
|
||||
def unique(input)
|
||||
@n = (@n || 0) + 1
|
||||
input + @n.to_s
|
||||
end
|
||||
end
|
||||
|
||||
Liquid::Template.register_filter(ExtraFilters)
|
||||
|
||||
task :build do
|
||||
Bonsai.root_dir = Dir.pwd
|
||||
Bonsai::Exporter.publish!
|
||||
end
|
||||
|
||||
task :serve do
|
||||
begin
|
||||
Bonsai.log "Press Control+C to quit"
|
||||
|
||||
require 'rack'
|
||||
require 'sinatra'
|
||||
require 'watch'
|
||||
require 'launchy'
|
||||
|
||||
Bonsai.root_dir = Dir.pwd
|
||||
|
||||
server = fork {
|
||||
app = Rack::Builder.app {
|
||||
map "/jq" do
|
||||
use Bonsai::StaticPassThrough, :root => Bonsai.root_dir + "/output", :urls => ["/"]
|
||||
end
|
||||
run Bonsai::DevelopmentServer
|
||||
}
|
||||
Rack::Handler.default.run(app, :Port => 5000) do
|
||||
Launchy.open("http://localhost:5000/jq/")
|
||||
end
|
||||
}
|
||||
Watch.new("{content,templates,public}/**/*") { Bonsai::Exporter.process! }
|
||||
rescue Interrupt
|
||||
Process.kill("QUIT", server)
|
||||
Process.wait(server)
|
||||
exit
|
||||
end
|
||||
end
|
||||
Executable
+235
@@ -0,0 +1,235 @@
|
||||
#!/usr/bin/env python3
|
||||
from datetime import date
|
||||
from io import StringIO
|
||||
from lxml import etree
|
||||
import markdown
|
||||
from markdown.extensions import Extension
|
||||
import re
|
||||
import sys
|
||||
import yaml
|
||||
|
||||
# Prevent our markdown parser from trying to help by interpreting things in angle brackets as HTML tags.
|
||||
class EscapeHtml(Extension):
|
||||
def extendMarkdown(self, md, md_globals):
|
||||
del md.preprocessors['html_block']
|
||||
del md.inlinePatterns['html']
|
||||
|
||||
class RoffWalker(object):
|
||||
def __init__(self, tree, output=sys.stdout):
|
||||
self.tree = tree
|
||||
self.target = output
|
||||
self.f = StringIO()
|
||||
|
||||
def walk(self):
|
||||
self._walk(self.tree, parent_tag=None)
|
||||
# We don't want to start lines with \. because that can confuse man
|
||||
# For lines that start with \., we need to prefix them with \& so it
|
||||
# knows not to treat that line as a directive
|
||||
data = re.sub(r'^\\\.', r'\&.', self.f.getvalue(), flags=re.MULTILINE)
|
||||
self.target.write(data)
|
||||
|
||||
def _ul_is_special(self, root):
|
||||
if len(root) != 1:
|
||||
return False
|
||||
child = root[0]
|
||||
if child.tag != 'li':
|
||||
return False
|
||||
msg = ''.join(child.itertext()).strip()
|
||||
return msg.endswith(':')
|
||||
|
||||
def _walk_child(self, root):
|
||||
if len(root) > 0:
|
||||
self._walk(root[0], parent_tag=root.tag)
|
||||
|
||||
def _write_element(self, root, ensure_newline=True):
|
||||
if root.text is not None:
|
||||
text = self._sanitize(root.text)
|
||||
self.__write_raw(text)
|
||||
self._walk_child(root)
|
||||
self._write_tail(root, ensure_newline=ensure_newline)
|
||||
|
||||
def _write_tail(self, root, ensure_newline=False, inline=False):
|
||||
if root.tail is not None:
|
||||
if inline or root.tail != '\n':
|
||||
text = self._sanitize(root.tail)
|
||||
if text.endswith('\n'):
|
||||
ensure_newline = False
|
||||
self.__write_raw(text)
|
||||
if ensure_newline:
|
||||
self.__write_raw('\n')
|
||||
|
||||
def _walk(self, root, parent_tag=None):
|
||||
last_tag = None
|
||||
while root is not None:
|
||||
if root.tag == 'h1':
|
||||
self.__write_cmd('.TH "JQ" "1" "{}" "" ""'.format(date.today().strftime('%B %Y')))
|
||||
self.__write_cmd('.SH "NAME"')
|
||||
# TODO: properly parse this
|
||||
self.__write_raw(r'\fBjq\fR \- Command\-line JSON processor' + "\n")
|
||||
|
||||
elif root.tag == 'h2':
|
||||
self.__write_cmd('.SH "{}"'.format(''.join(root.itertext()).strip()))
|
||||
|
||||
elif root.tag == 'h3':
|
||||
text = ''.join(root.itertext()).strip()
|
||||
self.__write_cmd('.SS "{}"'.format(self._h3_sanitize(text)))
|
||||
|
||||
elif root.tag == 'p':
|
||||
if last_tag not in ['h2', 'h3'] and parent_tag not in ['li']:
|
||||
self.__write_cmd('.P')
|
||||
self._write_element(root, ensure_newline=(parent_tag != 'li'))
|
||||
|
||||
elif root.tag == 'ul':
|
||||
if self._ul_is_special(root):
|
||||
li = root[0]
|
||||
self.__write_cmd('.TP')
|
||||
self._write_element(li)
|
||||
next = root.getnext()
|
||||
while next is not None and next.tag == 'p':
|
||||
if next.getnext() is not None and next.getnext().tag == 'pre':
|
||||
# we don't want to .IP these, because it'll look funny with the code indent
|
||||
break
|
||||
self.__write_cmd('.IP')
|
||||
self._write_element(next)
|
||||
root = next
|
||||
next = root.getnext()
|
||||
else:
|
||||
self._walk_child(root)
|
||||
self._write_tail(root)
|
||||
# A pre tag after the end of a list doesn't want two of the indentation commands
|
||||
if root.getnext() is None or root.getnext().tag != 'pre':
|
||||
self.__write_cmd('.IP "" 0')
|
||||
|
||||
elif root.tag == 'li':
|
||||
self.__write_cmd(r'.IP "\(bu" 4')
|
||||
if root.text is not None and root.text.strip() != '':
|
||||
text = self._sanitize(root.text)
|
||||
self.__write_raw(text)
|
||||
self._walk_child(root)
|
||||
self._write_tail(root, ensure_newline=True)
|
||||
|
||||
elif root.tag == 'strong':
|
||||
if root.text is not None:
|
||||
text = self._sanitize(root.text)
|
||||
self.__write_raw('\\fB{}\\fR'.format(text))
|
||||
|
||||
self._write_tail(root, inline=True)
|
||||
|
||||
elif root.tag == 'em':
|
||||
if root.text is not None:
|
||||
text = self._sanitize(root.text)
|
||||
self.__write_raw('\\fI{}\\fR'.format(text))
|
||||
self._write_tail(root, inline=True)
|
||||
|
||||
elif root.tag == 'code':
|
||||
if root.text is not None:
|
||||
text = self._code_sanitize(root.text)
|
||||
self.__write_raw('\\fB{}\\fR'.format(text))
|
||||
self._write_tail(root, inline=True)
|
||||
|
||||
elif root.tag == 'pre':
|
||||
self.__write_cmd('.IP "" 4')
|
||||
self.__write_cmd('.nf\n') # extra newline for spacing reasons
|
||||
next = root
|
||||
first = True
|
||||
while next is not None and next.tag == 'pre':
|
||||
if not first:
|
||||
self.__write_raw('\n')
|
||||
text = ''.join(next.itertext(with_tail=False))
|
||||
self.__write_raw(self._pre_sanitize(text))
|
||||
first = False
|
||||
root = next
|
||||
next = next.getnext()
|
||||
self.__write_cmd('.fi')
|
||||
self.__write_cmd('.IP "" 0')
|
||||
|
||||
else:
|
||||
self._walk_child(root)
|
||||
|
||||
last_tag = root.tag
|
||||
root = root.getnext()
|
||||
|
||||
def _base_sanitize(self, text):
|
||||
text = re.sub(r'\\', r'\\e', text)
|
||||
text = re.sub(r'\.', r'\\.', text)
|
||||
text = re.sub("'", r"\'", text)
|
||||
text = re.sub('-', r'\-', text)
|
||||
return text
|
||||
|
||||
def _pre_sanitize(self, text):
|
||||
return self._base_sanitize(text)
|
||||
|
||||
def _code_sanitize(self, text):
|
||||
text = self._base_sanitize(text)
|
||||
text = re.sub(r'\s', ' ', text)
|
||||
return text
|
||||
|
||||
def _h3_sanitize(self, text):
|
||||
text = self._base_sanitize(text)
|
||||
text = re.sub(' \n|\n ', ' ', text)
|
||||
text = re.sub('\n', ' ', text)
|
||||
return text
|
||||
|
||||
def _sanitize(self, text):
|
||||
text = self._base_sanitize(text)
|
||||
text = re.sub(r'<([^>]+)>', r'\\fI\1\\fR', text)
|
||||
text = re.sub(r' +', ' ', text)
|
||||
text = re.sub('\n', ' ', text)
|
||||
return text
|
||||
|
||||
def __write_cmd(self, dat):
|
||||
print('.', dat, sep='\n', file=self.f)
|
||||
pass
|
||||
|
||||
def __write_raw(self, dat):
|
||||
print(dat, sep='', end='', file=self.f)
|
||||
pass
|
||||
|
||||
def load_yml_file(fn):
|
||||
with open(fn) as f:
|
||||
return yaml.load(f)
|
||||
|
||||
def dedent_body(body):
|
||||
lines = [re.sub(r'^ (\S)', r'\1', l) for l in body.split('\n')]
|
||||
return '\n'.join(lines)
|
||||
|
||||
def convert_manual_to_markdown():
|
||||
f = StringIO()
|
||||
manual = load_yml_file("content/manual/manual.yml")
|
||||
f.write(manual.get('manpage_intro', '\n'))
|
||||
f.write(dedent_body(manual.get('body', '\n')))
|
||||
for section in manual.get('sections', []):
|
||||
f.write('## {}\n'.format(section.get('title', '').upper()))
|
||||
f.write(dedent_body(section.get('body', '\n')))
|
||||
f.write('\n')
|
||||
for entry in section.get('entries', []):
|
||||
f.write('### {}\n'.format(entry.get('title', '')))
|
||||
f.write(dedent_body(entry.get('body', '\n')))
|
||||
f.write('\n')
|
||||
if entry.get('examples') is not None:
|
||||
f.write("~~~~\n")
|
||||
first = True
|
||||
for example in entry.get('examples'):
|
||||
if not first:
|
||||
f.write('\n')
|
||||
f.write("jq '{}'\n".format(example.get('program', '')))
|
||||
f.write(" {}\n".format(example.get('input', '')))
|
||||
output = [str(x) for x in example.get('output', [])]
|
||||
f.write("=> {}\n".format(', '.join(output)))
|
||||
first = False
|
||||
f.write("~~~~\n")
|
||||
f.write('\n')
|
||||
f.write(manual.get('manpage_epilogue', ''))
|
||||
return f.getvalue()
|
||||
|
||||
# Convert manual.yml to our special markdown format
|
||||
markdown_data = convert_manual_to_markdown()
|
||||
|
||||
# Convert markdown to html
|
||||
html_data = markdown.markdown(markdown_data, extensions=[EscapeHtml(), 'fenced_code'])
|
||||
|
||||
# Parse the html into a tree so we can walk it
|
||||
tr = etree.HTML(html_data, etree.HTMLParser())
|
||||
|
||||
# Convert the markdown to ROFF
|
||||
RoffWalker(tr).walk()
|
||||
Executable
+13
@@ -0,0 +1,13 @@
|
||||
#!/usr/bin/env python3
|
||||
import yaml
|
||||
|
||||
with open("content/manual/manual.yml") as f:
|
||||
manual = yaml.load(f)
|
||||
for section in manual.get('sections', []):
|
||||
for entry in section.get('entries', []):
|
||||
for example in entry.get('examples', []):
|
||||
print(example.get('program', '').replace('\n', ' '))
|
||||
print(example.get('input', ''))
|
||||
for s in example.get('output', []):
|
||||
print(s)
|
||||
print('')
|
||||
Executable
+62
@@ -0,0 +1,62 @@
|
||||
#!/usr/bin/env python3
|
||||
import glob
|
||||
import itertools
|
||||
from jinja2 import Environment, FileSystemLoader, Markup, select_autoescape, contextfunction
|
||||
from markdown import markdown
|
||||
import os
|
||||
import os.path
|
||||
import re
|
||||
import shutil
|
||||
import yaml
|
||||
|
||||
env = Environment(
|
||||
loader=FileSystemLoader('templates'),
|
||||
autoescape=select_autoescape(['html.j2']),
|
||||
)
|
||||
|
||||
def load_yml_file(fn):
|
||||
with open(fn) as f:
|
||||
return yaml.load(f)
|
||||
|
||||
env.filters['search_id'] = lambda input: input.replace(r'`', '')
|
||||
env.filters['section_id'] = lambda input: re.sub(r"[^a-zA-Z0-9_]", '', input)
|
||||
env.filters['entry_id'] = lambda input: re.sub(r"[ `]", '', input)
|
||||
env.filters['markdownify'] = lambda input: Markup(markdown(input))
|
||||
env.filters['no_paragraph'] = lambda input: Markup(re.sub(r"</?p>", '', input))
|
||||
|
||||
env.globals['unique_id'] = contextfunction(lambda ctx: str(next(ctx['unique_ctr'])))
|
||||
|
||||
|
||||
env.globals.update(load_yml_file('site.yml'))
|
||||
|
||||
|
||||
env.globals['navigation'] = ['tutorial', 'download', 'manual']
|
||||
|
||||
def generate_file(env, fname='content/1.tutorial/default.yml'):
|
||||
path, base = os.path.split(fname)
|
||||
path = os.path.relpath(path, 'content')
|
||||
output_dir = os.path.join('output', path)
|
||||
output_path = os.path.join(output_dir, 'index.html')
|
||||
|
||||
template_name = re.sub(r".yml$", '.html.j2', base)
|
||||
|
||||
ctx = load_yml_file(fname)
|
||||
ctx.update(unique_ctr=itertools.count(1), permalink=path)
|
||||
os.makedirs(output_dir, exist_ok=True)
|
||||
env.get_template(template_name).stream(ctx).dump(output_path, encoding='utf-8')
|
||||
|
||||
|
||||
def copy_public_files(root=''):
|
||||
for f in os.scandir(os.path.join('public', root)):
|
||||
src = os.path.join(root, f.name)
|
||||
dst = os.path.join('output', src)
|
||||
if f.is_dir():
|
||||
os.makedirs(dst, exist_ok=True)
|
||||
copy_public_files(src)
|
||||
else:
|
||||
shutil.copyfile(f.path, dst)
|
||||
|
||||
copy_public_files()
|
||||
|
||||
for fn in glob.glob('content/**/*.yml', recursive=True):
|
||||
generate_file(env, fn)
|
||||
@@ -31,6 +31,11 @@ body:
|
||||
[Arch](https://www.archlinux.org/packages/?sort=&q=jq&maintainer=&flagged=)
|
||||
repository. Install using `sudo pacman -Sy jq`.
|
||||
|
||||
* jq 1.6 binaries for
|
||||
[64-bit](https://github.com/stedolan/jq/releases/download/jq-1.6/jq-linux64)
|
||||
or
|
||||
[32-bit](https://github.com/stedolan/jq/releases/download/jq-1.6/jq-linux32).
|
||||
|
||||
* jq 1.5 binaries for
|
||||
[64-bit](https://github.com/stedolan/jq/releases/download/jq-1.5/jq-linux64)
|
||||
or
|
||||
@@ -51,6 +56,9 @@ body:
|
||||
* Use [Homebrew](http://brew.sh/) to install jq 1.5 with
|
||||
`brew install jq`.
|
||||
|
||||
* jq 1.6 binary for
|
||||
[64-bit](https://github.com/stedolan/jq/releases/download/jq-1.6/jq-osx-amd64).
|
||||
|
||||
* jq 1.5 binary for
|
||||
[64-bit](https://github.com/stedolan/jq/releases/download/jq-1.5/jq-osx-amd64).
|
||||
|
||||
@@ -66,8 +74,13 @@ body:
|
||||
|
||||
### FreeBSD
|
||||
|
||||
* Use [FreshPorts](https://www.freshports.org/textproc/jq/) to install
|
||||
jq 1.4 with `pkg install jq`.
|
||||
* `pkg install jq` as root installs a pre-built
|
||||
[binary package](https://www.freebsd.org/doc/en_US.ISO8859-1/books/handbook/pkgng-intro.html).
|
||||
|
||||
* `make -C /usr/ports/textproc/jq install clean` as root installs the
|
||||
[jq](https://www.freshports.org/textproc/jq/)
|
||||
[port](https://www.freebsd.org/doc/en_US.ISO8859-1/books/handbook/ports-using.html)
|
||||
from source.
|
||||
|
||||
### Solaris
|
||||
|
||||
@@ -84,6 +97,11 @@ body:
|
||||
* Use [Chocolatey NuGet](https://chocolatey.org/) to install jq 1.5 with
|
||||
`chocolatey install jq`.
|
||||
|
||||
* jq 1.6 executables for
|
||||
[64-bit](https://github.com/stedolan/jq/releases/download/jq-1.6/jq-win64.exe)
|
||||
or
|
||||
[32-bit](https://github.com/stedolan/jq/releases/download/jq-1.6/jq-win32.exe).
|
||||
|
||||
* jq 1.5 executables for
|
||||
[64-bit](https://github.com/stedolan/jq/releases/download/jq-1.5/jq-win64.exe)
|
||||
or
|
||||
@@ -104,6 +122,8 @@ body:
|
||||
SHA-256 checksums are provided for all release and pre-release binaries.
|
||||
They can be found under
|
||||
[sig/v1.x/sha256sum.txt](https://github.com/stedolan/jq/tree/master/sig).
|
||||
The checksums for jq 1.6 are in
|
||||
[sig/v1.6/sha256sum.txt](https://raw.githubusercontent.com/stedolan/jq/master/sig/v1.6/sha256sum.txt).
|
||||
The checksums for jq 1.5 are in
|
||||
[sig/v1.5/sha256sum.txt](https://raw.githubusercontent.com/stedolan/jq/master/sig/v1.5/sha256sum.txt).
|
||||
|
||||
@@ -111,6 +131,8 @@ body:
|
||||
[jq Package Signing Key](https://raw.githubusercontent.com/stedolan/jq/master/sig/jq-release.key).
|
||||
The signatures can be found under
|
||||
[sig/v1.x/\*.asc](https://github.com/stedolan/jq/tree/master/sig).
|
||||
The signatures for jq 1.6 are in
|
||||
[sig/v1.5/\*.asc](https://github.com/stedolan/jq/tree/master/sig/v1.6).
|
||||
The signatures for jq 1.5 are in
|
||||
[sig/v1.5/\*.asc](https://github.com/stedolan/jq/tree/master/sig/v1.5).
|
||||
You can use [GnuPG](https://gnupg.org/) to verify a signature by downloading
|
||||
@@ -118,6 +140,7 @@ body:
|
||||
|
||||
### From source on Linux, OS X, Cygwin, and other POSIX-like operating systems
|
||||
|
||||
* [Source tarball for jq 1.6](https://github.com/stedolan/jq/releases/download/jq-1.6/jq-1.6.tar.gz)
|
||||
* [Source tarball for jq 1.5](https://github.com/stedolan/jq/releases/download/jq-1.5/jq-1.5.tar.gz)
|
||||
|
||||
You can build it using the usual `./configure && make && sudo
|
||||
@@ -157,14 +180,13 @@ body:
|
||||
|
||||
#### Building the documentation
|
||||
|
||||
jq's documentation is compiled into static HTML using
|
||||
[Bonsai](http://www.tinytree.info). To view the documentation
|
||||
locally, run `rake serve` (or `bundle exec rake serve`) from the
|
||||
docs/ subdirectory. To build the docs just `rake build` from the
|
||||
docs subdirectory. You'll need a few Ruby dependencies, which can
|
||||
be installed by following the instructions in `docs/README.md`.
|
||||
jq's documentation is compiled into static HTML using Python.
|
||||
To build the docs, run `pipenv run python3 build_website.py` from
|
||||
the docs/ subdirectory. To serve them locally, you can run
|
||||
`python3 -m SimpleHTTPServer`. You'll need a few Python dependencies,
|
||||
which can be installed by following the instructions in `docs/README.md`.
|
||||
|
||||
The man page is built by `make jq.1`, or just `make`, also from
|
||||
the YAML docs, and you'll still need the Ruby dependencies to
|
||||
the YAML docs, and you'll still need the Python dependencies to
|
||||
build the manpage.
|
||||
|
||||
@@ -33,6 +33,12 @@ tail: |
|
||||
[Freenode](https://webchat.freenode.net/).
|
||||
|
||||
news:
|
||||
- date: 1 November 2018
|
||||
body: |
|
||||
jq 1.6 released. See installation options on the [download](/jq/download/)
|
||||
page, and the [release notes](https://github.com/stedolan/jq/releases/tag/jq-1.6)
|
||||
for details.
|
||||
|
||||
- date: 15 August 2015
|
||||
body: |
|
||||
|
||||
File diff suppressed because it is too large.
Load diff
@@ -441,7 +441,7 @@ sections:
|
||||
input: 5
|
||||
output: [6]
|
||||
|
||||
- title: `length`
|
||||
- title: '`length`'
|
||||
body: |
|
||||
|
||||
The builtin function `length` gets the length of various
|
||||
@@ -462,7 +462,7 @@ sections:
|
||||
input: '[[1,2], "string", {"a":2}, null]'
|
||||
output: [2, 6, 1, 0]
|
||||
|
||||
- title: `keys`
|
||||
- title: '`keys`'
|
||||
body: |
|
||||
|
||||
The builtin function `keys`, when given an object, returns
|
||||
@@ -485,7 +485,7 @@ sections:
|
||||
input: '[42,3,35]'
|
||||
output: ['[0,1,2]']
|
||||
|
||||
- title: `has`
|
||||
- title: '`has`'
|
||||
body: |
|
||||
|
||||
The builtin function `has` returns whether the input object
|
||||
@@ -504,7 +504,7 @@ sections:
|
||||
input: '[[0,1], ["a","b","c"]]'
|
||||
output: ['[false, true]']
|
||||
|
||||
- title: `to_entries`, `from_entries`, `with_entries`
|
||||
- title: '`to_entries`, `from_entries`, `with_entries`'
|
||||
body: |
|
||||
|
||||
These functions convert between an object and an array of
|
||||
@@ -529,14 +529,14 @@ sections:
|
||||
output: ['{"KEY_a": 1, "KEY_b": 2}']
|
||||
|
||||
|
||||
- title: `select`
|
||||
- title: '`select`'
|
||||
body: |
|
||||
|
||||
The function `select(foo)` produces its input unchanged if
|
||||
`foo` returns true for that input, and produces no output
|
||||
otherwise.
|
||||
|
||||
It's useful for filtering lists: `[1,2,3] | map(select(. >= 2))`
|
||||
It's useful for filtering lists: '`[1,2,3] | map(select(. >= 2))`'
|
||||
will give you `[3]`.
|
||||
|
||||
examples:
|
||||
@@ -544,7 +544,7 @@ sections:
|
||||
input: '[1,5,3,0,7]'
|
||||
output: ['[5,3,7]']
|
||||
|
||||
- title: `empty`
|
||||
- title: '`empty`'
|
||||
body: |
|
||||
|
||||
`empty` returns no results. None at all. Not even `null`.
|
||||
@@ -559,7 +559,7 @@ sections:
|
||||
input: 'null'
|
||||
output: ['[1,2,3]']
|
||||
|
||||
- title: `map(x)`
|
||||
- title: '`map(x)`'
|
||||
body: |
|
||||
|
||||
For any filter `x`, `map(x)` will run that filter for each
|
||||
@@ -574,7 +574,7 @@ sections:
|
||||
input: '[1,2,3]'
|
||||
output: ['[2,3,4]']
|
||||
|
||||
- title: `add`
|
||||
- title: '`add`'
|
||||
body: |
|
||||
|
||||
The filter `add` takes as input an array, and produces as
|
||||
@@ -596,7 +596,7 @@ sections:
|
||||
input: '[]'
|
||||
output: ["null"]
|
||||
|
||||
- title: `range`
|
||||
- title: '`range`'
|
||||
body: |
|
||||
|
||||
The `range` function produces a range of numbers. `range(4;10)`
|
||||
@@ -612,7 +612,7 @@ sections:
|
||||
input: 'null'
|
||||
output: ['[2,3]']
|
||||
|
||||
- title: `tonumber`
|
||||
- title: '`tonumber`'
|
||||
body: |
|
||||
|
||||
The `tonumber` function parses its input as a number. It
|
||||
@@ -624,7 +624,7 @@ sections:
|
||||
input: '[1, "1"]'
|
||||
output: [1, 1]
|
||||
|
||||
- title: `tostring`
|
||||
- title: '`tostring`'
|
||||
body: |
|
||||
|
||||
The `tostring` function prints its input as a
|
||||
@@ -636,7 +636,7 @@ sections:
|
||||
input: '[1, "1", [1]]'
|
||||
output: ['"1"', '"1"', '"[1]"']
|
||||
|
||||
- title: `type`
|
||||
- title: '`type`'
|
||||
body: |
|
||||
|
||||
The `type` function returns the type of its argument as a
|
||||
@@ -648,7 +648,7 @@ sections:
|
||||
input: '[0, false, [], {}, null, "hello"]'
|
||||
output: ['["number", "boolean", "array", "object", "null", "string"]']
|
||||
|
||||
- title: `sort, sort_by`
|
||||
- title: '`sort, sort_by`'
|
||||
body: |
|
||||
|
||||
The `sort` functions sorts its input, which must be an
|
||||
@@ -680,7 +680,7 @@ sections:
|
||||
input: '[{"foo":4, "bar":10}, {"foo":3, "bar":100}, {"foo":2, "bar":1}]'
|
||||
output: ['[{"foo":2, "bar":1}, {"foo":3, "bar":100}, {"foo":4, "bar":10}]']
|
||||
|
||||
- title: `group_by`
|
||||
- title: '`group_by`'
|
||||
body: |
|
||||
|
||||
`group_by(.foo)` takes as input an array, groups the
|
||||
@@ -697,7 +697,7 @@ sections:
|
||||
input: '[{"foo":1, "bar":10}, {"foo":3, "bar":100}, {"foo":1, "bar":1}]'
|
||||
output: ['[[{"foo":1, "bar":10}, {"foo":1, "bar":1}], [{"foo":3, "bar":100}]]']
|
||||
|
||||
- title: `min`, `max`, `min_by`, `max_by`
|
||||
- title: '`min`, `max`, `min_by`, `max_by`'
|
||||
body: |
|
||||
|
||||
Find the minimum or maximum element of the input array. The
|
||||
@@ -713,7 +713,7 @@ sections:
|
||||
input: '[{"foo":1, "bar":14}, {"foo":2, "bar":3}]'
|
||||
output: ['{"foo":2, "bar":3}']
|
||||
|
||||
- title: `unique`
|
||||
- title: '`unique`'
|
||||
body: |
|
||||
|
||||
The `unique` function takes as input an array and produces
|
||||
@@ -725,7 +725,7 @@ sections:
|
||||
input: '[1,2,5,3,5,3,1,3]'
|
||||
output: ['[1,2,3,5]']
|
||||
|
||||
- title: `reverse`
|
||||
- title: '`reverse`'
|
||||
body: |
|
||||
|
||||
This function reverses an array.
|
||||
@@ -735,7 +735,7 @@ sections:
|
||||
input: '[1,2,3,4]'
|
||||
output: ['[4,3,2,1]']
|
||||
|
||||
- title: `contains`
|
||||
- title: '`contains`'
|
||||
body: |
|
||||
|
||||
The filter `contains(b)` will produce true if b is
|
||||
@@ -764,7 +764,7 @@ sections:
|
||||
input: '{"foo": 12, "bar":[1,2,{"barp":12, "blip":13}]}'
|
||||
output: ['false']
|
||||
|
||||
- title: `recurse`
|
||||
- title: '`recurse`'
|
||||
body: |
|
||||
|
||||
The `recurse` function allows you to search through a
|
||||
@@ -796,7 +796,7 @@ sections:
|
||||
- '{"foo":[]}'
|
||||
|
||||
|
||||
- title: "String interpolation - `\(foo)`"
|
||||
- title: String interpolation - `\(foo)`
|
||||
body: |
|
||||
|
||||
Inside a string, you can put an expression inside parens
|
||||
@@ -882,7 +882,7 @@ sections:
|
||||
|
||||
- title: Conditionals and Comparisons
|
||||
entries:
|
||||
- title: `==`, `!=`
|
||||
- title: '`==`, `!=`'
|
||||
body: |
|
||||
|
||||
The expression 'a == b' will produce 'true' if the result of a and b
|
||||
@@ -931,7 +931,7 @@ sections:
|
||||
input: 2
|
||||
output: ['"many"']
|
||||
|
||||
- title: `>, >=, <=, <`
|
||||
- title: '`>, >=, <=, <`'
|
||||
body: |
|
||||
|
||||
The comparison operators `>`, `>=`, `<=`, `<` return whether
|
||||
File renamed without changes.
File renamed without changes.
@@ -1,10 +1,10 @@
|
||||
---
|
||||
headline: jq Manual (development version)
|
||||
headline: jq 1.6 Manual
|
||||
|
||||
history: |
|
||||
|
||||
*For released versions, see [jq 1.5](/jq/manual/v1.5),
|
||||
[jq 1.4](/jq/manual/v1.4) or [jq 1.3](/jq/manual/v1.3).*
|
||||
*The manual for the development version of jq can be found
|
||||
[here](/jq/manual).*
|
||||
|
||||
body: |
|
||||
|
||||
@@ -160,6 +160,9 @@ sections:
|
||||
terminal. You can force it to produce color even if writing to
|
||||
a pipe or a file using `-C`, and disable color with `-M`.
|
||||
|
||||
Colors can be configured with the `JQ_COLORS` environment
|
||||
variable (see below).
|
||||
|
||||
* `--ascii-output` / `-a`:
|
||||
|
||||
jq usually outputs non-ASCII Unicode codepoints as UTF-8, even
|
||||
@@ -209,6 +212,9 @@ sections:
|
||||
problem or system error, 3 if there was a jq program compile
|
||||
error, or 0 if the jq program ran.
|
||||
|
||||
Another way to set the exit status is with the `halt_error`
|
||||
builtin function.
|
||||
|
||||
* `--arg name value`:
|
||||
|
||||
This option passes a value to the jq program as a predefined
|
||||
@@ -217,6 +223,9 @@ sections:
|
||||
`value` will be treated as a string, so `--arg foo 123` will
|
||||
bind `$foo` to `"123"`.
|
||||
|
||||
Named arguments are also available to the jq program as
|
||||
`$ARGS.named`.
|
||||
|
||||
* `--argjson name JSON-text`:
|
||||
|
||||
This option passes a JSON-encoded value to the jq program as a
|
||||
@@ -227,10 +236,17 @@ sections:
|
||||
|
||||
This option reads all the JSON texts in the named file and binds
|
||||
an array of the parsed JSON values to the given global variable.
|
||||
If you run jq with `--argfile foo bar`, then `$foo` is available
|
||||
If you run jq with `--slurpfile foo bar`, then `$foo` is available
|
||||
in the program and has an array whose elements correspond to the
|
||||
texts in the file named `bar`.
|
||||
|
||||
* `--rawfile variable-name filename`:
|
||||
|
||||
This option reads in the named file and binds its contents to the given
|
||||
global variable. If you run jq with `--rawfile foo bar`, then `$foo` is
|
||||
available in the program and has a string whose contents are to the texs
|
||||
in the file named `bar`.
|
||||
|
||||
* `--argfile variable-name filename`:
|
||||
|
||||
Do not use. Use `--slurpfile` instead.
|
||||
@@ -239,6 +255,16 @@ sections:
|
||||
one text, then that is used, else an array of texts is used as
|
||||
in `--slurpfile`.)
|
||||
|
||||
* `--args`:
|
||||
|
||||
Remaining arguments are positional string arguments. These are
|
||||
available to the jq program as `$ARGS.positional[]`.
|
||||
|
||||
* `--jsonargs`:
|
||||
|
||||
Remaining arguments are positional JSON text arguments. These
|
||||
are available to the jq program as `$ARGS.positional[]`.
|
||||
|
||||
* `--run-tests [filename]`:
|
||||
|
||||
Runs the tests in the given file or standard input. This must
|
||||
@@ -255,12 +281,12 @@ sections:
|
||||
|
||||
- title: Basic filters
|
||||
entries:
|
||||
- title: "`.`"
|
||||
- title: "Identity: `.`"
|
||||
body: |
|
||||
|
||||
The absolute simplest (and least interesting) filter
|
||||
is `.`. This is a filter that takes its input and
|
||||
produces it unchanged as output.
|
||||
The absolute simplest filter is `.` . This is a filter that
|
||||
takes its input and produces it unchanged as output. That is,
|
||||
this is the identity operator.
|
||||
|
||||
Since jq by default pretty-prints all output, this trivial
|
||||
program can be a useful way of formatting JSON output from,
|
||||
@@ -271,18 +297,25 @@ sections:
|
||||
input: '"Hello, world!"'
|
||||
output: ['"Hello, world!"']
|
||||
|
||||
- title: "`.foo`, `.foo.bar`"
|
||||
- title: "Object Identifier-Index: `.foo`, `.foo.bar`"
|
||||
body: |
|
||||
|
||||
The simplest *useful* filter is `.foo`. When given a
|
||||
JSON object (aka dictionary or hash) as input, it produces
|
||||
the value at the key "foo", or null if there's none present.
|
||||
|
||||
If the key contains special characters, you need to surround
|
||||
it with double quotes like this: `."foo$"`.
|
||||
|
||||
A filter of the form `.foo.bar` is equivalent to `.foo|.bar`.
|
||||
|
||||
This syntax only works for simple, identifier-like keys, that
|
||||
is, keys that are all made of alphanumeric characters and
|
||||
underscore, and which do not start with a digit.
|
||||
|
||||
If the key contains special characters, you need to surround
|
||||
it with double quotes like this: `."foo$"`, or else `.["foo$"]`.
|
||||
|
||||
For example `.["foo::bar"]` and `.["foo.bar"]` work while
|
||||
`.foo::bar` does not, and `.foo.bar` means `.["foo"].["bar"]`.
|
||||
|
||||
examples:
|
||||
- program: '.foo'
|
||||
input: '{"foo": 42, "bar": "less interesting data"}'
|
||||
@@ -294,7 +327,7 @@ sections:
|
||||
input: '{"foo": 42}'
|
||||
output: [42]
|
||||
|
||||
- title: "`.foo?`"
|
||||
- title: "Optional Object Identifier-Index: `.foo?`"
|
||||
body: |
|
||||
|
||||
Just like `.foo`, but does not output even an error when `.`
|
||||
@@ -314,37 +347,22 @@ sections:
|
||||
input: '[1,2]'
|
||||
output: ['[]']
|
||||
|
||||
- title: "`.[<string>]`, `.[2]`, `.[10:15]`"
|
||||
- title: "Generic Object Index: `.[<string>]`"
|
||||
body: |
|
||||
|
||||
You can also look up fields of an object using syntax like
|
||||
`.["foo"]` (.foo above is a shorthand version of this). This
|
||||
one works for arrays as well, if the key is an
|
||||
integer. Arrays are zero-based (like javascript), so `.[2]`
|
||||
returns the third element of the array.
|
||||
`.["foo"]` (.foo above is a shorthand version of this, but
|
||||
only for identifier-like strings).
|
||||
|
||||
The `.[10:15]` syntax can be used to return a subarray of an
|
||||
array or substring of a string. The array returned by
|
||||
`.[10:15]` will be of length 5, containing the elements from
|
||||
index 10 (inclusive) to index 15 (exclusive). Either index may
|
||||
be negative (in which case it counts backwards from the end of
|
||||
the array), or omitted (in which case it refers to the start
|
||||
or end of the array).
|
||||
- title: "Array Index: `.[2]`"
|
||||
body: |
|
||||
|
||||
The `.[2]` syntax can be used to return the element at the
|
||||
given index. Negative indices are allowed, with -1 referring
|
||||
to the last element, -2 referring to the next to last element,
|
||||
and so on.
|
||||
When the index value is an integer, `.[<value>]` can index
|
||||
arrays. Arrays are zero-based, so `.[2]` returns the third
|
||||
element.
|
||||
|
||||
The `.foo` syntax only works for simply keys i.e. keys that
|
||||
are all alphanumeric characters. `.[<string>]` works with
|
||||
keys that contain special characters such as colons and dots.
|
||||
For example `.["foo::bar"]` and `.["foo.bar"]` work while
|
||||
`.foo::bar` and `.foo.bar` would not.
|
||||
|
||||
The `?` "operator" can also be used with the slice operator,
|
||||
as in `.[10:15]?`, which outputs values where the inputs are
|
||||
slice-able.
|
||||
Negative indices are allowed, with -1 referring to the last
|
||||
element, -2 referring to the next to last element, and so on.
|
||||
|
||||
examples:
|
||||
- program: '.[0]'
|
||||
@@ -355,6 +373,22 @@ sections:
|
||||
input: '[{"name":"JSON", "good":true}, {"name":"XML", "good":false}]'
|
||||
output: ['null']
|
||||
|
||||
- program: '.[-2]'
|
||||
input: '[1,2,3]'
|
||||
output: ['2']
|
||||
|
||||
- title: "Array/String Slice: `.[10:15]`"
|
||||
body: |
|
||||
|
||||
The `.[10:15]` syntax can be used to return a subarray of an
|
||||
array or substring of a string. The array returned by
|
||||
`.[10:15]` will be of length 5, containing the elements from
|
||||
index 10 (inclusive) to index 15 (exclusive). Either index may
|
||||
be negative (in which case it counts backwards from the end of
|
||||
the array), or omitted (in which case it refers to the start
|
||||
or end of the array).
|
||||
|
||||
examples:
|
||||
- program: '.[2:4]'
|
||||
input: '["a","b","c","d","e"]'
|
||||
output: ['["c", "d"]']
|
||||
@@ -371,11 +405,7 @@ sections:
|
||||
input: '["a","b","c","d","e"]'
|
||||
output: ['["d", "e"]']
|
||||
|
||||
- program: '.[-2]'
|
||||
input: '[1,2,3]'
|
||||
output: ['2']
|
||||
|
||||
- title: "`.[]`"
|
||||
- title: "Array/Object Value Iterator: `.[]`"
|
||||
body: |
|
||||
|
||||
If you use the `.[index]` syntax, but omit the index
|
||||
@@ -408,15 +438,16 @@ sections:
|
||||
Like `.[]`, but no errors will be output if . is not an array
|
||||
or object.
|
||||
|
||||
- title: "`,`"
|
||||
- title: "Comma: `,`"
|
||||
body: |
|
||||
|
||||
If two filters are separated by a comma, then the
|
||||
input will be fed into both and there will be multiple
|
||||
outputs: first, all of the outputs produced by the left
|
||||
expression, and then all of the outputs produced by the
|
||||
right. For instance, filter `.foo, .bar`, produces
|
||||
both the "foo" fields and "bar" fields as separate outputs.
|
||||
same input will be fed into both and the two filters' output
|
||||
value streams will be concatenated in order: first, all of the
|
||||
outputs produced by the left expression, and then all of the
|
||||
outputs produced by the right. For instance, filter `.foo,
|
||||
.bar`, produces both the "foo" fields and "bar" fields as
|
||||
separate outputs.
|
||||
|
||||
examples:
|
||||
- program: '.foo, .bar'
|
||||
@@ -431,7 +462,7 @@ sections:
|
||||
input: '["a","b","c","d","e"]'
|
||||
output: ['"e"', '"c"']
|
||||
|
||||
- title: "`|`"
|
||||
- title: "Pipe: `|`"
|
||||
body: |
|
||||
|
||||
The | operator combines two filters by feeding the output(s) of
|
||||
@@ -481,16 +512,16 @@ sections:
|
||||
instead.
|
||||
|
||||
entries:
|
||||
- title: Array construction - `[]`
|
||||
- title: "Array construction: `[]`"
|
||||
body: |
|
||||
|
||||
As in JSON, `[]` is used to construct arrays, as in
|
||||
`[1,2,3]`. The elements of the arrays can be any jq
|
||||
expression. All of the results produced by all of the
|
||||
expressions are collected into one big array. You can use it
|
||||
to construct an array out of a known quantity of values (as
|
||||
in `[.foo, .bar, .baz]`) or to "collect" all the results of a
|
||||
filter into an array (as in `[.items[].name]`)
|
||||
expression, including a pipeline. All of the results produced
|
||||
by all of the expressions are collected into one big array.
|
||||
You can use it to construct an array out of a known quantity
|
||||
of values (as in `[.foo, .bar, .baz]`) or to "collect" all the
|
||||
results of a filter into an array (as in `[.items[].name]`)
|
||||
|
||||
Once you understand the "," operator, you can look at jq's array
|
||||
syntax in a different light: the expression `[1,2,3]` is not using a
|
||||
@@ -506,30 +537,37 @@ sections:
|
||||
- program: "[.user, .projects[]]"
|
||||
input: '{"user":"stedolan", "projects": ["jq", "wikiflow"]}'
|
||||
output: ['["stedolan", "jq", "wikiflow"]']
|
||||
- title: Objects - `{}`
|
||||
- program: "[ .[] | . * 2]"
|
||||
input: '[1, 2, 3]'
|
||||
output: ['[2, 4, 6]']
|
||||
|
||||
- title: "Object Construction: `{}`"
|
||||
body: |
|
||||
|
||||
Like JSON, `{}` is for constructing objects (aka
|
||||
dictionaries or hashes), as in: `{"a": 42, "b": 17}`.
|
||||
|
||||
If the keys are "sensible" (all alphabetic characters), then
|
||||
the quotes can be left off. The value can be any expression
|
||||
(although you may need to wrap it in parentheses if it's a
|
||||
complicated one), which gets applied to the {} expression's
|
||||
input (remember, all filters have an input and an
|
||||
output).
|
||||
If the keys are "identifier-like", then the quotes can be left
|
||||
off, as in `{a:42, b:17}`. Keys generated by expressions need
|
||||
to be parenthesized, e.g., `{("a"+"b"):59}`.
|
||||
|
||||
The value can be any expression (although you may need to
|
||||
wrap it in parentheses if it's a complicated one), which gets
|
||||
applied to the {} expression's input (remember, all filters
|
||||
have an input and an output).
|
||||
|
||||
{foo: .bar}
|
||||
|
||||
will produce the JSON object `{"foo": 42}` if given the JSON
|
||||
object `{"bar":42, "baz":43}`. You can use this to select
|
||||
particular fields of an object: if the input is an object
|
||||
with "user", "title", "id", and "content" fields and you
|
||||
just want "user" and "title", you can write
|
||||
object `{"bar":42, "baz":43}` as its input. You can use this
|
||||
to select particular fields of an object: if the input is an
|
||||
object with "user", "title", "id", and "content" fields and
|
||||
you just want "user" and "title", you can write
|
||||
|
||||
{user: .user, title: .title}
|
||||
|
||||
Because that's so common, there's a shortcut syntax: `{user, title}`.
|
||||
Because that is so common, there's a shortcut syntax for it:
|
||||
`{user, title}`.
|
||||
|
||||
If one of the expressions produces multiple results,
|
||||
multiple dictionaries will be produced. If the input's
|
||||
@@ -564,17 +602,35 @@ sections:
|
||||
input: '{"user":"stedolan","titles":["JQ Primer", "More JQ"]}'
|
||||
output: ['{"stedolan": ["JQ Primer", "More JQ"]}']
|
||||
|
||||
- title: "Recursive Descent: `..`"
|
||||
body: |
|
||||
|
||||
Recursively descends `.`, producing every value. This is the
|
||||
same as the zero-argument `recurse` builtin (see below). This
|
||||
is intended to resemble the XPath `//` operator. Note that
|
||||
`..a` does not work; use `..|.a` instead. In the example
|
||||
below we use `..|.a?` to find all the values of object keys
|
||||
"a" in any object found "below" `.`.
|
||||
|
||||
This is particularly useful in conjunction with `path(EXP)`
|
||||
(also see below) and the `?` operator.
|
||||
|
||||
examples:
|
||||
- program: '..|.a?'
|
||||
input: '[[{"a":1}]]'
|
||||
output: ['1']
|
||||
|
||||
- title: Builtin operators and functions
|
||||
body: |
|
||||
|
||||
Some jq operator (for instance, `+`) do different things
|
||||
Some jq operators (for instance, `+`) do different things
|
||||
depending on the type of their arguments (arrays, numbers,
|
||||
etc.). However, jq never does implicit type conversions. If you
|
||||
try to add a string to an object you'll get an error message and
|
||||
no result.
|
||||
|
||||
entries:
|
||||
- title: Addition - `+`
|
||||
- title: "Addition: `+`"
|
||||
body: |
|
||||
|
||||
The operator `+` takes two filters, applies them both
|
||||
@@ -613,7 +669,7 @@ sections:
|
||||
input: 'null'
|
||||
output: ['{"a": 42, "b": 2, "c": 3}']
|
||||
|
||||
- title: Subtraction - `-`
|
||||
- title: "Subtraction: `-`"
|
||||
body: |
|
||||
|
||||
As well as normal arithmetic subtraction on numbers, the `-`
|
||||
@@ -628,7 +684,7 @@ sections:
|
||||
input: '["xml", "yaml", "json"]'
|
||||
output: ['["json"]']
|
||||
|
||||
- title: Multiplication, division, modulo - `*`, `/`, and `%`
|
||||
- title: "Multiplication, division, modulo: `*`, `/`, and `%`"
|
||||
body: |
|
||||
|
||||
These infix operators behave as expected when given two numbers.
|
||||
@@ -943,7 +999,25 @@ sections:
|
||||
|
||||
Produces an error, just like `.a` applied to values other than
|
||||
null and objects would, but with the given message as the
|
||||
error's value.
|
||||
error's value. Errors can be caught with try/catch; see below.
|
||||
|
||||
- title: "`halt`"
|
||||
body: |
|
||||
|
||||
Stops the jq program with no further outputs. jq will exit
|
||||
with exit status `0`.
|
||||
|
||||
- title: "`halt_error`, `halt_error(exit_code)`"
|
||||
body: |
|
||||
|
||||
Stops the jq program with no further outputs. The input will
|
||||
be printed on `stderr` as raw output (i.e., strings will not
|
||||
have double quotes) with no decoration, not even a newline.
|
||||
|
||||
The given `exit_code` (defaulting to `5`) will be jq's exit
|
||||
status.
|
||||
|
||||
For example, `"Error: somthing went wrong\n"|halt_error(1)`.
|
||||
|
||||
- title: "`$__loc__`"
|
||||
body: |
|
||||
@@ -1627,27 +1701,22 @@ sections:
|
||||
output:
|
||||
- '[{"a":{"b":2}}]'
|
||||
|
||||
|
||||
- title: "`..`"
|
||||
- title: "`$ENV`, `env`"
|
||||
body: |
|
||||
|
||||
Short-hand for `recurse` without arguments. This is intended
|
||||
to resemble the XPath `//` operator. Note that `..a` does not
|
||||
work; use `..|a` instead. In the example below we use
|
||||
`..|.a?` to find all the values of object keys "a" in any
|
||||
object found "below" `.`.
|
||||
`$ENV` is an object representing the environment variables as
|
||||
set when the jq program started.
|
||||
|
||||
`env` outputs an object representing jq's current environment.
|
||||
|
||||
At the moment there is no builtin for setting environment
|
||||
variables.
|
||||
|
||||
examples:
|
||||
- program: '..|.a?'
|
||||
input: '[[{"a":1}]]'
|
||||
output: ['1']
|
||||
- program: '$ENV.PAGER'
|
||||
input: 'null'
|
||||
output: ['"less"']
|
||||
|
||||
- title: "`env`"
|
||||
body: |
|
||||
|
||||
Outputs an object representing jq's environment.
|
||||
|
||||
examples:
|
||||
- program: 'env.PAGER'
|
||||
input: 'null'
|
||||
output: ['"less"']
|
||||
@@ -1770,6 +1839,11 @@ sections:
|
||||
|
||||
The input is converted to base64 as specified by RFC 4648.
|
||||
|
||||
* `@base64d`:
|
||||
|
||||
The inverse of `@base64`, input is decoded as specified by RFC 4648.
|
||||
Note\: If the decoded string is not UTF-8, the results are undefined.
|
||||
|
||||
This syntax can be combined with string interpolation in a
|
||||
useful way. You can follow a `@foo` token with a string
|
||||
literal. The contents of the string literal will *not* be
|
||||
@@ -1799,6 +1873,14 @@ sections:
|
||||
input: "\"O'Hara's Ale\""
|
||||
output: ["\"echo 'O'\\\\''Hara'\\\\''s Ale'\""]
|
||||
|
||||
- program: '@base64'
|
||||
input: '"This is a message"'
|
||||
output: ['"VGhpcyBpcyBhIG1lc3NhZ2U="']
|
||||
|
||||
- program: '@base64d'
|
||||
input: '"VGhpcyBpcyBhIG1lc3NhZ2U="'
|
||||
output: ['"This is a message"']
|
||||
|
||||
- title: "Dates"
|
||||
body: |
|
||||
|
||||
@@ -1822,19 +1904,25 @@ sections:
|
||||
the Unix epoch.
|
||||
|
||||
Low-level jq interfaces to the C-library time functions are
|
||||
also provided: `strptime`, `strftime`, `mktime`, and `gmtime`.
|
||||
Refer to your host operating system's documentation for the
|
||||
format strings used by `strptime` and `strftime`. Note: these
|
||||
are not necessarily stable interfaces in jq, particularly as
|
||||
to their localization functionality.
|
||||
also provided: `strptime`, `strftime`, `strflocaltime`,
|
||||
`mktime`, `gmtime`, and `localtime`. Refer to your host
|
||||
operating system's documentation for the format strings used
|
||||
by `strptime` and `strftime`. Note: these are not necessarily
|
||||
stable interfaces in jq, particularly as to their localization
|
||||
functionality.
|
||||
|
||||
The `gmtime` builtin consumes a number of seconds since the
|
||||
Unix epoch and outputs a "broken down time" representation of
|
||||
time as an array of numbers representing (in this order): the
|
||||
year, the month (zero-based), the day of the month, the hour
|
||||
of the day, the minute of the hour, the second of the minute,
|
||||
the day of the week, and the day of the year -- all one-based
|
||||
unless otherwise stated.
|
||||
Greenwhich Meridian time as an array of numbers representing
|
||||
(in this order): the year, the month (zero-based), the day of
|
||||
the month (one-based), the hour of the day, the minute of the
|
||||
hour, the second of the minute, the day of the week, and the
|
||||
day of the year -- all one-based unless otherwise stated. The
|
||||
day of the week number may be wrong on some systems for dates
|
||||
before March 1st 1900, or after December 31 2099.
|
||||
|
||||
The `localtime` builtin works like the `gmtime` builtin, but
|
||||
using the local timezone setting.
|
||||
|
||||
The `mktime` builtin consumes "broken down time"
|
||||
representations of time output by `gmtime` and `strptime`.
|
||||
@@ -1843,15 +1931,17 @@ sections:
|
||||
`fmt` argument. The output is in the "broken down time"
|
||||
representation consumed by `gmtime` and output by `mktime`.
|
||||
|
||||
The `strftime(fmt)` builtin formats a time with the given
|
||||
format.
|
||||
The `strftime(fmt)` builtin formats a time (GMT) with the
|
||||
given format. The `strflocaltime` does the same, but using
|
||||
the local timezone setting.
|
||||
|
||||
The format strings for `strptime` and `strftime` are described
|
||||
in typical C library documentation. The format string for ISO
|
||||
8601 datetime is `"%Y-%m-%dT%H:%M:%SZ"`.
|
||||
|
||||
jq may not support some or all of this date functionality on
|
||||
some systems.
|
||||
some systems. In particular, the `%u` and `%j` specifiers for
|
||||
`strptime(fmt)` are not supported on macOS.
|
||||
|
||||
examples:
|
||||
- program: 'fromdate'
|
||||
@@ -1906,6 +1996,14 @@ sections:
|
||||
This builtin outputs `true` if any value in the source stream
|
||||
appears in the second stream, otherwise it outputs `false`.
|
||||
|
||||
- title: "`builtins`"
|
||||
body: |
|
||||
|
||||
Returns a list of all builtin functions in the format `name/arity`.
|
||||
Since functions with the same name but different arities are considered
|
||||
separate functions, `all/0`, `all/1`, and `all/2` would all be present
|
||||
in the list.
|
||||
|
||||
- title: Conditionals and Comparisons
|
||||
entries:
|
||||
- title: "`==`, `!=`"
|
||||
@@ -2012,7 +2110,7 @@ sections:
|
||||
input: 'null'
|
||||
output: ['[false, true]']
|
||||
|
||||
- title: Alternative operator - `//`
|
||||
- title: "Alternative operator: `//`"
|
||||
body: |
|
||||
|
||||
A filter of the form `a // b` produces the same
|
||||
@@ -2089,7 +2187,7 @@ sections:
|
||||
|
||||
because no label `$out` is visible.
|
||||
|
||||
- title: "`?` operator"
|
||||
- title: "Error Suppression / Optional Operator: `?`"
|
||||
body: |
|
||||
|
||||
The `?` operator, used as `EXP?`, is shorthand for `try EXP`.
|
||||
@@ -2312,7 +2410,7 @@ sections:
|
||||
Finally, there is a module/library system.
|
||||
|
||||
entries:
|
||||
- title: Variables
|
||||
- title: "Variable / Symbolic Binding Operator: `... as $identifier | ...`"
|
||||
body: |
|
||||
|
||||
In jq, all filters have an input and an output, so manual
|
||||
@@ -2412,6 +2510,52 @@ sections:
|
||||
input: '[[0], [0, 1], [2, 1, 0]]'
|
||||
output: ['{"a":0,"b":null}', '{"a":0,"b":1}', '{"a":2,"b":1}']
|
||||
|
||||
- title: 'Destructuring Alternative Operator: `?//`'
|
||||
body: |
|
||||
|
||||
The destructuring alternative operator provides a concise mechanism
|
||||
for destructuring an input that can take one of several forms.
|
||||
|
||||
Suppose we have an API that returns a list of resources and events
|
||||
associated with them, and we want to get the user_id and timestamp of
|
||||
the first event for each resource. The API (having been clumsily
|
||||
converted from XML) will only wrap the events in an array if the resource
|
||||
has multiple events:
|
||||
|
||||
{"resources": [{"id": 1, "kind": "widget", "events": {"action": "create", "user_id": 1, "ts": 13}},
|
||||
{"id": 2, "kind": "widget", "events": [{"action": "create", "user_id": 1, "ts": 14}, {"action": "destroy", "user_id": 1, "ts": 15}]}]}
|
||||
|
||||
We can use the destructuring alternative operator to handle this structural change simply:
|
||||
|
||||
.resources[] as {$id, $kind, events: {$user_id, $ts}} ?// {$id, $kind, events: [{$user_id, $ts}]} | {$user_id, $kind, $id, $ts}
|
||||
|
||||
Or, if we aren't sure if the input is an array of values or an object:
|
||||
|
||||
.[] as [$id, $kind, $user_id, $ts] ?// {$id, $kind, $user_id, $ts} | ...
|
||||
|
||||
Each alternative need not define all of the same variables, but all named
|
||||
variables will be available to the subsequent expression. Variables not
|
||||
matched in the alternative that succeeded will be `null`:
|
||||
|
||||
.resources[] as {$id, $kind, events: {$user_id, $ts}} ?// {$id, $kind, events: [{$first_user_id, $first_ts}]} | {$user_id, $first_user_id, $kind, $id, $ts, $first_ts}
|
||||
|
||||
Additionally, if the subsequent expression returns an error, the
|
||||
alternative operator will attempt to try the next binding. Errors
|
||||
that occur during the final alternative are passed through.
|
||||
|
||||
[[3]] | .[] as [$a] ?// [$b] | if $a != null then error("err: \($a)") else {$a,$b} end
|
||||
|
||||
examples:
|
||||
- program: '.[] as {$a, $b, c: {$d, $e}} ?// {$a, $b, c: [{$d, $e}]} | {$a, $b, $d, $e}'
|
||||
input: '[{"a": 1, "b": 2, "c": {"d": 3, "e": 4}}, {"a": 1, "b": 2, "c": [{"d": 3, "e": 4}]}]'
|
||||
output: ['{"a":1,"b":2,"d":3,"e":4}', '{"a":1,"b":2,"d":3,"e":4}']
|
||||
- program: '.[] as {$a, $b, c: {$d}} ?// {$a, $b, c: [{$e}]} | {$a, $b, $d, $e}'
|
||||
input: '[{"a": 1, "b": 2, "c": {"d": 3, "e": 4}}, {"a": 1, "b": 2, "c": [{"d": 3, "e": 4}]}]'
|
||||
output: ['{"a":1,"b":2,"d":3,"e":null}', '{"a":1,"b":2,"d":null,"e":4}']
|
||||
- program: '.[] as [$a] ?// [$b] | if $a != null then error("err: \($a)") else {$a,$b} end'
|
||||
input: '[[3]]'
|
||||
output: ['{"a":null,"b":3}']
|
||||
|
||||
- title: 'Defining Functions'
|
||||
body: |
|
||||
|
||||
@@ -2505,18 +2649,20 @@ sections:
|
||||
(2 as $item | . + $item) |
|
||||
(1 as $item | . + $item)
|
||||
|
||||
If the reduction update expression outputs `empty` (that is,
|
||||
no values), then the reduction state is left as-is. For
|
||||
example, `reduce range(4) as $n ({}; if .==2 then empty else
|
||||
.[$n|tostring] |= $n)` will produce `{"0":0,"1":1,"3":3}`.
|
||||
|
||||
examples:
|
||||
- program: 'reduce .[] as $item (0; . + $item)'
|
||||
input: '[10,2,5,3]'
|
||||
output: ['20']
|
||||
- program: 'reduce .[] as $n ([]; if $n%2==0 then empty else . + [$n] end)'
|
||||
input: '[0,1,2,3,4,5]'
|
||||
output: ['[1,3,5]']
|
||||
|
||||
- title: "`isempty(exp)`"
|
||||
body: |
|
||||
|
||||
Returns true if `exp` produces no outputs, false otherwise.
|
||||
|
||||
examples:
|
||||
- program: 'isempty(empty)'
|
||||
input: 'null'
|
||||
output: ['true']
|
||||
|
||||
- title: "`limit(n; exp)`"
|
||||
body: |
|
||||
@@ -2703,11 +2849,13 @@ sections:
|
||||
jq itself. These two builtins, and jq's own reading actions, can
|
||||
be interleaved with each other.
|
||||
|
||||
One builtin provides minimal output capabilities, `debug`.
|
||||
(Recall that a jq program's output values are always output as
|
||||
JSON texts on `stdout`.) The `debug` builtin can have
|
||||
Two builtins provide minimal output capabilities, `debug`, and
|
||||
`stderr`. (Recall that a jq program's output values are always
|
||||
output as JSON texts on `stdout`.) The `debug` builtin can have
|
||||
application-specific behavior, such as for executables that use
|
||||
the libjq C API but aren't the jq executable itself.
|
||||
the libjq C API but aren't the jq executable itself. The `stderr`
|
||||
builtin outputs its input in raw mode to stder with no additional
|
||||
decoration, not even a newline.
|
||||
|
||||
Most jq builtins are referentially transparent, and yield constant
|
||||
and repeatable value streams when applied to constant inputs.
|
||||
@@ -2735,6 +2883,12 @@ sections:
|
||||
`["DEBUG:", <input-value>]` and prints that and a newline on
|
||||
stderr, compactly. This may change in the future.
|
||||
|
||||
- title: "`stderr`"
|
||||
body: |
|
||||
|
||||
Prints its input in raw and compact mode to stderr with no
|
||||
additional decoration, not even a newline.
|
||||
|
||||
- title: "`input_filename`"
|
||||
body: |
|
||||
|
||||
@@ -2806,7 +2960,6 @@ sections:
|
||||
|
||||
- title: Assignment
|
||||
body: |
|
||||
|
||||
Assignment works a little differently in jq than in most
|
||||
programming languages. jq doesn't distinguish between references
|
||||
to and copies of something - two objects or arrays are either
|
||||
@@ -2815,14 +2968,21 @@ sections:
|
||||
|
||||
If an object has two fields which are arrays, `.foo` and `.bar`,
|
||||
and you append something to `.foo`, then `.bar` will not get
|
||||
bigger. Even if you've just set `.bar = .foo`. If you're used to
|
||||
programming in languages like Python, Java, Ruby, Javascript,
|
||||
etc. then you can think of it as though jq does a full deep copy
|
||||
of every object before it does the assignment (for performance,
|
||||
it doesn't actually do that, but that's the general idea).
|
||||
bigger, even if you've previously set `.bar = .foo`. If you're
|
||||
used to programming in languages like Python, Java, Ruby,
|
||||
Javascript, etc. then you can think of it as though jq does a full
|
||||
deep copy of every object before it does the assignment (for
|
||||
performance it doesn't actually do that, but that's the general
|
||||
idea).
|
||||
|
||||
This means that it's impossible to build circular values in jq
|
||||
(such as an array whose first element is itself). This is quite
|
||||
intentional, and ensures that anything a jq program can produce
|
||||
can be represented in JSON.
|
||||
|
||||
All the assignment operators in jq have path expressions on the
|
||||
left-hand side.
|
||||
left-hand side (LHS). The right-hand side (RHS) provides values
|
||||
to set to the paths named by the LHS path expressions.
|
||||
|
||||
Values in jq are always immutable. Internally, assignment works
|
||||
by using a reduction to compute new, replacement values for `.` that
|
||||
@@ -2833,65 +2993,26 @@ sections:
|
||||
sub-expression, `.`, sees the original value, not the modified
|
||||
value.
|
||||
|
||||
Most users will want to use modification assignment operators,
|
||||
such as `|=` or `+=`, rather than `=`.
|
||||
|
||||
Note that the LHS of assignment operators refers to a value in
|
||||
`.`. Thus `$var.foo = 1` won't work as expected (`$var.foo` is
|
||||
not a valid or useful path expression in `.`); use `$var | .foo =
|
||||
1` instead.
|
||||
|
||||
Note too that `.a,.b=0` does not set `.a` and `.b`, but
|
||||
`(.a,.b)=0` sets both.
|
||||
|
||||
entries:
|
||||
- title: "`=`"
|
||||
- title: "Update-assignment: `|=`"
|
||||
body: |
|
||||
|
||||
The filter `.foo = 1` will take as input an object
|
||||
and produce as output an object with the "foo" field set to
|
||||
1. There is no notion of "modifying" or "changing" something
|
||||
in jq - all jq values are immutable. For instance,
|
||||
|
||||
.foo = .bar | .foo.baz = 1
|
||||
|
||||
will not have the side-effect of setting .bar.baz to be set
|
||||
to 1, as the similar-looking program in Javascript, Python,
|
||||
Ruby or other languages would. Unlike these languages (but
|
||||
like Haskell and some other functional languages), there is
|
||||
no notion of two arrays or objects being "the same array" or
|
||||
"the same object". They can be equal, or not equal, but if
|
||||
we change one of them in no circumstances will the other
|
||||
change behind our backs.
|
||||
|
||||
This means that it's impossible to build circular values in
|
||||
jq (such as an array whose first element is itself). This is
|
||||
quite intentional, and ensures that anything a jq program
|
||||
can produce can be represented in JSON.
|
||||
|
||||
Note that the left-hand side of '=' refers to a value in `.`.
|
||||
Thus `$var.foo = 1` won't work as expected (`$var.foo` is not
|
||||
a valid or useful path expression in `.`); use `$var | .foo =
|
||||
1` instead.
|
||||
|
||||
If the right-hand side of '=' produces multiple values, then
|
||||
for each such value jq will set the paths on the left-hand
|
||||
side to the value and then it will output the modified `.`.
|
||||
For example, `(.a,.b)=range(2)` outputs `{"a":0,"b":0}`, then
|
||||
`{"a":1,"b":1}`. The "update" assignment forms (see below) do
|
||||
not do this.
|
||||
|
||||
Note too that `.a,.b=0` does not set `.a` and `.b`, but
|
||||
`(.a,.b)=0` sets both.
|
||||
|
||||
- title: "`|=`"
|
||||
body: |
|
||||
As well as the assignment operator '=', jq provides the "update"
|
||||
operator '|=', which takes a filter on the right-hand side and
|
||||
works out the new value for the property of `.` being assigned
|
||||
to by running the old value through this expression. For
|
||||
instance, .foo |= .+1 will build an object with the "foo"
|
||||
field set to the input's "foo" plus 1.
|
||||
|
||||
This example should show the difference between '=' and '|=':
|
||||
|
||||
Provide input '{"a": {"b": 10}, "b": 20}' to the programs:
|
||||
|
||||
.a = .b
|
||||
.a |= .b
|
||||
|
||||
The former will set the "a" field of the input to the "b" field of the
|
||||
input, and produce the output {"a": 20}. The latter will set the "a"
|
||||
field of the input to the "a" field's "b" field, producing {"a": 10}.
|
||||
This is the "update" operator '|='. It takes a filter on the
|
||||
right-hand side and works out the new value for the property
|
||||
of `.` being assigned to by running the old value through this
|
||||
expression. For instance, (.foo, .bar) |= .+1 will build an
|
||||
object with the "foo" field set to the input's "foo" plus 1,
|
||||
and the "bar" field set to the input's "bar" plus 1.
|
||||
|
||||
The left-hand side can be any general path expression; see `path()`.
|
||||
|
||||
@@ -2904,26 +3025,66 @@ sections:
|
||||
the left-hand side path will be deleted, as with `del(path)`.
|
||||
|
||||
If the right-hand side outputs multiple values, only the first
|
||||
one will be used (NOTE: it used to be that only the last one was
|
||||
used).
|
||||
one will be used (COMPATIBILITY NOTE: in jq 1.5 and earlier
|
||||
releases, it used to be that only the last one was used).
|
||||
|
||||
examples:
|
||||
|
||||
- program: '(..|select(type=="boolean")) |= if . then 1 else 0 end'
|
||||
input: '[true,false,[5,true,[true,[false]],false]]'
|
||||
output: ['[1,0,[5,1,[1,[0]],0]]']
|
||||
|
||||
- title: "`+=`, `-=`, `*=`, `/=`, `%=`, `//=`"
|
||||
- title: "Arithmetic update-assignment: `+=`, `-=`, `*=`, `/=`, `%=`, `//=`"
|
||||
body: |
|
||||
|
||||
jq has a few operators of the form `a op= b`, which are all
|
||||
equivalent to `a |= . op b`. So, `+= 1` can be used to increment values.
|
||||
equivalent to `a |= . op b`. So, `+= 1` can be used to
|
||||
increment values, being the same as `|= . + 1`.
|
||||
|
||||
examples:
|
||||
- program: .foo += 1
|
||||
input: '{"foo": 42}'
|
||||
output: ['{"foo": 43}']
|
||||
|
||||
- title: "Plain assignment: `=`"
|
||||
body: |
|
||||
|
||||
This is the plain assignment operator. Unlike the others, the
|
||||
input to the right-hand-side (RHS) is the same as the input to
|
||||
the left-hand-side (LHS) rather than the value at the LHS
|
||||
path, and all values output by the RHS will be used (as shown
|
||||
below).
|
||||
|
||||
If the RHS of '=' produces multiple values, then for each such
|
||||
value jq will set the paths on the left-hand side to the value
|
||||
and then it will output the modified `.`. For example,
|
||||
`(.a,.b)=range(2)` outputs `{"a":0,"b":0}`, then
|
||||
`{"a":1,"b":1}`. The "update" assignment forms (see above) do
|
||||
not do this.
|
||||
|
||||
This example should show the difference between '=' and '|=':
|
||||
|
||||
Provide input '{"a": {"b": 10}, "b": 20}' to the programs:
|
||||
|
||||
.a = .b
|
||||
|
||||
.a |= .b
|
||||
|
||||
The former will set the "a" field of the input to the "b"
|
||||
field of the input, and produce the output {"a": 20, "b": 20}.
|
||||
The latter will set the "a" field of the input to the "a"
|
||||
field's "b" field, producing {"a": 10, "b": 20}.
|
||||
|
||||
Another example of the difference between '=' and '|=':
|
||||
|
||||
null|(.a,.b)=range(3)
|
||||
|
||||
outputs '{"a":0,"b":0}', '{"a":1,"b":1}', and '{"a":2,"b":2}',
|
||||
while
|
||||
|
||||
null|(.a,.b)|=range(3)
|
||||
|
||||
outputs just '{"a":0,"b":0}'.
|
||||
|
||||
- title: Complex assignments
|
||||
body: |
|
||||
Lots more things are allowed on the left-hand side of a jq assignment
|
||||
@@ -3085,3 +3246,42 @@ sections:
|
||||
could then use to, for example, search for, download, and
|
||||
install missing dependencies.
|
||||
|
||||
- title: Colors
|
||||
body: |
|
||||
|
||||
To configure alternative colors just set the `JQ_COLORS`
|
||||
environment variable to colon-delimited list of partial terminal
|
||||
escape sequences like `"1;31"`, in this order:
|
||||
|
||||
- color for `null`
|
||||
- color for `false`
|
||||
- color for `true`
|
||||
- color for numbers
|
||||
- color for strings
|
||||
- color for arrays
|
||||
- color for objects
|
||||
|
||||
The default color scheme is the same as setting
|
||||
`"JQ_COLORS=1;30:0;39:0;39:0;39:0;32:1;39:1;39"`.
|
||||
|
||||
This is not a manual for VT100/ANSI escapes. However, each of
|
||||
these color specifications should consist of two numbers separated
|
||||
by a semi-colon, where the first number is one of these:
|
||||
|
||||
- 1 (bright)
|
||||
- 2 (dim)
|
||||
- 4 (underscore)
|
||||
- 5 (blink)
|
||||
- 7 (reverse)
|
||||
- 8 (hidden)
|
||||
|
||||
and the second is one of these:
|
||||
|
||||
- 30 (black)
|
||||
- 31 (red)
|
||||
- 32 (green)
|
||||
- 33 (yellow)
|
||||
- 34 (blue)
|
||||
- 35 (magenta)
|
||||
- 36 (cyan)
|
||||
- 37 (white)
|
||||
File renamed without changes.
@@ -0,0 +1,173 @@
|
||||
body {
|
||||
padding-top: 80px;
|
||||
}
|
||||
|
||||
.container {
|
||||
max-width: 970px;
|
||||
}
|
||||
|
||||
/* index.liquid *******************************************/
|
||||
#blurb {
|
||||
padding-top: 40px;
|
||||
}
|
||||
#blurb p {
|
||||
font-size: 1.9em;
|
||||
}
|
||||
#blurb .btn-group {
|
||||
margin: 4px;
|
||||
}
|
||||
|
||||
#multiblurb {
|
||||
line-height: 1.7;
|
||||
text-align: center;
|
||||
font-size: 12pt;
|
||||
}
|
||||
#multiblurb code {
|
||||
border: 0;
|
||||
font-size: 12pt;
|
||||
}
|
||||
|
||||
#news {
|
||||
font-size: 12pt;
|
||||
}
|
||||
#news .date {
|
||||
font-style: italic;
|
||||
}
|
||||
|
||||
/* default.liquid *****************************************/
|
||||
.tutorial-example {
|
||||
position: relative;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
.tutorial-example pre {
|
||||
margin-bottom: 0px;
|
||||
}
|
||||
.tutorial-example a {
|
||||
position: absolute;
|
||||
top: 0px;
|
||||
right: 0px;
|
||||
padding: 15px 8px;
|
||||
color: #777777;
|
||||
font-weight: bold;
|
||||
line-height: 10px;
|
||||
font-size: 12px;
|
||||
border-left: 1px solid #DDDDDD;
|
||||
display: block;
|
||||
}
|
||||
.tutorial-example .accordion-body pre {
|
||||
margin: 0 4px;
|
||||
border-top: 0;
|
||||
border-top-left-radius: 0;
|
||||
border-top-right-radius: 0;
|
||||
}
|
||||
|
||||
@media print {
|
||||
.tutorial-example a {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
/* manual.liquid ******************************************/
|
||||
section {
|
||||
padding-top: 24px;
|
||||
}
|
||||
|
||||
h3 code {
|
||||
border: 0;
|
||||
font-size: 20px;
|
||||
}
|
||||
|
||||
@media (max-width: 991px) {
|
||||
#navcolumn {
|
||||
/* Put nav column above manual content */
|
||||
position: relative !important;
|
||||
margin-bottom: 60px;
|
||||
}
|
||||
}
|
||||
@media (min-width: 992px) {
|
||||
#manualcontent {
|
||||
/* Put nav column left of manual content */
|
||||
padding-left: 280px;
|
||||
}
|
||||
}
|
||||
.nav-pills {
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
.nav-pills li a {
|
||||
padding: 8px 12px;
|
||||
}
|
||||
|
||||
.manual-example table {
|
||||
border-top: 1px solid #E5E5E5;
|
||||
}
|
||||
.manual-example table td {
|
||||
white-space: pre-wrap;
|
||||
font-family: Monaco, Menlo, Consolas, "Courier New", monospace;
|
||||
}
|
||||
.manual-example table td.jqprogram {
|
||||
font-weight: bold;
|
||||
}
|
||||
.manual-example table th {
|
||||
text-align: right;
|
||||
padding-right: 10px;
|
||||
}
|
||||
|
||||
@media print {
|
||||
#navcolumn {
|
||||
display: none !important;
|
||||
}
|
||||
|
||||
.manual-example {
|
||||
display: block !important;
|
||||
height: auto !important;
|
||||
}
|
||||
|
||||
.jqplay-btn {
|
||||
display: none !important;
|
||||
}
|
||||
}
|
||||
/* shared/_footer.liquid **********************************/
|
||||
footer {
|
||||
background-color: #F5F5F5;
|
||||
padding: 20px 0;
|
||||
margin-top: 40px;
|
||||
color: #999999;
|
||||
text-align: center;
|
||||
}
|
||||
footer p {
|
||||
margin: 8px 0;
|
||||
}
|
||||
|
||||
/* typeahead **********************************************/
|
||||
.twitter-typeahead {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.tt-menu {
|
||||
width: 100%;
|
||||
background-color: #fff;
|
||||
padding: 8px 0;
|
||||
border: 1px solid #ccc;
|
||||
border: 1px solid rgba(0, 0, 0, 0.2);
|
||||
-webkit-border-radius: 8px;
|
||||
-moz-border-radius: 8px;
|
||||
border-radius: 8px;
|
||||
-webkit-box-shadow: 0 5px 10px rgba(0, 0, 0, 0.2);
|
||||
-moz-box-shadow: 0 5px 10px rgba(0, 0, 0, 0.2);
|
||||
box-shadow: 0 5px 10px rgba(0, 0, 0, 0.2);
|
||||
}
|
||||
|
||||
.tt-suggestion {
|
||||
padding: 3px 20px;
|
||||
}
|
||||
.tt-suggestion:hover {
|
||||
cursor: pointer;
|
||||
color: #fff;
|
||||
background-color: #446e9b;
|
||||
}
|
||||
.tt-suggestion.tt-cursor {
|
||||
color: #fff;
|
||||
background-color: #446e9b;
|
||||
}
|
||||
.tt-suggestion p {
|
||||
margin: 0;
|
||||
}
|
||||
@@ -44,7 +44,7 @@ $(function() {
|
||||
$.each($('.manual-example table'), function(index, value) {
|
||||
$value = $(value)
|
||||
var j = $value.find('tr:nth-child(2) td:first').text();
|
||||
var q = $value.find('.jqprogram').text().replace(/^jq /, '').replace(/^'(.+)'$/, '$1');
|
||||
var q = $value.find('.jqprogram').text().replace(/^jq /, '').replace(/(\r\n|\n|\r)/gm," ").replace(/^'(.+)'$/, '$1');
|
||||
var url = 'https://jqplay.org/jq?q=' + encodeURIComponent(q) +'&j=' + encodeURIComponent(j)
|
||||
var $last_tr = $value.find('tr:last');
|
||||
$last_tr.after('<tr class="jqplay-btn"><th><a href="' + url + '" class="btn btn-primary btn-sm">Run</a></th><th></th></tr><tr><th></th><th></th></tr>');
|
||||
|
||||
@@ -1,26 +1,26 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
{% include "shared/head" %}
|
||||
{% include "shared/_head.html.j2" %}
|
||||
|
||||
<body id="{{slug}}">
|
||||
{% include "shared/navbar" %}
|
||||
{% include "shared/_navbar.html.j2" %}
|
||||
|
||||
<div class="container">
|
||||
<div class="row">
|
||||
<h1>{{headline}}</h1>
|
||||
{% for item in body %}
|
||||
{% if item.text %}
|
||||
{{ item.text | replace: '$JQ_VERSION', jq_version | markdownify }}
|
||||
{{ item.text | replace('$JQ_VERSION', jq_version) | markdownify }}
|
||||
{% endif %}
|
||||
|
||||
{% if item.command %}
|
||||
{% capture resultID %}{{ "result" | unique}}{% endcapture %}
|
||||
{% set resultID = unique_id() %}
|
||||
<div class="tutorial-example">
|
||||
<div class="accordion-heading">
|
||||
<pre>{{item.command}}</pre>
|
||||
<a data-toggle="collapse" href="#{{resultID}}">Show result</a>
|
||||
<a data-toggle="collapse" href="#result{{resultID}}">Show result</a>
|
||||
</div>
|
||||
<div id="{{resultID}}" class="accordion-body collapse">
|
||||
<div id="result{{resultID}}" class="accordion-body collapse">
|
||||
<pre>{{item.result}}</pre>
|
||||
</div>
|
||||
</div>
|
||||
@@ -29,6 +29,6 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{% include "shared/footer" %}
|
||||
{% include "shared/_footer.html.j2" %}
|
||||
</body>
|
||||
</html>
|
||||
@@ -1,9 +1,9 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
{% include "shared/head" %}
|
||||
{% include "shared/_head.html.j2" %}
|
||||
|
||||
<body id="{{slug}}">
|
||||
{% include "shared/navbar" %}
|
||||
<body id="{{ slug }}">
|
||||
{% include "shared/_navbar.html.j2" %}
|
||||
|
||||
<div class="container">
|
||||
<div class="row">
|
||||
@@ -20,14 +20,14 @@
|
||||
<div class="btn-toolbar" role="toolbar">
|
||||
<div class="btn-group" role="group">
|
||||
<a class="btn btn-primary dropdown-toggle" data-toggle="dropdown" href="#">
|
||||
Download jq 1.5
|
||||
Download jq 1.6
|
||||
<span class="caret"></span>
|
||||
</a>
|
||||
<ul class="dropdown-menu">
|
||||
<li><a href="https://github.com/stedolan/jq/releases/download/jq-1.5/jq-linux64">Linux (64-bit)</a></li>
|
||||
<li><a href="https://github.com/stedolan/jq/releases/download/jq-1.5/jq-osx-amd64">OS X (64-bit)</a></li>
|
||||
<li><a href="https://github.com/stedolan/jq/releases/download/jq-1.5/jq-win64.exe">Windows (64-bit)</a></li>
|
||||
<li><a href="/jq/download/">Other platforms, older versions, and source</a></li>
|
||||
<li><a href="https://github.com/stedolan/jq/releases/download/jq-1.6/jq-linux64">Linux (64-bit)</a></li>
|
||||
<li><a href="https://github.com/stedolan/jq/releases/download/jq-1.6/jq-osx-amd64">OS X (64-bit)</a></li>
|
||||
<li><a href="https://github.com/stedolan/jq/releases/download/jq-1.6/jq-win64.exe">Windows (64-bit)</a></li>
|
||||
<li><a href="{{root}}/download/">Other platforms, older versions, and source</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
@@ -68,6 +68,6 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{% include "shared/footer" %}
|
||||
{% include "shared/_footer.html.j2" %}
|
||||
</body>
|
||||
</html>
|
||||
+16
-16
@@ -1,9 +1,9 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
{% include "shared/head" %}
|
||||
{% include "shared/_head.html.j2" %}
|
||||
|
||||
<body id="{{slug}}" data-spy="scroll" data-target="#navcolumn" data-offset="100">
|
||||
{% include "shared/navbar" %}
|
||||
{% include "shared/_navbar.html.j2" %}
|
||||
|
||||
<div class="container">
|
||||
<div class="row">
|
||||
@@ -32,41 +32,41 @@
|
||||
{% for section in sections %}
|
||||
<section id="{{section.title | section_id}}">
|
||||
<h2>{{section.title}}</h2>
|
||||
{{section.body | markdownify}}
|
||||
{{ (section.body | markdownify) if section.body }}
|
||||
{% for entry in section.entries %}
|
||||
<section id="{{entry.title | entry_id}}">
|
||||
<h3>
|
||||
{{entry.title | markdownify | no_paragraph}}
|
||||
{{entry.title | markdownify | no_paragraph }}
|
||||
{% if entry.subtitle %}<small>{{entry.subtitle}}</small>{% endif %}
|
||||
</h3>
|
||||
{{entry.body | markdownify}}
|
||||
|
||||
{% if entry.examples %}
|
||||
<div>
|
||||
{% capture exampleID %}{{ "" | unique }}{% endcapture %}
|
||||
{% set exampleID = unique_id() %}
|
||||
<a data-toggle="collapse" href="#example{{exampleID}}">
|
||||
<i class="glyphicon glyphicon-chevron-right"></i>
|
||||
{% if entry.examples[1] %}Examples{%else%}Example{%endif%}
|
||||
{% if entry.examples | length > 1 %}Examples{%else%}Example{%endif%}
|
||||
</a>
|
||||
<div id="example{{exampleID}}" class="manual-example collapse">
|
||||
{% for example in entry.examples %}
|
||||
<table>
|
||||
<tr><th></th><td class="jqprogram">jq '{{example.program | escape}}'</td></tr>
|
||||
<tr><th>Input</th><td>{{example.input | escape}}</td></tr>
|
||||
{% unless example.output[0] %}
|
||||
<tr><th></th><td class="jqprogram">jq '{{ example.program }}'</td></tr>
|
||||
<tr><th>Input</th><td>{{ example.input }}</td></tr>
|
||||
{% if not example.output[0] %}
|
||||
<tr>
|
||||
<th>Output</th>
|
||||
<td><i>none</i></td>
|
||||
</tr>
|
||||
{% endunless %}
|
||||
{% endif %}
|
||||
{% for output in example.output %}
|
||||
<tr>
|
||||
{% if forloop.index == 1 %}
|
||||
{% if loop.first %}
|
||||
<th>Output</th>
|
||||
{% else %}
|
||||
<th></th>
|
||||
{% endif %}
|
||||
<td>{{output | escape}}</td>
|
||||
<td>{{ output }}</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</table>
|
||||
@@ -82,15 +82,15 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{% include "shared/footer" %}
|
||||
{% include "shared/_footer.html.j2" %}
|
||||
<script>
|
||||
var section_map = {
|
||||
{% for section in sections %}
|
||||
{% for entry in section.entries %}
|
||||
{{entry.title | search_id | json}} : {{entry.title | entry_id | json}},
|
||||
{{entry.title | search_id | tojson }} : {{entry.title | entry_id | tojson }},
|
||||
{% endfor %}
|
||||
{{section.title | search_id | json}} : {{section.title | section_id | json}}
|
||||
{% unless forloop.last %},{% endunless %}
|
||||
{{section.title | search_id | tojson }} : {{section.title | section_id | tojson }}
|
||||
{% if not loop.last %},{% endif %}
|
||||
{% endfor %}
|
||||
};
|
||||
</script>
|
||||
+1
-1
@@ -1,6 +1,6 @@
|
||||
<footer>
|
||||
<div class="container">
|
||||
<p>This website is made with <a href="http://www.tinytree.info">Bonsai</a> and <a href="http://getbootstrap.com">Bootstrap</a>, themed with <a href="https://bootswatch.com">Bootswatch</a>.</p>
|
||||
<p>This website is made with <a href="http://getbootstrap.com">Bootstrap</a>, themed with <a href="https://bootswatch.com">Bootswatch</a>.</p>
|
||||
<p>jq is licensed under the MIT license (code) and the <a href="https://creativecommons.org/licenses/by/3.0/">CC-BY-3.0</a> license (docs).</p>
|
||||
</div>
|
||||
</footer>
|
||||
@@ -7,9 +7,9 @@
|
||||
<title>{{headline}}</title>
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
|
||||
<link rel="canonical" href="{{url}}{{permalink | remove:'/index'}}" />
|
||||
<link rel="canonical" href="{{ url }}{{ permalink | replace('/index','') }}" />
|
||||
<link href="https://maxcdn.bootstrapcdn.com/bootswatch/3.3.5/spacelab/bootstrap.min.css" rel="stylesheet" integrity="sha256-j7Dtnd7ZjexEiPNbscbopFn9+Cs0b3TLipKsWAPHZIM= sha512-RFhfi6P8zWMAJrEGU+CPjuxPh3r/UUBGqQ+/o6WKPIVZmQqeOipGotH2ihRULuQ8wsMBoK15TSZqc/7VYWyuIw==" crossorigin="anonymous">
|
||||
<link rel="stylesheet" href="{{root}}/css/base.css" type="text/css">
|
||||
<link rel="stylesheet" href="{{ root }}/css/base.css" type="text/css">
|
||||
<!--[if lt IE 9]>
|
||||
<script src="https://oss.maxcdn.com/html5shiv/3.7.2/html5shiv.min.js"></script>
|
||||
<script src="https://oss.maxcdn.com/respond/1.4.2/respond.min.js"></script>
|
||||
+2
-2
@@ -13,8 +13,8 @@
|
||||
<div class="navbar-collapse collapse" id="nav-collapse">
|
||||
<ul class="nav navbar-nav">
|
||||
{% for item in navigation %}
|
||||
<li {% if item.permalink == permalink %} class="active" {% endif %}>
|
||||
<a href="{{root}}{{item.permalink}}">{{item.name}}</a>
|
||||
<li {% if item == permalink %} class="active" {% endif %}>
|
||||
<a href="{{root}}/{{item}}">{{item | capitalize}}</a>
|
||||
</li>
|
||||
{% endfor %}
|
||||
<li><a href="https://github.com/stedolan/jq/issues">Issues</a></li>
|
||||
+288
-105
@@ -1,7 +1,7 @@
|
||||
.\" generated with Ronn/v0.7.3
|
||||
.\" http://github.com/rtomayko/ronn/tree/0.7.3
|
||||
.
|
||||
.TH "JQ" "1" "February 2017" "" ""
|
||||
.TH "JQ" "1" "December 2017" "" ""
|
||||
.
|
||||
.SH "NAME"
|
||||
\fBjq\fR \- Command\-line JSON processor
|
||||
@@ -105,6 +105,9 @@ Use the given number of spaces (no more than 8) for indentation\.
|
||||
.IP
|
||||
By default, jq outputs colored JSON if writing to a terminal\. You can force it to produce color even if writing to a pipe or a file using \fB\-C\fR, and disable color with \fB\-M\fR\.
|
||||
.
|
||||
.IP
|
||||
Colors can be configured with the \fBJQ_COLORS\fR environment variable (see below)\.
|
||||
.
|
||||
.IP "\(bu" 4
|
||||
\fB\-\-ascii\-output\fR / \fB\-a\fR:
|
||||
.
|
||||
@@ -153,12 +156,18 @@ Prepend \fBdirectory\fR to the search list for modules\. If this option is used
|
||||
.IP
|
||||
Sets the exit status of jq to 0 if the last output values was neither \fBfalse\fR nor \fBnull\fR, 1 if the last output value was either \fBfalse\fR or \fBnull\fR, or 4 if no valid result was ever produced\. Normally jq exits with 2 if there was any usage problem or system error, 3 if there was a jq program compile error, or 0 if the jq program ran\.
|
||||
.
|
||||
.IP
|
||||
Another way to set the exit status is with the \fBhalt_error\fR builtin function\.
|
||||
.
|
||||
.IP "\(bu" 4
|
||||
\fB\-\-arg name value\fR:
|
||||
.
|
||||
.IP
|
||||
This option passes a value to the jq program as a predefined variable\. If you run jq with \fB\-\-arg foo bar\fR, then \fB$foo\fR is available in the program and has the value \fB"bar"\fR\. Note that \fBvalue\fR will be treated as a string, so \fB\-\-arg foo 123\fR will bind \fB$foo\fR to \fB"123"\fR\.
|
||||
.
|
||||
.IP
|
||||
Named arguments are also available to the jq program as \fB$ARGS\.named\fR\.
|
||||
.
|
||||
.IP "\(bu" 4
|
||||
\fB\-\-argjson name JSON\-text\fR:
|
||||
.
|
||||
@@ -181,6 +190,18 @@ Do not use\. Use \fB\-\-slurpfile\fR instead\.
|
||||
(This option is like \fB\-\-slurpfile\fR, but when the file has just one text, then that is used, else an array of texts is used as in \fB\-\-slurpfile\fR\.)
|
||||
.
|
||||
.IP "\(bu" 4
|
||||
\fB\-\-args\fR:
|
||||
.
|
||||
.IP
|
||||
Remaining arguments are positional string arguments\. These are available to the jq program as \fB$ARGS\.positional[]\fR\.
|
||||
.
|
||||
.IP "\(bu" 4
|
||||
\fB\-\-jsonargs\fR:
|
||||
.
|
||||
.IP
|
||||
Remaining arguments are positional JSON text arguments\. These are available to the jq program as \fB$ARGS\.positional[]\fR\.
|
||||
.
|
||||
.IP "\(bu" 4
|
||||
\fB\-\-run\-tests [filename]\fR:
|
||||
.
|
||||
.IP
|
||||
@@ -193,8 +214,8 @@ Be warned that this option can change backwards\-incompatibly\.
|
||||
.
|
||||
.SH "BASIC FILTERS"
|
||||
.
|
||||
.SS "\."
|
||||
The absolute simplest (and least interesting) filter is \fB\.\fR\. This is a filter that takes its input and produces it unchanged as output\.
|
||||
.SS "Identity: \."
|
||||
The absolute simplest filter is \fB\.\fR \. This is a filter that takes its input and produces it unchanged as output\. That is, this is the identity operator\.
|
||||
.
|
||||
.P
|
||||
Since jq by default pretty\-prints all output, this trivial program can be a useful way of formatting JSON output from, say, \fBcurl\fR\.
|
||||
@@ -211,14 +232,20 @@ jq \'\.\'
|
||||
.
|
||||
.IP "" 0
|
||||
.
|
||||
.SS "\.foo, \.foo\.bar"
|
||||
.SS "Object Identifier\-Index: \.foo, \.foo\.bar"
|
||||
The simplest \fIuseful\fR filter is \fB\.foo\fR\. When given a JSON object (aka dictionary or hash) as input, it produces the value at the key "foo", or null if there\'s none present\.
|
||||
.
|
||||
.P
|
||||
If the key contains special characters, you need to surround it with double quotes like this: \fB\."foo$"\fR\.
|
||||
A filter of the form \fB\.foo\.bar\fR is equivalent to \fB\.foo|\.bar\fR\.
|
||||
.
|
||||
.P
|
||||
A filter of the form \fB\.foo\.bar\fR is equivalent to \fB\.foo|\.bar\fR\.
|
||||
This syntax only works for simple, identifier\-like keys, that is, keys that are all made of alphanumeric characters and underscore, and which do not start with a digit\.
|
||||
.
|
||||
.P
|
||||
If the key contains special characters, you need to surround it with double quotes like this: \fB\."foo$"\fR, or else \fB\.["foo$"]\fR\.
|
||||
.
|
||||
.P
|
||||
For example \fB\.["foo::bar"]\fR and \fB\.["foo\.bar"]\fR work while \fB\.foo::bar\fR does not, and \fB\.foo\.bar\fR means \fB\.["foo"]\.["bar"]\fR\.
|
||||
.
|
||||
.IP "" 4
|
||||
.
|
||||
@@ -240,7 +267,7 @@ jq \'\.["foo"]\'
|
||||
.
|
||||
.IP "" 0
|
||||
.
|
||||
.SS "\.foo?"
|
||||
.SS "Optional Object Identifier\-Index: \.foo?"
|
||||
Just like \fB\.foo\fR, but does not output even an error when \fB\.\fR is not an array or an object\.
|
||||
.
|
||||
.IP "" 4
|
||||
@@ -267,20 +294,14 @@ jq \'[\.foo?]\'
|
||||
.
|
||||
.IP "" 0
|
||||
.
|
||||
.SS "\.[<string>], \.[2], \.[10:15]"
|
||||
You can also look up fields of an object using syntax like \fB\.["foo"]\fR (\.foo above is a shorthand version of this)\. This one works for arrays as well, if the key is an integer\. Arrays are zero\-based (like javascript), so \fB\.[2]\fR returns the third element of the array\.
|
||||
.SS "Generic Object Index: \.[<string>]"
|
||||
You can also look up fields of an object using syntax like \fB\.["foo"]\fR (\.foo above is a shorthand version of this, but only for identifier\-like strings)\.
|
||||
.
|
||||
.SS "Array Index: \.[2]"
|
||||
When the index value is an integer, \fB\.[<value>]\fR can index arrays\. Arrays are zero\-based, so \fB\.[2]\fR returns the third element\.
|
||||
.
|
||||
.P
|
||||
The \fB\.[10:15]\fR syntax can be used to return a subarray of an array or substring of a string\. The array returned by \fB\.[10:15]\fR will be of length 5, containing the elements from index 10 (inclusive) to index 15 (exclusive)\. Either index may be negative (in which case it counts backwards from the end of the array), or omitted (in which case it refers to the start or end of the array)\.
|
||||
.
|
||||
.P
|
||||
The \fB\.[2]\fR syntax can be used to return the element at the given index\. Negative indices are allowed, with \-1 referring to the last element, \-2 referring to the next to last element, and so on\.
|
||||
.
|
||||
.P
|
||||
The \fB\.foo\fR syntax only works for simply keys i\.e\. keys that are all alphanumeric characters\. \fB\.[<string>]\fR works with keys that contain special characters such as colons and dots\. For example \fB\.["foo::bar"]\fR and \fB\.["foo\.bar"]\fR work while \fB\.foo::bar\fR and \fB\.foo\.bar\fR would not\.
|
||||
.
|
||||
.P
|
||||
The \fB?\fR "operator" can also be used with the slice operator, as in \fB\.[10:15]?\fR, which outputs values where the inputs are slice\-able\.
|
||||
Negative indices are allowed, with \-1 referring to the last element, \-2 referring to the next to last element, and so on\.
|
||||
.
|
||||
.IP "" 4
|
||||
.
|
||||
@@ -294,6 +315,21 @@ jq \'\.[2]\'
|
||||
[{"name":"JSON", "good":true}, {"name":"XML", "good":false}]
|
||||
=> null
|
||||
|
||||
jq \'\.[\-2]\'
|
||||
[1,2,3]
|
||||
=> 2
|
||||
.
|
||||
.fi
|
||||
.
|
||||
.IP "" 0
|
||||
.
|
||||
.SS "Array/String Slice: \.[10:15]"
|
||||
The \fB\.[10:15]\fR syntax can be used to return a subarray of an array or substring of a string\. The array returned by \fB\.[10:15]\fR will be of length 5, containing the elements from index 10 (inclusive) to index 15 (exclusive)\. Either index may be negative (in which case it counts backwards from the end of the array), or omitted (in which case it refers to the start or end of the array)\.
|
||||
.
|
||||
.IP "" 4
|
||||
.
|
||||
.nf
|
||||
|
||||
jq \'\.[2:4]\'
|
||||
["a","b","c","d","e"]
|
||||
=> ["c", "d"]
|
||||
@@ -309,16 +345,12 @@ jq \'\.[:3]\'
|
||||
jq \'\.[\-2:]\'
|
||||
["a","b","c","d","e"]
|
||||
=> ["d", "e"]
|
||||
|
||||
jq \'\.[\-2]\'
|
||||
[1,2,3]
|
||||
=> 2
|
||||
.
|
||||
.fi
|
||||
.
|
||||
.IP "" 0
|
||||
.
|
||||
.SS "\.[]"
|
||||
.SS "Array/Object Value Iterator: \.[]"
|
||||
If you use the \fB\.[index]\fR syntax, but omit the index entirely, it will return \fIall\fR of the elements of an array\. Running \fB\.[]\fR with the input \fB[1,2,3]\fR will produce the numbers as three separate results, rather than as a single array\.
|
||||
.
|
||||
.P
|
||||
@@ -347,8 +379,8 @@ jq \'\.[]\'
|
||||
.SS "\.[]?"
|
||||
Like \fB\.[]\fR, but no errors will be output if \. is not an array or object\.
|
||||
.
|
||||
.SS ","
|
||||
If two filters are separated by a comma, then the input will be fed into both and there will be multiple outputs: first, all of the outputs produced by the left expression, and then all of the outputs produced by the right\. For instance, filter \fB\.foo, \.bar\fR, produces both the "foo" fields and "bar" fields as separate outputs\.
|
||||
.SS "Comma: ,"
|
||||
If two filters are separated by a comma, then the same input will be fed into both and the two filters\' output value streams will be concatenated in order: first, all of the outputs produced by the left expression, and then all of the outputs produced by the right\. For instance, filter \fB\.foo, \.bar\fR, produces both the "foo" fields and "bar" fields as separate outputs\.
|
||||
.
|
||||
.IP "" 4
|
||||
.
|
||||
@@ -370,7 +402,7 @@ jq \'\.[4,2]\'
|
||||
.
|
||||
.IP "" 0
|
||||
.
|
||||
.SS "|"
|
||||
.SS "Pipe: |"
|
||||
The | operator combines two filters by feeding the output(s) of the one on the left into the input of the one on the right\. It\'s pretty much the same as the Unix shell\'s pipe, if you\'re used to that\.
|
||||
.
|
||||
.P
|
||||
@@ -415,8 +447,8 @@ jq supports the same set of datatypes as JSON \- numbers, strings, booleans, arr
|
||||
.P
|
||||
Booleans, null, strings and numbers are written the same way as in javascript\. Just like everything else in jq, these simple values take an input and produce an output \- \fB42\fR is a valid jq expression that takes an input, ignores it, and returns 42 instead\.
|
||||
.
|
||||
.SS "Array construction \- []"
|
||||
As in JSON, \fB[]\fR is used to construct arrays, as in \fB[1,2,3]\fR\. The elements of the arrays can be any jq expression\. All of the results produced by all of the expressions are collected into one big array\. You can use it to construct an array out of a known quantity of values (as in \fB[\.foo, \.bar, \.baz]\fR) or to "collect" all the results of a filter into an array (as in \fB[\.items[]\.name]\fR)
|
||||
.SS "Array construction: []"
|
||||
As in JSON, \fB[]\fR is used to construct arrays, as in \fB[1,2,3]\fR\. The elements of the arrays can be any jq expression, including a pipeline\. All of the results produced by all of the expressions are collected into one big array\. You can use it to construct an array out of a known quantity of values (as in \fB[\.foo, \.bar, \.baz]\fR) or to "collect" all the results of a filter into an array (as in \fB[\.items[]\.name]\fR)
|
||||
.
|
||||
.P
|
||||
Once you understand the "," operator, you can look at jq\'s array syntax in a different light: the expression \fB[1,2,3]\fR is not using a built\-in syntax for comma\-separated arrays, but is instead applying the \fB[]\fR operator (collect results) to the expression 1,2,3 (which produces three different results)\.
|
||||
@@ -431,16 +463,23 @@ If you have a filter \fBX\fR that produces four results, then the expression \fB
|
||||
jq \'[\.user, \.projects[]]\'
|
||||
{"user":"stedolan", "projects": ["jq", "wikiflow"]}
|
||||
=> ["stedolan", "jq", "wikiflow"]
|
||||
|
||||
jq \'[ \.[] | \. * 2]\'
|
||||
[1, 2, 3]
|
||||
=> [2, 4, 6]
|
||||
.
|
||||
.fi
|
||||
.
|
||||
.IP "" 0
|
||||
.
|
||||
.SS "Objects \- {}"
|
||||
.SS "Object Construction: {}"
|
||||
Like JSON, \fB{}\fR is for constructing objects (aka dictionaries or hashes), as in: \fB{"a": 42, "b": 17}\fR\.
|
||||
.
|
||||
.P
|
||||
If the keys are "sensible" (all alphabetic characters), then the quotes can be left off\. The value can be any expression (although you may need to wrap it in parentheses if it\'s a complicated one), which gets applied to the {} expression\'s input (remember, all filters have an input and an output)\.
|
||||
If the keys are "identifier\-like", then the quotes can be left off, as in \fB{a:42, b:17}\fR\. Keys generated by expressions need to be parenthesized, e\.g\., \fB{("a"+"b"):59}\fR\.
|
||||
.
|
||||
.P
|
||||
The value can be any expression (although you may need to wrap it in parentheses if it\'s a complicated one), which gets applied to the {} expression\'s input (remember, all filters have an input and an output)\.
|
||||
.
|
||||
.IP "" 4
|
||||
.
|
||||
@@ -453,7 +492,7 @@ If the keys are "sensible" (all alphabetic characters), then the quotes can be l
|
||||
.IP "" 0
|
||||
.
|
||||
.P
|
||||
will produce the JSON object \fB{"foo": 42}\fR if given the JSON object \fB{"bar":42, "baz":43}\fR\. You can use this to select particular fields of an object: if the input is an object with "user", "title", "id", and "content" fields and you just want "user" and "title", you can write
|
||||
will produce the JSON object \fB{"foo": 42}\fR if given the JSON object \fB{"bar":42, "baz":43}\fR as its input\. You can use this to select particular fields of an object: if the input is an object with "user", "title", "id", and "content" fields and you just want "user" and "title", you can write
|
||||
.
|
||||
.IP "" 4
|
||||
.
|
||||
@@ -466,7 +505,7 @@ will produce the JSON object \fB{"foo": 42}\fR if given the JSON object \fB{"bar
|
||||
.IP "" 0
|
||||
.
|
||||
.P
|
||||
Because that\'s so common, there\'s a shortcut syntax: \fB{user, title}\fR\.
|
||||
Because that is so common, there\'s a shortcut syntax for it: \fB{user, title}\fR\.
|
||||
.
|
||||
.P
|
||||
If one of the expressions produces multiple results, multiple dictionaries will be produced\. If the input\'s
|
||||
@@ -542,10 +581,28 @@ jq \'{(\.user): \.titles}\'
|
||||
.
|
||||
.IP "" 0
|
||||
.
|
||||
.SS "Recursive Descent: \.\."
|
||||
Recursively descends \fB\.\fR, producing every value\. This is the same as the zero\-argument \fBrecurse\fR builtin (see below)\. This is intended to resemble the XPath \fB//\fR operator\. Note that \fB\.\.a\fR does not work; use \fB\.\.|\.a\fR instead\. In the example below we use \fB\.\.|\.a?\fR to find all the values of object keys "a" in any object found "below" \fB\.\fR\.
|
||||
.
|
||||
.P
|
||||
This is particularly useful in conjunction with \fBpath(EXP)\fR (also see below) and the \fB?\fR operator\.
|
||||
.
|
||||
.IP "" 4
|
||||
.
|
||||
.nf
|
||||
|
||||
jq \'\.\.|\.a?\'
|
||||
[[{"a":1}]]
|
||||
=> 1
|
||||
.
|
||||
.fi
|
||||
.
|
||||
.IP "" 0
|
||||
.
|
||||
.SH "BUILTIN OPERATORS AND FUNCTIONS"
|
||||
Some jq operator (for instance, \fB+\fR) do different things depending on the type of their arguments (arrays, numbers, etc\.)\. However, jq never does implicit type conversions\. If you try to add a string to an object you\'ll get an error message and no result\.
|
||||
.
|
||||
.SS "Addition \- +"
|
||||
.SS "Addition: +"
|
||||
The operator \fB+\fR takes two filters, applies them both to the same input, and adds the results together\. What "adding" means depends on the types involved:
|
||||
.
|
||||
.IP "\(bu" 4
|
||||
@@ -593,7 +650,7 @@ jq \'{a: 1} + {b: 2} + {c: 3} + {a: 42}\'
|
||||
.
|
||||
.IP "" 0
|
||||
.
|
||||
.SS "Subtraction \- \-"
|
||||
.SS "Subtraction: \-"
|
||||
As well as normal arithmetic subtraction on numbers, the \fB\-\fR operator can be used on arrays to remove all occurrences of the second array\'s elements from the first array\.
|
||||
.
|
||||
.IP "" 4
|
||||
@@ -612,7 +669,7 @@ jq \'\. \- ["xml", "yaml"]\'
|
||||
.
|
||||
.IP "" 0
|
||||
.
|
||||
.SS "Multiplication, division, modulo \- *, /, and %"
|
||||
.SS "Multiplication, division, modulo: *, /, and %"
|
||||
These infix operators behave as expected when given two numbers\. Division by zero raises an error\. \fBx % y\fR computes x modulo y\.
|
||||
.
|
||||
.P
|
||||
@@ -970,7 +1027,19 @@ jq \'[1,2,empty,3]\'
|
||||
.IP "" 0
|
||||
.
|
||||
.SS "error(message)"
|
||||
Produces an error, just like \fB\.a\fR applied to values other than null and objects would, but with the given message as the error\'s value\.
|
||||
Produces an error, just like \fB\.a\fR applied to values other than null and objects would, but with the given message as the error\'s value\. Errors can be caught with try/catch; see below\.
|
||||
.
|
||||
.SS "halt"
|
||||
Stops the jq program with no further outputs\. jq will exit with exit status \fB0\fR\.
|
||||
.
|
||||
.SS "halt_error, halt_error(exit_code)"
|
||||
Stops the jq program with no further outputs\. The input will be printed on \fBstderr\fR as raw output (i\.e\., strings will not have double quotes) with no decoration, not even a newline\.
|
||||
.
|
||||
.P
|
||||
The given \fBexit_code\fR (defaulting to \fB5\fR) will be jq\'s exit status\.
|
||||
.
|
||||
.P
|
||||
For example, \fB"Error: somthing went wrong\en"|halt_error(1)\fR\.
|
||||
.
|
||||
.SS "$__loc__"
|
||||
Produces an object with a "file" key and a "line" key, with the filename and line number where \fB$__loc__\fR occurs, as values\.
|
||||
@@ -1783,27 +1852,22 @@ jq \'walk( if type == "object" then with_entries( \.key |= sub( "^_+"; "") ) els
|
||||
.
|
||||
.IP "" 0
|
||||
.
|
||||
.SS "\.\."
|
||||
Short\-hand for \fBrecurse\fR without arguments\. This is intended to resemble the XPath \fB//\fR operator\. Note that \fB\.\.a\fR does not work; use \fB\.\.|a\fR instead\. In the example below we use \fB\.\.|\.a?\fR to find all the values of object keys "a" in any object found "below" \fB\.\fR\.
|
||||
.SS "$ENV, env"
|
||||
\fB$ENV\fR is an object representing the environment variables as set when the jq program started\.
|
||||
.
|
||||
.P
|
||||
\fBenv\fR outputs an object representing jq\'s current environment\.
|
||||
.
|
||||
.P
|
||||
At the moment there is no builtin for setting environment variables\.
|
||||
.
|
||||
.IP "" 4
|
||||
.
|
||||
.nf
|
||||
|
||||
jq \'\.\.|\.a?\'
|
||||
[[{"a":1}]]
|
||||
=> 1
|
||||
.
|
||||
.fi
|
||||
.
|
||||
.IP "" 0
|
||||
.
|
||||
.SS "env"
|
||||
Outputs an object representing jq\'s environment\.
|
||||
.
|
||||
.IP "" 4
|
||||
.
|
||||
.nf
|
||||
jq \'$ENV\.PAGER\'
|
||||
null
|
||||
=> "less"
|
||||
|
||||
jq \'env\.PAGER\'
|
||||
null
|
||||
@@ -1940,6 +2004,12 @@ The input is escaped suitable for use in a command\-line for a POSIX shell\. If
|
||||
.IP
|
||||
The input is converted to base64 as specified by RFC 4648\.
|
||||
.
|
||||
.TP
|
||||
\fB@base64d\fR:
|
||||
.
|
||||
.IP
|
||||
The inverse of \fB@base64\fR, input is decoded as specified by RFC 4648\. Note: If the decoded string is not UTF\-8, the results are undefined\.
|
||||
.
|
||||
.P
|
||||
This syntax can be combined with string interpolation in a useful way\. You can follow a \fB@foo\fR token with a string literal\. The contents of the string literal will \fInot\fR be escaped\. However, all interpolations made inside that string literal will be escaped\. For instance,
|
||||
.
|
||||
@@ -1980,6 +2050,14 @@ jq \'@html\'
|
||||
jq \'@sh "echo \e(\.)"\'
|
||||
"O\'Hara\'s Ale"
|
||||
=> "echo \'O\'\e\e\'\'Hara\'\e\e\'\'s Ale\'"
|
||||
|
||||
jq \'@base64\'
|
||||
"This is a message"
|
||||
=> "VGhpcyBpcyBhIG1lc3NhZ2U="
|
||||
|
||||
jq \'@base64d\'
|
||||
"VGhpcyBpcyBhIG1lc3NhZ2U="
|
||||
=> "This is a message"
|
||||
.
|
||||
.fi
|
||||
.
|
||||
@@ -2001,10 +2079,13 @@ The \fBtodate\fR builtin is an alias for \fBtodateiso8601\fR\.
|
||||
The \fBnow\fR builtin outputs the current time, in seconds since the Unix epoch\.
|
||||
.
|
||||
.P
|
||||
Low\-level jq interfaces to the C\-library time functions are also provided: \fBstrptime\fR, \fBstrftime\fR, \fBmktime\fR, and \fBgmtime\fR\. Refer to your host operating system\'s documentation for the format strings used by \fBstrptime\fR and \fBstrftime\fR\. Note: these are not necessarily stable interfaces in jq, particularly as to their localization functionality\.
|
||||
Low\-level jq interfaces to the C\-library time functions are also provided: \fBstrptime\fR, \fBstrftime\fR, \fBstrflocaltime\fR, \fBmktime\fR, \fBgmtime\fR, and \fBlocaltime\fR\. Refer to your host operating system\'s documentation for the format strings used by \fBstrptime\fR and \fBstrftime\fR\. Note: these are not necessarily stable interfaces in jq, particularly as to their localization functionality\.
|
||||
.
|
||||
.P
|
||||
The \fBgmtime\fR builtin consumes a number of seconds since the Unix epoch and outputs a "broken down time" representation of time as an array of numbers representing (in this order): the year, the month (zero\-based), the day of the month, the hour of the day, the minute of the hour, the second of the minute, the day of the week, and the day of the year \-\- all one\-based unless otherwise stated\.
|
||||
The \fBgmtime\fR builtin consumes a number of seconds since the Unix epoch and outputs a "broken down time" representation of Greenwhich Meridian time as an array of numbers representing (in this order): the year, the month (zero\-based), the day of the month (one\-based), the hour of the day, the minute of the hour, the second of the minute, the day of the week, and the day of the year \-\- all one\-based unless otherwise stated\. The day of the week number may be wrong on some systems for dates before March 1st 1900, or after December 31 2099\.
|
||||
.
|
||||
.P
|
||||
The \fBlocaltime\fR builtin works like the \fBgmtime\fR builtin, but using the local timezone setting\.
|
||||
.
|
||||
.P
|
||||
The \fBmktime\fR builtin consumes "broken down time" representations of time output by \fBgmtime\fR and \fBstrptime\fR\.
|
||||
@@ -2013,13 +2094,13 @@ The \fBmktime\fR builtin consumes "broken down time" representations of time out
|
||||
The \fBstrptime(fmt)\fR builtin parses input strings matching the \fBfmt\fR argument\. The output is in the "broken down time" representation consumed by \fBgmtime\fR and output by \fBmktime\fR\.
|
||||
.
|
||||
.P
|
||||
The \fBstrftime(fmt)\fR builtin formats a time with the given format\.
|
||||
The \fBstrftime(fmt)\fR builtin formats a time (GMT) with the given format\. The \fBstrflocaltime\fR does the same, but using the local timezone setting\.
|
||||
.
|
||||
.P
|
||||
The format strings for \fBstrptime\fR and \fBstrftime\fR are described in typical C library documentation\. The format string for ISO 8601 datetime is \fB"%Y\-%m\-%dT%H:%M:%SZ"\fR\.
|
||||
.
|
||||
.P
|
||||
jq may not support some or all of this date functionality on some systems\.
|
||||
jq may not support some or all of this date functionality on some systems\. In particular, the \fB%u\fR and \fB%j\fR specifiers for \fBstrptime(fmt)\fR are not supported on macOS\.
|
||||
.
|
||||
.IP "" 4
|
||||
.
|
||||
@@ -2080,6 +2161,9 @@ IN(source; s):
|
||||
.IP
|
||||
This builtin outputs \fBtrue\fR if any value in the source stream appears in the second stream, otherwise it outputs \fBfalse\fR\.
|
||||
.
|
||||
.SS "builtins"
|
||||
Returns a list of all builtin functions in the format \fBname/arity\fR\. Since functions with the same name but different arities are considered separate functions, \fBall/0\fR, \fBall/1\fR, and \fBall/2\fR would all be present in the list\.
|
||||
.
|
||||
.SH "CONDITIONALS AND COMPARISONS"
|
||||
.
|
||||
.SS "==, !="
|
||||
@@ -2179,7 +2263,7 @@ jq \'[true, false | not]\'
|
||||
.
|
||||
.IP "" 0
|
||||
.
|
||||
.SS "Alternative operator \- //"
|
||||
.SS "Alternative operator: //"
|
||||
A filter of the form \fBa // b\fR produces the same results as \fBa\fR, if \fBa\fR produces results other than \fBfalse\fR and \fBnull\fR\. Otherwise, \fBa // b\fR produces the same results as \fBb\fR\.
|
||||
.
|
||||
.P
|
||||
@@ -2294,7 +2378,7 @@ break $out
|
||||
.P
|
||||
because no label \fB$out\fR is visible\.
|
||||
.
|
||||
.SS "? operator"
|
||||
.SS "Error Suppression / Optional Operator: ?"
|
||||
The \fB?\fR operator, used as \fBEXP?\fR, is shorthand for \fBtry EXP\fR\.
|
||||
.
|
||||
.IP "" 4
|
||||
@@ -2514,7 +2598,7 @@ Some minimal I/O support (besides reading JSON from standard input, and writing
|
||||
.P
|
||||
Finally, there is a module/library system\.
|
||||
.
|
||||
.SS "Variables"
|
||||
.SS "Variable / Symbolic Binding Operator: \.\.\. as $identifier | \.\.\."
|
||||
In jq, all filters have an input and an output, so manual plumbing is not necessary to pass a value from one part of a program to the next\. Many expressions, for instance \fBa + b\fR, pass their input to two distinct subexpressions (here \fBa\fR and \fBb\fR are both passed the same input), so variables aren\'t usually necessary in order to use a value twice\.
|
||||
.
|
||||
.P
|
||||
@@ -2777,25 +2861,25 @@ For each result that \fB\.[]\fR produces, \fB\. + $item\fR is run to accumulate
|
||||
0 | (3 as $item | \. + $item) |
|
||||
(2 as $item | \. + $item) |
|
||||
(1 as $item | \. + $item)
|
||||
|
||||
jq \'reduce \.[] as $item (0; \. + $item)\'
|
||||
[10,2,5,3]
|
||||
=> 20
|
||||
.
|
||||
.fi
|
||||
.
|
||||
.IP "" 0
|
||||
.
|
||||
.P
|
||||
If the reduction update expression outputs \fBempty\fR (that is, no values), then the reduction state is left as\-is\. For example, \fBreduce range(4) as $n ({}; if \.==2 then empty else \.[$n|tostring] |= $n)\fR will produce \fB{"0":0,"1":1,"3":3}\fR\.
|
||||
.SS "isempty(exp)"
|
||||
Returns true if \fBexp\fR produces no outputs, false otherwise\.
|
||||
.
|
||||
.IP "" 4
|
||||
.
|
||||
.nf
|
||||
|
||||
jq \'reduce \.[] as $item (0; \. + $item)\'
|
||||
[10,2,5,3]
|
||||
=> 20
|
||||
|
||||
jq \'reduce \.[] as $n ([]; if $n%2==0 then empty else \. + [$n] end)\'
|
||||
[0,1,2,3,4,5]
|
||||
=> [1,3,5]
|
||||
jq \'isempty(empty)\'
|
||||
null
|
||||
=> true
|
||||
.
|
||||
.fi
|
||||
.
|
||||
@@ -2955,7 +3039,7 @@ See your system\'s manual for more information on each of these\.
|
||||
At this time jq has minimal support for I/O, mostly in the form of control over when inputs are read\. Two builtins functions are provided for this, \fBinput\fR and \fBinputs\fR, that read from the same sources (e\.g\., \fBstdin\fR, files named on the command\-line) as jq itself\. These two builtins, and jq\'s own reading actions, can be interleaved with each other\.
|
||||
.
|
||||
.P
|
||||
One builtin provides minimal output capabilities, \fBdebug\fR\. (Recall that a jq program\'s output values are always output as JSON texts on \fBstdout\fR\.) The \fBdebug\fR builtin can have application\-specific behavior, such as for executables that use the libjq C API but aren\'t the jq executable itself\.
|
||||
Two builtins provide minimal output capabilities, \fBdebug\fR, and \fBstderr\fR\. (Recall that a jq program\'s output values are always output as JSON texts on \fBstdout\fR\.) The \fBdebug\fR builtin can have application\-specific behavior, such as for executables that use the libjq C API but aren\'t the jq executable itself\. The \fBstderr\fR builtin outputs its input in raw mode to stder with no additional decoration, not even a newline\.
|
||||
.
|
||||
.P
|
||||
Most jq builtins are referentially transparent, and yield constant and repeatable value streams when applied to constant inputs\. This is not true of I/O builtins\.
|
||||
@@ -2972,6 +3056,9 @@ This is primarily useful for reductions over a program\'s inputs\.
|
||||
.SS "debug"
|
||||
Causes a debug message based on the input value to be produced\. The jq executable wraps the input value with \fB["DEBUG:", <input\-value>]\fR and prints that and a newline on stderr, compactly\. This may change in the future\.
|
||||
.
|
||||
.SS "stderr"
|
||||
Prints its input in raw and compact mode to stderr with no additional decoration, not even a newline\.
|
||||
.
|
||||
.SS "input_filename"
|
||||
Returns the name of the file whose input is currently being filtered\. Note that this will not work well unless jq is running in a UTF\-8 locale\.
|
||||
.
|
||||
@@ -3042,49 +3129,28 @@ jq \'\. as $dot|fromstream($dot|tostream)|\.==$dot\'
|
||||
Assignment works a little differently in jq than in most programming languages\. jq doesn\'t distinguish between references to and copies of something \- two objects or arrays are either equal or not equal, without any further notion of being "the same object" or "not the same object"\.
|
||||
.
|
||||
.P
|
||||
If an object has two fields which are arrays, \fB\.foo\fR and \fB\.bar\fR, and you append something to \fB\.foo\fR, then \fB\.bar\fR will not get bigger\. Even if you\'ve just set \fB\.bar = \.foo\fR\. If you\'re used to programming in languages like Python, Java, Ruby, Javascript, etc\. then you can think of it as though jq does a full deep copy of every object before it does the assignment (for performance, it doesn\'t actually do that, but that\'s the general idea)\.
|
||||
.
|
||||
.P
|
||||
All the assignment operators in jq have path expressions on the left\-hand side\.
|
||||
.
|
||||
.P
|
||||
Values in jq are always immutable\. Internally, assignment works by using a reduction to compute new, replacement values for \fB\.\fR that have had all the desired assignments applied to \fB\.\fR, then outputting the modified value\. This might be made clear by this example: \fB{a:{b:{c:1}}} | (\.a\.b|=3), \.\fR\. This will output \fB{"a":{"b":3}}\fR and \fB{"a":{"b":{"c":1}}}\fR because the last sub\-expression, \fB\.\fR, sees the original value, not the modified value\.
|
||||
.
|
||||
.SS "="
|
||||
The filter \fB\.foo = 1\fR will take as input an object and produce as output an object with the "foo" field set to 1\. There is no notion of "modifying" or "changing" something in jq \- all jq values are immutable\. For instance,
|
||||
.
|
||||
.P
|
||||
\&\.foo = \.bar | \.foo\.baz = 1
|
||||
.
|
||||
.P
|
||||
will not have the side\-effect of setting \.bar\.baz to be set to 1, as the similar\-looking program in Javascript, Python, Ruby or other languages would\. Unlike these languages (but like Haskell and some other functional languages), there is no notion of two arrays or objects being "the same array" or "the same object"\. They can be equal, or not equal, but if we change one of them in no circumstances will the other change behind our backs\.
|
||||
If an object has two fields which are arrays, \fB\.foo\fR and \fB\.bar\fR, and you append something to \fB\.foo\fR, then \fB\.bar\fR will not get bigger, even if you\'ve previously set \fB\.bar = \.foo\fR\. If you\'re used to programming in languages like Python, Java, Ruby, Javascript, etc\. then you can think of it as though jq does a full deep copy of every object before it does the assignment (for performance it doesn\'t actually do that, but that\'s the general idea)\.
|
||||
.
|
||||
.P
|
||||
This means that it\'s impossible to build circular values in jq (such as an array whose first element is itself)\. This is quite intentional, and ensures that anything a jq program can produce can be represented in JSON\.
|
||||
.
|
||||
.P
|
||||
Note that the left\-hand side of \'=\' refers to a value in \fB\.\fR\. Thus \fB$var\.foo = 1\fR won\'t work as expected (\fB$var\.foo\fR is not a valid or useful path expression in \fB\.\fR); use \fB$var | \.foo = 1\fR instead\.
|
||||
All the assignment operators in jq have path expressions on the left\-hand side (LHS)\. The right\-hand side (RHS) procides values to set to the paths named by the LHS path expressions\.
|
||||
.
|
||||
.P
|
||||
If the right\-hand side of \'=\' produces multiple values, then for each such value jq will set the paths on the left\-hand side to the value and then it will output the modified \fB\.\fR\. For example, \fB(\.a,\.b)=range(2)\fR outputs \fB{"a":0,"b":0}\fR, then \fB{"a":1,"b":1}\fR\. The "update" assignment forms (see below) do not do this\.
|
||||
Values in jq are always immutable\. Internally, assignment works by using a reduction to compute new, replacement values for \fB\.\fR that have had all the desired assignments applied to \fB\.\fR, then outputting the modified value\. This might be made clear by this example: \fB{a:{b:{c:1}}} | (\.a\.b|=3), \.\fR\. This will output \fB{"a":{"b":3}}\fR and \fB{"a":{"b":{"c":1}}}\fR because the last sub\-expression, \fB\.\fR, sees the original value, not the modified value\.
|
||||
.
|
||||
.P
|
||||
Most users will want to use modification assignment operators, such as \fB|=\fR or \fB+=\fR, rather than \fB=\fR\.
|
||||
.
|
||||
.P
|
||||
Note that the LHS of assignment operators refers to a value in \fB\.\fR\. Thus \fB$var\.foo = 1\fR won\'t work as expected (\fB$var\.foo\fR is not a valid or useful path expression in \fB\.\fR); use \fB$var | \.foo = 1\fR instead\.
|
||||
.
|
||||
.P
|
||||
Note too that \fB\.a,\.b=0\fR does not set \fB\.a\fR and \fB\.b\fR, but \fB(\.a,\.b)=0\fR sets both\.
|
||||
.
|
||||
.SS "|="
|
||||
As well as the assignment operator \'=\', jq provides the "update" operator \'|=\', which takes a filter on the right\-hand side and works out the new value for the property of \fB\.\fR being assigned to by running the old value through this expression\. For instance, \.foo |= \.+1 will build an object with the "foo" field set to the input\'s "foo" plus 1\.
|
||||
.
|
||||
.P
|
||||
This example should show the difference between \'=\' and \'|=\':
|
||||
.
|
||||
.P
|
||||
Provide input \'{"a": {"b": 10}, "b": 20}\' to the programs:
|
||||
.
|
||||
.P
|
||||
\&\.a = \.b \.a |= \.b
|
||||
.
|
||||
.P
|
||||
The former will set the "a" field of the input to the "b" field of the input, and produce the output {"a": 20}\. The latter will set the "a" field of the input to the "a" field\'s "b" field, producing {"a": 10}\.
|
||||
.SS "Update\-assignment: |="
|
||||
This is the "update" operator \'|=\'\. It takes a filter on the right\-hand side and works out the new value for the property of \fB\.\fR being assigned to by running the old value through this expression\. For instance, (\.foo, \.bar) |= \.+1 will build an object with the "foo" field set to the input\'s "foo" plus 1, and the "bar" field set to the input\'s "bar" plus 1\.
|
||||
.
|
||||
.P
|
||||
The left\-hand side can be any general path expression; see \fBpath()\fR\.
|
||||
@@ -3096,7 +3162,7 @@ Note that the left\-hand side of \'|=\' refers to a value in \fB\.\fR\. Thus \fB
|
||||
If the right\-hand side outputs no values (i\.e\., \fBempty\fR), then the left\-hand side path will be deleted, as with \fBdel(path)\fR\.
|
||||
.
|
||||
.P
|
||||
If the right\-hand side outputs multiple values, only the first one will be used (NOTE: it used to be that only the last one was used)\.
|
||||
If the right\-hand side outputs multiple values, only the first one will be used (COMPATIBILITY NOTE: in jq 1\.5 and earlier releases, it used to be that only the last one was used)\.
|
||||
.
|
||||
.IP "" 4
|
||||
.
|
||||
@@ -3110,8 +3176,8 @@ jq \'(\.\.|select(type=="boolean")) |= if \. then 1 else 0 end\'
|
||||
.
|
||||
.IP "" 0
|
||||
.
|
||||
.SS "+=, \-=, *=, /=, %=, //="
|
||||
jq has a few operators of the form \fBa op= b\fR, which are all equivalent to \fBa |= \. op b\fR\. So, \fB+= 1\fR can be used to increment values\.
|
||||
.SS "Arithmetic update\-assignment: +=, \-=, *=, /=, %=, //="
|
||||
jq has a few operators of the form \fBa op= b\fR, which are all equivalent to \fBa |= \. op b\fR\. So, \fB+= 1\fR can be used to increment values, being the same as \fB|= \. + 1\fR\.
|
||||
.
|
||||
.IP "" 4
|
||||
.
|
||||
@@ -3125,6 +3191,42 @@ jq \'\.foo += 1\'
|
||||
.
|
||||
.IP "" 0
|
||||
.
|
||||
.SS "Plain assignment: ="
|
||||
This is the plain assignment operator\. Unlike the others, the input to the right\-hand\-side (RHS) is the same as the input to the left\-hand\-side (LHS) rather than the value at the LHS path, and all values output by the RHS will be used (as shown below)\.
|
||||
.
|
||||
.P
|
||||
If the RHS of \'=\' produces multiple values, then for each such value jq will set the paths on the left\-hand side to the value and then it will output the modified \fB\.\fR\. For example, \fB(\.a,\.b)=range(2)\fR outputs \fB{"a":0,"b":0}\fR, then \fB{"a":1,"b":1}\fR\. The "update" assignment forms (see above) do not do this\.
|
||||
.
|
||||
.P
|
||||
This example should show the difference between \'=\' and \'|=\':
|
||||
.
|
||||
.P
|
||||
Provide input \'{"a": {"b": 10}, "b": 20}\' to the programs:
|
||||
.
|
||||
.P
|
||||
\&\.a = \.b
|
||||
.
|
||||
.P
|
||||
\&\.a |= \.b
|
||||
.
|
||||
.P
|
||||
The former will set the "a" field of the input to the "b" field of the input, and produce the output {"a": 20, "b": 20}\. The latter will set the "a" field of the input to the "a" field\'s "b" field, producing {"a": 10, "b": 20}\.
|
||||
.
|
||||
.P
|
||||
Another example of the difference between \'=\' and \'|=\':
|
||||
.
|
||||
.P
|
||||
null|(\.a,\.b)=range(3)
|
||||
.
|
||||
.P
|
||||
outputs \'{"a":0,"b":0}\', \'{"a":1,"b":1}\', and \'{"a":2,"b":2}\', while
|
||||
.
|
||||
.P
|
||||
null|(\.a,\.b)|=range(3)
|
||||
.
|
||||
.P
|
||||
outputs just \'{"a":0,"b":0}\'\.
|
||||
.
|
||||
.SS "Complex assignments"
|
||||
Lots more things are allowed on the left\-hand side of a jq assignment than in most languages\. We\'ve already seen simple field accesses on the left hand side, and it\'s no surprise that array accesses work just as well:
|
||||
.
|
||||
@@ -3259,6 +3361,87 @@ Takes a module name as input and outputs the module\'s metadata as an object, wi
|
||||
.P
|
||||
Programs can use this to query a module\'s metadata, which they could then use to, for example, search for, download, and install missing dependencies\.
|
||||
.
|
||||
.SH "COLORS"
|
||||
To configure alternative colors just set the \fBJQ_COLORS\fR environment variable to colon\-delimited list of partial terminal escape sequences like \fB"1;31"\fR, in this order:
|
||||
.
|
||||
.IP "\(bu" 4
|
||||
color for \fBnull\fR
|
||||
.
|
||||
.IP "\(bu" 4
|
||||
color for \fBfalse\fR
|
||||
.
|
||||
.IP "\(bu" 4
|
||||
color for \fBtrue\fR
|
||||
.
|
||||
.IP "\(bu" 4
|
||||
color for numbers
|
||||
.
|
||||
.IP "\(bu" 4
|
||||
color for strings
|
||||
.
|
||||
.IP "\(bu" 4
|
||||
color for arrays
|
||||
.
|
||||
.IP "\(bu" 4
|
||||
color for objects
|
||||
.
|
||||
.IP "" 0
|
||||
.
|
||||
.P
|
||||
The default color scheme is the same as setting \fB"JQ_COLORS=1;30:0;39:0;39:0;39:0;32:1;39:1;39"\fR\.
|
||||
.
|
||||
.P
|
||||
This is not a manual for VT100/ANSI escapes\. However, each of these color specifications should consist of two numbers separated by a semi\-colon, where the first number is one of these:
|
||||
.
|
||||
.IP "\(bu" 4
|
||||
1 (bright)
|
||||
.
|
||||
.IP "\(bu" 4
|
||||
2 (dim)
|
||||
.
|
||||
.IP "\(bu" 4
|
||||
4 (underscore)
|
||||
.
|
||||
.IP "\(bu" 4
|
||||
5 (blink)
|
||||
.
|
||||
.IP "\(bu" 4
|
||||
7 (reverse)
|
||||
.
|
||||
.IP "\(bu" 4
|
||||
8 (hidden)
|
||||
.
|
||||
.IP "" 0
|
||||
.
|
||||
.P
|
||||
and the second is one of these:
|
||||
.
|
||||
.IP "\(bu" 4
|
||||
30 (black)
|
||||
.
|
||||
.IP "\(bu" 4
|
||||
31 (red)
|
||||
.
|
||||
.IP "\(bu" 4
|
||||
32 (green)
|
||||
.
|
||||
.IP "\(bu" 4
|
||||
33 (yellow)
|
||||
.
|
||||
.IP "\(bu" 4
|
||||
34 (blue)
|
||||
.
|
||||
.IP "\(bu" 4
|
||||
35 (magenta)
|
||||
.
|
||||
.IP "\(bu" 4
|
||||
36 (cyan)
|
||||
.
|
||||
.IP "\(bu" 4
|
||||
37 (white)
|
||||
.
|
||||
.IP "" 0
|
||||
.
|
||||
.SH "BUGS"
|
||||
Presumably\. Report them or discuss them at:
|
||||
.
|
||||
|
||||
Submodule
+1
Submodule modules/oniguruma added at 6fa38f4084.
@@ -11,7 +11,7 @@ set -o xtrace
|
||||
scriptdir=`dirname "$0"`
|
||||
cd "$scriptdir"/../docs
|
||||
rm -rf output
|
||||
rake build
|
||||
pipenv run python3 build_website.py
|
||||
cd ..
|
||||
|
||||
# copy to /tmp
|
||||
|
||||
+7
-2
@@ -1,5 +1,10 @@
|
||||
#!/bin/sh
|
||||
set -e
|
||||
cd `dirname "$0"`
|
||||
git rev-parse --verify -q jq-1.0 > /dev/null 2>&1
|
||||
git describe --tags --match 'jq-*' --dirty | sed 's/^jq-//'
|
||||
if git rev-parse --verify -q jq-1.0 > /dev/null 2>&1; then
|
||||
git describe --tags --match 'jq-*' --dirty | sed 's/^jq-//'
|
||||
else
|
||||
b=`git rev-parse --abbrev-ref HEAD`
|
||||
c=`git describe --always --dirty`
|
||||
echo "${b}-${c}"
|
||||
fi
|
||||
@@ -0,0 +1,16 @@
|
||||
-----BEGIN PGP SIGNATURE-----
|
||||
|
||||
iQIzBAABCAAdFiEET9cB1vqbPS31rJNdrxkEDHFSNAIFAlvbqFAACgkQrxkEDHFS
|
||||
NAIQNRAArTVuw4JwSbxKzC4Y+ib1Vaz5d8ntQwmiR1FBJ5hbi5dMhqD81Kl4slU0
|
||||
0IDBia7Bnvthk0/H+UZHjUdkCNW8rubg87zJOt8t5XXQmIK81cPgKhxqhPA6JPA8
|
||||
RNmBGs5gAxycm8x4X0EMOVTsVqAfq8NYakO6MctRlv4ut3lXfWe+prIMBpFQZr7l
|
||||
CqvmBY8xhLF09LLBEB+RTjhDeW7E02cYFJIW9bbcvpECmnX/fJTjGwN6+Blaq4ln
|
||||
nJwODDZ61TGC2ZAYcAoqcA8bkU8ms0x7RCpZrRbYG4HJstSYM8RaxUxvtW2zn6Si
|
||||
7FpMvrXnDn6MWr3wph96oRTEkKQsRhxoxo7zumaiKkaAGKjyN7fFx9z89QrKoxrO
|
||||
CAzGdHjt6YSmXH60ArGUWKGRilyvxieKddXD3fMJ5Sz/u+yHFRNDIuOQt+KZaOfC
|
||||
UlvKXQamexkXmK3PEl5ZPxcuK+G/NLFawPlLLHVvdLx6B5o4r94ycfLLYqzDCiby
|
||||
v0EDZrgVropUMUkJJMjSmioXoizK9KZ/fl5fj+2miHEZS+s+YwZ5aoPi5IvrFUhi
|
||||
sYz3wEKHp3aYzTPhPt3wBU9aaWoTradSCP4pt4eJV/VeVND+vygILdoD6xFWJoc4
|
||||
BT2tBrUR6uqdQXaCn3BF0lCfI17fab1elYwBkh291wZeKF7qQ8Y=
|
||||
=cTl4
|
||||
-----END PGP SIGNATURE-----
|
||||
@@ -0,0 +1,16 @@
|
||||
-----BEGIN PGP SIGNATURE-----
|
||||
|
||||
iQIzBAABCAAdFiEET9cB1vqbPS31rJNdrxkEDHFSNAIFAlvbqF8ACgkQrxkEDHFS
|
||||
NAK+UhAAx+mFwf7XRYLznTU4HoDzW3yn/H+amvYpphvtcn/eB0GsZRpZ3+EIkMUe
|
||||
VF9Qxidw39tZeLROdX84iaYSu1bTpd6vAdx54rMUZyPVry6m5hqUD78LccPvIDCk
|
||||
EyFuRuAAqlRicyNuqtlr9BUBoI7Z2c4YOA3Y4oXlOXtleL3SXKXcGvhcWQ4owZp5
|
||||
MN6tT/lNu6sj8l5v0Y4GxtfDg8YOap2s9MdaNNkAgt3O3J98gadAin+vQ34ekKWr
|
||||
zsB7QjZaAr8OSGyWCY7BTc7UJbsTIzFzSPvYGeIP2iCBSpR+qcM5/bgrE+Q4l2TA
|
||||
t+p6vF1UVo9MkhEhD/2Ru/ovjqRKIVKA0J5YvEzr2jWjSAWpc75y80rovqW/vjE4
|
||||
GHBJootozgrUwe/ESTATzdyfFQG8Du8znnQp1Wv3upRPieCKiLiUkRZenOTvBIvW
|
||||
rHhgBR73vfhyD92exum4Z97hglwzjWJZRYohyKz+D0tYffH5lG4DV2pToREMqslq
|
||||
9Yr3eGREMD/6j/zIsfzKxFeC/Ar+vmZrate9bUPwX7WYdwwbRcmx7zI6hIszFDnc
|
||||
RONpuJokLxcgfVC3Q8Tzo5cRsAJ0eiQwO/K3oU/ZSAjJkV17E+cy3KFm/C0BSvXx
|
||||
BHxK2D3vqCsbtlfkLurxzjvFKIAQtFO3yfSI5mIMnWfRCgvoHcU=
|
||||
=O93f
|
||||
-----END PGP SIGNATURE-----
|
||||
@@ -0,0 +1,16 @@
|
||||
-----BEGIN PGP SIGNATURE-----
|
||||
|
||||
iQIzBAABCAAdFiEET9cB1vqbPS31rJNdrxkEDHFSNAIFAlvbqGUACgkQrxkEDHFS
|
||||
NAJT0hAAvuHiFUoXoHh6fJ16dY+ZvY4lWqAUap+mAWABuMpWGT0nfjq30eqvCY0e
|
||||
Vjk46ENJPNjCNQnxMRq3kWMqsaniWLgU/sbXK1Ws7RldhJbxjnL8y/jHtNHQtrKV
|
||||
+RhcJ3FhDzLSHOnIsTYYzHFZ1D+qeU+XMafVnnZSCj4SjEMZjzo9VyHJqyu+iG0w
|
||||
z2spLd6zFp1+6EtZkdv10NsiB69HhNxKUn0BUE2KOfasLpFZSoMN2OXNx9qatTJ0
|
||||
l/+PKzSb8DkRh3uHtowyV7VHrPUQ8/WcyRqwNKMmJaB3WDDwsPJxgQT3sJVpVQaj
|
||||
4d768B23NbR3LZbqPgczu50kn/6Q/DJTm7J/TNxKIOfnCsIMsiIhQ+oLe7/8c0ph
|
||||
xXhNZqzi06jB0kulmGus/YQY+Nojquzc+mMOysoxYo1iWscxWoK3GrImO3YMACk5
|
||||
0zH6pHgLBlWZ/5ScKmvAn8tI/EfSUnsR/QMmFEZsc/80tkZBAJd3HPeamRtLqCon
|
||||
T8aSP+xwwzo1BFhtdVddYXlXFWIwtn9VhEYJvXN12eyhW+S+dKyUlJCdCAozkntD
|
||||
QxOILIc8RF4F3KvQTo+t91hbjyPGm760/iQgHU20UpRxaIkJylQ6p6rWsV1wEMMr
|
||||
QEENmAs0IyawgJGJ1Jr7AKOR1fqEqW2BJxQ9zi9re+dXOvVWMI0=
|
||||
=J3nx
|
||||
-----END PGP SIGNATURE-----
|
||||
@@ -0,0 +1,16 @@
|
||||
-----BEGIN PGP SIGNATURE-----
|
||||
|
||||
iQIzBAABCAAdFiEET9cB1vqbPS31rJNdrxkEDHFSNAIFAlvbqGgACgkQrxkEDHFS
|
||||
NAKFsQ//fWWzw7pWIt2v0vCn3VOAqWv0oB3+YEhqmYNv3UJ5cJBeOndR4C0kT2uH
|
||||
MnTIR5WCLw9iSiNkHUX+oiWT3Rw8i8oHkA1Ub8sIin1RksV0TosTQa++9XqdO92q
|
||||
HBBLUe1Y9BcIU0MgEKDtLXKwg6wqW6zAl0WI5/fIPT8Du7evhI32s9zVTMH4AahO
|
||||
ZUM+OXmNCRDVvVp5LS3fZR8E+R3+Mt2ZplGb+aLmoX+hvVTc15GfVtFLGMz2bF1p
|
||||
LVG2/qs9XQP9nCaPm/JCB1F04nHTAuUb5BzJtdJUpqBJGN8diQrxLigjyxKsPVNw
|
||||
118r4Q/6FELTlMg9+6qOPal++ihjuCNWex5Mqz2+ZnANvtRYVx/5bF+DY848JmqP
|
||||
V2k0JF/pRnwaCd9ONzYjkWcCMeoAAvwPygl20NFBUgTHC/xdh/0j+glG0IUW7Wt8
|
||||
2cJyG2OpGqR8CHTc0WDwV1SM937B9CuPjYANottrl2mDmdhhykO5gpDSjf7/is4y
|
||||
9sWr4MqI62aI8xBVdLmTsF4kzoWfC5go4SAHvXlsBr7bMW90hkcuuY0giB0mWZng
|
||||
VQtRtpddKmhstLGoKMWf7TblX4uXA5Ym03LzvwhGl3PlwKh4HvkVYBE7EaSKuJ1g
|
||||
s3WxN0vP6mP1nMFF26fC9IapYWIcTXjyJ7Hmo0Okwn543E37cQk=
|
||||
=KfqY
|
||||
-----END PGP SIGNATURE-----
|
||||
@@ -0,0 +1,16 @@
|
||||
-----BEGIN PGP SIGNATURE-----
|
||||
|
||||
iQIzBAABCAAdFiEET9cB1vqbPS31rJNdrxkEDHFSNAIFAlvbqGsACgkQrxkEDHFS
|
||||
NAK2Sg/+O0nHwVg185EwKoORwL7TQ9V8VSV04Ska0lKJ0Biu0w0U8k+bkzhY5IZ5
|
||||
IdNUZb5lhnPpLosCLlzHlWD3fNkBbpYLgxDrGDS7EujXcp8lI1WrwVNuY2Gbz7c3
|
||||
09YAeF2ENnutS4KP0BUfdEVMlEqQIVpGldl5jKgHxlZ8nP80z6IpNL9FnX/qwkZu
|
||||
/fyRthz1XAEcDHfzBtSbJCJ58QoGwl4S96TsuLZnnBuNgNk97SIeiDzg7tN1FmF2
|
||||
wyg7mPZhIdRaGfgRLCqO7+qFGiYS4esJj4SbLiWDREVKhqqnT5B/vF5SmshsssUm
|
||||
6MH+eK0BAGIntTs0fIGtgkiiuZI4bJpkxgfr5P6/F85U2d1YAJSyJubVMJhxx1U3
|
||||
vKjUYzj4ub9GieT80FFmNAbuUTOFY3y5ZLDl3WRXD23/FpXTxWvTMNWs/Ypt67iT
|
||||
SLcpC1iAETTsaAzLnPxPtwjApFJv/+Wy+CXZl1xAwDHH4vnSddRaSjjl9qsi7yNh
|
||||
U4KUAVzpHfgmsK0DY1P4wXbBEuXCVcUBhGIQ7z5/Bw0F943C1+tFz7MckQmbsvay
|
||||
O3+D8NeQGNb0wCIt/Ro/xIqSb8yrI4tL3+phnUxiZGxc0z6nTw1O9BdMRX6l5/5O
|
||||
84QzUal8MoDWTy0pb1eqUaXp6Cek4c9Lqvc8WPq/csAE2jb/PGM=
|
||||
=xykx
|
||||
-----END PGP SIGNATURE-----
|
||||
@@ -0,0 +1,5 @@
|
||||
319af6123aaccb174f768a1a89fb586d471e891ba217fe518f81ef05af51edd9 jq-linux32
|
||||
af986793a515d500ab2d35f8d2aecd656e764504b789b66d7e1a0b727a124c44 jq-linux64
|
||||
5c0a0a3ea600f302ee458b30317425dd9632d1ad8882259fcaf4e9b868b2b1ef jq-osx-amd64
|
||||
0012cb4c0eb6eaf97b842e676e423a69a8fea95055d93830551b4a5a54494bd8 jq-win32.exe
|
||||
a51d36968dcbdeabb3142c6f5cf9b401a65dc3a095f3144bd0c118d5bb192753 jq-win64.exe
|
||||
+453
-126
@@ -29,11 +29,14 @@ void *alloca (size_t);
|
||||
#include <ctype.h>
|
||||
#include <limits.h>
|
||||
#include <math.h>
|
||||
#ifdef HAVE_ONIGURUMA
|
||||
#ifdef HAVE_LIBONIG
|
||||
#include <oniguruma.h>
|
||||
#endif
|
||||
#include <string.h>
|
||||
#include <time.h>
|
||||
#ifdef WIN32
|
||||
#include <windows.h>
|
||||
#endif
|
||||
#include "builtin.h"
|
||||
#include "compile.h"
|
||||
#include "jq_parser.h"
|
||||
@@ -41,6 +44,7 @@ void *alloca (size_t);
|
||||
#include "linker.h"
|
||||
#include "locfile.h"
|
||||
#include "jv_unicode.h"
|
||||
#include "jv_alloc.h"
|
||||
|
||||
|
||||
static jv type_error(jv bad, const char* msg) {
|
||||
@@ -66,6 +70,17 @@ static jv type_error2(jv bad1, jv bad2, const char* msg) {
|
||||
return err;
|
||||
}
|
||||
|
||||
static inline jv ret_error(jv bad, jv msg) {
|
||||
jv_free(bad);
|
||||
return jv_invalid_with_msg(msg);
|
||||
}
|
||||
|
||||
static inline jv ret_error2(jv bad1, jv bad2, jv msg) {
|
||||
jv_free(bad1);
|
||||
jv_free(bad2);
|
||||
return jv_invalid_with_msg(msg);
|
||||
}
|
||||
|
||||
static jv f_plus(jq_state *jq, jv input, jv a, jv b) {
|
||||
jv_free(input);
|
||||
if (jv_get_kind(a) == JV_KIND_NULL) {
|
||||
@@ -101,9 +116,15 @@ static jv f_ ## name(jq_state *jq, jv input) { \
|
||||
|
||||
#define LIBM_DDD(name) \
|
||||
static jv f_ ## name(jq_state *jq, jv input, jv a, jv b) { \
|
||||
if (jv_get_kind(a) != JV_KIND_NUMBER || jv_get_kind(b) != JV_KIND_NUMBER) \
|
||||
return type_error(input, "number required"); \
|
||||
jv_free(input); \
|
||||
if (jv_get_kind(a) != JV_KIND_NUMBER) { \
|
||||
jv_free(b); \
|
||||
return type_error(a, "number required"); \
|
||||
} \
|
||||
if (jv_get_kind(b) != JV_KIND_NUMBER) { \
|
||||
jv_free(a); \
|
||||
return type_error(b, "number required"); \
|
||||
} \
|
||||
jv ret = jv_number(name(jv_number_value(a), jv_number_value(b))); \
|
||||
jv_free(a); \
|
||||
jv_free(b); \
|
||||
@@ -113,9 +134,22 @@ static jv f_ ## name(jq_state *jq, jv input, jv a, jv b) { \
|
||||
|
||||
#define LIBM_DDDD(name) \
|
||||
static jv f_ ## name(jq_state *jq, jv input, jv a, jv b, jv c) { \
|
||||
if (jv_get_kind(a) != JV_KIND_NUMBER || jv_get_kind(b) != JV_KIND_NUMBER) \
|
||||
return type_error(input, "number required"); \
|
||||
jv_free(input); \
|
||||
if (jv_get_kind(a) != JV_KIND_NUMBER) { \
|
||||
jv_free(b); \
|
||||
jv_free(c); \
|
||||
return type_error(a, "number required"); \
|
||||
} \
|
||||
if (jv_get_kind(b) != JV_KIND_NUMBER) { \
|
||||
jv_free(a); \
|
||||
jv_free(c); \
|
||||
return type_error(b, "number required"); \
|
||||
} \
|
||||
if (jv_get_kind(c) != JV_KIND_NUMBER) { \
|
||||
jv_free(a); \
|
||||
jv_free(b); \
|
||||
return type_error(c, "number required"); \
|
||||
} \
|
||||
jv ret = jv_number(name(jv_number_value(a), jv_number_value(b), jv_number_value(c))); \
|
||||
jv_free(a); \
|
||||
jv_free(b); \
|
||||
@@ -131,6 +165,41 @@ static jv f_ ## name(jq_state *jq, jv input, jv a, jv b, jv c) { \
|
||||
#undef LIBM_DDD
|
||||
#undef LIBM_DD
|
||||
|
||||
#ifdef HAVE_FREXP
|
||||
static jv f_frexp(jq_state *jq, jv input) {
|
||||
if (jv_get_kind(input) != JV_KIND_NUMBER) {
|
||||
return type_error(input, "number required");
|
||||
}
|
||||
int exp;
|
||||
double d = frexp(jv_number_value(input), &exp);
|
||||
jv ret = JV_ARRAY(jv_number(d), jv_number(exp));
|
||||
jv_free(input);
|
||||
return ret;
|
||||
}
|
||||
#endif
|
||||
#ifdef HAVE_MODF
|
||||
static jv f_modf(jq_state *jq, jv input) {
|
||||
if (jv_get_kind(input) != JV_KIND_NUMBER) {
|
||||
return type_error(input, "number required");
|
||||
}
|
||||
double i;
|
||||
jv ret = JV_ARRAY(jv_number(modf(jv_number_value(input), &i)));
|
||||
jv_free(input);
|
||||
return jv_array_append(ret, jv_number(i));
|
||||
}
|
||||
#endif
|
||||
#ifdef HAVE_LGAMMA_R
|
||||
static jv f_lgamma_r(jq_state *jq, jv input) {
|
||||
if (jv_get_kind(input) != JV_KIND_NUMBER) {
|
||||
return type_error(input, "number required");
|
||||
}
|
||||
int sign;
|
||||
jv ret = JV_ARRAY(jv_number(lgamma_r(jv_number_value(input), &sign)));
|
||||
jv_free(input);
|
||||
return jv_array_append(ret, jv_number(sign));
|
||||
}
|
||||
#endif
|
||||
|
||||
static jv f_negate(jq_state *jq, jv input) {
|
||||
if (jv_get_kind(input) != JV_KIND_NUMBER) {
|
||||
return type_error(input, "cannot be negated");
|
||||
@@ -142,7 +211,7 @@ static jv f_negate(jq_state *jq, jv input) {
|
||||
|
||||
static jv f_startswith(jq_state *jq, jv a, jv b) {
|
||||
if (jv_get_kind(a) != JV_KIND_STRING || jv_get_kind(b) != JV_KIND_STRING)
|
||||
return jv_invalid_with_msg(jv_string("startswith() requires string inputs"));
|
||||
return ret_error2(a, b, jv_string("startswith() requires string inputs"));
|
||||
int alen = jv_string_length_bytes(jv_copy(a));
|
||||
int blen = jv_string_length_bytes(jv_copy(b));
|
||||
jv ret;
|
||||
@@ -158,7 +227,7 @@ static jv f_startswith(jq_state *jq, jv a, jv b) {
|
||||
|
||||
static jv f_endswith(jq_state *jq, jv a, jv b) {
|
||||
if (jv_get_kind(a) != JV_KIND_STRING || jv_get_kind(b) != JV_KIND_STRING)
|
||||
return jv_invalid_with_msg(jv_string("endswith() requires string inputs"));
|
||||
return ret_error2(a, b, jv_string("endswith() requires string inputs"));
|
||||
const char *astr = jv_string_value(a);
|
||||
const char *bstr = jv_string_value(b);
|
||||
size_t alen = jv_string_length_bytes(jv_copy(a));
|
||||
@@ -396,6 +465,24 @@ static jv f_utf8bytelength(jq_state *jq, jv input) {
|
||||
|
||||
#define CHARS_ALPHANUM "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789"
|
||||
|
||||
static const unsigned char BASE64_ENCODE_TABLE[64 + 1] = CHARS_ALPHANUM "+/";
|
||||
static const unsigned char BASE64_INVALID_ENTRY = 0xFF;
|
||||
static const unsigned char BASE64_DECODE_TABLE[255] = {
|
||||
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
|
||||
62, // +
|
||||
0xFF, 0xFF, 0xFF,
|
||||
63, // /
|
||||
52, 53, 54, 55, 56, 57, 58, 59, 60, 61, // 0-9
|
||||
0xFF, 0xFF, 0xFF,
|
||||
99, // =
|
||||
0xFF, 0xFF, 0xFF,
|
||||
0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, // A-Z
|
||||
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
|
||||
26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, // a-z
|
||||
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF
|
||||
};
|
||||
|
||||
|
||||
static jv escape_string(jv input, const char* escapings) {
|
||||
|
||||
assert(jv_get_kind(input) == JV_KIND_STRING);
|
||||
@@ -548,7 +635,6 @@ static jv f_format(jq_state *jq, jv input, jv fmt) {
|
||||
jv_free(fmt);
|
||||
input = f_tostring(jq, input);
|
||||
jv line = jv_string("");
|
||||
const char b64[64 + 1] = CHARS_ALPHANUM "+/";
|
||||
const unsigned char* data = (const unsigned char*)jv_string_value(input);
|
||||
int len = jv_string_length_bytes(jv_copy(input));
|
||||
for (int i=0; i<len; i+=3) {
|
||||
@@ -560,7 +646,7 @@ static jv f_format(jq_state *jq, jv input, jv fmt) {
|
||||
}
|
||||
char buf[4];
|
||||
for (int j=0; j<4; j++) {
|
||||
buf[j] = b64[(code >> (18 - j*6)) & 0x3f];
|
||||
buf[j] = BASE64_ENCODE_TABLE[(code >> (18 - j*6)) & 0x3f];
|
||||
}
|
||||
if (n < 3) buf[3] = '=';
|
||||
if (n < 2) buf[2] = '=';
|
||||
@@ -568,6 +654,49 @@ static jv f_format(jq_state *jq, jv input, jv fmt) {
|
||||
}
|
||||
jv_free(input);
|
||||
return line;
|
||||
} else if (!strcmp(fmt_s, "base64d")) {
|
||||
jv_free(fmt);
|
||||
input = f_tostring(jq, input);
|
||||
const unsigned char* data = (const unsigned char*)jv_string_value(input);
|
||||
int len = jv_string_length_bytes(jv_copy(input));
|
||||
size_t decoded_len = (3 * len) / 4; // 3 usable bytes for every 4 bytes of input
|
||||
char *result = jv_mem_calloc(decoded_len, sizeof(char));
|
||||
memset(result, 0, decoded_len * sizeof(char));
|
||||
uint32_t ri = 0;
|
||||
int input_bytes_read=0;
|
||||
uint32_t code = 0;
|
||||
for (int i=0; i<len && data[i] != '='; i++) {
|
||||
if (BASE64_DECODE_TABLE[data[i]] == BASE64_INVALID_ENTRY) {
|
||||
free(result);
|
||||
return type_error(input, "is not valid base64 data");
|
||||
}
|
||||
|
||||
code <<= 6;
|
||||
code |= BASE64_DECODE_TABLE[data[i]];
|
||||
input_bytes_read++;
|
||||
|
||||
if (input_bytes_read == 4) {
|
||||
result[ri++] = (code >> 16) & 0xFF;
|
||||
result[ri++] = (code >> 8) & 0xFF;
|
||||
result[ri++] = code & 0xFF;
|
||||
input_bytes_read = 0;
|
||||
code = 0;
|
||||
}
|
||||
}
|
||||
if (input_bytes_read == 3) {
|
||||
result[ri++] = (code >> 10) & 0xFF;
|
||||
result[ri++] = (code >> 2) & 0xFF;
|
||||
} else if (input_bytes_read == 2) {
|
||||
result[ri++] = (code >> 4) & 0xFF;
|
||||
} else if (input_bytes_read == 1) {
|
||||
free(result);
|
||||
return type_error(input, "trailing base64 byte found");
|
||||
}
|
||||
|
||||
jv line = jv_string_sized(result, ri);
|
||||
jv_free(input);
|
||||
free(result);
|
||||
return line;
|
||||
} else {
|
||||
jv_free(input);
|
||||
return jv_invalid_with_msg(jv_string_concat(fmt, jv_string(" is not a valid format")));
|
||||
@@ -618,7 +747,7 @@ static jv f_group_by_impl(jq_state *jq, jv input, jv keys) {
|
||||
}
|
||||
}
|
||||
|
||||
#ifdef HAVE_ONIGURUMA
|
||||
#ifdef HAVE_LIBONIG
|
||||
static int f_match_name_iter(const UChar* name, const UChar *name_end, int ngroups,
|
||||
int *groups, regex_t *reg, void *arg) {
|
||||
jv captures = *(jv*)arg;
|
||||
@@ -736,7 +865,7 @@ static jv f_match(jq_state *jq, jv input, jv regex, jv modifiers, jv testmode) {
|
||||
if (region->end[0] == region->beg[0]) {
|
||||
unsigned long idx;
|
||||
const char *fr = (const char*)input_string;
|
||||
for (idx = 0; fr != input_string+region->beg[0]; idx++) {
|
||||
for (idx = 0; fr < input_string+region->beg[0]; idx++) {
|
||||
fr += jvp_utf8_decode_length(*fr);
|
||||
}
|
||||
jv match = jv_object_set(jv_object(), jv_string("offset"), jv_number(idx));
|
||||
@@ -773,7 +902,7 @@ static jv f_match(jq_state *jq, jv input, jv regex, jv modifiers, jv testmode) {
|
||||
cap = jv_object_set(cap, jv_string("string"), jv_null());
|
||||
} else {
|
||||
fr = input_string;
|
||||
for (idx = 0; fr != input_string+region->beg[i]; idx++) {
|
||||
for (idx = 0; fr < input_string+region->beg[i]; idx++) {
|
||||
fr += jvp_utf8_decode_length(*fr);
|
||||
}
|
||||
cap = jv_object_set(jv_object(), jv_string("offset"), jv_number(idx));
|
||||
@@ -785,7 +914,7 @@ static jv f_match(jq_state *jq, jv input, jv regex, jv modifiers, jv testmode) {
|
||||
continue;
|
||||
}
|
||||
fr = input_string;
|
||||
for (idx = len = 0; fr != input_string+region->end[i]; len++) {
|
||||
for (idx = len = 0; fr < input_string+region->end[i]; len++) {
|
||||
if (fr == input_string+region->beg[i]) idx = len, len=0;
|
||||
fr += jvp_utf8_decode_length(*fr);
|
||||
}
|
||||
@@ -806,7 +935,7 @@ static jv f_match(jq_state *jq, jv input, jv regex, jv modifiers, jv testmode) {
|
||||
break;
|
||||
} else { /* Error */
|
||||
UChar ebuf[ONIG_MAX_ERROR_MESSAGE_LEN];
|
||||
onig_error_code_to_str(ebuf, onigret, einfo);
|
||||
onig_error_code_to_str(ebuf, onigret, &einfo);
|
||||
jv_free(result);
|
||||
result = jv_invalid_with_msg(jv_string_concat(jv_string("Regex failure: "),
|
||||
jv_string((char*)ebuf)));
|
||||
@@ -822,11 +951,11 @@ static jv f_match(jq_state *jq, jv input, jv regex, jv modifiers, jv testmode) {
|
||||
jv_free(regex);
|
||||
return result;
|
||||
}
|
||||
#else /* ! HAVE_ONIGURUMA */
|
||||
#else /* !HAVE_LIBONIG */
|
||||
static jv f_match(jq_state *jq, jv input, jv regex, jv modifiers, jv testmode) {
|
||||
return jv_invalid_with_msg(jv_string("jq was compiled without ONIGURUMA regex libary. match/test/sub and related functions are not available."));
|
||||
}
|
||||
#endif /* HAVE_ONIGURUMA */
|
||||
#endif /* HAVE_LIBONIG */
|
||||
|
||||
static jv minmax_by(jv values, jv keys, int is_min) {
|
||||
if (jv_get_kind(values) != JV_KIND_ARRAY)
|
||||
@@ -927,21 +1056,20 @@ static jv f_nan(jq_state *jq, jv input) {
|
||||
return jv_number(NAN);
|
||||
}
|
||||
|
||||
static jv f_error(jq_state *jq, jv input, jv msg) {
|
||||
jv_free(input);
|
||||
return jv_invalid_with_msg(msg);
|
||||
static jv f_error(jq_state *jq, jv input) {
|
||||
return jv_invalid_with_msg(input);
|
||||
}
|
||||
|
||||
// FIXME Should autoconf check for this!
|
||||
#ifndef WIN32
|
||||
extern const char **environ;
|
||||
extern char **environ;
|
||||
#endif
|
||||
|
||||
static jv f_env(jq_state *jq, jv input) {
|
||||
jv_free(input);
|
||||
jv env = jv_object();
|
||||
const char *var, *val;
|
||||
for (const char **e = environ; *e != NULL; e++) {
|
||||
for (char **e = environ; *e != NULL; e++) {
|
||||
var = e[0];
|
||||
val = strchr(e[0], '=');
|
||||
if (val == NULL)
|
||||
@@ -952,6 +1080,21 @@ static jv f_env(jq_state *jq, jv input) {
|
||||
return env;
|
||||
}
|
||||
|
||||
static jv f_halt(jq_state *jq, jv input) {
|
||||
jv_free(input);
|
||||
jq_halt(jq, jv_invalid(), jv_invalid());
|
||||
return jv_true();
|
||||
}
|
||||
|
||||
static jv f_halt_error(jq_state *jq, jv input, jv a) {
|
||||
if (jv_get_kind(a) != JV_KIND_NUMBER) {
|
||||
jv_free(a);
|
||||
return type_error(input, "halt_error/1: number required");
|
||||
}
|
||||
jq_halt(jq, a, input);
|
||||
return jv_true();
|
||||
}
|
||||
|
||||
static jv f_get_search_list(jq_state *jq, jv input) {
|
||||
jv_free(input);
|
||||
return jq_get_lib_dirs(jq);
|
||||
@@ -969,17 +1112,14 @@ static jv f_get_jq_origin(jq_state *jq, jv input) {
|
||||
|
||||
static jv f_string_split(jq_state *jq, jv a, jv b) {
|
||||
if (jv_get_kind(a) != JV_KIND_STRING || jv_get_kind(b) != JV_KIND_STRING) {
|
||||
jv_free(a);
|
||||
jv_free(b);
|
||||
return jv_invalid_with_msg(jv_string("split input and separator must be strings"));
|
||||
return ret_error2(a, b, jv_string("split input and separator must be strings"));
|
||||
}
|
||||
return jv_string_split(a, b);
|
||||
}
|
||||
|
||||
static jv f_string_explode(jq_state *jq, jv a) {
|
||||
if (jv_get_kind(a) != JV_KIND_STRING) {
|
||||
jv_free(a);
|
||||
return jv_invalid_with_msg(jv_string("explode input must be a string"));
|
||||
return ret_error(a, jv_string("explode input must be a string"));
|
||||
}
|
||||
return jv_string_explode(a);
|
||||
}
|
||||
@@ -990,21 +1130,22 @@ static jv f_string_indexes(jq_state *jq, jv a, jv b) {
|
||||
|
||||
static jv f_string_implode(jq_state *jq, jv a) {
|
||||
if (jv_get_kind(a) != JV_KIND_ARRAY) {
|
||||
jv_free(a);
|
||||
return jv_invalid_with_msg(jv_string("implode input must be an array"));
|
||||
return ret_error(a, jv_string("implode input must be an array"));
|
||||
}
|
||||
return jv_string_implode(a);
|
||||
}
|
||||
|
||||
static jv f_setpath(jq_state *jq, jv a, jv b, jv c) { return jv_setpath(a, b, c); }
|
||||
static jv f_getpath(jq_state *jq, jv a, jv b) { return jv_getpath(a, b); }
|
||||
extern jv _jq_path_append(jq_state *, jv, jv, jv);
|
||||
static jv f_getpath(jq_state *jq, jv a, jv b) {
|
||||
return _jq_path_append(jq, a, b, jv_getpath(jv_copy(a), jv_copy(b)));
|
||||
}
|
||||
static jv f_delpaths(jq_state *jq, jv a, jv b) { return jv_delpaths(a, b); }
|
||||
static jv f_has(jq_state *jq, jv a, jv b) { return jv_has(a, b); }
|
||||
|
||||
static jv f_modulemeta(jq_state *jq, jv a) {
|
||||
if (jv_get_kind(a) != JV_KIND_STRING) {
|
||||
jv_free(a);
|
||||
return jv_invalid_with_msg(jv_string("modulemeta input module name must be a string"));
|
||||
return ret_error(a, jv_string("modulemeta input module name must be a string"));
|
||||
}
|
||||
return load_module_meta(jq, a);
|
||||
}
|
||||
@@ -1033,7 +1174,6 @@ static jv f_debug(jq_state *jq, jv input) {
|
||||
|
||||
static jv f_stderr(jq_state *jq, jv input) {
|
||||
jv_dumpf(jv_copy(input), stderr, 0);
|
||||
fprintf(stderr, "\n");
|
||||
return input;
|
||||
}
|
||||
|
||||
@@ -1048,6 +1188,27 @@ static jv tm2jv(struct tm *tm) {
|
||||
jv_number(tm->tm_yday));
|
||||
}
|
||||
|
||||
#if defined(WIN32) && !defined(HAVE_SETENV)
|
||||
static int setenv(const char *var, const char *val, int ovr)
|
||||
{
|
||||
BOOL b;
|
||||
char c[2];
|
||||
if (!ovr)
|
||||
{
|
||||
DWORD d;
|
||||
d = GetEnvironmentVariableA (var, c, 2);
|
||||
if (0 != d && GetLastError () != ERROR_ENVVAR_NOT_FOUND) {
|
||||
return d;
|
||||
}
|
||||
}
|
||||
b = SetEnvironmentVariableA (var, val);
|
||||
if (b) {
|
||||
return 0;
|
||||
}
|
||||
return 1;
|
||||
}
|
||||
#endif
|
||||
|
||||
/*
|
||||
* mktime() has side-effects and anyways, returns time in the local
|
||||
* timezone, not UTC. We want timegm(), which isn't standard.
|
||||
@@ -1066,61 +1227,170 @@ static jv tm2jv(struct tm *tm) {
|
||||
static time_t my_mktime(struct tm *tm) {
|
||||
#ifdef HAVE_TIMEGM
|
||||
return timegm(tm);
|
||||
#else /* HAVE_TIMEGM */
|
||||
#elif HAVE_TM_TM_GMT_OFF
|
||||
|
||||
time_t t = mktime(tm);
|
||||
if (t == (time_t)-1)
|
||||
return t;
|
||||
#ifdef HAVE_TM_TM_GMT_OFF
|
||||
return t + tm.tm_gmtoff;
|
||||
#elif defined(HAVE_TM_TM_GMT_OFF)
|
||||
return t + tm.__tm_gmtoff;
|
||||
return t + tm->tm_gmtoff;
|
||||
#elif HAVE_TM___TM_GMT_OFF
|
||||
time_t t = mktime(tm);
|
||||
if (t == (time_t)-1)
|
||||
return t;
|
||||
return t + tm->__tm_gmtoff;
|
||||
#elif WIN32
|
||||
return _mkgmtime(tm);
|
||||
#else
|
||||
return (time_t)-2; /* Not supported */
|
||||
char *tz;
|
||||
|
||||
tz = (tz = getenv("TZ")) != NULL ? strdup(tz) : NULL;
|
||||
if (tz != NULL)
|
||||
setenv("TZ", "", 1);
|
||||
time_t t = mktime(tm);
|
||||
if (tz != NULL)
|
||||
setenv("TZ", tz, 1);
|
||||
return t;
|
||||
#endif
|
||||
#endif /* !HAVE_TIMEGM */
|
||||
}
|
||||
|
||||
#ifdef HAVE_STRPTIME
|
||||
/* Compute and set tm_wday */
|
||||
static void set_tm_wday(struct tm *tm) {
|
||||
/*
|
||||
* https://en.wikipedia.org/wiki/Determination_of_the_day_of_the_week#Gauss.27s_algorithm
|
||||
* https://cs.uwaterloo.ca/~alopez-o/math-faq/node73.html
|
||||
*
|
||||
* Tested with dates from 1900-01-01 through 2100-01-01. This
|
||||
* algorithm produces the wrong day-of-the-week number for dates in
|
||||
* the range 1900-01-01..1900-02-28, and for 2100-01-01..2100-02-28.
|
||||
* Since this is only needed on OS X and *BSD, we might just document
|
||||
* this.
|
||||
*/
|
||||
int century = (1900 + tm->tm_year) / 100;
|
||||
int year = (1900 + tm->tm_year) % 100;
|
||||
if (tm->tm_mon < 2)
|
||||
year--;
|
||||
/*
|
||||
* The month value in the wday computation below is shifted so that
|
||||
* March is 1, April is 2, .., January is 11, and February is 12.
|
||||
*/
|
||||
int mon = tm->tm_mon - 1;
|
||||
if (mon < 1)
|
||||
mon += 12;
|
||||
int wday =
|
||||
(tm->tm_mday + (int)floor((2.6 * mon - 0.2)) + year + (int)floor(year / 4.0) + (int)floor(century / 4.0) - 2 * century) % 7;
|
||||
if (wday < 0)
|
||||
wday += 7;
|
||||
#if 0
|
||||
/* See commentary above */
|
||||
assert(wday == tm->tm_wday || tm->tm_wday == 8);
|
||||
#endif
|
||||
tm->tm_wday = wday;
|
||||
}
|
||||
/*
|
||||
* Compute and set tm_yday.
|
||||
*
|
||||
*/
|
||||
static void set_tm_yday(struct tm *tm) {
|
||||
static const int d[] = {0, 31, 59, 90, 120, 151, 181, 212, 243, 273, 304, 334};
|
||||
int mon = tm->tm_mon;
|
||||
int year = 1900 + tm->tm_year;
|
||||
int leap_day = 0;
|
||||
if (tm->tm_mon > 1 &&
|
||||
((year % 4 == 0 && year % 100 != 0) || (year % 400 == 0)))
|
||||
leap_day = 1;
|
||||
|
||||
/* Bound check index into d[] */
|
||||
if (mon < 0)
|
||||
mon = -mon;
|
||||
if (mon > 11)
|
||||
mon %= 12;
|
||||
|
||||
int yday = d[mon] + leap_day + tm->tm_mday - 1;
|
||||
assert(yday == tm->tm_yday || tm->tm_yday == 367);
|
||||
tm->tm_yday = yday;
|
||||
}
|
||||
|
||||
#ifndef HAVE_STRPTIME
|
||||
static char *strptime(const char *s, const char *format, struct tm *tm) {
|
||||
if (strcmp(format, "%Y-%m-%dT%H:%M:%SZ"))
|
||||
return NULL;
|
||||
|
||||
int count, end;
|
||||
count = sscanf(s, "%d-%d-%dT%d:%d:%d%n",
|
||||
&tm->tm_year, &tm->tm_mon, &tm->tm_mday,
|
||||
&tm->tm_hour, &tm->tm_min, &tm->tm_sec,
|
||||
&end );
|
||||
if (count == 6 && s[end] == 'Z') {
|
||||
tm->tm_year -= 1900;
|
||||
tm->tm_mon--;
|
||||
return (char*)s + end + 1;
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
#endif
|
||||
|
||||
static jv f_strptime(jq_state *jq, jv a, jv b) {
|
||||
if (jv_get_kind(a) != JV_KIND_STRING || jv_get_kind(b) != JV_KIND_STRING)
|
||||
return jv_invalid_with_msg(jv_string("strptime/1 requires string inputs and arguments"));
|
||||
if (jv_get_kind(a) != JV_KIND_STRING || jv_get_kind(b) != JV_KIND_STRING) {
|
||||
return ret_error2(a, b, jv_string("strptime/1 requires string inputs and arguments"));
|
||||
}
|
||||
|
||||
struct tm tm;
|
||||
memset(&tm, 0, sizeof(tm));
|
||||
tm.tm_wday = 8; // sentinel
|
||||
tm.tm_yday = 367; // sentinel
|
||||
const char *input = jv_string_value(a);
|
||||
const char *fmt = jv_string_value(b);
|
||||
const char *end = strptime(input, fmt, &tm);
|
||||
|
||||
#ifndef HAVE_STRPTIME
|
||||
if (strcmp(fmt, "%Y-%m-%dT%H:%M:%SZ")) {
|
||||
return ret_error2(a, b, jv_string("strptime/1 only supports ISO 8601 on this platform"));
|
||||
}
|
||||
#endif
|
||||
const char *end = strptime(input, fmt, &tm);
|
||||
if (end == NULL || (*end != '\0' && !isspace(*end))) {
|
||||
jv e = jv_invalid_with_msg(jv_string_fmt("date \"%s\" does not match format \"%s\"", input, fmt));
|
||||
jv_free(a);
|
||||
jv_free(b);
|
||||
return e;
|
||||
return ret_error2(a, b, jv_string_fmt("date \"%s\" does not match format \"%s\"", input, fmt));
|
||||
}
|
||||
jv_free(b);
|
||||
if (tm.tm_wday == 0 && tm.tm_yday == 0 && my_mktime(&tm) == (time_t)-2) {
|
||||
jv_free(a);
|
||||
return jv_invalid_with_msg(jv_string("strptime/1 not supported on this platform"));
|
||||
}
|
||||
/*
|
||||
* This is OS X or some *BSD whose strptime() is just not that
|
||||
* helpful!
|
||||
*
|
||||
* We don't know that the format string did involve parsing a
|
||||
* year, or a month (if tm->tm_mon == 0). But with our invalid
|
||||
* day-of-week and day-of-year sentinel checks above, the worst
|
||||
* this can do is produce garbage.
|
||||
*/
|
||||
#ifdef __APPLE__
|
||||
/*
|
||||
* Apple has made it worse, and different versions of the OS have different
|
||||
* behaviors. Some versions just don't touch the fields, but others do, and
|
||||
* sometimes provide wrong answers, at that! We can't tell at compile-time
|
||||
* which behavior the target system will have, so instead we always use our
|
||||
* functions to set these on OS X, and document that %u and %j are
|
||||
* unsupported on OS X.
|
||||
*/
|
||||
set_tm_wday(&tm);
|
||||
set_tm_yday(&tm);
|
||||
#else
|
||||
if (tm.tm_wday == 8 && tm.tm_mday != 0 && tm.tm_mon >= 0 && tm.tm_mon <= 11)
|
||||
set_tm_wday(&tm);
|
||||
if (tm.tm_yday == 367 && tm.tm_mday != 0 && tm.tm_mon >= 0 && tm.tm_mon <= 11)
|
||||
set_tm_yday(&tm);
|
||||
#endif
|
||||
jv r = tm2jv(&tm);
|
||||
if (*end != '\0')
|
||||
r = jv_array_append(r, jv_string(end));
|
||||
jv_free(a); // must come after `*end` because `end` is a pointer into `a`'s string
|
||||
return r;
|
||||
}
|
||||
#else
|
||||
static jv f_strptime(jq_state *jq, jv a, jv b) {
|
||||
jv_free(a);
|
||||
jv_free(b);
|
||||
return jv_invalid_with_msg(jv_string("strptime/1 not implemented on this platform"));
|
||||
}
|
||||
#endif
|
||||
|
||||
#define TO_TM_FIELD(t, j, i) \
|
||||
do { \
|
||||
jv n = jv_array_get(jv_copy(j), (i)); \
|
||||
if (jv_get_kind(n) != (JV_KIND_NUMBER)) \
|
||||
if (jv_get_kind(n) != (JV_KIND_NUMBER)) { \
|
||||
jv_free(j); \
|
||||
return 0; \
|
||||
} \
|
||||
t = jv_number_value(n); \
|
||||
jv_free(n); \
|
||||
} while (0)
|
||||
@@ -1153,9 +1423,9 @@ static int jv2tm(jv a, struct tm *tm) {
|
||||
|
||||
static jv f_mktime(jq_state *jq, jv a) {
|
||||
if (jv_get_kind(a) != JV_KIND_ARRAY)
|
||||
return jv_invalid_with_msg(jv_string("mktime requires array inputs"));
|
||||
return ret_error(a, jv_string("mktime requires array inputs"));
|
||||
if (jv_array_length(jv_copy(a)) < 6)
|
||||
return jv_invalid_with_msg(jv_string("mktime requires parsed datetime inputs"));
|
||||
return ret_error(a, jv_string("mktime requires parsed datetime inputs"));
|
||||
struct tm tm;
|
||||
if (!jv2tm(a, &tm))
|
||||
return jv_invalid_with_msg(jv_string("mktime requires parsed datetime inputs"));
|
||||
@@ -1170,7 +1440,7 @@ static jv f_mktime(jq_state *jq, jv a) {
|
||||
#ifdef HAVE_GMTIME_R
|
||||
static jv f_gmtime(jq_state *jq, jv a) {
|
||||
if (jv_get_kind(a) != JV_KIND_NUMBER)
|
||||
return jv_invalid_with_msg(jv_string("gmtime() requires numeric inputs"));
|
||||
return ret_error(a, jv_string("gmtime() requires numeric inputs"));
|
||||
struct tm tm, *tmp;
|
||||
memset(&tm, 0, sizeof(tm));
|
||||
double fsecs = jv_number_value(a);
|
||||
@@ -1185,7 +1455,7 @@ static jv f_gmtime(jq_state *jq, jv a) {
|
||||
#elif defined HAVE_GMTIME
|
||||
static jv f_gmtime(jq_state *jq, jv a) {
|
||||
if (jv_get_kind(a) != JV_KIND_NUMBER)
|
||||
return jv_invalid_with_msg(jv_string("gmtime requires numeric inputs"));
|
||||
return ret_error(a, jv_string("gmtime requires numeric inputs"));
|
||||
struct tm tm, *tmp;
|
||||
memset(&tm, 0, sizeof(tm));
|
||||
double fsecs = jv_number_value(a);
|
||||
@@ -1204,16 +1474,56 @@ static jv f_gmtime(jq_state *jq, jv a) {
|
||||
}
|
||||
#endif
|
||||
|
||||
#ifdef HAVE_LOCALTIME_R
|
||||
static jv f_localtime(jq_state *jq, jv a) {
|
||||
if (jv_get_kind(a) != JV_KIND_NUMBER)
|
||||
return ret_error(a, jv_string("localtime() requires numeric inputs"));
|
||||
struct tm tm, *tmp;
|
||||
memset(&tm, 0, sizeof(tm));
|
||||
double fsecs = jv_number_value(a);
|
||||
time_t secs = fsecs;
|
||||
jv_free(a);
|
||||
tmp = localtime_r(&secs, &tm);
|
||||
if (tmp == NULL)
|
||||
return jv_invalid_with_msg(jv_string("error converting number of seconds since epoch to datetime"));
|
||||
a = tm2jv(tmp);
|
||||
return jv_array_set(a, 5, jv_number(jv_number_value(jv_array_get(jv_copy(a), 5)) + (fsecs - floor(fsecs))));
|
||||
}
|
||||
#elif defined HAVE_GMTIME
|
||||
static jv f_localtime(jq_state *jq, jv a) {
|
||||
if (jv_get_kind(a) != JV_KIND_NUMBER)
|
||||
return ret_error(a, jv_string("localtime requires numeric inputs"));
|
||||
struct tm tm, *tmp;
|
||||
memset(&tm, 0, sizeof(tm));
|
||||
double fsecs = jv_number_value(a);
|
||||
time_t secs = fsecs;
|
||||
jv_free(a);
|
||||
tmp = localtime(&secs);
|
||||
if (tmp == NULL)
|
||||
return jv_invalid_with_msg(jv_string("error converting number of seconds since epoch to datetime"));
|
||||
a = tm2jv(tmp);
|
||||
return jv_array_set(a, 5, jv_number(jv_number_value(jv_array_get(jv_copy(a), 5)) + (fsecs - floor(fsecs))));
|
||||
}
|
||||
#else
|
||||
static jv f_localtime(jq_state *jq, jv a) {
|
||||
jv_free(a);
|
||||
return jv_invalid_with_msg(jv_string("localtime not implemented on this platform"));
|
||||
}
|
||||
#endif
|
||||
|
||||
#ifdef HAVE_STRFTIME
|
||||
static jv f_strftime(jq_state *jq, jv a, jv b) {
|
||||
if (jv_get_kind(a) == JV_KIND_NUMBER) {
|
||||
a = f_gmtime(jq, a);
|
||||
} else if (jv_get_kind(a) != JV_KIND_ARRAY) {
|
||||
return jv_invalid_with_msg(jv_string("strftime/1 requires parsed datetime inputs"));
|
||||
return ret_error2(a, b, jv_string("strftime/1 requires parsed datetime inputs"));
|
||||
} else if (jv_get_kind(b) != JV_KIND_STRING) {
|
||||
return ret_error2(a, b, jv_string("strftime/1 requires a string format"));
|
||||
}
|
||||
struct tm tm;
|
||||
if (!jv2tm(a, &tm))
|
||||
return jv_invalid_with_msg(jv_string("strftime/1 requires parsed datetime inputs")); \
|
||||
return ret_error(b, jv_string("strftime/1 requires parsed datetime inputs"));
|
||||
|
||||
const char *fmt = jv_string_value(b);
|
||||
size_t alloced = strlen(fmt) + 100;
|
||||
char *buf = alloca(alloced);
|
||||
@@ -1225,13 +1535,43 @@ static jv f_strftime(jq_state *jq, jv a, jv b) {
|
||||
return jv_string(buf);
|
||||
}
|
||||
#else
|
||||
static jv f_strftime(jq_state *jq, jv a) {
|
||||
static jv f_strftime(jq_state *jq, jv a, jv b) {
|
||||
jv_free(a);
|
||||
jv_free(b);
|
||||
return jv_invalid_with_msg(jv_string("strftime/1 not implemented on this platform"));
|
||||
}
|
||||
#endif
|
||||
|
||||
#ifdef HAVE_STRFTIME
|
||||
static jv f_strflocaltime(jq_state *jq, jv a, jv b) {
|
||||
if (jv_get_kind(a) == JV_KIND_NUMBER) {
|
||||
a = f_localtime(jq, a);
|
||||
} else if (jv_get_kind(a) != JV_KIND_ARRAY) {
|
||||
return ret_error2(a, b, jv_string("strflocaltime/1 requires parsed datetime inputs"));
|
||||
} else if (jv_get_kind(b) != JV_KIND_STRING) {
|
||||
return ret_error2(a, b, jv_string("strflocaltime/1 requires a string format"));
|
||||
}
|
||||
struct tm tm;
|
||||
if (!jv2tm(a, &tm))
|
||||
return jv_invalid_with_msg(jv_string("strflocaltime/1 requires parsed datetime inputs"));
|
||||
const char *fmt = jv_string_value(b);
|
||||
size_t alloced = strlen(fmt) + 100;
|
||||
char *buf = alloca(alloced);
|
||||
size_t n = strftime(buf, alloced, fmt, &tm);
|
||||
jv_free(b);
|
||||
/* POSIX doesn't provide errno values for strftime() failures; weird */
|
||||
if (n == 0 || n > alloced)
|
||||
return jv_invalid_with_msg(jv_string("strflocaltime/1: unknown system failure"));
|
||||
return jv_string(buf);
|
||||
}
|
||||
#else
|
||||
static jv f_strflocaltime(jq_state *jq, jv a, jv b) {
|
||||
jv_free(a);
|
||||
jv_free(b);
|
||||
return jv_invalid_with_msg(jv_string("strflocaltime/1 not implemented on this platform"));
|
||||
}
|
||||
#endif
|
||||
|
||||
#ifdef HAVE_GETTIMEOFDAY
|
||||
static jv f_now(jq_state *jq, jv a) {
|
||||
jv_free(a);
|
||||
@@ -1262,19 +1602,28 @@ static jv f_current_line(jq_state *jq, jv a) {
|
||||
}
|
||||
|
||||
#define LIBM_DD(name) \
|
||||
{(cfunction_ptr)f_ ## name, "_" #name, 1},
|
||||
{(cfunction_ptr)f_ ## name, #name, 1},
|
||||
#define LIBM_DD_NO(name)
|
||||
|
||||
#define LIBM_DDD(name) \
|
||||
{(cfunction_ptr)f_ ## name, "_" #name, 3},
|
||||
{(cfunction_ptr)f_ ## name, #name, 3},
|
||||
#define LIBM_DDD_NO(name)
|
||||
|
||||
#define LIBM_DDDD(name) \
|
||||
{(cfunction_ptr)f_ ## name, "_" #name, 4},
|
||||
{(cfunction_ptr)f_ ## name, #name, 4},
|
||||
#define LIBM_DDDD_NO(name)
|
||||
|
||||
static const struct cfunction function_list[] = {
|
||||
#include "libm.h"
|
||||
#ifdef HAVE_FREXP
|
||||
{(cfunction_ptr)f_frexp,"frexp", 1},
|
||||
#endif
|
||||
#ifdef HAVE_MODF
|
||||
{(cfunction_ptr)f_modf,"modf", 1},
|
||||
#endif
|
||||
#ifdef HAVE_LGAMMA_R
|
||||
{(cfunction_ptr)f_lgamma_r,"lgamma_r", 1},
|
||||
#endif
|
||||
{(cfunction_ptr)f_plus, "_plus", 3},
|
||||
{(cfunction_ptr)f_negate, "_negate", 1},
|
||||
{(cfunction_ptr)f_minus, "_minus", 3},
|
||||
@@ -1321,21 +1670,25 @@ static const struct cfunction function_list[] = {
|
||||
{(cfunction_ptr)f_max, "max", 1},
|
||||
{(cfunction_ptr)f_min_by_impl, "_min_by_impl", 2},
|
||||
{(cfunction_ptr)f_max_by_impl, "_max_by_impl", 2},
|
||||
{(cfunction_ptr)f_error, "error", 2},
|
||||
{(cfunction_ptr)f_error, "error", 1},
|
||||
{(cfunction_ptr)f_format, "format", 2},
|
||||
{(cfunction_ptr)f_env, "env", 1},
|
||||
{(cfunction_ptr)f_halt, "halt", 1},
|
||||
{(cfunction_ptr)f_halt_error, "halt_error", 2},
|
||||
{(cfunction_ptr)f_get_search_list, "get_search_list", 1},
|
||||
{(cfunction_ptr)f_get_prog_origin, "get_prog_origin", 1},
|
||||
{(cfunction_ptr)f_get_jq_origin, "get_jq_origin", 1},
|
||||
{(cfunction_ptr)f_match, "_match_impl", 4},
|
||||
{(cfunction_ptr)f_modulemeta, "modulemeta", 1},
|
||||
{(cfunction_ptr)f_input, "_input", 1},
|
||||
{(cfunction_ptr)f_input, "input", 1},
|
||||
{(cfunction_ptr)f_debug, "debug", 1},
|
||||
{(cfunction_ptr)f_stderr, "stderr", 1},
|
||||
{(cfunction_ptr)f_strptime, "strptime", 2},
|
||||
{(cfunction_ptr)f_strftime, "strftime", 2},
|
||||
{(cfunction_ptr)f_strflocaltime, "strflocaltime", 2},
|
||||
{(cfunction_ptr)f_mktime, "mktime", 1},
|
||||
{(cfunction_ptr)f_gmtime, "gmtime", 1},
|
||||
{(cfunction_ptr)f_localtime, "localtime", 1},
|
||||
{(cfunction_ptr)f_now, "now", 1},
|
||||
{(cfunction_ptr)f_current_filename, "input_filename", 1},
|
||||
{(cfunction_ptr)f_current_line, "input_line_number", 1},
|
||||
@@ -1390,37 +1743,30 @@ static block bind_bytecoded_builtins(block b) {
|
||||
BLOCK(gen_param("start"), gen_param("end")),
|
||||
range));
|
||||
}
|
||||
|
||||
return block_bind_referenced(builtins, b, OP_IS_CALL_PSEUDO);
|
||||
return BLOCK(builtins, b);
|
||||
}
|
||||
|
||||
#define LIBM_DD(name) "def " #name ": _" #name ";"
|
||||
#define LIBM_DDD(name) "def " #name "(a;b): _" #name "(a;b);"
|
||||
#define LIBM_DDDD(name) "def " #name "(a;b;c): _" #name "(a;b;c);"
|
||||
#define LIBM_DD_NO(name)
|
||||
#define LIBM_DDD_NO(name)
|
||||
#define LIBM_DDDD_NO(name)
|
||||
|
||||
static const char* const jq_builtins =
|
||||
/* Include supported math functions first */
|
||||
#include "libm.h"
|
||||
/* Include jq-coded builtins next (some depend on math) */
|
||||
static const char jq_builtins[] =
|
||||
/* Include jq-coded builtins */
|
||||
#include "src/builtin.inc"
|
||||
|
||||
/* Include unsupported math functions next */
|
||||
#undef LIBM_DDDD_NO
|
||||
#undef LIBM_DDD_NO
|
||||
#undef LIBM_DD_NO
|
||||
#undef LIBM_DDDD
|
||||
#undef LIBM_DDD
|
||||
#undef LIBM_DD
|
||||
#define LIBM_DD(name)
|
||||
#define LIBM_DDD(name)
|
||||
#define LIBM_DDDD(name)
|
||||
#define LIBM_DD_NO(name) "def " #name ": \"Error: " #name "() not found at build time\"|error;"
|
||||
#define LIBM_DDD_NO(name) "def " #name "(a;b): \"Error: " #name "() not found at build time\"|error;"
|
||||
#define LIBM_DDDD_NO(name) "def " #name "(a;b;c): \"Error: " #name "() not found at build time\"|error;"
|
||||
#define LIBM_DD_NO(name) "def " #name ": \"Error: " #name "/0 not found at build time\"|error;"
|
||||
#define LIBM_DDD_NO(name) "def " #name "(a;b): \"Error: " #name "/2 not found at build time\"|error;"
|
||||
#define LIBM_DDDD_NO(name) "def " #name "(a;b;c): \"Error: " #name "/3 not found at build time\"|error;"
|
||||
#include "libm.h"
|
||||
#ifndef HAVE_FREXP
|
||||
"def frexp: \"Error: frexp/0 not found found at build time\"|error;"
|
||||
#endif
|
||||
#ifndef HAVE_MODF
|
||||
"def modf: \"Error: modf/0 not found found at build time\"|error;"
|
||||
#endif
|
||||
#ifndef HAVE_LGAMMA_R
|
||||
"def lgamma_r: \"Error: lgamma_r/0 not found found at build time\"|error;"
|
||||
#endif
|
||||
;
|
||||
|
||||
#undef LIBM_DDDD_NO
|
||||
@@ -1431,42 +1777,23 @@ static const char* const jq_builtins =
|
||||
#undef LIBM_DD
|
||||
|
||||
|
||||
static int builtins_bind_one(jq_state *jq, block* bb, const char* code) {
|
||||
struct locfile* src;
|
||||
src = locfile_init(jq, "<builtin>", code, strlen(code));
|
||||
block funcs;
|
||||
int nerrors = jq_parse_library(src, &funcs);
|
||||
if (nerrors == 0) {
|
||||
*bb = block_bind_referenced(funcs, *bb, OP_IS_CALL_PSEUDO);
|
||||
}
|
||||
locfile_free(src);
|
||||
return nerrors;
|
||||
}
|
||||
|
||||
static int slurp_lib(jq_state *jq, block* bb) {
|
||||
int nerrors = 0;
|
||||
char* home = getenv("HOME");
|
||||
if (home) { // silently ignore no $HOME
|
||||
jv filename = jv_string_append_str(jv_string(home), "/.jq");
|
||||
jv data = jv_load_file(jv_string_value(filename), 1);
|
||||
if (jv_is_valid(data)) {
|
||||
nerrors = builtins_bind_one(jq, bb, jv_string_value(data) );
|
||||
}
|
||||
jv_free(filename);
|
||||
jv_free(data);
|
||||
}
|
||||
return nerrors;
|
||||
static block gen_builtin_list(block builtins) {
|
||||
jv list = jv_array_append(block_list_funcs(builtins, 1), jv_string("builtins/0"));
|
||||
return BLOCK(builtins, gen_function("builtins", gen_noop(), gen_const(list)));
|
||||
}
|
||||
|
||||
int builtins_bind(jq_state *jq, block* bb) {
|
||||
int nerrors = slurp_lib(jq, bb);
|
||||
if (nerrors) {
|
||||
block_free(*bb);
|
||||
return nerrors;
|
||||
}
|
||||
nerrors = builtins_bind_one(jq, bb, jq_builtins);
|
||||
block builtins;
|
||||
struct locfile* src = locfile_init(jq, "<builtin>", jq_builtins, sizeof(jq_builtins)-1);
|
||||
int nerrors = jq_parse_library(src, &builtins);
|
||||
assert(!nerrors);
|
||||
*bb = bind_bytecoded_builtins(*bb);
|
||||
*bb = gen_cbinding(function_list, sizeof(function_list)/sizeof(function_list[0]), *bb);
|
||||
locfile_free(src);
|
||||
|
||||
builtins = bind_bytecoded_builtins(builtins);
|
||||
builtins = gen_cbinding(function_list, sizeof(function_list)/sizeof(function_list[0]), builtins);
|
||||
builtins = gen_builtin_list(builtins);
|
||||
|
||||
*bb = block_bind_incremental(builtins, *bb, OP_IS_CALL_PSEUDO);
|
||||
*bb = block_drop_unreferenced(*bb);
|
||||
return nerrors;
|
||||
}
|
||||
+28
-21
@@ -1,4 +1,5 @@
|
||||
def error: error(.);
|
||||
def halt_error: halt_error(5);
|
||||
def error(msg): msg|error;
|
||||
def map(f): [.[] | f];
|
||||
def select(f): if f then . else empty end;
|
||||
def sort_by(f): _sort_by_impl(map([f]));
|
||||
@@ -9,7 +10,7 @@ def max_by(f): _max_by_impl(map([f]));
|
||||
def min_by(f): _min_by_impl(map([f]));
|
||||
def add: reduce .[] as $x (null; . + $x);
|
||||
def del(f): delpaths([path(f)]);
|
||||
def _assign(paths; value): value as $v | reduce path(paths) as $p (.; setpath($p; $v));
|
||||
def _assign(paths; $value): reduce path(paths) as $p (.; setpath($p; $value));
|
||||
def _modify(paths; update): reduce path(paths) as $p (.; label $out | (setpath($p; getpath($p) | update) | ., break $out), delpaths([$p]));
|
||||
def map_values(f): .[] |= f;
|
||||
|
||||
@@ -166,7 +167,11 @@ def until(cond; next):
|
||||
def _until:
|
||||
if cond then . else (next|_until) end;
|
||||
_until;
|
||||
def limit($n; exp): if $n < 0 then exp else label $out | foreach exp as $item ([$n, null]; if .[0] < 1 then break $out else [.[0] -1, $item] end; .[1]) end;
|
||||
def limit($n; exp):
|
||||
if $n > 0 then label $out | foreach exp as $item ($n; .-1; $item, if . <= 0 then break $out else empty end)
|
||||
elif $n == 0 then empty
|
||||
else exp end;
|
||||
def isempty(g): 0 == ((label $go | g | (1, break $go)) // 0);
|
||||
def first(g): label $out | g | ., break $out;
|
||||
def last(g): reduce g as $item (null; $item);
|
||||
def nth($n; g): if $n < 0 then error("nth doesn't support negative indices") else last(limit($n + 1; g)) end;
|
||||
@@ -195,12 +200,11 @@ def transpose:
|
||||
end;
|
||||
def in(xs): . as $x | xs | has($x);
|
||||
def inside(xs): . as $x | xs | contains($x);
|
||||
def input: _input;
|
||||
def repeat(exp):
|
||||
def _repeat:
|
||||
exp, _repeat;
|
||||
_repeat;
|
||||
def inputs: try repeat(_input) catch if .=="break" then empty else .|error end;
|
||||
def inputs: try repeat(input) catch if .=="break" then empty else .|error end;
|
||||
# like ruby's downcase - only characters A to Z are affected
|
||||
def ascii_downcase:
|
||||
explode | map( if 65 <= . and . <= 90 then . + 32 else . end) | implode;
|
||||
@@ -212,22 +216,25 @@ def ascii_upcase:
|
||||
def truncate_stream(stream):
|
||||
. as $n | null | stream | . as $input | if (.[0]|length) > $n then setpath([0];$input[0][$n:]) else empty end;
|
||||
def fromstream(i):
|
||||
foreach i as $item (
|
||||
[null,false,null,false];
|
||||
if ($item[0]|length) == 0 then [null,false,.[2],.[3]]
|
||||
elif ($item|length) == 1 and ($item[0]|length) < 2 then [null,false,.[0],.[1]]
|
||||
else . end |
|
||||
. as $state |
|
||||
if ($item|length) > 1 and ($item[0]|length) > 0 then
|
||||
[.[0]|setpath(($item|.[0]); ($item|.[1])),
|
||||
true,
|
||||
$state[2],
|
||||
$state[3]]
|
||||
foreach i as $i (
|
||||
[null, null];
|
||||
|
||||
if ($i | length) == 2 then
|
||||
if ($i[0] | length) == 0 then .
|
||||
else [ ( .[0] | setpath($i[0]; $i[1]) ), .[1] ]
|
||||
end
|
||||
elif ($i[0] | length) == 1 then [ null, .[0] ]
|
||||
else .
|
||||
end;
|
||||
if ($item[0]|length) == 1 and ($item|length == 1) and .[3] then .[2] else empty end,
|
||||
if ($item[0]|length) == 0 then $item[1] else empty end
|
||||
);
|
||||
|
||||
if ($i | length) == 1 then
|
||||
if ($i[0] | length) == 1 then .[1]
|
||||
else empty
|
||||
end
|
||||
elif ($i[0] | length) == 0 then $i[1]
|
||||
else empty
|
||||
end
|
||||
);
|
||||
def tostream:
|
||||
{string:true,number:true,boolean:true,null:true} as $leaf_types |
|
||||
. as $dot |
|
||||
@@ -235,7 +242,7 @@ def tostream:
|
||||
else
|
||||
# We really need a _streaming_ form of `keys`.
|
||||
# We can use `range` for arrays, but not for objects.
|
||||
keys as $keys |
|
||||
keys_unsorted as $keys |
|
||||
$keys[-1] as $last|
|
||||
((# for each key
|
||||
$keys[] | . as $key |
|
||||
@@ -282,7 +289,7 @@ def bsearch(target):
|
||||
def walk(f):
|
||||
. as $in
|
||||
| if type == "object" then
|
||||
reduce keys[] as $key
|
||||
reduce keys_unsorted[] as $key
|
||||
( {}; . + { ($key): ($in[$key] | walk(f)) } ) | f
|
||||
elif type == "array" then map( walk(f) ) | f
|
||||
else f
|
||||
|
||||
+311
-121
@@ -5,6 +5,7 @@
|
||||
#include <math.h>
|
||||
#include <string.h>
|
||||
#include <stdlib.h>
|
||||
#include <unistd.h>
|
||||
#include "compile.h"
|
||||
#include "bytecode.h"
|
||||
#include "locfile.h"
|
||||
@@ -48,9 +49,11 @@ struct inst {
|
||||
// Unbound instructions (references to other things that may or may not
|
||||
// exist) are created by "gen_foo_unbound", and bindings are created by
|
||||
// block_bind(definition, body), which binds all instructions in
|
||||
// body which are unboudn and refer to "definition" by name.
|
||||
// body which are unbound and refer to "definition" by name.
|
||||
struct inst* bound_by;
|
||||
char* symbol;
|
||||
int any_unbound;
|
||||
int referenced;
|
||||
|
||||
int nformals;
|
||||
int nactuals;
|
||||
@@ -72,6 +75,8 @@ static inst* inst_new(opcode op) {
|
||||
i->bytecode_pos = -1;
|
||||
i->bound_by = 0;
|
||||
i->symbol = 0;
|
||||
i->any_unbound = 0;
|
||||
i->referenced = 0;
|
||||
i->nformals = -1;
|
||||
i->nactuals = -1;
|
||||
i->subfn = gen_noop();
|
||||
@@ -155,11 +160,19 @@ block gen_const_global(jv constant, const char *name) {
|
||||
inst* i = inst_new(STORE_GLOBAL);
|
||||
i->imm.constant = constant;
|
||||
i->symbol = strdup(name);
|
||||
i->any_unbound = 0;
|
||||
return inst_block(i);
|
||||
}
|
||||
|
||||
block gen_op_pushk_under(jv constant) {
|
||||
assert(opcode_describe(PUSHK_UNDER)->flags & OP_HAS_CONSTANT);
|
||||
inst* i = inst_new(PUSHK_UNDER);
|
||||
i->imm.constant = constant;
|
||||
return inst_block(i);
|
||||
}
|
||||
|
||||
int block_is_const(block b) {
|
||||
return (block_is_single(b) && b.first->op == LOADK);
|
||||
return (block_is_single(b) && (b.first->op == LOADK || b.first->op == PUSHK_UNDER));
|
||||
}
|
||||
|
||||
int block_is_const_inf(block b) {
|
||||
@@ -203,22 +216,29 @@ block gen_op_unbound(opcode op, const char* name) {
|
||||
assert(opcode_describe(op)->flags & OP_HAS_BINDING);
|
||||
inst* i = inst_new(op);
|
||||
i->symbol = strdup(name);
|
||||
i->any_unbound = 1;
|
||||
return inst_block(i);
|
||||
}
|
||||
|
||||
block gen_op_var_fresh(opcode op, const char* name) {
|
||||
assert(opcode_describe(op)->flags & OP_HAS_VARIABLE);
|
||||
return block_bind(gen_op_unbound(op, name),
|
||||
gen_noop(), OP_HAS_VARIABLE);
|
||||
block b = gen_op_unbound(op, name);
|
||||
b.first->bound_by = b.first;
|
||||
return b;
|
||||
}
|
||||
|
||||
block gen_op_bound(opcode op, block binder) {
|
||||
assert(block_is_single(binder));
|
||||
block b = gen_op_unbound(op, binder.first->symbol);
|
||||
b.first->bound_by = binder.first;
|
||||
b.first->any_unbound = 0;
|
||||
return b;
|
||||
}
|
||||
|
||||
block gen_dictpair(block k, block v) {
|
||||
return BLOCK(gen_subexp(k), gen_subexp(v), gen_op_simple(INSERT));
|
||||
}
|
||||
|
||||
|
||||
static void inst_join(inst* a, inst* b) {
|
||||
assert(a && b);
|
||||
@@ -298,21 +318,7 @@ static int block_count_actuals(block b) {
|
||||
return args;
|
||||
}
|
||||
|
||||
static int block_count_refs(block binder, block body) {
|
||||
int nrefs = 0;
|
||||
for (inst* i = body.first; i; i = i->next) {
|
||||
if (i != binder.first && i->bound_by == binder.first) {
|
||||
nrefs++;
|
||||
}
|
||||
// counting recurses into closures
|
||||
nrefs += block_count_refs(binder, i->subfn);
|
||||
// counting recurses into argument list
|
||||
nrefs += block_count_refs(binder, i->arglist);
|
||||
}
|
||||
return nrefs;
|
||||
}
|
||||
|
||||
static int block_bind_subblock(block binder, block body, int bindflags, int break_distance) {
|
||||
static int block_bind_subblock_inner(int* any_unbound, block binder, block body, int bindflags, int break_distance) {
|
||||
assert(block_is_single(binder));
|
||||
assert((opcode_describe(binder.first->op)->flags & bindflags) == (bindflags & ~OP_BIND_WILDCARD));
|
||||
assert(binder.first->symbol);
|
||||
@@ -324,6 +330,9 @@ static int block_bind_subblock(block binder, block body, int bindflags, int brea
|
||||
binder.first->nformals = block_count_formals(binder);
|
||||
int nrefs = 0;
|
||||
for (inst* i = body.first; i; i = i->next) {
|
||||
if (i->any_unbound == 0)
|
||||
continue;
|
||||
|
||||
int flags = opcode_describe(i->op)->flags;
|
||||
if ((flags & bindflags) == (bindflags & ~OP_BIND_WILDCARD) && i->bound_by == 0 &&
|
||||
(!strcmp(i->symbol, binder.first->symbol) ||
|
||||
@@ -345,14 +354,25 @@ static int block_bind_subblock(block binder, block body, int bindflags, int brea
|
||||
// a break whenever we come across a STOREV of *anonlabel...
|
||||
break_distance++;
|
||||
}
|
||||
|
||||
i->any_unbound = (i->symbol && !i->bound_by);
|
||||
|
||||
// binding recurses into closures
|
||||
nrefs += block_bind_subblock(binder, i->subfn, bindflags, break_distance);
|
||||
nrefs += block_bind_subblock_inner(&i->any_unbound, binder, i->subfn, bindflags, break_distance);
|
||||
// binding recurses into argument list
|
||||
nrefs += block_bind_subblock(binder, i->arglist, bindflags, break_distance);
|
||||
nrefs += block_bind_subblock_inner(&i->any_unbound, binder, i->arglist, bindflags, break_distance);
|
||||
|
||||
if (i->any_unbound)
|
||||
*any_unbound = 1;
|
||||
}
|
||||
return nrefs;
|
||||
}
|
||||
|
||||
static int block_bind_subblock(block binder, block body, int bindflags, int break_distance) {
|
||||
int any_unbound;
|
||||
return block_bind_subblock_inner(&any_unbound, binder, body, bindflags, break_distance);
|
||||
}
|
||||
|
||||
static int block_bind_each(block binder, block body, int bindflags) {
|
||||
assert(block_has_only_binders(binder, bindflags));
|
||||
bindflags |= OP_HAS_BINDING;
|
||||
@@ -363,7 +383,7 @@ static int block_bind_each(block binder, block body, int bindflags) {
|
||||
return nrefs;
|
||||
}
|
||||
|
||||
block block_bind(block binder, block body, int bindflags) {
|
||||
static block block_bind(block binder, block body, int bindflags) {
|
||||
block_bind_each(binder, body, bindflags);
|
||||
return block_join(binder, body);
|
||||
}
|
||||
@@ -401,72 +421,101 @@ block block_bind_library(block binder, block body, int bindflags, const char *li
|
||||
return body; // We don't return a join because we don't want those sticking around...
|
||||
}
|
||||
|
||||
// Bind binder to body and throw away any defs in binder not referenced
|
||||
// (directly or indirectly) from body.
|
||||
// Bind binder to body, then throw it away if not referenced.
|
||||
block block_bind_referenced(block binder, block body, int bindflags) {
|
||||
assert(block_is_single(binder));
|
||||
assert(block_has_only_binders(binder, bindflags));
|
||||
bindflags |= OP_HAS_BINDING;
|
||||
block refd = gen_noop();
|
||||
block unrefd = gen_noop();
|
||||
int nrefs;
|
||||
for (int last_kept = 0, kept = 0; ; ) {
|
||||
for (inst* curr; (curr = block_take(&binder));) {
|
||||
block b = inst_block(curr);
|
||||
nrefs = block_bind_each(b, body, bindflags);
|
||||
// Check if this binder is referenced from any of the ones we
|
||||
// already know are referenced by body.
|
||||
nrefs += block_count_refs(b, refd);
|
||||
nrefs += block_count_refs(b, body);
|
||||
if (nrefs) {
|
||||
refd = BLOCK(refd, b);
|
||||
kept++;
|
||||
} else {
|
||||
unrefd = BLOCK(unrefd, b);
|
||||
}
|
||||
|
||||
if (block_bind_subblock(binder, body, bindflags, 0) == 0) {
|
||||
block_free(binder);
|
||||
} else {
|
||||
body = BLOCK(binder, body);
|
||||
}
|
||||
return body;
|
||||
}
|
||||
|
||||
static inst* block_take_last(block* b) {
|
||||
inst* i = b->last;
|
||||
if (i == 0)
|
||||
return 0;
|
||||
if (i->prev) {
|
||||
i->prev->next = i->next;
|
||||
b->last = i->prev;
|
||||
i->prev = 0;
|
||||
} else {
|
||||
b->first = 0;
|
||||
b->last = 0;
|
||||
}
|
||||
return i;
|
||||
}
|
||||
|
||||
// Binds a sequence of binders, which *must not* alrady be bound to each other,
|
||||
// to body, throwing away unreferenced defs
|
||||
block block_bind_incremental(block binder, block body, int bindflags) {
|
||||
assert(block_has_only_binders(binder, bindflags));
|
||||
bindflags |= OP_HAS_BINDING;
|
||||
|
||||
inst* curr;
|
||||
while ((curr = block_take_last(&binder))) {
|
||||
body = block_bind_referenced(inst_block(curr), body, bindflags);
|
||||
}
|
||||
return body;
|
||||
}
|
||||
|
||||
block block_bind_self(block binder, int bindflags) {
|
||||
assert(block_has_only_binders(binder, bindflags));
|
||||
bindflags |= OP_HAS_BINDING;
|
||||
block body = gen_noop();
|
||||
|
||||
inst* curr;
|
||||
while ((curr = block_take_last(&binder))) {
|
||||
block b = inst_block(curr);
|
||||
block_bind_subblock(b, body, bindflags, 0);
|
||||
body = BLOCK(b, body);
|
||||
}
|
||||
return body;
|
||||
}
|
||||
|
||||
static void block_mark_referenced(block body) {
|
||||
int saw_top = 0;
|
||||
for (inst* i = body.last; i; i = i->prev) {
|
||||
if (saw_top && i->bound_by == i && !i->referenced)
|
||||
continue;
|
||||
if (i->op == TOP) {
|
||||
saw_top = 1;
|
||||
}
|
||||
if (kept == last_kept)
|
||||
break;
|
||||
last_kept = kept;
|
||||
binder = unrefd;
|
||||
unrefd = gen_noop();
|
||||
if (i->bound_by) {
|
||||
i->bound_by->referenced = 1;
|
||||
}
|
||||
|
||||
block_mark_referenced(i->arglist);
|
||||
block_mark_referenced(i->subfn);
|
||||
}
|
||||
block_free(unrefd);
|
||||
return block_join(refd, body);
|
||||
}
|
||||
|
||||
block block_drop_unreferenced(block body) {
|
||||
inst* curr;
|
||||
block_mark_referenced(body);
|
||||
|
||||
block refd = gen_noop();
|
||||
block unrefd = gen_noop();
|
||||
int drop;
|
||||
do {
|
||||
drop = 0;
|
||||
while ((curr = block_take(&body)) && curr->op != TOP) {
|
||||
block b = inst_block(curr);
|
||||
if (block_count_refs(b,refd) + block_count_refs(b,body) == 0) {
|
||||
unrefd = BLOCK(unrefd, b);
|
||||
drop++;
|
||||
} else {
|
||||
refd = BLOCK(refd, b);
|
||||
}
|
||||
inst* curr;
|
||||
while ((curr = block_take(&body))) {
|
||||
if (curr->bound_by == curr && !curr->referenced) {
|
||||
inst_free(curr);
|
||||
} else {
|
||||
refd = BLOCK(inst_block(curr), refd);
|
||||
}
|
||||
if (curr && curr->op == TOP) {
|
||||
body = BLOCK(inst_block(curr),body);
|
||||
}
|
||||
body = BLOCK(refd, body);
|
||||
refd = gen_noop();
|
||||
} while (drop != 0);
|
||||
block_free(unrefd);
|
||||
return body;
|
||||
}
|
||||
return refd;
|
||||
}
|
||||
|
||||
jv block_take_imports(block* body) {
|
||||
jv imports = jv_array();
|
||||
|
||||
inst* top = NULL;
|
||||
if (body->first && body->first->op == TOP) {
|
||||
top = block_take(body);
|
||||
}
|
||||
/* Parser should never generate TOP before imports */
|
||||
assert(!(body->first && body->first->op == TOP && body->first->next &&
|
||||
(body->first->next->op == MODULEMETA || body->first->next->op == DEPS)));
|
||||
|
||||
while (body->first && (body->first->op == MODULEMETA || body->first->op == DEPS)) {
|
||||
inst* dep = block_take(body);
|
||||
if (dep->op == DEPS) {
|
||||
@@ -474,12 +523,21 @@ jv block_take_imports(block* body) {
|
||||
}
|
||||
inst_free(dep);
|
||||
}
|
||||
if (top) {
|
||||
*body = block_join(inst_block(top),*body);
|
||||
}
|
||||
return imports;
|
||||
}
|
||||
|
||||
jv block_list_funcs(block body, int omit_underscores) {
|
||||
jv funcs = jv_object(); // Use the keys for set semantics.
|
||||
for (inst *pos = body.first; pos != NULL; pos = pos->next) {
|
||||
if (pos->op == CLOSURE_CREATE || pos->op == CLOSURE_CREATE_C) {
|
||||
if (pos->symbol != NULL && (!omit_underscores || pos->symbol[0] != '_')) {
|
||||
funcs = jv_object_set(funcs, jv_string_fmt("%s/%i", pos->symbol, pos->nformals), jv_null());
|
||||
}
|
||||
}
|
||||
}
|
||||
return jv_keys_unsorted(funcs);
|
||||
}
|
||||
|
||||
block gen_module(block metadata) {
|
||||
inst* i = inst_new(MODULEMETA);
|
||||
i->imm.constant = block_const(metadata);
|
||||
@@ -526,6 +584,7 @@ block gen_function(const char* name, block formals, block body) {
|
||||
}
|
||||
i->subfn = body;
|
||||
i->symbol = strdup(name);
|
||||
i->any_unbound = -1;
|
||||
i->arglist = formals;
|
||||
block b = inst_block(i);
|
||||
block_bind_subblock(b, b, OP_IS_CALL_PSEUDO | OP_HAS_BINDING, 0);
|
||||
@@ -550,9 +609,15 @@ block gen_call(const char* name, block args) {
|
||||
return b;
|
||||
}
|
||||
|
||||
|
||||
|
||||
block gen_subexp(block a) {
|
||||
if (block_is_noop(a)) {
|
||||
return gen_op_simple(DUP);
|
||||
}
|
||||
if (block_is_single(a) && a.first->op == LOADK) {
|
||||
jv c = block_const(a);
|
||||
block_free(a);
|
||||
return gen_op_pushk_under(c);
|
||||
}
|
||||
return BLOCK(gen_op_simple(SUBEXP_BEGIN), a, gen_op_simple(SUBEXP_END));
|
||||
}
|
||||
|
||||
@@ -570,17 +635,24 @@ block gen_const_object(block expr) {
|
||||
jv k = jv_null();
|
||||
jv v = jv_null();
|
||||
for (inst *i = expr.first; i; i = i->next) {
|
||||
if (i->op != SUBEXP_BEGIN ||
|
||||
if (i->op == PUSHK_UNDER) {
|
||||
k = jv_copy(i->imm.constant);
|
||||
i = i->next;
|
||||
} else if (i->op != SUBEXP_BEGIN ||
|
||||
i->next == NULL ||
|
||||
i->next->op != LOADK ||
|
||||
i->next->next == NULL ||
|
||||
i->next->next->op != SUBEXP_END) {
|
||||
is_const = 0;
|
||||
break;
|
||||
} else {
|
||||
k = jv_copy(i->next->imm.constant);
|
||||
i = i->next->next->next;
|
||||
}
|
||||
k = jv_copy(i->next->imm.constant);
|
||||
i = i->next->next->next;
|
||||
if (i == NULL ||
|
||||
if (i != NULL && i->op == PUSHK_UNDER) {
|
||||
v = jv_copy(i->imm.constant);
|
||||
i = i->next;
|
||||
} else if (i == NULL ||
|
||||
i->op != SUBEXP_BEGIN ||
|
||||
i->next == NULL ||
|
||||
i->next->op != LOADK ||
|
||||
@@ -588,9 +660,10 @@ block gen_const_object(block expr) {
|
||||
i->next->next->op != SUBEXP_END) {
|
||||
is_const = 0;
|
||||
break;
|
||||
} else {
|
||||
v = jv_copy(i->next->imm.constant);
|
||||
i = i->next->next->next;
|
||||
}
|
||||
v = jv_copy(i->next->imm.constant);
|
||||
i = i->next->next->next;
|
||||
if (i == NULL || i->op != INSERT) {
|
||||
is_const = 0;
|
||||
break;
|
||||
@@ -696,35 +769,94 @@ static block bind_matcher(block matcher, block body) {
|
||||
// block_has_only_binders(matcher), which is not true here as matchers
|
||||
// may also contain code to extract the correct elements
|
||||
for (inst* i = matcher.first; i; i = i->next) {
|
||||
if (i->op == STOREV && !i->bound_by)
|
||||
if ((i->op == STOREV || i->op == STOREVN) && !i->bound_by)
|
||||
block_bind_subblock(inst_block(i), body, OP_HAS_VARIABLE, 0);
|
||||
}
|
||||
return BLOCK(matcher, body);
|
||||
}
|
||||
|
||||
|
||||
// Extract destructuring var names from the block
|
||||
// *vars should be a jv_object (for set semantics)
|
||||
static void block_get_unbound_vars(block b, jv *vars) {
|
||||
assert(vars != NULL);
|
||||
assert(jv_get_kind(*vars) == JV_KIND_OBJECT);
|
||||
for (inst* i = b.first; i; i = i->next) {
|
||||
if (i->subfn.first) {
|
||||
block_get_unbound_vars(i->subfn, vars);
|
||||
continue;
|
||||
}
|
||||
if ((i->op == STOREV || i->op == STOREVN) && i->bound_by == NULL) {
|
||||
*vars = jv_object_set(*vars, jv_string(i->symbol), jv_true());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* Build wrappers around destructuring matchers so that we can chain them
|
||||
* when we have errors. The approach is as follows:
|
||||
* DESTRUCTURE_ALT NEXT_MATCHER (unless last matcher)
|
||||
* existing_matcher_block
|
||||
* JUMP BODY
|
||||
*/
|
||||
static block bind_alternation_matchers(block matchers, block body) {
|
||||
block preamble = {0};
|
||||
block altmatchers = {0};
|
||||
block mb = {0};
|
||||
block final_matcher = matchers;
|
||||
|
||||
// Pass through the matchers to find all destructured names.
|
||||
while (final_matcher.first && final_matcher.first->op == DESTRUCTURE_ALT) {
|
||||
block_append(&altmatchers, inst_block(block_take(&final_matcher)));
|
||||
}
|
||||
|
||||
// We don't have any alternations here, so we can use the simplest case.
|
||||
if (altmatchers.first == NULL) {
|
||||
return bind_matcher(final_matcher, body);
|
||||
}
|
||||
|
||||
// Collect var names
|
||||
jv all_vars = jv_object();
|
||||
block_get_unbound_vars(altmatchers, &all_vars);
|
||||
block_get_unbound_vars(final_matcher, &all_vars);
|
||||
|
||||
// We need a preamble of STOREVs to which to bind the matchers and the body.
|
||||
jv_object_keys_foreach(all_vars, key) {
|
||||
preamble = BLOCK(preamble,
|
||||
gen_op_simple(DUP),
|
||||
gen_const(jv_null()),
|
||||
gen_op_unbound(STOREV, jv_string_value(key)));
|
||||
jv_free(key);
|
||||
}
|
||||
jv_free(all_vars);
|
||||
|
||||
// Now we build each matcher in turn
|
||||
for (inst *i = altmatchers.first; i; i = i->next) {
|
||||
block submatcher = i->subfn;
|
||||
|
||||
// If we're successful, jump to the end of the matchers
|
||||
submatcher = BLOCK(submatcher, gen_op_target(JUMP, final_matcher));
|
||||
|
||||
// DESTRUCTURE_ALT to the end of this submatcher so we can skip to the next one on error
|
||||
mb = BLOCK(mb, gen_op_target(DESTRUCTURE_ALT, submatcher), submatcher);
|
||||
|
||||
// We're done with this inst and we don't want it anymore
|
||||
// But we can't let it free the submatcher block.
|
||||
i->subfn.first = i->subfn.last = NULL;
|
||||
}
|
||||
// We're done with these insts now.
|
||||
block_free(altmatchers);
|
||||
|
||||
return bind_matcher(preamble, BLOCK(mb, final_matcher, body));
|
||||
}
|
||||
|
||||
block gen_reduce(block source, block matcher, block init, block body) {
|
||||
block res_var = gen_op_var_fresh(STOREV, "reduce");
|
||||
block update_var = gen_op_bound(STOREV, res_var);
|
||||
block jmp = gen_op_target(JUMP, body);
|
||||
block loop = BLOCK(gen_op_simple(DUPN),
|
||||
source,
|
||||
bind_matcher(matcher,
|
||||
bind_alternation_matchers(matcher,
|
||||
BLOCK(gen_op_bound(LOADVN, res_var),
|
||||
/*
|
||||
* We fork to the body, jump to
|
||||
* the STOREV. This means that
|
||||
* if body produces no results
|
||||
* (i.e., it just does empty)
|
||||
* then we keep the current
|
||||
* reduction state as-is.
|
||||
*
|
||||
* To break out of a
|
||||
* reduction... use break.
|
||||
*/
|
||||
gen_op_target(FORK, jmp),
|
||||
jmp,
|
||||
body,
|
||||
update_var)),
|
||||
gen_op_bound(STOREV, res_var))),
|
||||
gen_op_simple(BACKTRACK));
|
||||
return BLOCK(gen_op_simple(DUP),
|
||||
init,
|
||||
@@ -742,7 +874,7 @@ block gen_foreach(block source, block matcher, block init, block update, block e
|
||||
source,
|
||||
// destructure the value into variable(s) for all the code
|
||||
// in the body to see
|
||||
bind_matcher(matcher,
|
||||
bind_alternation_matchers(matcher,
|
||||
// load the loop state variable
|
||||
BLOCK(gen_op_bound(LOADVN, state_var),
|
||||
// generate updated state
|
||||
@@ -848,6 +980,17 @@ block gen_or(block a, block b) {
|
||||
gen_const(jv_false())))));
|
||||
}
|
||||
|
||||
block gen_destructure_alt(block matcher) {
|
||||
for (inst *i = matcher.first; i; i = i->next) {
|
||||
if (i->op == STOREV) {
|
||||
i->op = STOREVN;
|
||||
}
|
||||
}
|
||||
inst* i = inst_new(DESTRUCTURE_ALT);
|
||||
i->subfn = matcher;
|
||||
return inst_block(i);
|
||||
}
|
||||
|
||||
block gen_var_binding(block var, const char* name, block body) {
|
||||
return gen_destructure(var, gen_op_unbound(STOREV, name), body);
|
||||
}
|
||||
@@ -860,9 +1003,16 @@ block gen_array_matcher(block left, block curr) {
|
||||
// `left` was returned by this function, so the third inst is the
|
||||
// constant containing the previously used index
|
||||
assert(left.first->op == DUP);
|
||||
assert(left.first->next->op == SUBEXP_BEGIN);
|
||||
assert(left.first->next->next->op == LOADK);
|
||||
index = 1 + (int) jv_number_value(left.first->next->next->imm.constant);
|
||||
assert(left.first->next != NULL);
|
||||
inst *i = NULL;
|
||||
if (left.first->next->op == PUSHK_UNDER) {
|
||||
i = left.first->next;
|
||||
} else {
|
||||
assert(left.first->next->op == SUBEXP_BEGIN);
|
||||
assert(left.first->next->next->op == LOADK);
|
||||
i = left.first->next->next;
|
||||
}
|
||||
index = 1 + (int) jv_number_value(i->imm.constant);
|
||||
}
|
||||
|
||||
// `left` goes at the end so that the const index is in a predictable place
|
||||
@@ -875,13 +1025,19 @@ block gen_object_matcher(block name, block curr) {
|
||||
curr);
|
||||
}
|
||||
|
||||
block gen_destructure(block var, block matcher, block body) {
|
||||
block gen_destructure(block var, block matchers, block body) {
|
||||
// var bindings can be added after coding the program; leave the TOP first.
|
||||
block top = gen_noop();
|
||||
if (body.first && body.first->op == TOP)
|
||||
top = inst_block(block_take(&body));
|
||||
|
||||
return BLOCK(top, gen_op_simple(DUP), var, bind_matcher(matcher, body));
|
||||
if (matchers.first && matchers.first->op == DESTRUCTURE_ALT) {
|
||||
block_append(&var, gen_op_simple(DUP));
|
||||
} else {
|
||||
top = BLOCK(top, gen_op_simple(DUP));
|
||||
}
|
||||
|
||||
return BLOCK(top, gen_subexp(var), gen_op_simple(POP), bind_alternation_matchers(matchers, body));
|
||||
}
|
||||
|
||||
// Like gen_var_binding(), but bind `break`'s wildcard unbound variable
|
||||
@@ -892,7 +1048,7 @@ static block gen_wildvar_binding(block var, const char* name, block body) {
|
||||
}
|
||||
|
||||
block gen_cond(block cond, block iftrue, block iffalse) {
|
||||
return BLOCK(gen_op_simple(DUP), cond,
|
||||
return BLOCK(gen_op_simple(DUP), BLOCK(gen_subexp(cond), gen_op_simple(POP)),
|
||||
gen_condbranch(BLOCK(gen_op_simple(POP), iftrue),
|
||||
BLOCK(gen_op_simple(POP), iffalse)));
|
||||
}
|
||||
@@ -960,7 +1116,8 @@ block gen_cbinding(const struct cfunction* cfunctions, int ncfunctions, block co
|
||||
inst* i = inst_new(CLOSURE_CREATE_C);
|
||||
i->imm.cfunc = &cfunctions[cfunc];
|
||||
i->symbol = strdup(i->imm.cfunc->name);
|
||||
code = block_bind(inst_block(i), code, OP_IS_CALL_PSEUDO);
|
||||
i->any_unbound = 0;
|
||||
code = BLOCK(inst_block(i), code);
|
||||
}
|
||||
return code;
|
||||
}
|
||||
@@ -985,16 +1142,46 @@ static int count_cfunctions(block b) {
|
||||
return n;
|
||||
}
|
||||
|
||||
#ifndef WIN32
|
||||
extern char **environ;
|
||||
#endif
|
||||
|
||||
static jv
|
||||
make_env(jv env)
|
||||
{
|
||||
if (jv_is_valid(env))
|
||||
return jv_copy(env);
|
||||
jv r = jv_object();
|
||||
if (environ == NULL)
|
||||
return r;
|
||||
for (size_t i = 0; environ[i] != NULL; i++) {
|
||||
const char *eq;
|
||||
|
||||
if ((eq = strchr(environ[i], '=')) == NULL)
|
||||
r = jv_object_delete(r, jv_string(environ[i]));
|
||||
else
|
||||
r = jv_object_set(r, jv_string_sized(environ[i], eq - environ[i]), jv_string(eq + 1));
|
||||
}
|
||||
return jv_copy(r);
|
||||
}
|
||||
|
||||
// Expands call instructions into a calling sequence
|
||||
static int expand_call_arglist(block* b) {
|
||||
static int expand_call_arglist(block* b, jv args, jv *env) {
|
||||
int errors = 0;
|
||||
block ret = gen_noop();
|
||||
for (inst* curr; (curr = block_take(b));) {
|
||||
if (opcode_describe(curr->op)->flags & OP_HAS_BINDING) {
|
||||
if (!curr->bound_by) {
|
||||
if (!curr->bound_by && curr->op == LOADV && strcmp(curr->symbol, "ENV") == 0) {
|
||||
curr->op = LOADK;
|
||||
*env = curr->imm.constant = make_env(*env);
|
||||
} else if (!curr->bound_by && curr->op == LOADV && jv_object_has(jv_copy(args), jv_string(curr->symbol))) {
|
||||
curr->op = LOADK;
|
||||
curr->imm.constant = jv_object_get(jv_copy(args), jv_string(curr->symbol));
|
||||
} else if (!curr->bound_by) {
|
||||
if (curr->symbol[0] == '*' && curr->symbol[1] >= '1' && curr->symbol[1] <= '3' && curr->symbol[2] == '\0')
|
||||
locfile_locate(curr->locfile, curr->source, "jq: error: break used outside labeled control structure");
|
||||
else if (curr->op == LOADV)
|
||||
locfile_locate(curr->locfile, curr->source, "jq: error: $%s is not defined", curr->symbol);
|
||||
else
|
||||
locfile_locate(curr->locfile, curr->source, "jq: error: %s/%d is not defined", curr->symbol, block_count_actuals(curr->arglist));
|
||||
errors++;
|
||||
@@ -1047,7 +1234,7 @@ static int expand_call_arglist(block* b) {
|
||||
i->subfn = gen_noop();
|
||||
inst_free(i);
|
||||
// arguments should be pushed in reverse order, prepend them to prelude
|
||||
errors += expand_call_arglist(&body);
|
||||
errors += expand_call_arglist(&body, args, env);
|
||||
prelude = BLOCK(gen_subexp(body), prelude);
|
||||
actual_args++;
|
||||
}
|
||||
@@ -1069,12 +1256,12 @@ static int expand_call_arglist(block* b) {
|
||||
return errors;
|
||||
}
|
||||
|
||||
static int compile(struct bytecode* bc, block b, struct locfile* lf) {
|
||||
static int compile(struct bytecode* bc, block b, struct locfile* lf, jv args, jv *env) {
|
||||
int errors = 0;
|
||||
int pos = 0;
|
||||
int var_frame_idx = 0;
|
||||
bc->nsubfunctions = 0;
|
||||
errors += expand_call_arglist(&b);
|
||||
errors += expand_call_arglist(&b, args, env);
|
||||
b = BLOCK(b, gen_op_simple(RET));
|
||||
jv localnames = jv_array();
|
||||
for (inst* curr = b.first; curr; curr = curr->next) {
|
||||
@@ -1119,7 +1306,7 @@ static int compile(struct bytecode* bc, block b, struct locfile* lf) {
|
||||
bc->codelen = pos;
|
||||
bc->debuginfo = jv_object_set(bc->debuginfo, jv_string("locals"), localnames);
|
||||
if (bc->nsubfunctions) {
|
||||
bc->subfunctions = jv_mem_alloc(sizeof(struct bytecode*) * bc->nsubfunctions);
|
||||
bc->subfunctions = jv_mem_calloc(sizeof(struct bytecode*), bc->nsubfunctions);
|
||||
for (inst* curr = b.first; curr; curr = curr->next) {
|
||||
if (curr->op == CLOSURE_CREATE) {
|
||||
struct bytecode* subfn = jv_mem_alloc(sizeof(struct bytecode));
|
||||
@@ -1137,14 +1324,14 @@ static int compile(struct bytecode* bc, block b, struct locfile* lf) {
|
||||
params = jv_array_append(params, jv_string(param->symbol));
|
||||
}
|
||||
subfn->debuginfo = jv_object_set(subfn->debuginfo, jv_string("params"), params);
|
||||
errors += compile(subfn, curr->subfn, lf);
|
||||
errors += compile(subfn, curr->subfn, lf, args, env);
|
||||
curr->subfn = gen_noop();
|
||||
}
|
||||
}
|
||||
} else {
|
||||
bc->subfunctions = 0;
|
||||
}
|
||||
uint16_t* code = jv_mem_alloc(sizeof(uint16_t) * bc->codelen);
|
||||
uint16_t* code = jv_mem_calloc(sizeof(uint16_t), bc->codelen);
|
||||
bc->code = code;
|
||||
pos = 0;
|
||||
jv constant_pool = jv_array();
|
||||
@@ -1202,17 +1389,20 @@ static int compile(struct bytecode* bc, block b, struct locfile* lf) {
|
||||
return errors;
|
||||
}
|
||||
|
||||
int block_compile(block b, struct bytecode** out, struct locfile* lf) {
|
||||
int block_compile(block b, struct bytecode** out, struct locfile* lf, jv args) {
|
||||
struct bytecode* bc = jv_mem_alloc(sizeof(struct bytecode));
|
||||
bc->parent = 0;
|
||||
bc->nclosures = 0;
|
||||
bc->globals = jv_mem_alloc(sizeof(struct symbol_table));
|
||||
int ncfunc = count_cfunctions(b);
|
||||
bc->globals->ncfunctions = 0;
|
||||
bc->globals->cfunctions = jv_mem_alloc(sizeof(struct cfunction) * ncfunc);
|
||||
bc->globals->cfunctions = jv_mem_calloc(sizeof(struct cfunction), ncfunc);
|
||||
bc->globals->cfunc_names = jv_array();
|
||||
bc->debuginfo = jv_object_set(jv_object(), jv_string("name"), jv_null());
|
||||
int nerrors = compile(bc, b, lf);
|
||||
jv env = jv_invalid();
|
||||
int nerrors = compile(bc, b, lf, args, &env);
|
||||
jv_free(args);
|
||||
jv_free(env);
|
||||
assert(bc->globals->ncfunctions == ncfunc);
|
||||
if (nerrors > 0) {
|
||||
bytecode_free(bc);
|
||||
|
||||
+7
-2
@@ -29,6 +29,7 @@ block gen_op_target(opcode op, block target);
|
||||
block gen_op_unbound(opcode op, const char* name);
|
||||
block gen_op_bound(opcode op, block binder);
|
||||
block gen_op_var_fresh(opcode op, const char* name);
|
||||
block gen_op_pushk_under(jv constant);
|
||||
|
||||
block gen_module(block metadata);
|
||||
jv block_module_meta(block b);
|
||||
@@ -49,11 +50,13 @@ block gen_definedor(block a, block b);
|
||||
block gen_condbranch(block iftrue, block iffalse);
|
||||
block gen_and(block a, block b);
|
||||
block gen_or(block a, block b);
|
||||
block gen_dictpair(block k, block v);
|
||||
|
||||
block gen_var_binding(block var, const char* name, block body);
|
||||
block gen_array_matcher(block left, block curr);
|
||||
block gen_object_matcher(block name, block curr);
|
||||
block gen_destructure(block var, block matcher, block body);
|
||||
block gen_destructure_alt(block matcher);
|
||||
|
||||
block gen_cond(block cond, block iftrue, block iffalse);
|
||||
block gen_try_handler(block handler);
|
||||
@@ -69,14 +72,16 @@ int block_has_only_binders(block, int bindflags);
|
||||
int block_has_main(block);
|
||||
int block_is_funcdef(block b);
|
||||
int block_is_single(block b);
|
||||
block block_bind(block binder, block body, int bindflags);
|
||||
block block_bind_library(block binder, block body, int bindflags, const char* libname);
|
||||
block block_bind_referenced(block binder, block body, int bindflags);
|
||||
block block_bind_incremental(block binder, block body, int bindflags);
|
||||
block block_bind_self(block binder, int bindflags);
|
||||
block block_drop_unreferenced(block body);
|
||||
|
||||
jv block_take_imports(block* body);
|
||||
jv block_list_funcs(block body, int omit_underscores);
|
||||
|
||||
int block_compile(block, struct bytecode**, struct locfile*);
|
||||
int block_compile(block, struct bytecode**, struct locfile*, jv);
|
||||
|
||||
void block_free(block);
|
||||
|
||||
|
||||
+126
-23
@@ -40,6 +40,10 @@ struct jq_state {
|
||||
int initial_execution;
|
||||
unsigned next_label;
|
||||
|
||||
int halted;
|
||||
jv exit_code;
|
||||
jv error_message;
|
||||
|
||||
jv attrs;
|
||||
jq_input_cb input_cb;
|
||||
void *input_cb_data;
|
||||
@@ -242,6 +246,28 @@ static void path_append(jq_state* jq, jv component, jv value_at_path) {
|
||||
}
|
||||
}
|
||||
|
||||
/* For f_getpath() */
|
||||
jv
|
||||
_jq_path_append(jq_state *jq, jv v, jv p, jv value_at_path) {
|
||||
if (jq->subexp_nest != 0 ||
|
||||
jv_get_kind(jq->path) != JV_KIND_ARRAY ||
|
||||
!jv_is_valid(value_at_path)) {
|
||||
jv_free(v);
|
||||
jv_free(p);
|
||||
return value_at_path;
|
||||
}
|
||||
if (!jv_identical(v, jv_copy(jq->value_at_path))) {
|
||||
jv_free(p);
|
||||
return value_at_path;
|
||||
}
|
||||
if (jv_get_kind(p) == JV_KIND_ARRAY)
|
||||
jq->path = jv_array_concat(jq->path, p);
|
||||
else
|
||||
jq->path = jv_array_append(jq->path, p);
|
||||
jv_free(jq->value_at_path);
|
||||
return jv_copy(jq->value_at_path = value_at_path);
|
||||
}
|
||||
|
||||
uint16_t* stack_restore(jq_state *jq){
|
||||
while (!stack_pop_will_free(&jq->stk, jq->fork_top)) {
|
||||
if (stack_pop_will_free(&jq->stk, jq->stk_top)) {
|
||||
@@ -285,6 +311,9 @@ static void jq_reset(jq_state *jq) {
|
||||
jv_free(jq->error);
|
||||
jq->error = jv_null();
|
||||
|
||||
jq->halted = 0;
|
||||
jv_free(jq->exit_code);
|
||||
jv_free(jq->error_message);
|
||||
if (jv_get_kind(jq->path) != JV_KIND_INVALID)
|
||||
jv_free(jq->path);
|
||||
jq->path = jv_null();
|
||||
@@ -320,6 +349,11 @@ jv jq_next(jq_state *jq) {
|
||||
jq->initial_execution = 0;
|
||||
assert(jv_get_kind(jq->error) == JV_KIND_NULL);
|
||||
while (1) {
|
||||
if (jq->halted) {
|
||||
if (jq->debug_trace_enabled)
|
||||
printf("\t<halted>\n");
|
||||
return jv_invalid();
|
||||
}
|
||||
uint16_t opcode = *pc;
|
||||
raising = 0;
|
||||
|
||||
@@ -331,10 +365,9 @@ jv jq_next(jq_state *jq) {
|
||||
if (!backtracking) {
|
||||
int stack_in = opdesc->stack_in;
|
||||
if (stack_in == -1) stack_in = pc[1];
|
||||
param = jq->stk_top;
|
||||
for (int i=0; i<stack_in; i++) {
|
||||
if (i == 0) {
|
||||
param = jq->stk_top;
|
||||
} else {
|
||||
if (i != 0) {
|
||||
printf(" | ");
|
||||
param = *stack_block_next(&jq->stk, param);
|
||||
}
|
||||
@@ -344,6 +377,12 @@ jv jq_next(jq_state *jq) {
|
||||
//printf(" -- ");
|
||||
//jv_dump(jv_copy(jq->path), 0);
|
||||
}
|
||||
if (jq->debug_trace_enabled & JQ_DEBUG_TRACE_DETAIL) {
|
||||
while ((param = *stack_block_next(&jq->stk, param))) {
|
||||
printf(" || ");
|
||||
jv_dump(jv_copy(*(jv*)stack_block(&jq->stk, param)), JV_PRINT_REFCOUNT);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
printf("\t<backtracking>");
|
||||
}
|
||||
@@ -417,6 +456,15 @@ jv jq_next(jq_state *jq) {
|
||||
break;
|
||||
}
|
||||
|
||||
case PUSHK_UNDER: {
|
||||
jv v = jv_array_get(jv_copy(frame_current(jq)->bc->constants), *pc++);
|
||||
assert(jv_is_valid(v));
|
||||
jv v2 = stack_pop(jq);
|
||||
stack_push(jq, v);
|
||||
stack_push(jq, v2);
|
||||
break;
|
||||
}
|
||||
|
||||
case POP: {
|
||||
jv_free(stack_pop(jq));
|
||||
break;
|
||||
@@ -514,6 +562,8 @@ jv jq_next(jq_state *jq) {
|
||||
break;
|
||||
}
|
||||
|
||||
case STOREVN:
|
||||
stack_save(jq, pc - 1, stack_get_pos(jq));
|
||||
case STOREV: {
|
||||
uint16_t level = *pc++;
|
||||
uint16_t v = *pc++;
|
||||
@@ -529,6 +579,16 @@ jv jq_next(jq_state *jq) {
|
||||
break;
|
||||
}
|
||||
|
||||
case ON_BACKTRACK(STOREVN): {
|
||||
uint16_t level = *pc++;
|
||||
uint16_t v = *pc++;
|
||||
jv* var = frame_local_var(jq, v, level);
|
||||
jv_free(*var);
|
||||
*var = jv_null();
|
||||
goto do_backtrack;
|
||||
break;
|
||||
}
|
||||
|
||||
case STORE_GLOBAL: {
|
||||
// Get the constant
|
||||
jv val = jv_array_get(jv_copy(frame_current(jq)->bc->constants), *pc++);
|
||||
@@ -739,21 +799,29 @@ jv jq_next(jq_state *jq) {
|
||||
}
|
||||
|
||||
case FORK_OPT:
|
||||
case DESTRUCTURE_ALT:
|
||||
case FORK: {
|
||||
stack_save(jq, pc - 1, stack_get_pos(jq));
|
||||
pc++; // skip offset this time
|
||||
break;
|
||||
}
|
||||
|
||||
case ON_BACKTRACK(FORK_OPT): {
|
||||
case ON_BACKTRACK(FORK_OPT):
|
||||
case ON_BACKTRACK(DESTRUCTURE_ALT): {
|
||||
if (jv_is_valid(jq->error)) {
|
||||
// `try EXP ...` backtracked here (no value, `empty`), so we backtrack more
|
||||
jv_free(stack_pop(jq));
|
||||
goto do_backtrack;
|
||||
}
|
||||
// `try EXP ...` exception caught in EXP
|
||||
jv_free(stack_pop(jq)); // free the input
|
||||
stack_push(jq, jv_invalid_get_msg(jq->error)); // push the error's message
|
||||
// DESTRUCTURE_ALT doesn't want the error message on the stack,
|
||||
// as we would just want to throw it away anyway.
|
||||
if (opcode != ON_BACKTRACK(DESTRUCTURE_ALT)) {
|
||||
jv_free(stack_pop(jq)); // free the input
|
||||
stack_push(jq, jv_invalid_get_msg(jq->error)); // push the error's message
|
||||
} else {
|
||||
jv_free(jq->error);
|
||||
}
|
||||
jq->error = jv_null();
|
||||
uint16_t offset = *pc++;
|
||||
pc += offset;
|
||||
@@ -932,6 +1000,10 @@ jq_state *jq_init(void) {
|
||||
jq->curr_frame = 0;
|
||||
jq->error = jv_null();
|
||||
|
||||
jq->halted = 0;
|
||||
jq->exit_code = jv_invalid();
|
||||
jq->error_message = jv_invalid();
|
||||
|
||||
jq->err_cb = default_err_cb;
|
||||
jq->err_cb_data = stderr;
|
||||
|
||||
@@ -974,11 +1046,7 @@ void jq_start(jq_state *jq, jv input, int flags) {
|
||||
|
||||
stack_push(jq, input);
|
||||
stack_save(jq, jq->bc->code, stack_get_pos(jq));
|
||||
if (flags & JQ_DEBUG_TRACE) {
|
||||
jq->debug_trace_enabled = 1;
|
||||
} else {
|
||||
jq->debug_trace_enabled = 0;
|
||||
}
|
||||
jq->debug_trace_enabled = flags & JQ_DEBUG_TRACE_ALL;
|
||||
jq->initial_execution = 1;
|
||||
}
|
||||
|
||||
@@ -1066,9 +1134,26 @@ static struct bytecode *optimize(struct bytecode *bc) {
|
||||
return optimize_code(bc);
|
||||
}
|
||||
|
||||
static jv
|
||||
args2obj(jv args)
|
||||
{
|
||||
if (jv_get_kind(args) == JV_KIND_OBJECT)
|
||||
return args;
|
||||
assert(jv_get_kind(args) == JV_KIND_ARRAY);
|
||||
jv r = jv_object();
|
||||
jv kk = jv_string("name");
|
||||
jv vk = jv_string("value");
|
||||
jv_array_foreach(args, i, v)
|
||||
r = jv_object_set(r, jv_object_get(jv_copy(v), kk), jv_object_get(v, vk));
|
||||
jv_free(args);
|
||||
jv_free(kk);
|
||||
jv_free(vk);
|
||||
return r;
|
||||
}
|
||||
|
||||
int jq_compile_args(jq_state *jq, const char* str, jv args) {
|
||||
jv_nomem_handler(jq->nomem_handler, jq->nomem_handler_data);
|
||||
assert(jv_get_kind(args) == JV_KIND_ARRAY);
|
||||
assert(jv_get_kind(args) == JV_KIND_ARRAY || jv_get_kind(args) == JV_KIND_OBJECT);
|
||||
struct locfile* locations;
|
||||
locations = locfile_init(jq, "<top-level>", str, strlen(str));
|
||||
block program;
|
||||
@@ -1079,29 +1164,22 @@ int jq_compile_args(jq_state *jq, const char* str, jv args) {
|
||||
}
|
||||
int nerrors = load_program(jq, locations, &program);
|
||||
if (nerrors == 0) {
|
||||
jv_array_foreach(args, i, arg) {
|
||||
jv name = jv_object_get(jv_copy(arg), jv_string("name"));
|
||||
jv value = jv_object_get(arg, jv_string("value"));
|
||||
program = gen_var_binding(gen_const(value), jv_string_value(name), program);
|
||||
jv_free(name);
|
||||
}
|
||||
|
||||
nerrors = builtins_bind(jq, &program);
|
||||
if (nerrors == 0) {
|
||||
nerrors = block_compile(program, &jq->bc, locations);
|
||||
nerrors = block_compile(program, &jq->bc, locations, args = args2obj(args));
|
||||
}
|
||||
}
|
||||
} else
|
||||
jv_free(args);
|
||||
if (nerrors)
|
||||
jq_report_error(jq, jv_string_fmt("jq: %d compile %s", nerrors, nerrors > 1 ? "errors" : "error"));
|
||||
if (jq->bc)
|
||||
jq->bc = optimize(jq->bc);
|
||||
jv_free(args);
|
||||
locfile_free(locations);
|
||||
return jq->bc != NULL;
|
||||
}
|
||||
|
||||
int jq_compile(jq_state *jq, const char* str) {
|
||||
return jq_compile_args(jq, str, jv_array());
|
||||
return jq_compile_args(jq, str, jv_object());
|
||||
}
|
||||
|
||||
jv jq_get_jq_origin(jq_state *jq) {
|
||||
@@ -1153,3 +1231,28 @@ void jq_get_debug_cb(jq_state *jq, jq_msg_cb *cb, void **data) {
|
||||
*cb = jq->debug_cb;
|
||||
*data = jq->debug_cb_data;
|
||||
}
|
||||
|
||||
void
|
||||
jq_halt(jq_state *jq, jv exit_code, jv error_message)
|
||||
{
|
||||
assert(!jq->halted);
|
||||
jq->halted = 1;
|
||||
jq->exit_code = exit_code;
|
||||
jq->error_message = error_message;
|
||||
}
|
||||
|
||||
int
|
||||
jq_halted(jq_state *jq)
|
||||
{
|
||||
return jq->halted;
|
||||
}
|
||||
|
||||
jv jq_get_exit_code(jq_state *jq)
|
||||
{
|
||||
return jv_copy(jq->exit_code);
|
||||
}
|
||||
|
||||
jv jq_get_error_message(jq_state *jq)
|
||||
{
|
||||
return jv_copy(jq->error_message);
|
||||
}
|
||||
@@ -4,7 +4,11 @@
|
||||
#include <stdio.h>
|
||||
#include "jv.h"
|
||||
|
||||
enum {JQ_DEBUG_TRACE = 1};
|
||||
enum {
|
||||
JQ_DEBUG_TRACE = 1,
|
||||
JQ_DEBUG_TRACE_DETAIL = 2,
|
||||
JQ_DEBUG_TRACE_ALL = JQ_DEBUG_TRACE | JQ_DEBUG_TRACE_DETAIL,
|
||||
};
|
||||
|
||||
typedef struct jq_state jq_state;
|
||||
typedef void (*jq_msg_cb)(void *, jv);
|
||||
@@ -22,6 +26,11 @@ void jq_start(jq_state *, jv value, int);
|
||||
jv jq_next(jq_state *);
|
||||
void jq_teardown(jq_state **);
|
||||
|
||||
void jq_halt(jq_state *, jv, jv);
|
||||
int jq_halted(jq_state *);
|
||||
jv jq_get_exit_code(jq_state *);
|
||||
jv jq_get_error_message(jq_state *);
|
||||
|
||||
typedef jv (*jq_input_cb)(jq_state *, void *);
|
||||
void jq_set_input_cb(jq_state *, jq_input_cb, void *);
|
||||
void jq_get_input_cb(jq_state *, jq_input_cb *, void **);
|
||||
@@ -57,4 +66,6 @@ jv jq_util_input_get_position(jq_state*);
|
||||
jv jq_util_input_get_current_filename(jq_state*);
|
||||
jv jq_util_input_get_current_line(jq_state*);
|
||||
|
||||
int jq_set_colors(const char *);
|
||||
|
||||
#endif /* !JQ_H */
|
||||
@@ -296,10 +296,18 @@ static jv jvp_array_slice(jv a, int start, int end) {
|
||||
jv_free(a);
|
||||
return jv_array();
|
||||
}
|
||||
// FIXME FIXME FIXME large offsets
|
||||
a.offset += start;
|
||||
a.size = end - start;
|
||||
return a;
|
||||
|
||||
if (a.offset + start >= 1 << (sizeof(a.offset) * CHAR_BIT)) {
|
||||
jv r = jv_array_sized(end - start);
|
||||
for (int i = start; i < end; i++)
|
||||
r = jv_array_append(r, jv_array_get(jv_copy(a), i));
|
||||
jv_free(a);
|
||||
return r;
|
||||
} else {
|
||||
a.offset += start;
|
||||
a.size = end - start;
|
||||
return a;
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
@@ -650,10 +658,12 @@ jv jv_string_indexes(jv j, jv k) {
|
||||
int idxlen = jv_string_length_bytes(jv_copy(k));
|
||||
jv a = jv_array();
|
||||
|
||||
p = jstr;
|
||||
while ((p = _jq_memmem(p, (jstr + jlen) - p, idxstr, idxlen)) != NULL) {
|
||||
a = jv_array_append(a, jv_number(p - jstr));
|
||||
p += idxlen;
|
||||
if (idxlen != 0) {
|
||||
p = jstr;
|
||||
while ((p = _jq_memmem(p, (jstr + jlen) - p, idxstr, idxlen)) != NULL) {
|
||||
a = jv_array_append(a, jv_number(p - jstr));
|
||||
p += idxlen;
|
||||
}
|
||||
}
|
||||
jv_free(j);
|
||||
jv_free(k);
|
||||
@@ -1106,6 +1116,16 @@ jv jv_object_get(jv object, jv key) {
|
||||
return val;
|
||||
}
|
||||
|
||||
int jv_object_has(jv object, jv key) {
|
||||
assert(jv_get_kind(object) == JV_KIND_OBJECT);
|
||||
assert(jv_get_kind(key) == JV_KIND_STRING);
|
||||
jv* slot = jvp_object_read(object, key);
|
||||
int res = slot ? 1 : 0;
|
||||
jv_free(object);
|
||||
jv_free(key);
|
||||
return res;
|
||||
}
|
||||
|
||||
jv jv_object_set(jv object, jv key, jv value) {
|
||||
assert(jv_get_kind(object) == JV_KIND_OBJECT);
|
||||
assert(jv_get_kind(key) == JV_KIND_STRING);
|
||||
@@ -1324,7 +1344,13 @@ int jv_contains(jv a, jv b) {
|
||||
} else if (jv_get_kind(a) == JV_KIND_ARRAY) {
|
||||
r = jv_array_contains(jv_copy(a), jv_copy(b));
|
||||
} else if (jv_get_kind(a) == JV_KIND_STRING) {
|
||||
r = strstr(jv_string_value(a), jv_string_value(b)) != 0;
|
||||
int b_len = jv_string_length_bytes(jv_copy(b));
|
||||
if (b_len != 0) {
|
||||
r = _jq_memmem(jv_string_value(a), jv_string_length_bytes(jv_copy(a)),
|
||||
jv_string_value(b), b_len) != 0;
|
||||
} else {
|
||||
r = 1;
|
||||
}
|
||||
} else {
|
||||
r = jv_equal(jv_copy(a), jv_copy(b));
|
||||
}
|
||||
|
||||
@@ -99,9 +99,6 @@ jv jv_array_indexes(jv, jv);
|
||||
__attribute__ ((__format__( __printf__, fmt_arg_num, args_num)))
|
||||
#define JV_VPRINTF_LIKE(fmt_arg_num) \
|
||||
__attribute__ ((__format__( __printf__, fmt_arg_num, 0)))
|
||||
#else
|
||||
#define JV_PRINTF_LIKE(fmt_arg_num, args_num)
|
||||
#define JV_VPRINTF_LIKE(fmt_arg_num)
|
||||
#endif
|
||||
|
||||
|
||||
@@ -115,8 +112,8 @@ const char* jv_string_value(jv);
|
||||
jv jv_string_indexes(jv j, jv k);
|
||||
jv jv_string_slice(jv j, int start, int end);
|
||||
jv jv_string_concat(jv, jv);
|
||||
jv jv_string_vfmt(const char* fmt, va_list ap) JV_VPRINTF_LIKE(1);
|
||||
jv jv_string_fmt(const char* fmt, ...) JV_PRINTF_LIKE(1, 2);
|
||||
jv jv_string_vfmt(const char*, va_list) JV_VPRINTF_LIKE(1);
|
||||
jv jv_string_fmt(const char*, ...) JV_PRINTF_LIKE(1, 2);
|
||||
jv jv_string_append_codepoint(jv a, uint32_t c);
|
||||
jv jv_string_append_buf(jv a, const char* buf, int len);
|
||||
jv jv_string_append_str(jv a, const char* str);
|
||||
@@ -126,6 +123,7 @@ jv jv_string_implode(jv j);
|
||||
|
||||
jv jv_object(void);
|
||||
jv jv_object_get(jv object, jv key);
|
||||
int jv_object_has(jv object, jv key);
|
||||
jv jv_object_set(jv object, jv key, jv value);
|
||||
jv jv_object_delete(jv object, jv key);
|
||||
int jv_object_length(jv object);
|
||||
@@ -147,6 +145,15 @@ jv jv_object_iter_value(jv, int);
|
||||
: 0; \
|
||||
jv_i__ = jv_object_iter_next(t, jv_i__)) \
|
||||
|
||||
#define jv_object_keys_foreach(t, k) \
|
||||
for (int jv_i__ = jv_object_iter(t), jv_j__ = 1; jv_j__; jv_j__ = 0) \
|
||||
for (jv k; \
|
||||
jv_object_iter_valid((t), jv_i__) ? \
|
||||
(k = jv_object_iter_key(t, jv_i__), \
|
||||
1) \
|
||||
: 0; \
|
||||
jv_i__ = jv_object_iter_next(t, jv_i__))
|
||||
|
||||
#define JV_OBJECT_1(k1) (jv_object_set(jv_object(),(k1),jv_null()))
|
||||
#define JV_OBJECT_2(k1,v1) (jv_object_set(jv_object(),(k1),(v1)))
|
||||
#define JV_OBJECT_3(k1,v1,k2) (jv_object_set(JV_OBJECT_2((k1),(v1)),(k2),jv_null()))
|
||||
@@ -188,10 +195,10 @@ jv jv_object_iter_value(jv, int);
|
||||
|
||||
int jv_get_refcnt(jv);
|
||||
|
||||
enum {
|
||||
enum jv_print_flags {
|
||||
JV_PRINT_PRETTY = 1,
|
||||
JV_PRINT_ASCII = 2,
|
||||
JV_PRINT_COLOR = 4,
|
||||
JV_PRINT_COLOR = 4, JV_PRINT_COLOUR = 4,
|
||||
JV_PRINT_SORTED = 8,
|
||||
JV_PRINT_INVALID = 16,
|
||||
JV_PRINT_REFCOUNT = 32,
|
||||
|
||||
+2
-2
@@ -465,7 +465,7 @@ jv jv_keys_unsorted(jv x) {
|
||||
jv jv_keys(jv x) {
|
||||
if (jv_get_kind(x) == JV_KIND_OBJECT) {
|
||||
int nkeys = jv_object_length(jv_copy(x));
|
||||
jv* keys = jv_mem_alloc(sizeof(jv) * nkeys);
|
||||
jv* keys = jv_mem_calloc(sizeof(jv), nkeys);
|
||||
int kidx = 0;
|
||||
jv_object_foreach(x, key, value) {
|
||||
keys[kidx++] = key;
|
||||
@@ -587,7 +587,7 @@ static struct sort_entry* sort_items(jv objects, jv keys) {
|
||||
assert(jv_get_kind(keys) == JV_KIND_ARRAY);
|
||||
assert(jv_array_length(jv_copy(objects)) == jv_array_length(jv_copy(keys)));
|
||||
int n = jv_array_length(jv_copy(objects));
|
||||
struct sort_entry* entries = jv_mem_alloc(sizeof(struct sort_entry) * n);
|
||||
struct sort_entry* entries = jv_mem_calloc(sizeof(struct sort_entry), n);
|
||||
for (int i=0; i<n; i++) {
|
||||
entries[i].object = jv_array_get(jv_copy(objects), i);
|
||||
entries[i].key = jv_array_get(jv_copy(keys), i);
|
||||
|
||||
+25
-6
@@ -1,16 +1,33 @@
|
||||
#include <sys/stat.h>
|
||||
|
||||
#include <errno.h>
|
||||
#include <fcntl.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <unistd.h>
|
||||
#include "jv.h"
|
||||
#include "jv_unicode.h"
|
||||
|
||||
jv jv_load_file(const char* filename, int raw) {
|
||||
FILE* file = fopen(filename, "r");
|
||||
struct jv_parser* parser;
|
||||
struct stat sb;
|
||||
int fd = open(filename, O_RDONLY);
|
||||
if (fd == -1) {
|
||||
return jv_invalid_with_msg(jv_string_fmt("Could not open %s: %s",
|
||||
filename,
|
||||
strerror(errno)));
|
||||
}
|
||||
if (fstat(fd, &sb) == -1 || S_ISDIR(sb.st_mode)) {
|
||||
close(fd);
|
||||
return jv_invalid_with_msg(jv_string_fmt("Could not open %s: %s",
|
||||
filename,
|
||||
"It's a directory"));
|
||||
}
|
||||
FILE* file = fdopen(fd, "r");
|
||||
struct jv_parser* parser = NULL;
|
||||
jv data;
|
||||
if (!file) {
|
||||
close(fd);
|
||||
return jv_invalid_with_msg(jv_string_fmt("Could not open %s: %s",
|
||||
filename,
|
||||
strerror(errno)));
|
||||
@@ -30,10 +47,12 @@ jv jv_load_file(const char* filename, int raw) {
|
||||
while (!feof(file) && !ferror(file)) {
|
||||
size_t n = fread(buf, 1, sizeof(buf)-max_utf8_len, file);
|
||||
int len = 0;
|
||||
if (jvp_utf8_backtrack(buf+(n-1), buf, &len) && len > 0) {
|
||||
if (!feof(file) && !ferror(file)) {
|
||||
n += fread(buf+n, 1, len, file);
|
||||
}
|
||||
|
||||
if (n == 0)
|
||||
continue;
|
||||
if (jvp_utf8_backtrack(buf+(n-1), buf, &len) && len > 0 &&
|
||||
!feof(file) && !ferror(file)) {
|
||||
n += fread(buf+n, 1, len, file);
|
||||
}
|
||||
|
||||
if (raw) {
|
||||
|
||||
+51
-4
@@ -12,6 +12,7 @@
|
||||
#include "jv.h"
|
||||
#include "jv_dtoa.h"
|
||||
#include "jv_unicode.h"
|
||||
#include "jv_alloc.h"
|
||||
|
||||
#ifndef MAX_PRINT_DEPTH
|
||||
#define MAX_PRINT_DEPTH (256)
|
||||
@@ -26,20 +27,66 @@
|
||||
static const jv_kind color_kinds[] =
|
||||
{JV_KIND_NULL, JV_KIND_FALSE, JV_KIND_TRUE, JV_KIND_NUMBER,
|
||||
JV_KIND_STRING, JV_KIND_ARRAY, JV_KIND_OBJECT};
|
||||
static const char* const colors[] =
|
||||
static char color_bufs[sizeof(color_kinds)/sizeof(color_kinds[0])][16];
|
||||
static const char *color_bufps[8];
|
||||
static const char* def_colors[] =
|
||||
{COL("1;30"), COL("0;39"), COL("0;39"), COL("0;39"),
|
||||
COL("0;32"), COL("1;39"), COL("1;39")};
|
||||
#define FIELD_COLOR COL("34;1")
|
||||
|
||||
static const char **colors = def_colors;
|
||||
|
||||
int
|
||||
jq_set_colors(const char *c)
|
||||
{
|
||||
const char *e;
|
||||
size_t i;
|
||||
|
||||
if (c == NULL)
|
||||
return 1;
|
||||
colors = def_colors;
|
||||
memset(color_bufs, 0, sizeof(color_bufs));
|
||||
for (i = 0; i < sizeof(def_colors) / sizeof(def_colors[0]); i++)
|
||||
color_bufps[i] = def_colors[i];
|
||||
for (i = 0; i < sizeof(def_colors) / sizeof(def_colors[0]) && *c != '\0'; i++, c = e) {
|
||||
if ((e = strchr(c, ':')) == NULL)
|
||||
e = c + strlen(c);
|
||||
if ((size_t)(e - c) > sizeof(color_bufs[i]) - 4 /* ESC [ m NUL */)
|
||||
return 0;
|
||||
color_bufs[i][0] = ESC[0];
|
||||
color_bufs[i][1] = '[';
|
||||
(void) strncpy(&color_bufs[i][2], c, e - c);
|
||||
if (strspn(&color_bufs[i][2], "0123456789;") < strlen(&color_bufs[i][2]))
|
||||
return 0;
|
||||
color_bufs[i][2 + (e - c)] = 'm';
|
||||
color_bufps[i] = color_bufs[i];
|
||||
if (e[0] == ':')
|
||||
e++;
|
||||
}
|
||||
colors = color_bufps;
|
||||
return 1;
|
||||
}
|
||||
|
||||
static void put_buf(const char *s, int len, FILE *fout, jv *strout, int is_tty) {
|
||||
if (strout) {
|
||||
*strout = jv_string_append_buf(*strout, s, len);
|
||||
} else {
|
||||
#ifdef WIN32
|
||||
/* See util.h */
|
||||
if (is_tty)
|
||||
WriteFile((HANDLE)_get_osfhandle(fileno(fout)), s, len, NULL, NULL);
|
||||
else
|
||||
if (is_tty) {
|
||||
wchar_t *ws;
|
||||
size_t wl;
|
||||
if (len == -1)
|
||||
len = strlen(s);
|
||||
wl = MultiByteToWideChar(CP_UTF8, 0, s, len, NULL, 0);
|
||||
ws = jv_mem_calloc((wl + 1), sizeof(*ws));
|
||||
if (!ws)
|
||||
return;
|
||||
wl = MultiByteToWideChar(CP_UTF8, 0, s, len, ws, wl + 1);
|
||||
ws[wl] = 0;
|
||||
WriteConsoleW((HANDLE)_get_osfhandle(fileno(fout)), ws, wl, NULL, NULL);
|
||||
free(ws);
|
||||
} else
|
||||
fwrite(s, 1, len, fout);
|
||||
#else
|
||||
fwrite(s, 1, len, fout);
|
||||
|
||||
+1
-1
@@ -9,7 +9,7 @@
|
||||
// *missing_bytes. If there are no leading bytes or an invalid byte is
|
||||
// encountered, NULL is returned and *missing_bytes is not altered.
|
||||
const char* jvp_utf8_backtrack(const char* start, const char* min, int *missing_bytes) {
|
||||
assert(min < start);
|
||||
assert(min <= start);
|
||||
if (min == start) {
|
||||
return min;
|
||||
}
|
||||
|
||||
+307
-282
File diff suppressed because it is too large.
Load diff
+16
-12
@@ -2,9 +2,9 @@
|
||||
#define jq_yyHEADER_H 1
|
||||
#define jq_yyIN_HEADER 1
|
||||
|
||||
#line 6 "lexer.h"
|
||||
#line 6 "src/lexer.h"
|
||||
|
||||
#line 8 "lexer.h"
|
||||
#line 8 "src/lexer.h"
|
||||
|
||||
#define YY_INT_ALIGNED short int
|
||||
|
||||
@@ -12,8 +12,8 @@
|
||||
|
||||
#define FLEX_SCANNER
|
||||
#define YY_FLEX_MAJOR_VERSION 2
|
||||
#define YY_FLEX_MINOR_VERSION 5
|
||||
#define YY_FLEX_SUBMINOR_VERSION 35
|
||||
#define YY_FLEX_MINOR_VERSION 6
|
||||
#define YY_FLEX_SUBMINOR_VERSION 0
|
||||
#if YY_FLEX_SUBMINOR_VERSION > 0
|
||||
#define FLEX_BETA
|
||||
#endif
|
||||
@@ -214,7 +214,7 @@ void jq_yypop_buffer_state (yyscan_t yyscanner );
|
||||
|
||||
YY_BUFFER_STATE jq_yy_scan_buffer (char *base,yy_size_t size ,yyscan_t yyscanner );
|
||||
YY_BUFFER_STATE jq_yy_scan_string (yyconst char *yy_str ,yyscan_t yyscanner );
|
||||
YY_BUFFER_STATE jq_yy_scan_bytes (yyconst char *bytes,int len ,yyscan_t yyscanner );
|
||||
YY_BUFFER_STATE jq_yy_scan_bytes (yyconst char *bytes,yy_size_t len ,yyscan_t yyscanner );
|
||||
|
||||
void *jq_yyalloc (yy_size_t ,yyscan_t yyscanner );
|
||||
void *jq_yyrealloc (void *,yy_size_t ,yyscan_t yyscanner );
|
||||
@@ -222,7 +222,7 @@ void jq_yyfree (void * ,yyscan_t yyscanner );
|
||||
|
||||
/* Begin user sect3 */
|
||||
|
||||
#define jq_yywrap(n) 1
|
||||
#define jq_yywrap(yyscanner) (/*CONSTCOND*/1)
|
||||
#define YY_SKIP_YYWRAP
|
||||
|
||||
#define yytext_ptr yytext_r
|
||||
@@ -266,19 +266,23 @@ void jq_yyset_extra (YY_EXTRA_TYPE user_defined ,yyscan_t yyscanner );
|
||||
|
||||
FILE *jq_yyget_in (yyscan_t yyscanner );
|
||||
|
||||
void jq_yyset_in (FILE * in_str ,yyscan_t yyscanner );
|
||||
void jq_yyset_in (FILE * _in_str ,yyscan_t yyscanner );
|
||||
|
||||
FILE *jq_yyget_out (yyscan_t yyscanner );
|
||||
|
||||
void jq_yyset_out (FILE * out_str ,yyscan_t yyscanner );
|
||||
void jq_yyset_out (FILE * _out_str ,yyscan_t yyscanner );
|
||||
|
||||
int jq_yyget_leng (yyscan_t yyscanner );
|
||||
yy_size_t jq_yyget_leng (yyscan_t yyscanner );
|
||||
|
||||
char *jq_yyget_text (yyscan_t yyscanner );
|
||||
|
||||
int jq_yyget_lineno (yyscan_t yyscanner );
|
||||
|
||||
void jq_yyset_lineno (int line_number ,yyscan_t yyscanner );
|
||||
void jq_yyset_lineno (int _line_number ,yyscan_t yyscanner );
|
||||
|
||||
int jq_yyget_column (yyscan_t yyscanner );
|
||||
|
||||
void jq_yyset_column (int _column_no ,yyscan_t yyscanner );
|
||||
|
||||
YYSTYPE * jq_yyget_lval (yyscan_t yyscanner );
|
||||
|
||||
@@ -354,9 +358,9 @@ extern int jq_yylex \
|
||||
#undef YY_DECL
|
||||
#endif
|
||||
|
||||
#line 129 "lexer.l"
|
||||
#line 130 "src/lexer.l"
|
||||
|
||||
|
||||
#line 361 "lexer.h"
|
||||
#line 365 "src/lexer.h"
|
||||
#undef jq_yyIN_HEADER
|
||||
#endif /* jq_yyHEADER_H */
|
||||
@@ -71,6 +71,7 @@ struct lexer_param;
|
||||
"<=" { return LESSEQ; }
|
||||
">=" { return GREATEREQ; }
|
||||
".." { return REC; }
|
||||
"?//" { return ALTERNATION; }
|
||||
"."|"?"|"="|";"|","|":"|"|"|"+"|"-"|"*"|"/"|"%"|"\$"|"<"|">" { return yytext[0];}
|
||||
|
||||
"["|"{"|"(" {
|
||||
|
||||
+6
-16
@@ -144,10 +144,10 @@ LIBM_DD(y1)
|
||||
LIBM_DD_NO(y1)
|
||||
#endif
|
||||
#ifdef HAVE_JN
|
||||
LIBM_DID(jn)
|
||||
LIBM_DDD(jn)
|
||||
#endif
|
||||
#ifdef HAVE_YN
|
||||
LIBM_DID(yn)
|
||||
LIBM_DDD(yn)
|
||||
#endif
|
||||
#ifdef HAVE_CEIL
|
||||
LIBM_DD(ceil)
|
||||
@@ -159,7 +159,7 @@ LIBM_DDD(copysign)
|
||||
#else
|
||||
LIBM_DDD_NO(copysign)
|
||||
#endif
|
||||
#ifdef HAVE_DREM
|
||||
#if defined(HAVE_DREM) && !defined(WIN32)
|
||||
LIBM_DDD(drem)
|
||||
#else
|
||||
LIBM_DDD_NO(drem)
|
||||
@@ -174,7 +174,7 @@ LIBM_DD(erfc)
|
||||
#else
|
||||
LIBM_DD_NO(erfc)
|
||||
#endif
|
||||
#ifdef HAVE_EXP10
|
||||
#if defined(HAVE_EXP10) && !defined(WIN32)
|
||||
LIBM_DD(exp10)
|
||||
#else
|
||||
LIBM_DD_NO(exp10)
|
||||
@@ -214,11 +214,6 @@ LIBM_DDD(fmod)
|
||||
#else
|
||||
LIBM_DDD_NO(fmod)
|
||||
#endif
|
||||
#ifdef HAVE_FREXP
|
||||
LIBM_DDD(frexp)
|
||||
#else
|
||||
LIBM_DDD_NO(frexp)
|
||||
#endif
|
||||
#ifdef HAVE_GAMMA
|
||||
LIBM_DD(gamma)
|
||||
#else
|
||||
@@ -239,11 +234,6 @@ LIBM_DD(logb)
|
||||
#else
|
||||
LIBM_DD_NO(logb)
|
||||
#endif
|
||||
#ifdef HAVE_MODF
|
||||
LIBM_DDD(modf)
|
||||
#else
|
||||
LIBM_DDD_NO(modf)
|
||||
#endif
|
||||
#ifdef HAVE_NEARBYINT
|
||||
LIBM_DD(nearbyint)
|
||||
#else
|
||||
@@ -259,7 +249,7 @@ LIBM_DDD(nexttoward)
|
||||
#else
|
||||
LIBM_DDD_NO(nexttoward)
|
||||
#endif
|
||||
#ifdef HAVE_POW10
|
||||
#if defined(HAVE_POW10) && !defined(WIN32)
|
||||
LIBM_DD(pow10)
|
||||
#else
|
||||
LIBM_DD_NO(pow10)
|
||||
@@ -284,7 +274,7 @@ LIBM_DDD(scalbln)
|
||||
#else
|
||||
LIBM_DDD_NO(scalbln)
|
||||
#endif
|
||||
#ifdef HAVE_SIGNIFICAND
|
||||
#if defined(HAVE_SIGNIFICAND) && !defined(WIN32)
|
||||
LIBM_DD(significand)
|
||||
#else
|
||||
LIBM_DD_NO(significand)
|
||||
|
||||
+65
-27
@@ -19,6 +19,7 @@
|
||||
#include "parser.h"
|
||||
#include "util.h"
|
||||
#include "compile.h"
|
||||
#include "jv_alloc.h"
|
||||
|
||||
struct lib_loading_state {
|
||||
char **names;
|
||||
@@ -97,12 +98,9 @@ static jv validate_relpath(jv name) {
|
||||
return res;
|
||||
}
|
||||
jv components = jv_string_split(jv_copy(name), jv_string("/"));
|
||||
jv rp = jv_array_get(jv_copy(components), 0);
|
||||
components = jv_array_slice(components, 1, jv_array_length(jv_copy(components)));
|
||||
jv_array_foreach(components, i, x) {
|
||||
if (!strcmp(jv_string_value(x), "..")) {
|
||||
jv_free(x);
|
||||
jv_free(rp);
|
||||
jv_free(components);
|
||||
jv res = jv_invalid_with_msg(jv_string_fmt("Relative paths to modules may not traverse to parent directories (%s)", s));
|
||||
jv_free(name);
|
||||
@@ -110,18 +108,16 @@ static jv validate_relpath(jv name) {
|
||||
}
|
||||
if (i > 0 && jv_equal(jv_copy(x), jv_array_get(jv_copy(components), i - 1))) {
|
||||
jv_free(x);
|
||||
jv_free(rp);
|
||||
jv_free(components);
|
||||
jv res = jv_invalid_with_msg(jv_string_fmt("module names must not have equal consecutive components: %s",
|
||||
jv_string_value(name)));
|
||||
jv_free(name);
|
||||
return res;
|
||||
}
|
||||
rp = jv_string_concat(rp, jv_string_concat(jv_string("/"), x));
|
||||
jv_free(x);
|
||||
}
|
||||
jv_free(components);
|
||||
jv_free(name);
|
||||
return rp;
|
||||
return name;
|
||||
}
|
||||
|
||||
// Assumes name has been validated
|
||||
@@ -137,10 +133,20 @@ static jv jv_basename(jv name) {
|
||||
|
||||
// Asummes validated relative path to module
|
||||
static jv find_lib(jq_state *jq, jv rel_path, jv search, const char *suffix, jv jq_origin, jv lib_origin) {
|
||||
if (jv_get_kind(search) != JV_KIND_ARRAY)
|
||||
return jv_invalid_with_msg(jv_string_fmt("Module search path must be an array"));
|
||||
if (jv_get_kind(rel_path) != JV_KIND_STRING)
|
||||
if (!jv_is_valid(rel_path)) {
|
||||
jv_free(search);
|
||||
return rel_path;
|
||||
}
|
||||
if (jv_get_kind(rel_path) != JV_KIND_STRING) {
|
||||
jv_free(rel_path);
|
||||
jv_free(search);
|
||||
return jv_invalid_with_msg(jv_string_fmt("Module path must be a string"));
|
||||
}
|
||||
if (jv_get_kind(search) != JV_KIND_ARRAY) {
|
||||
jv_free(rel_path);
|
||||
jv_free(search);
|
||||
return jv_invalid_with_msg(jv_string_fmt("Module search path must be an array"));
|
||||
}
|
||||
|
||||
struct stat st;
|
||||
int ret;
|
||||
@@ -232,14 +238,17 @@ static int process_dependencies(jq_state *jq, jv jq_origin, jv lib_origin, block
|
||||
jv deps = block_take_imports(src_block);
|
||||
block bk = *src_block;
|
||||
int nerrors = 0;
|
||||
const char *as_str = NULL;
|
||||
|
||||
jv_array_foreach(deps, i, dep) {
|
||||
const char *as_str = NULL;
|
||||
int is_data = jv_get_kind(jv_object_get(jv_copy(dep), jv_string("is_data"))) == JV_KIND_TRUE;
|
||||
int raw = 0;
|
||||
jv v = jv_object_get(jv_copy(dep), jv_string("raw"));
|
||||
if (jv_get_kind(v) == JV_KIND_TRUE)
|
||||
raw = 1;
|
||||
int optional = 0;
|
||||
if (jv_get_kind(jv_object_get(jv_copy(dep), jv_string("optional"))) == JV_KIND_TRUE)
|
||||
optional = 1;
|
||||
jv_free(v);
|
||||
jv relpath = validate_relpath(jv_object_get(jv_copy(dep), jv_string("relpath")));
|
||||
jv as = jv_object_get(jv_copy(dep), jv_string("as"));
|
||||
@@ -253,33 +262,51 @@ static int process_dependencies(jq_state *jq, jv jq_origin, jv lib_origin, block
|
||||
jv resolved = find_lib(jq, relpath, search, is_data ? ".json" : ".jq", jv_copy(jq_origin), jv_copy(lib_origin));
|
||||
// XXX ...move the rest of this into a callback.
|
||||
if (!jv_is_valid(resolved)) {
|
||||
jv_free(as);
|
||||
if (optional) {
|
||||
jv_free(resolved);
|
||||
continue;
|
||||
}
|
||||
jv emsg = jv_invalid_get_msg(resolved);
|
||||
jq_report_error(jq, jv_string_fmt("jq: error: %s\n",jv_string_value(emsg)));
|
||||
jv_free(emsg);
|
||||
jv_free(as);
|
||||
jv_free(deps);
|
||||
jv_free(jq_origin);
|
||||
jv_free(lib_origin);
|
||||
return 1;
|
||||
}
|
||||
uint64_t state_idx = 0;
|
||||
for (; state_idx < lib_state->ct; ++state_idx) {
|
||||
if (strcmp(lib_state->names[state_idx],jv_string_value(resolved)) == 0)
|
||||
break;
|
||||
}
|
||||
if (state_idx < lib_state->ct) { // Found
|
||||
jv_free(resolved);
|
||||
// Bind the library to the program
|
||||
bk = block_bind_library(lib_state->defs[state_idx], bk, OP_IS_CALL_PSEUDO, as_str);
|
||||
} else { // Not found. Add it to the table before binding.
|
||||
block dep_def_block = gen_noop();
|
||||
|
||||
if (is_data) {
|
||||
// Can't reuse data libs because the wrong name is bound
|
||||
block dep_def_block;
|
||||
nerrors += load_library(jq, resolved, is_data, raw, as_str, &dep_def_block, lib_state);
|
||||
// resolved has been freed
|
||||
if (nerrors == 0) {
|
||||
// Bind the library to the program
|
||||
// Bind as both $data::data and $data for backward compatibility vs common sense
|
||||
bk = block_bind_library(dep_def_block, bk, OP_IS_CALL_PSEUDO, as_str);
|
||||
bk = block_bind_library(dep_def_block, bk, OP_IS_CALL_PSEUDO, NULL);
|
||||
}
|
||||
} else {
|
||||
uint64_t state_idx = 0;
|
||||
for (; state_idx < lib_state->ct; ++state_idx) {
|
||||
if (strcmp(lib_state->names[state_idx],jv_string_value(resolved)) == 0)
|
||||
break;
|
||||
}
|
||||
|
||||
if (state_idx < lib_state->ct) { // Found
|
||||
jv_free(resolved);
|
||||
// Bind the library to the program
|
||||
bk = block_bind_library(lib_state->defs[state_idx], bk, OP_IS_CALL_PSEUDO, as_str);
|
||||
} else { // Not found. Add it to the table before binding.
|
||||
block dep_def_block = gen_noop();
|
||||
nerrors += load_library(jq, resolved, is_data, raw, as_str, &dep_def_block, lib_state);
|
||||
// resolved has been freed
|
||||
if (nerrors == 0) {
|
||||
// Bind the library to the program
|
||||
bk = block_bind_library(dep_def_block, bk, OP_IS_CALL_PSEUDO, as_str);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
jv_free(as);
|
||||
}
|
||||
jv_free(lib_origin);
|
||||
@@ -321,11 +348,12 @@ static int load_library(jq_state *jq, jv lib_path, int is_data, int raw, const c
|
||||
jv_string(dirname(lib_origin)),
|
||||
&program, lib_state);
|
||||
free(lib_origin);
|
||||
program = block_bind_self(program, OP_IS_CALL_PSEUDO);
|
||||
}
|
||||
}
|
||||
state_idx = lib_state->ct++;
|
||||
lib_state->names = realloc(lib_state->names, lib_state->ct * sizeof(const char *));
|
||||
lib_state->defs = realloc(lib_state->defs, lib_state->ct * sizeof(block));
|
||||
lib_state->names = jv_mem_realloc(lib_state->names, lib_state->ct * sizeof(const char *));
|
||||
lib_state->defs = jv_mem_realloc(lib_state->defs, lib_state->ct * sizeof(block));
|
||||
lib_state->names[state_idx] = strdup(jv_string_value(lib_path));
|
||||
lib_state->defs[state_idx] = program;
|
||||
*out_block = program;
|
||||
@@ -372,6 +400,16 @@ int load_program(jq_state *jq, struct locfile* src, block *out_block) {
|
||||
if (nerrors)
|
||||
return nerrors;
|
||||
|
||||
char* home = getenv("HOME");
|
||||
if (home) { // silently ignore no $HOME
|
||||
/* Import ~/.jq as a library named "" found in $HOME */
|
||||
block import = gen_import_meta(gen_import("", NULL, 0),
|
||||
gen_const(JV_OBJECT(
|
||||
jv_string("optional"), jv_true(),
|
||||
jv_string("search"), jv_string(home))));
|
||||
program = BLOCK(import, program);
|
||||
}
|
||||
|
||||
nerrors = process_dependencies(jq, jq_get_jq_origin(jq), jq_get_prog_origin(jq), &program, &lib_state);
|
||||
block libs = gen_noop();
|
||||
for (uint64_t i = 0; i < lib_state.ct; ++i) {
|
||||
|
||||
+1
-1
@@ -21,7 +21,7 @@ struct locfile* locfile_init(jq_state *jq, const char *fname, const char* data,
|
||||
for (int i=0; i<length; i++) {
|
||||
if (data[i] == '\n') l->nlines++;
|
||||
}
|
||||
l->linemap = jv_mem_alloc(sizeof(int) * (l->nlines + 1));
|
||||
l->linemap = jv_mem_calloc(sizeof(int), (l->nlines + 1));
|
||||
l->linemap[0] = 0;
|
||||
int line = 1;
|
||||
for (int i=0; i<length; i++) {
|
||||
|
||||
+228
-110
@@ -41,7 +41,7 @@ static const char* progname;
|
||||
* For a longer help message we could use a better option parsing
|
||||
* strategy, one that lets stack options.
|
||||
*/
|
||||
static void usage(int code) {
|
||||
static void usage(int code, int keep_it_short) {
|
||||
FILE *f = stderr;
|
||||
|
||||
if (code == 0)
|
||||
@@ -49,7 +49,9 @@ static void usage(int code) {
|
||||
|
||||
int ret = fprintf(f,
|
||||
"jq - commandline JSON processor [version %s]\n"
|
||||
"\nUsage:\n\n\t%s [options] <jq filter> [file...]\n\n"
|
||||
"\nUsage:\t%s [options] <jq filter> [file...]\n"
|
||||
"\t%s [options] --args <jq filter> [strings...]\n"
|
||||
"\t%s [options] --jsonargs <jq filter> [JSON_TEXTS...]\n\n"
|
||||
"jq is a tool for processing JSON inputs, applying the given filter to\n"
|
||||
"its JSON text inputs and producing the filter's results as JSON on\n"
|
||||
"standard output.\n\n"
|
||||
@@ -59,22 +61,36 @@ static void usage(int code) {
|
||||
"For more advanced filters see the jq(1) manpage (\"man jq\")\n"
|
||||
"and/or https://stedolan.github.io/jq\n\n"
|
||||
"Example:\n\n\t$ echo '{\"foo\": 0}' | jq .\n"
|
||||
"\t{\n\t\t\"foo\": 0\n\t}\n\n"
|
||||
"Some of the options include:\n"
|
||||
" -c compact instead of pretty-printed output;\n"
|
||||
" -n use `null` as the single input value;\n"
|
||||
" -e set the exit status code based on the output;\n"
|
||||
" -s read (slurp) all inputs into an array; apply filter to it;\n"
|
||||
" -r output raw strings, not JSON texts;\n"
|
||||
" -R read raw strings, not JSON texts;\n"
|
||||
" -C colorize JSON;\n"
|
||||
" -M monochrome (don't colorize JSON);\n"
|
||||
" -S sort keys of objects on output;\n"
|
||||
" --tab use tabs for indentation;\n"
|
||||
" --arg a v set variable $a to value <v>;\n"
|
||||
" --argjson a v set variable $a to JSON value <v>;\n"
|
||||
" --slurpfile a f set variable $a to an array of JSON texts read from <f>;\n"
|
||||
"\nSee the manpage for more options.\n", JQ_VERSION, progname);
|
||||
"\t{\n\t\t\"foo\": 0\n\t}\n\n",
|
||||
JQ_VERSION, progname, progname, progname);
|
||||
if (keep_it_short) {
|
||||
fprintf(f,
|
||||
"For a listing of options, use %s --help.\n",
|
||||
progname);
|
||||
} else {
|
||||
(void) fprintf(f,
|
||||
"Some of the options include:\n"
|
||||
" -c compact instead of pretty-printed output;\n"
|
||||
" -n use `null` as the single input value;\n"
|
||||
" -e set the exit status code based on the output;\n"
|
||||
" -s read (slurp) all inputs into an array; apply filter to it;\n"
|
||||
" -r output raw strings, not JSON texts;\n"
|
||||
" -R read raw strings, not JSON texts;\n"
|
||||
" -C colorize JSON;\n"
|
||||
" -M monochrome (don't colorize JSON);\n"
|
||||
" -S sort keys of objects on output;\n"
|
||||
" --tab use tabs for indentation;\n"
|
||||
" --arg a v set variable $a to value <v>;\n"
|
||||
" --argjson a v set variable $a to JSON value <v>;\n"
|
||||
" --slurpfile a f set variable $a to an array of JSON texts read from <f>;\n"
|
||||
" --rawfile a f set variable $a to a string consisting of the contents of <f>;\n"
|
||||
" --args remaining arguments are string arguments, not files;\n"
|
||||
" --jsonargs remaining arguments are JSON arguments, not files;\n"
|
||||
" -- terminates argument processing;\n\n"
|
||||
"Named arguments are also available as $ARGS.named[], while\n"
|
||||
"positional arguments are available as $ARGS.positional[].\n"
|
||||
"\nSee the manpage for more options.\n");
|
||||
}
|
||||
exit((ret < 0 && code == 0) ? 2 : code);
|
||||
}
|
||||
|
||||
@@ -122,13 +138,25 @@ enum {
|
||||
RAW_NO_LF = 1024,
|
||||
UNBUFFERED_OUTPUT = 2048,
|
||||
EXIT_STATUS = 4096,
|
||||
SEQ = 8192,
|
||||
RUN_TESTS = 16384,
|
||||
EXIT_STATUS_EXACT = 8192,
|
||||
SEQ = 16384,
|
||||
RUN_TESTS = 32768,
|
||||
/* debugging only */
|
||||
DUMP_DISASM = 32768,
|
||||
DUMP_DISASM = 65536,
|
||||
};
|
||||
static int options = 0;
|
||||
|
||||
enum {
|
||||
JQ_OK = 0,
|
||||
JQ_OK_NULL_KIND = -1, /* exit 0 if --exit-status is not set*/
|
||||
JQ_ERROR_SYSTEM = 2,
|
||||
JQ_ERROR_COMPILE = 3,
|
||||
JQ_OK_NO_OUTPUT = -4, /* exit 0 if --exit-status is not set*/
|
||||
JQ_ERROR_UNKNOWN = 5,
|
||||
};
|
||||
#define jq_exit_with_status(r) exit(abs(r))
|
||||
#define jq_exit(r) exit( r > 0 ? r : 0 )
|
||||
|
||||
static const char *skip_shebang(const char *p) {
|
||||
if (strncmp(p, "#!", sizeof("#!") - 1) != 0)
|
||||
return p;
|
||||
@@ -145,19 +173,23 @@ static const char *skip_shebang(const char *p) {
|
||||
}
|
||||
|
||||
static int process(jq_state *jq, jv value, int flags, int dumpopts) {
|
||||
int ret = 14; // No valid results && -e -> exit(4)
|
||||
int ret = JQ_OK_NO_OUTPUT; // No valid results && -e -> exit(4)
|
||||
jq_start(jq, value, flags);
|
||||
jv result;
|
||||
while (jv_is_valid(result = jq_next(jq))) {
|
||||
if ((options & RAW_OUTPUT) && jv_get_kind(result) == JV_KIND_STRING) {
|
||||
fwrite(jv_string_value(result), 1, jv_string_length_bytes(jv_copy(result)), stdout);
|
||||
ret = 0;
|
||||
if (options & ASCII_OUTPUT) {
|
||||
jv_dumpf(jv_copy(result), stdout, JV_PRINT_ASCII);
|
||||
} else {
|
||||
fwrite(jv_string_value(result), 1, jv_string_length_bytes(jv_copy(result)), stdout);
|
||||
}
|
||||
ret = JQ_OK;
|
||||
jv_free(result);
|
||||
} else {
|
||||
if (jv_get_kind(result) == JV_KIND_FALSE || jv_get_kind(result) == JV_KIND_NULL)
|
||||
ret = 11;
|
||||
ret = JQ_OK_NULL_KIND;
|
||||
else
|
||||
ret = 0;
|
||||
ret = JQ_OK;
|
||||
if (options & SEQ)
|
||||
priv_fwrite("\036", 1, stdout, dumpopts & JV_PRINT_ISATTY);
|
||||
jv_dump(result, dumpopts);
|
||||
@@ -167,7 +199,29 @@ static int process(jq_state *jq, jv value, int flags, int dumpopts) {
|
||||
if (options & UNBUFFERED_OUTPUT)
|
||||
fflush(stdout);
|
||||
}
|
||||
if (jv_invalid_has_msg(jv_copy(result))) {
|
||||
if (jq_halted(jq)) {
|
||||
// jq program invoked `halt` or `halt_error`
|
||||
options |= EXIT_STATUS_EXACT;
|
||||
jv exit_code = jq_get_exit_code(jq);
|
||||
if (!jv_is_valid(exit_code))
|
||||
ret = JQ_OK;
|
||||
else if (jv_get_kind(exit_code) == JV_KIND_NUMBER)
|
||||
ret = jv_number_value(exit_code);
|
||||
else
|
||||
ret = JQ_ERROR_UNKNOWN;
|
||||
jv_free(exit_code);
|
||||
jv error_message = jq_get_error_message(jq);
|
||||
if (jv_get_kind(error_message) == JV_KIND_STRING) {
|
||||
fprintf(stderr, "%s", jv_string_value(error_message));
|
||||
} else if (jv_get_kind(error_message) == JV_KIND_NULL) {
|
||||
// Halt with no output
|
||||
} else if (jv_is_valid(error_message)) {
|
||||
error_message = jv_dump_string(jv_copy(error_message), 0);
|
||||
fprintf(stderr, "%s\n", jv_string_value(error_message));
|
||||
} // else no message on stderr; use --debug-trace to see a message
|
||||
fflush(stderr);
|
||||
jv_free(error_message);
|
||||
} else if (jv_invalid_has_msg(jv_copy(result))) {
|
||||
// Uncaught jq exception
|
||||
jv msg = jv_invalid_get_msg(jv_copy(result));
|
||||
jv input_pos = jq_util_input_get_position(jq);
|
||||
@@ -179,7 +233,7 @@ static int process(jq_state *jq, jv value, int flags, int dumpopts) {
|
||||
fprintf(stderr, "jq: error (at %s) (not a string): %s\n",
|
||||
jv_string_value(input_pos), jv_string_value(msg));
|
||||
}
|
||||
ret = 5;
|
||||
ret = JQ_ERROR_UNKNOWN;
|
||||
jv_free(input_pos);
|
||||
jv_free(msg);
|
||||
}
|
||||
@@ -193,25 +247,12 @@ static void debug_cb(void *data, jv input) {
|
||||
fprintf(stderr, "\n");
|
||||
}
|
||||
|
||||
int main(int argc, char* argv[]) {
|
||||
jq_state *jq = NULL;
|
||||
int ret = 0;
|
||||
int compiled = 0;
|
||||
int parser_flags = 0;
|
||||
int nfiles = 0;
|
||||
int badwrite;
|
||||
jv program_arguments = jv_array();
|
||||
|
||||
#ifdef WIN32
|
||||
SetConsoleOutputCP(CP_UTF8);
|
||||
fflush(stdout);
|
||||
fflush(stderr);
|
||||
_setmode(fileno(stdout), _O_TEXT | _O_U8TEXT);
|
||||
_setmode(fileno(stderr), _O_TEXT | _O_U8TEXT);
|
||||
int wargc;
|
||||
wchar_t **wargv = CommandLineToArgvW(GetCommandLineW(), &wargc);
|
||||
assert(wargc == argc);
|
||||
int umain(int argc, char* argv[]);
|
||||
|
||||
int wmain(int argc, wchar_t* wargv[]) {
|
||||
size_t arg_sz;
|
||||
char **argv = alloca(argc * sizeof(wchar_t*));
|
||||
for (int i = 0; i < argc; i++) {
|
||||
argv[i] = alloca((arg_sz = WideCharToMultiByte(CP_UTF8,
|
||||
0,
|
||||
@@ -219,6 +260,28 @@ int main(int argc, char* argv[]) {
|
||||
-1, 0, 0, 0, 0)));
|
||||
WideCharToMultiByte(CP_UTF8, 0, wargv[i], -1, argv[i], arg_sz, 0, 0);
|
||||
}
|
||||
return umain(argc, argv);
|
||||
}
|
||||
|
||||
int umain(int argc, char* argv[]) {
|
||||
#else /*}*/
|
||||
int main(int argc, char* argv[]) {
|
||||
#endif
|
||||
jq_state *jq = NULL;
|
||||
int ret = JQ_OK_NO_OUTPUT;
|
||||
int compiled = 0;
|
||||
int parser_flags = 0;
|
||||
int nfiles = 0;
|
||||
int last_result = -1; /* -1 = no result, 0=null or false, 1=true */
|
||||
int badwrite;
|
||||
jv ARGS = jv_array(); /* positional arguments */
|
||||
jv program_arguments = jv_object(); /* named arguments */
|
||||
|
||||
#ifdef WIN32
|
||||
fflush(stdout);
|
||||
fflush(stderr);
|
||||
_setmode(fileno(stdout), _O_TEXT | _O_U8TEXT);
|
||||
_setmode(fileno(stderr), _O_TEXT | _O_U8TEXT);
|
||||
#endif
|
||||
|
||||
if (argc) progname = argv[0];
|
||||
@@ -226,7 +289,7 @@ int main(int argc, char* argv[]) {
|
||||
jq = jq_init();
|
||||
if (jq == NULL) {
|
||||
perror("malloc");
|
||||
ret = 2;
|
||||
ret = JQ_ERROR_SYSTEM;
|
||||
goto out;
|
||||
}
|
||||
|
||||
@@ -235,21 +298,35 @@ int main(int argc, char* argv[]) {
|
||||
|
||||
jq_util_input_state *input_state = jq_util_input_init(NULL, NULL); // XXX add err_cb
|
||||
|
||||
int further_args_are_files = 0;
|
||||
int further_args_are_strings = 0;
|
||||
int further_args_are_json = 0;
|
||||
int args_done = 0;
|
||||
int jq_flags = 0;
|
||||
size_t short_opts = 0;
|
||||
jv lib_search_paths = jv_null();
|
||||
for (int i=1; i<argc; i++, short_opts = 0) {
|
||||
if (further_args_are_files) {
|
||||
jq_util_input_add_input(input_state, argv[i]);
|
||||
nfiles++;
|
||||
} else if (!strcmp(argv[i], "--")) {
|
||||
if (!program) usage(2);
|
||||
further_args_are_files = 1;
|
||||
} else if (!isoptish(argv[i])) {
|
||||
if (program) {
|
||||
if (args_done) {
|
||||
if (further_args_are_strings) {
|
||||
ARGS = jv_array_append(ARGS, jv_string(argv[i]));
|
||||
} else if (further_args_are_json) {
|
||||
ARGS = jv_array_append(ARGS, jv_parse(argv[i]));
|
||||
} else {
|
||||
jq_util_input_add_input(input_state, argv[i]);
|
||||
nfiles++;
|
||||
}
|
||||
} else if (!strcmp(argv[i], "--")) {
|
||||
if (!program) usage(2, 1);
|
||||
args_done = 1;
|
||||
} else if (!isoptish(argv[i])) {
|
||||
if (program) {
|
||||
if (further_args_are_strings) {
|
||||
ARGS = jv_array_append(ARGS, jv_string(argv[i]));
|
||||
} else if (further_args_are_json) {
|
||||
ARGS = jv_array_append(ARGS, jv_parse(argv[i]));
|
||||
} else {
|
||||
jq_util_input_add_input(input_state, argv[i]);
|
||||
nfiles++;
|
||||
}
|
||||
} else {
|
||||
program = argv[i];
|
||||
}
|
||||
@@ -295,7 +372,7 @@ int main(int argc, char* argv[]) {
|
||||
}
|
||||
if (isoption(argv[i], 0, "unbuffered", &short_opts)) {
|
||||
options |= UNBUFFERED_OUTPUT;
|
||||
if (!short_opts) continue;
|
||||
continue;
|
||||
}
|
||||
if (isoption(argv[i], 'S', "sort-keys", &short_opts)) {
|
||||
options |= SORTED_OUTPUT;
|
||||
@@ -320,7 +397,7 @@ int main(int argc, char* argv[]) {
|
||||
if (isoption(argv[i], 0, "tab", &short_opts)) {
|
||||
dumpopts &= ~JV_PRINT_INDENT_FLAGS(7);
|
||||
dumpopts |= JV_PRINT_TAB | JV_PRINT_PRETTY;
|
||||
if (!short_opts) continue;
|
||||
continue;
|
||||
}
|
||||
if (isoption(argv[i], 0, "indent", &short_opts)) {
|
||||
if (i >= argc - 1) {
|
||||
@@ -335,100 +412,113 @@ int main(int argc, char* argv[]) {
|
||||
}
|
||||
dumpopts |= JV_PRINT_INDENT_FLAGS(indent);
|
||||
i++;
|
||||
if (!short_opts) continue;
|
||||
continue;
|
||||
}
|
||||
if (isoption(argv[i], 0, "seq", &short_opts)) {
|
||||
options |= SEQ;
|
||||
if (!short_opts) continue;
|
||||
continue;
|
||||
}
|
||||
if (isoption(argv[i], 0, "stream", &short_opts)) {
|
||||
parser_flags |= JV_PARSE_STREAMING;
|
||||
if (!short_opts) continue;
|
||||
continue;
|
||||
}
|
||||
if (isoption(argv[i], 0, "stream-errors", &short_opts)) {
|
||||
parser_flags |= JV_PARSE_STREAM_ERRORS;
|
||||
if (!short_opts) continue;
|
||||
continue;
|
||||
}
|
||||
if (isoption(argv[i], 'e', "exit-status", &short_opts)) {
|
||||
options |= EXIT_STATUS;
|
||||
if (!short_opts) continue;
|
||||
}
|
||||
// FIXME: For --arg* we should check that the varname is acceptable
|
||||
if (isoption(argv[i], 0, "args", &short_opts)) {
|
||||
further_args_are_strings = 1;
|
||||
further_args_are_json = 0;
|
||||
continue;
|
||||
}
|
||||
if (isoption(argv[i], 0, "jsonargs", &short_opts)) {
|
||||
further_args_are_strings = 0;
|
||||
further_args_are_json = 1;
|
||||
continue;
|
||||
}
|
||||
if (isoption(argv[i], 0, "arg", &short_opts)) {
|
||||
if (i >= argc - 2) {
|
||||
fprintf(stderr, "%s: --arg takes two parameters (e.g. --arg varname value)\n", progname);
|
||||
die();
|
||||
}
|
||||
jv arg = jv_object();
|
||||
arg = jv_object_set(arg, jv_string("name"), jv_string(argv[i+1]));
|
||||
arg = jv_object_set(arg, jv_string("value"), jv_string(argv[i+2]));
|
||||
program_arguments = jv_array_append(program_arguments, arg);
|
||||
if (!jv_object_has(jv_copy(program_arguments), jv_string(argv[i+1])))
|
||||
program_arguments = jv_object_set(program_arguments, jv_string(argv[i+1]), jv_string(argv[i+2]));
|
||||
i += 2; // skip the next two arguments
|
||||
if (!short_opts) continue;
|
||||
continue;
|
||||
}
|
||||
if (isoption(argv[i], 0, "argjson", &short_opts)) {
|
||||
if (i >= argc - 2) {
|
||||
fprintf(stderr, "%s: --argjson takes two parameters (e.g. --argjson varname text)\n", progname);
|
||||
die();
|
||||
}
|
||||
jv v = jv_parse(argv[i+2]);
|
||||
if (!jv_is_valid(v)) {
|
||||
fprintf(stderr, "%s: invalid JSON text passed to --argjson\n", progname);
|
||||
die();
|
||||
if (!jv_object_has(jv_copy(program_arguments), jv_string(argv[i+1]))) {
|
||||
jv v = jv_parse(argv[i+2]);
|
||||
if (!jv_is_valid(v)) {
|
||||
fprintf(stderr, "%s: invalid JSON text passed to --argjson\n", progname);
|
||||
die();
|
||||
}
|
||||
program_arguments = jv_object_set(program_arguments, jv_string(argv[i+1]), v);
|
||||
}
|
||||
jv arg = jv_object();
|
||||
arg = jv_object_set(arg, jv_string("name"), jv_string(argv[i+1]));
|
||||
arg = jv_object_set(arg, jv_string("value"), v);
|
||||
program_arguments = jv_array_append(program_arguments, arg);
|
||||
i += 2; // skip the next two arguments
|
||||
if (!short_opts) continue;
|
||||
continue;
|
||||
}
|
||||
if (isoption(argv[i], 0, "argfile", &short_opts) ||
|
||||
isoption(argv[i], 0, "rawfile", &short_opts) ||
|
||||
isoption(argv[i], 0, "slurpfile", &short_opts)) {
|
||||
int raw = isoption(argv[i], 0, "rawfile", &short_opts);
|
||||
const char *which;
|
||||
if (isoption(argv[i], 0, "argfile", &short_opts))
|
||||
which = "argfile";
|
||||
else if (raw)
|
||||
which = "rawfile";
|
||||
else
|
||||
which = "slurpfile";
|
||||
if (i >= argc - 2) {
|
||||
fprintf(stderr, "%s: --%s takes two parameters (e.g. --%s varname filename)\n", progname, which, which);
|
||||
die();
|
||||
}
|
||||
jv arg = jv_object();
|
||||
arg = jv_object_set(arg, jv_string("name"), jv_string(argv[i+1]));
|
||||
jv data = jv_load_file(argv[i+2], 0);
|
||||
if (!jv_is_valid(data)) {
|
||||
data = jv_invalid_get_msg(data);
|
||||
fprintf(stderr, "%s: Bad JSON in --%s %s %s: %s\n", progname, which,
|
||||
argv[i+1], argv[i+2], jv_string_value(data));
|
||||
jv_free(data);
|
||||
jv_free(arg);
|
||||
ret = 2;
|
||||
goto out;
|
||||
if (!jv_object_has(jv_copy(program_arguments), jv_string(argv[i+1]))) {
|
||||
jv data = jv_load_file(argv[i+2], raw);
|
||||
if (!jv_is_valid(data)) {
|
||||
data = jv_invalid_get_msg(data);
|
||||
fprintf(stderr, "%s: Bad JSON in --%s %s %s: %s\n", progname, which,
|
||||
argv[i+1], argv[i+2], jv_string_value(data));
|
||||
jv_free(data);
|
||||
ret = JQ_ERROR_SYSTEM;
|
||||
goto out;
|
||||
}
|
||||
if (strcmp(which, "argfile") == 0 &&
|
||||
jv_get_kind(data) == JV_KIND_ARRAY && jv_array_length(jv_copy(data)) == 1)
|
||||
data = jv_array_get(data, 0);
|
||||
program_arguments = jv_object_set(program_arguments, jv_string(argv[i+1]), data);
|
||||
}
|
||||
if (isoption(argv[i], 0, "argfile", &short_opts) &&
|
||||
jv_get_kind(data) == JV_KIND_ARRAY && jv_array_length(jv_copy(data)) == 1)
|
||||
data = jv_array_get(data, 0);
|
||||
arg = jv_object_set(arg, jv_string("value"), data);
|
||||
program_arguments = jv_array_append(program_arguments, arg);
|
||||
i += 2; // skip the next two arguments
|
||||
if (!short_opts) continue;
|
||||
continue;
|
||||
}
|
||||
if (isoption(argv[i], 0, "debug-dump-disasm", &short_opts)) {
|
||||
options |= DUMP_DISASM;
|
||||
continue;
|
||||
}
|
||||
if (isoption(argv[i], 0, "debug-trace=all", &short_opts)) {
|
||||
jq_flags |= JQ_DEBUG_TRACE_ALL;
|
||||
if (!short_opts) continue;
|
||||
}
|
||||
if (isoption(argv[i], 0, "debug-trace", &short_opts)) {
|
||||
jq_flags |= JQ_DEBUG_TRACE;
|
||||
if (!short_opts) continue;
|
||||
continue;
|
||||
}
|
||||
if (isoption(argv[i], 'h', "help", &short_opts)) {
|
||||
usage(0);
|
||||
usage(0, 0);
|
||||
if (!short_opts) continue;
|
||||
}
|
||||
if (isoption(argv[i], 'V', "version", &short_opts)) {
|
||||
printf("jq-%s\n", JQ_VERSION);
|
||||
ret = 0;
|
||||
ret = JQ_OK;
|
||||
goto out;
|
||||
}
|
||||
if (isoption(argv[i], 0, "run-tests", &short_opts)) {
|
||||
@@ -451,11 +541,20 @@ int main(int argc, char* argv[]) {
|
||||
|
||||
#ifdef USE_ISATTY
|
||||
if (isatty(STDOUT_FILENO)) {
|
||||
dumpopts |= JV_PRINT_ISATTY;
|
||||
#ifndef WIN32
|
||||
/* Disable color by default on Windows builds as Windows
|
||||
terminals tend not to display it correctly */
|
||||
dumpopts |= JV_PRINT_COLOR;
|
||||
dumpopts |= JV_PRINT_ISATTY | JV_PRINT_COLOR;
|
||||
#else
|
||||
/* Verify we actually have the console, as the NUL device is also regarded as
|
||||
tty. Windows can handle color if ANSICON (or ConEmu) is installed, or
|
||||
Windows 10 supports the virtual terminal */
|
||||
DWORD mode;
|
||||
HANDLE con = GetStdHandle(STD_OUTPUT_HANDLE);
|
||||
if (GetConsoleMode(con, &mode)) {
|
||||
dumpopts |= JV_PRINT_ISATTY;
|
||||
if (getenv("ANSICON") != NULL ||
|
||||
SetConsoleMode(con, mode | 4/*ENABLE_VIRTUAL_TERMINAL_PROCESSING*/))
|
||||
dumpopts |= JV_PRINT_COLOR;
|
||||
}
|
||||
#endif
|
||||
}
|
||||
#endif
|
||||
@@ -464,6 +563,9 @@ int main(int argc, char* argv[]) {
|
||||
if (options & COLOR_OUTPUT) dumpopts |= JV_PRINT_COLOR;
|
||||
if (options & NO_COLOR_OUTPUT) dumpopts &= ~JV_PRINT_COLOR;
|
||||
|
||||
if (getenv("JQ_COLORS") != NULL && !jq_set_colors(getenv("JQ_COLORS")))
|
||||
fprintf(stderr, "Failed to set $JQ_COLORS\n");
|
||||
|
||||
if (jv_get_kind(lib_search_paths) == JV_KIND_NULL) {
|
||||
// Default search path list
|
||||
lib_search_paths = JV_ARRAY(jv_string("~/.jq"),
|
||||
@@ -490,7 +592,7 @@ int main(int argc, char* argv[]) {
|
||||
program = ".";
|
||||
#endif
|
||||
|
||||
if (!program) usage(2);
|
||||
if (!program) usage(2, 1);
|
||||
|
||||
if (options & FROM_FILE) {
|
||||
char *program_origin = strdup(program);
|
||||
@@ -504,19 +606,25 @@ int main(int argc, char* argv[]) {
|
||||
data = jv_invalid_get_msg(data);
|
||||
fprintf(stderr, "%s: %s\n", progname, jv_string_value(data));
|
||||
jv_free(data);
|
||||
ret = 2;
|
||||
ret = JQ_ERROR_SYSTEM;
|
||||
goto out;
|
||||
}
|
||||
jq_set_attr(jq, jv_string("PROGRAM_ORIGIN"), jq_realpath(jv_string(dirname(program_origin))));
|
||||
ARGS = JV_OBJECT(jv_string("positional"), ARGS,
|
||||
jv_string("named"), jv_copy(program_arguments));
|
||||
program_arguments = jv_object_set(program_arguments, jv_string("ARGS"), jv_copy(ARGS));
|
||||
compiled = jq_compile_args(jq, skip_shebang(jv_string_value(data)), jv_copy(program_arguments));
|
||||
free(program_origin);
|
||||
jv_free(data);
|
||||
} else {
|
||||
jq_set_attr(jq, jv_string("PROGRAM_ORIGIN"), jq_realpath(jv_string("."))); // XXX is this good?
|
||||
ARGS = JV_OBJECT(jv_string("positional"), ARGS,
|
||||
jv_string("named"), jv_copy(program_arguments));
|
||||
program_arguments = jv_object_set(program_arguments, jv_string("ARGS"), jv_copy(ARGS));
|
||||
compiled = jq_compile_args(jq, program, jv_copy(program_arguments));
|
||||
}
|
||||
if (!compiled){
|
||||
ret = 3;
|
||||
ret = JQ_ERROR_COMPILE;
|
||||
goto out;
|
||||
}
|
||||
|
||||
@@ -550,6 +658,8 @@ int main(int argc, char* argv[]) {
|
||||
(jv_is_valid((value = jq_util_input_next_input(input_state))) || jv_invalid_has_msg(jv_copy(value)))) {
|
||||
if (jv_is_valid(value)) {
|
||||
ret = process(jq, value, jq_flags, dumpopts);
|
||||
if (ret <= 0 && ret != JQ_OK_NO_OUTPUT)
|
||||
last_result = (ret != JQ_OK_NULL_KIND);
|
||||
continue;
|
||||
}
|
||||
|
||||
@@ -557,7 +667,7 @@ int main(int argc, char* argv[]) {
|
||||
jv msg = jv_invalid_get_msg(value);
|
||||
if (!(options & SEQ)) {
|
||||
// --seq -> errors are not fatal
|
||||
ret = 4;
|
||||
ret = JQ_OK_NO_OUTPUT;
|
||||
fprintf(stderr, "parse error: %s\n", jv_string_value(msg));
|
||||
jv_free(msg);
|
||||
break;
|
||||
@@ -568,21 +678,29 @@ int main(int argc, char* argv[]) {
|
||||
}
|
||||
|
||||
if (jq_util_input_errors(input_state) != 0)
|
||||
ret = 2;
|
||||
ret = JQ_ERROR_SYSTEM;
|
||||
|
||||
out:
|
||||
badwrite = ferror(stdout);
|
||||
if (fclose(stdout)!=0 || badwrite) {
|
||||
fprintf(stderr,"Error: writing output failed: %s\n", strerror(errno));
|
||||
ret = 2;
|
||||
ret = JQ_ERROR_SYSTEM;
|
||||
}
|
||||
|
||||
jv_free(ARGS);
|
||||
jv_free(program_arguments);
|
||||
jq_util_input_free(&input_state);
|
||||
jq_teardown(&jq);
|
||||
if (ret >= 10 && (options & EXIT_STATUS))
|
||||
return ret - 10;
|
||||
if (ret >= 10)
|
||||
return 0;
|
||||
return ret;
|
||||
|
||||
if (options & (EXIT_STATUS|EXIT_STATUS_EXACT)) {
|
||||
if (ret != JQ_OK_NO_OUTPUT)
|
||||
jq_exit_with_status(ret);
|
||||
else
|
||||
switch (last_result) {
|
||||
case -1: jq_exit_with_status(JQ_OK_NO_OUTPUT);
|
||||
case 0: jq_exit_with_status(JQ_OK_NULL_KIND);
|
||||
default: jq_exit_with_status(JQ_OK);
|
||||
}
|
||||
} else
|
||||
jq_exit(ret);
|
||||
}
|
||||
@@ -2,6 +2,7 @@ OP(LOADK, CONSTANT, 1, 1)
|
||||
OP(DUP, NONE, 1, 2)
|
||||
OP(DUPN, NONE, 1, 2)
|
||||
OP(DUP2, NONE, 2, 3)
|
||||
OP(PUSHK_UNDER, CONSTANT, 1, 2)
|
||||
OP(POP, NONE, 1, 0)
|
||||
OP(LOADV, VARIABLE, 1, 1)
|
||||
OP(LOADVN, VARIABLE, 1, 1)
|
||||
@@ -42,3 +43,6 @@ OP(CLOSURE_PARAM_REGULAR, DEFINITION, 0, 0)
|
||||
OP(DEPS, CONSTANT, 0, 0)
|
||||
OP(MODULEMETA, CONSTANT, 0, 0)
|
||||
OP(GENLABEL, NONE, 0, 1)
|
||||
|
||||
OP(DESTRUCTURE_ALT, BRANCH, 0, 0)
|
||||
OP(STOREVN, VARIABLE, 1, 0)
|
||||
+1265
-1160
File diff suppressed because it is too large.
Load diff
+25
-21
@@ -1,8 +1,8 @@
|
||||
/* A Bison parser, made by GNU Bison 3.0.2. */
|
||||
/* A Bison parser, made by GNU Bison 3.0.5. */
|
||||
|
||||
/* Bison interface for Yacc-like parsers in C
|
||||
|
||||
Copyright (C) 1984, 1989-1990, 2000-2013 Free Software Foundation, Inc.
|
||||
Copyright (C) 1984, 1989-1990, 2000-2015, 2018 Free Software Foundation, Inc.
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
@@ -40,7 +40,7 @@
|
||||
extern int yydebug;
|
||||
#endif
|
||||
/* "%code requires" blocks. */
|
||||
#line 11 "src/parser.y" /* yacc.c:1909 */
|
||||
#line 11 "src/parser.y" /* yacc.c:1910 */
|
||||
|
||||
#include "locfile.h"
|
||||
struct lexer_param;
|
||||
@@ -57,7 +57,7 @@ struct lexer_param;
|
||||
} \
|
||||
} while (0)
|
||||
|
||||
#line 61 "src/parser.h" /* yacc.c:1909 */
|
||||
#line 61 "src/parser.h" /* yacc.c:1910 */
|
||||
|
||||
/* Token type. */
|
||||
#ifndef YYTOKENTYPE
|
||||
@@ -101,13 +101,14 @@ struct lexer_param;
|
||||
SETDEFINEDOR = 292,
|
||||
LESSEQ = 293,
|
||||
GREATEREQ = 294,
|
||||
QQSTRING_START = 295,
|
||||
QQSTRING_TEXT = 296,
|
||||
QQSTRING_INTERP_START = 297,
|
||||
QQSTRING_INTERP_END = 298,
|
||||
QQSTRING_END = 299,
|
||||
FUNCDEF = 300,
|
||||
NONOPT = 301
|
||||
ALTERNATION = 295,
|
||||
QQSTRING_START = 296,
|
||||
QQSTRING_TEXT = 297,
|
||||
QQSTRING_INTERP_START = 298,
|
||||
QQSTRING_INTERP_END = 299,
|
||||
QQSTRING_END = 300,
|
||||
FUNCDEF = 301,
|
||||
NONOPT = 302
|
||||
};
|
||||
#endif
|
||||
/* Tokens. */
|
||||
@@ -148,26 +149,29 @@ struct lexer_param;
|
||||
#define SETDEFINEDOR 292
|
||||
#define LESSEQ 293
|
||||
#define GREATEREQ 294
|
||||
#define QQSTRING_START 295
|
||||
#define QQSTRING_TEXT 296
|
||||
#define QQSTRING_INTERP_START 297
|
||||
#define QQSTRING_INTERP_END 298
|
||||
#define QQSTRING_END 299
|
||||
#define FUNCDEF 300
|
||||
#define NONOPT 301
|
||||
#define ALTERNATION 295
|
||||
#define QQSTRING_START 296
|
||||
#define QQSTRING_TEXT 297
|
||||
#define QQSTRING_INTERP_START 298
|
||||
#define QQSTRING_INTERP_END 299
|
||||
#define QQSTRING_END 300
|
||||
#define FUNCDEF 301
|
||||
#define NONOPT 302
|
||||
|
||||
/* Value type. */
|
||||
#if ! defined YYSTYPE && ! defined YYSTYPE_IS_DECLARED
|
||||
typedef union YYSTYPE YYSTYPE;
|
||||
|
||||
union YYSTYPE
|
||||
{
|
||||
#line 31 "src/parser.y" /* yacc.c:1909 */
|
||||
#line 31 "src/parser.y" /* yacc.c:1910 */
|
||||
|
||||
jv literal;
|
||||
block blk;
|
||||
|
||||
#line 170 "src/parser.h" /* yacc.c:1909 */
|
||||
#line 172 "src/parser.h" /* yacc.c:1910 */
|
||||
};
|
||||
|
||||
typedef union YYSTYPE YYSTYPE;
|
||||
# define YYSTYPE_IS_TRIVIAL 1
|
||||
# define YYSTYPE_IS_DECLARED 1
|
||||
#endif
|
||||
|
||||
+33
-11
@@ -83,6 +83,7 @@ struct lexer_param;
|
||||
%token SETDEFINEDOR "//="
|
||||
%token LESSEQ "<="
|
||||
%token GREATEREQ ">="
|
||||
%token ALTERNATION "?//"
|
||||
|
||||
%token QQSTRING_START
|
||||
%token <literal> QQSTRING_TEXT
|
||||
@@ -118,7 +119,7 @@ struct lexer_param;
|
||||
%type <blk> FuncDef FuncDefs
|
||||
%type <blk> Module Import Imports ImportWhat ImportFrom
|
||||
%type <blk> Param Params Arg Args
|
||||
%type <blk> Pattern ArrayPats ObjPats ObjPat
|
||||
%type <blk> Patterns RepPatterns Pattern ArrayPats ObjPats ObjPat
|
||||
%type <literal> Keyword
|
||||
%{
|
||||
#include "lexer.h"
|
||||
@@ -176,10 +177,6 @@ static jv check_object_key(block k) {
|
||||
return jv_invalid();
|
||||
}
|
||||
|
||||
static block gen_dictpair(block k, block v) {
|
||||
return BLOCK(gen_subexp(k), gen_subexp(v), gen_op_simple(INSERT));
|
||||
}
|
||||
|
||||
static block gen_index(block obj, block key) {
|
||||
return BLOCK(gen_subexp(key), obj, gen_op_simple(INDEX));
|
||||
}
|
||||
@@ -334,7 +331,7 @@ FuncDefs:
|
||||
$$ = gen_noop();
|
||||
} |
|
||||
FuncDef FuncDefs {
|
||||
$$ = block_bind($1, $2, OP_IS_CALL_PSEUDO);
|
||||
$$ = block_join($1, $2);
|
||||
}
|
||||
|
||||
Exp:
|
||||
@@ -342,25 +339,27 @@ FuncDef Exp %prec FUNCDEF {
|
||||
$$ = block_bind_referenced($1, $2, OP_IS_CALL_PSEUDO);
|
||||
} |
|
||||
|
||||
Term "as" Pattern '|' Exp {
|
||||
Term "as" Patterns '|' Exp {
|
||||
$$ = gen_destructure($1, $3, $5);
|
||||
} |
|
||||
|
||||
"reduce" Term "as" Pattern '(' Exp ';' Exp ')' {
|
||||
"reduce" Term "as" Patterns '(' Exp ';' Exp ')' {
|
||||
$$ = gen_reduce($2, $4, $6, $8);
|
||||
} |
|
||||
|
||||
"foreach" Term "as" Pattern '(' Exp ';' Exp ';' Exp ')' {
|
||||
"foreach" Term "as" Patterns '(' Exp ';' Exp ';' Exp ')' {
|
||||
$$ = gen_foreach($2, $4, $6, $8, $10);
|
||||
} |
|
||||
|
||||
"foreach" Term "as" Pattern '(' Exp ';' Exp ')' {
|
||||
"foreach" Term "as" Patterns '(' Exp ';' Exp ')' {
|
||||
$$ = gen_foreach($2, $4, $6, $8, gen_noop());
|
||||
} |
|
||||
|
||||
"if" Exp "then" Exp ElseBody {
|
||||
$$ = gen_cond($2, $4, $5);
|
||||
} |
|
||||
"if" Exp "then" Exp "end" {
|
||||
$$ = gen_cond($2, $4, gen_noop());
|
||||
} |
|
||||
"if" Exp "then" error {
|
||||
FAIL(@$, "Possibly unterminated 'if' statement");
|
||||
$$ = $2;
|
||||
@@ -778,6 +777,22 @@ Exp {
|
||||
$$ = gen_lambda($1);
|
||||
}
|
||||
|
||||
RepPatterns:
|
||||
RepPatterns "?//" Pattern {
|
||||
$$ = BLOCK($1, gen_destructure_alt($3));
|
||||
} |
|
||||
Pattern {
|
||||
$$ = gen_destructure_alt($1);
|
||||
}
|
||||
|
||||
Patterns:
|
||||
RepPatterns "?//" Pattern {
|
||||
$$ = BLOCK($1, $3);
|
||||
} |
|
||||
Pattern {
|
||||
$$ = $1;
|
||||
}
|
||||
|
||||
Pattern:
|
||||
'$' IDENT {
|
||||
$$ = gen_op_unbound(STOREV, jv_string_value($2));
|
||||
@@ -810,6 +825,9 @@ ObjPat:
|
||||
'$' IDENT {
|
||||
$$ = gen_object_matcher(gen_const($2), gen_op_unbound(STOREV, jv_string_value($2)));
|
||||
} |
|
||||
'$' IDENT ':' Pattern {
|
||||
$$ = gen_object_matcher(gen_const($2), BLOCK(gen_op_simple(DUP), gen_op_unbound(STOREV, jv_string_value($2)), $4));
|
||||
} |
|
||||
IDENT ':' Pattern {
|
||||
$$ = gen_object_matcher(gen_const($1), $3);
|
||||
} |
|
||||
@@ -913,6 +931,10 @@ IDENT ':' ExpD {
|
||||
$$ = gen_dictpair($1, BLOCK(gen_op_simple(POP), gen_op_simple(DUP2),
|
||||
gen_op_simple(DUP2), gen_op_simple(INDEX)));
|
||||
}
|
||||
| '$' IDENT ':' ExpD {
|
||||
$$ = gen_dictpair(gen_location(@$, locations, gen_op_unbound(LOADV, jv_string_value($2))),
|
||||
$4);
|
||||
}
|
||||
| '$' IDENT {
|
||||
$$ = gen_dictpair(gen_const($2),
|
||||
gen_location(@$, locations, gen_op_unbound(LOADV, jv_string_value($2))));
|
||||
|
||||
+17
-10
@@ -52,7 +52,7 @@ FILE *fopen(const char *fname, const char *mode) {
|
||||
MultiByteToWideChar(CP_UTF8, 0, fname, -1, wfname, sz);
|
||||
|
||||
sz = sizeof(wchar_t) * MultiByteToWideChar(CP_UTF8, 0, mode, -1, NULL, 0);
|
||||
wchar_t *wmode = alloca(sz); // +2 is not needed, but just in case
|
||||
wchar_t *wmode = alloca(sz + 2); // +2 is not needed, but just in case
|
||||
MultiByteToWideChar(CP_UTF8, 0, mode, -1, wmode, sz);
|
||||
return _wfopen(wfname, wmode);
|
||||
}
|
||||
@@ -139,7 +139,7 @@ jv jq_realpath(jv path) {
|
||||
path_max = PATH_MAX;
|
||||
#endif
|
||||
if (path_max > 0) {
|
||||
buf = malloc(sizeof(char) * path_max);
|
||||
buf = jv_mem_alloc(path_max);
|
||||
}
|
||||
#ifdef WIN32
|
||||
char *tmp = _fullpath(buf, jv_string_value(path), path_max);
|
||||
@@ -309,7 +309,7 @@ static int jq_util_input_read_more(jq_util_input_state *state) {
|
||||
state->buf_valid_len = 0;
|
||||
if (state->current_input) {
|
||||
char *res;
|
||||
memset(state->buf, 0, sizeof(state->buf));
|
||||
memset(state->buf, 0xff, sizeof(state->buf));
|
||||
|
||||
while (!(res = fgets(state->buf, sizeof(state->buf), state->current_input)) &&
|
||||
ferror(state->current_input) && errno == EINTR)
|
||||
@@ -334,20 +334,22 @@ static int jq_util_input_read_more(jq_util_input_state *state) {
|
||||
size_t i;
|
||||
|
||||
/*
|
||||
* XXX We can't know how many bytes we've read!
|
||||
* XXX We don't know how many bytes we've read!
|
||||
*
|
||||
* We can't use getline() because there need not be any newlines
|
||||
* in the input. The only entirely correct choices are: use
|
||||
* fgetc() or fread(). Using fread() will complicate buffer
|
||||
* management here.
|
||||
*
|
||||
* For now we guess how much fgets() read.
|
||||
* For now we check how much fgets() read by scanning backwards for the
|
||||
* terminating '\0'. This only works because we previously memset our
|
||||
* buffer with something nonzero.
|
||||
*/
|
||||
for (p = state->buf, i = 0; i < sizeof(state->buf); i++) {
|
||||
if (state->buf[i] != '\0')
|
||||
p = &state->buf[i];
|
||||
for (p = state->buf, i = sizeof(state->buf) - 1; i > 0; i--) {
|
||||
if (state->buf[i] == '\0')
|
||||
break;
|
||||
}
|
||||
state->buf_valid_len = p - state->buf + 1;
|
||||
state->buf_valid_len = i;
|
||||
} else if (p == NULL) {
|
||||
state->buf_valid_len = sizeof(state->buf) - 1;
|
||||
} else {
|
||||
@@ -410,6 +412,7 @@ jv jq_util_input_get_current_line(jq_state* jq) {
|
||||
// When slurping, it returns just one value
|
||||
jv jq_util_input_next_input(jq_util_input_state *state) {
|
||||
int is_last = 0;
|
||||
int has_more = 0;
|
||||
jv value = jv_invalid(); // need more input
|
||||
do {
|
||||
if (state->parser == NULL) {
|
||||
@@ -441,6 +444,10 @@ jv jq_util_input_next_input(jq_util_input_state *state) {
|
||||
}
|
||||
value = jv_parser_next(state->parser);
|
||||
if (jv_is_valid(state->slurped)) {
|
||||
// When slurping an input that doesn't have a trailing newline,
|
||||
// we might have more than one value on the same line, so let's check
|
||||
// to see if we have more data to parse.
|
||||
has_more = jv_parser_remaining(state->parser);
|
||||
if (jv_is_valid(value)) {
|
||||
state->slurped = jv_array_append(state->slurped, value);
|
||||
value = jv_invalid();
|
||||
@@ -450,7 +457,7 @@ jv jq_util_input_next_input(jq_util_input_state *state) {
|
||||
return value;
|
||||
}
|
||||
}
|
||||
} while (!is_last);
|
||||
} while (!is_last || has_more);
|
||||
|
||||
if (jv_is_valid(state->slurped)) {
|
||||
value = state->slurped;
|
||||
|
||||
@@ -0,0 +1,35 @@
|
||||
# Tests are groups of three lines: program, input, expected output
|
||||
# Blank lines and lines starting with # are ignored
|
||||
|
||||
@base64
|
||||
"<>&'\"\t"
|
||||
"PD4mJyIJ"
|
||||
|
||||
# decoding encoded output results in same text
|
||||
(@base64|@base64d)
|
||||
"<>&'\"\t"
|
||||
"<>&'\"\t"
|
||||
|
||||
# regression test for #436
|
||||
@base64
|
||||
"foóbar\n"
|
||||
"Zm/Ds2Jhcgo="
|
||||
|
||||
@base64d
|
||||
"Zm/Ds2Jhcgo="
|
||||
"foóbar\n"
|
||||
|
||||
# optional trailing equals padding (With padding, this is cWl4YmF6Cg==)
|
||||
@base64d
|
||||
"cWl4YmF6Cg"
|
||||
"qixbaz\n"
|
||||
|
||||
# invalid base64 characters (whitespace)
|
||||
. | try @base64d catch .
|
||||
"Not base64 data"
|
||||
"string (\"Not base64...) is not valid base64 data"
|
||||
|
||||
# invalid base64 (too many bytes, QUJD = "ABCD"
|
||||
. | try @base64d catch .
|
||||
"QUJDa"
|
||||
"string (\"QUJDa\") trailing base64 byte found"
|
||||
Executable
+5
@@ -0,0 +1,5 @@
|
||||
#!/bin/sh
|
||||
|
||||
. "${0%/*}/setup" "$@"
|
||||
|
||||
$VALGRIND $Q $JQ -L "$mods" --run-tests $JQTESTDIR/base64.test
|
||||
+223
-5
@@ -61,7 +61,7 @@ null
|
||||
null
|
||||
"interpolation"
|
||||
|
||||
@text,@json,([1,.] | (@csv, @tsv)),@html,@uri,@sh,@base64
|
||||
@text,@json,([1,.] | (@csv, @tsv)),@html,@uri,@sh,@base64,(@base64 | @base64d)
|
||||
"<>&'\"\t"
|
||||
"<>&'\"\t"
|
||||
"\"<>&'\\\"\\t\""
|
||||
@@ -71,12 +71,17 @@ null
|
||||
"%3C%3E%26'%22%09"
|
||||
"'<>&'\\''\"\t'"
|
||||
"PD4mJyIJ"
|
||||
"<>&'\"\t"
|
||||
|
||||
# regression test for #436
|
||||
@base64
|
||||
"foóbar\n"
|
||||
"Zm/Ds2Jhcgo="
|
||||
|
||||
@base64d
|
||||
"Zm/Ds2Jhcgo="
|
||||
"foóbar\n"
|
||||
|
||||
@uri
|
||||
"\u03bc"
|
||||
"%CE%BC"
|
||||
@@ -273,7 +278,7 @@ null
|
||||
|
||||
%%FAIL
|
||||
. as $foo | break $foo
|
||||
jq: error: *label-foo/0 is not defined at <top-level>, line 1:
|
||||
jq: error: $*label-foo is not defined at <top-level>, line 1:
|
||||
|
||||
[.[]|[.,1]|until(.[0] < 1; [.[0] - 1, .[1] * .[0]])|.[1]]
|
||||
[1,2,3,4,5]
|
||||
@@ -299,6 +304,14 @@ null
|
||||
[11,22,33,44,55,66,77,88,99]
|
||||
[11,22,33]
|
||||
|
||||
[limit(0; error)]
|
||||
"badness"
|
||||
[]
|
||||
|
||||
[limit(1; 1, error)]
|
||||
"badness"
|
||||
[1]
|
||||
|
||||
[first(range(.)), last(range(.)), nth(0; range(.)), nth(5; range(.)), try nth(-1; range(.)) catch .]
|
||||
10
|
||||
[0,9,0,5,"nth doesn't support negative indices"]
|
||||
@@ -378,6 +391,15 @@ del(.[2:4],.[0],.[-2:])
|
||||
[0,1,"a","b",4,5,6,7]
|
||||
[0,1,"a","b","c",4,5,6,7]
|
||||
|
||||
# Slices at large offsets (issue #1108)
|
||||
#
|
||||
# This is written this way because [range(<large number>)] is
|
||||
# significantly slower under valgrind than .[<large number>] = value.
|
||||
#
|
||||
# We range down rather than up so that we have just one realloc.
|
||||
reduce range(65540;65536;-1) as $i ([]; .[$i] = $i)|.[65536:]
|
||||
null
|
||||
[null,65537,65538,65539,65540]
|
||||
|
||||
#
|
||||
# Variables
|
||||
@@ -691,10 +713,127 @@ reduce [[1,2,10], [3,4,10]][] as [$i,$j] (0; . + $i * $j)
|
||||
null
|
||||
14
|
||||
|
||||
# Test fix for #1313 (reduce should handle empty updates)
|
||||
reduce range(5) as $n ([]; select($n%2 == 1) | . + [$n])
|
||||
# This, while useless, should still compile.
|
||||
reduce . as $n (.; .)
|
||||
null
|
||||
[1,3]
|
||||
null
|
||||
|
||||
# Destructuring
|
||||
. as {$a, b: [$c, {$d}]} | [$a, $c, $d]
|
||||
{"a":1, "b":[2,{"d":3}]}
|
||||
[1,2,3]
|
||||
|
||||
. as {$a, $b:[$c, $d]}| [$a, $b, $c, $d]
|
||||
{"a":1, "b":[2,{"d":3}]}
|
||||
[1,[2,{"d":3}],2,{"d":3}]
|
||||
|
||||
# Destructuring with alternation
|
||||
.[] | . as {$a, b: [$c, {$d}]} ?// [$a, {$b}, $e] ?// $f | [$a, $b, $c, $d, $e, $f]
|
||||
[{"a":1, "b":[2,{"d":3}]}, [4, {"b":5, "c":6}, 7, 8, 9], "foo"]
|
||||
[1, null, 2, 3, null, null]
|
||||
[4, 5, null, null, 7, null]
|
||||
[null, null, null, null, null, "foo"]
|
||||
|
||||
# Destructuring DUP/POP issues
|
||||
.[] | . as {a:$a} ?// {a:$a} ?// {a:$a} | $a
|
||||
[[3],[4],[5],6]
|
||||
# Runtime error: "jq: Cannot index array with string \"c\""
|
||||
|
||||
.[] as {a:$a} ?// {a:$a} ?// {a:$a} | $a
|
||||
[[3],[4],[5],6]
|
||||
# Runtime error: "jq: Cannot index array with string \"c\""
|
||||
|
||||
[[3],[4],[5],6][] | . as {a:$a} ?// {a:$a} ?// {a:$a} | $a
|
||||
null
|
||||
# Runtime error: "jq: Cannot index array with string \"c\""
|
||||
|
||||
[[3],[4],[5],6] | .[] as {a:$a} ?// {a:$a} ?// {a:$a} | $a
|
||||
null
|
||||
# Runtime error: "jq: Cannot index array with string \"c\""
|
||||
|
||||
.[] | . as {a:$a} ?// {a:$a} ?// $a | $a
|
||||
[[3],[4],[5],6]
|
||||
[3]
|
||||
[4]
|
||||
[5]
|
||||
6
|
||||
|
||||
.[] as {a:$a} ?// {a:$a} ?// $a | $a
|
||||
[[3],[4],[5],6]
|
||||
[3]
|
||||
[4]
|
||||
[5]
|
||||
6
|
||||
|
||||
[[3],[4],[5],6][] | . as {a:$a} ?// {a:$a} ?// $a | $a
|
||||
null
|
||||
[3]
|
||||
[4]
|
||||
[5]
|
||||
6
|
||||
|
||||
[[3],[4],[5],6] | .[] as {a:$a} ?// {a:$a} ?// $a | $a
|
||||
null
|
||||
[3]
|
||||
[4]
|
||||
[5]
|
||||
6
|
||||
|
||||
.[] | . as {a:$a} ?// $a ?// {a:$a} | $a
|
||||
[[3],[4],[5],6]
|
||||
[3]
|
||||
[4]
|
||||
[5]
|
||||
6
|
||||
|
||||
.[] as {a:$a} ?// $a ?// {a:$a} | $a
|
||||
[[3],[4],[5],6]
|
||||
[3]
|
||||
[4]
|
||||
[5]
|
||||
6
|
||||
|
||||
[[3],[4],[5],6][] | . as {a:$a} ?// $a ?// {a:$a} | $a
|
||||
null
|
||||
[3]
|
||||
[4]
|
||||
[5]
|
||||
6
|
||||
|
||||
[[3],[4],[5],6] | .[] as {a:$a} ?// $a ?// {a:$a} | $a
|
||||
null
|
||||
[3]
|
||||
[4]
|
||||
[5]
|
||||
6
|
||||
|
||||
.[] | . as $a ?// {a:$a} ?// {a:$a} | $a
|
||||
[[3],[4],[5],6]
|
||||
[3]
|
||||
[4]
|
||||
[5]
|
||||
6
|
||||
|
||||
.[] as $a ?// {a:$a} ?// {a:$a} | $a
|
||||
[[3],[4],[5],6]
|
||||
[3]
|
||||
[4]
|
||||
[5]
|
||||
6
|
||||
|
||||
[[3],[4],[5],6][] | . as $a ?// {a:$a} ?// {a:$a} | $a
|
||||
null
|
||||
[3]
|
||||
[4]
|
||||
[5]
|
||||
6
|
||||
|
||||
[[3],[4],[5],6] | .[] as $a ?// {a:$a} ?// {a:$a} | $a
|
||||
null
|
||||
[3]
|
||||
[4]
|
||||
[5]
|
||||
6
|
||||
|
||||
. as $dot|any($dot[];not)
|
||||
[1,2,3,4,true,false,1,2,3,4,5]
|
||||
@@ -837,6 +976,18 @@ def inc(x): x |= .+1; inc(.[].a)
|
||||
[{"a":1,"b":2},{"a":2,"b":4},{"a":7,"b":8}]
|
||||
[{"a":2,"b":2},{"a":3,"b":4},{"a":8,"b":8}]
|
||||
|
||||
# #1358, getpath/1 should work in path expressions
|
||||
.[] | try (getpath(["a",0,"b"]) |= 5) catch .
|
||||
[null,{"b":0},{"a":0},{"a":null},{"a":[0,1]},{"a":{"b":1}},{"a":[{}]},{"a":[{"c":3}]}]
|
||||
{"a":[{"b":5}]}
|
||||
{"b":0,"a":[{"b":5}]}
|
||||
"Cannot index number with number"
|
||||
{"a":[{"b":5}]}
|
||||
"Cannot index number with string \"b\""
|
||||
"Cannot index object with number"
|
||||
{"a":[{"b":5}]}
|
||||
{"a":[{"c":3,"b":5}]}
|
||||
|
||||
.[2][3] = 1
|
||||
[4]
|
||||
[4, null, [null, null, null, 1]]
|
||||
@@ -893,6 +1044,19 @@ null
|
||||
null
|
||||
[5,6]
|
||||
|
||||
[if true then 3 end]
|
||||
7
|
||||
[3]
|
||||
|
||||
[if false then 3 end]
|
||||
7
|
||||
[7]
|
||||
|
||||
[if false then 3 else . end]
|
||||
7
|
||||
[7]
|
||||
|
||||
|
||||
[.[] | [.foo[] // .bar]]
|
||||
[{"foo":[1,2], "bar": 42}, {"foo":[1], "bar": null}, {"foo":[null,false,3], "bar": 18}, {"foo":[], "bar":42}, {"foo": [null,false,null], "bar": 41}]
|
||||
[[1,2], [1], [3], [42], [41]]
|
||||
@@ -948,6 +1112,28 @@ null
|
||||
{}
|
||||
[true, true, false]
|
||||
|
||||
# containment operator (embedded NULs!)
|
||||
[contains(""), contains("\u0000")]
|
||||
"\u0000"
|
||||
[true, true]
|
||||
|
||||
[contains(""), contains("a"), contains("ab"), contains("c"), contains("d")]
|
||||
"ab\u0000cd"
|
||||
[true, true, true, true, true]
|
||||
|
||||
[contains("cd"), contains("b\u0000"), contains("ab\u0000")]
|
||||
"ab\u0000cd"
|
||||
[true, true, true]
|
||||
|
||||
[contains("b\u0000c"), contains("b\u0000cd"), contains("b\u0000cd")]
|
||||
"ab\u0000cd"
|
||||
[true, true, true]
|
||||
|
||||
[contains("@"), contains("\u0000@"), contains("\u0000what")]
|
||||
"ab\u0000cd"
|
||||
[false, false, false]
|
||||
|
||||
|
||||
# Try/catch and general `?` operator
|
||||
[.[]|try if . == 0 then error("foo") elif . == 1 then .a elif . == 2 then empty else . end catch .]
|
||||
[0,1,2,3]
|
||||
@@ -1227,6 +1413,10 @@ include "c"; [a, c]
|
||||
null
|
||||
[0,"acmehbah"]
|
||||
|
||||
import "data" as $e; import "data" as $d; [$d[].this,$e[].that,$d::d[].this,$e::e[].that]|join(";")
|
||||
null
|
||||
"is a test;is too;is a test;is too"
|
||||
|
||||
%%FAIL
|
||||
module (.+1); 0
|
||||
jq: error: Module metadata must be constant at <top-level>, line 1:
|
||||
@@ -1351,3 +1541,31 @@ IN(range(5;20); range(10))
|
||||
null
|
||||
true
|
||||
|
||||
# Regression test for #1347
|
||||
(.a as $x | .b) = "b"
|
||||
{"a":null,"b":null}
|
||||
{"a":null,"b":"b"}
|
||||
|
||||
# Regression test for #1368
|
||||
(.. | select(type == "object" and has("b") and (.b | type) == "array")|.b) |= .[0]
|
||||
{"a": {"b": [1, {"b": 3}]}}
|
||||
{"a": {"b": 1}}
|
||||
|
||||
isempty(empty)
|
||||
null
|
||||
true
|
||||
|
||||
isempty(range(3))
|
||||
null
|
||||
false
|
||||
|
||||
isempty(1,error("foo"))
|
||||
null
|
||||
false
|
||||
|
||||
# Regression test for #1815
|
||||
index("")
|
||||
""
|
||||
null
|
||||
|
||||
|
||||
+1
-1
@@ -3,5 +3,5 @@
|
||||
. "${0%/*}/setup" "$@"
|
||||
|
||||
# We set PAGER because there's a mantest for `env` that uses it.
|
||||
(cd $JQBASEDIR/docs && rake mantests) |
|
||||
(cd $JQBASEDIR/docs && pipenv run python3 build_mantests.py) |
|
||||
env PAGER=less $VALGRIND $Q $JQ -L "$mods" --run-tests
|
||||
@@ -5,6 +5,13 @@
|
||||
"2015-03-05T23:51:47Z"
|
||||
[[2015,2,5,23,51,47,4,63],1425599507]
|
||||
|
||||
# Check day-of-week and day of year computations
|
||||
# (should trip an assert if this fails)
|
||||
# This date range
|
||||
last(range(365 * 67)|("1970-03-01T01:02:03Z"|strptime("%Y-%m-%dT%H:%M:%SZ")|mktime) + (86400 * .)|strftime("%Y-%m-%dT%H:%M:%SZ")|strptime("%Y-%m-%dT%H:%M:%SZ"))
|
||||
null
|
||||
[2037,1,11,1,2,3,3,41]
|
||||
|
||||
# %e is not available on mingw/WIN32
|
||||
strftime("%A, %B %e, %Y")
|
||||
1435677542.822351
|
||||
|
||||
+3
-1
@@ -15,9 +15,11 @@ JQ=$JQBASEDIR/jq
|
||||
if [ -z "${NO_VALGRIND-}" ] && which valgrind > /dev/null; then
|
||||
VALGRIND="valgrind --error-exitcode=1 --leak-check=full \
|
||||
--suppressions=$JQTESTDIR/onig.supp"
|
||||
VG_EXIT0=--error-exitcode=0
|
||||
Q=-q
|
||||
else
|
||||
VALGRIND=
|
||||
VG_EXIT0=
|
||||
Q=
|
||||
fi
|
||||
|
||||
@@ -33,7 +35,7 @@ clean () {
|
||||
fi
|
||||
}
|
||||
trap clean EXIT
|
||||
d=`mktemp -d -t jqXXXXXX || true`
|
||||
d=$(mktemp -d -t jqXXXXXX || true)
|
||||
if [ -z "$d" ]; then
|
||||
echo "Your OS does not support mktemp(1) -d" 1>&2
|
||||
exit 1
|
||||
|
||||
+141
-29
@@ -29,50 +29,46 @@ $VALGRIND $Q $JQ -Rne '[inputs] == ["a\u0000b", "c\u0000d", "e"]' $d/input
|
||||
## move all of these into tests/all.test
|
||||
|
||||
# String constant folding (addition only)
|
||||
n=`$VALGRIND $Q $JQ -n --debug-dump-disasm '"foo"' | wc -l`
|
||||
if [ $n -ne 5 ]; then
|
||||
echo "Constant expression folding for strings didn't work"
|
||||
exit 1
|
||||
fi
|
||||
nref=$($VALGRIND $Q $JQ -n --debug-dump-disasm '"foo"' | wc -l)
|
||||
|
||||
# Numeric constant folding (not all ops yet)
|
||||
n=`$VALGRIND $Q $JQ -n --debug-dump-disasm '1+1' | wc -l`
|
||||
if [ $n -ne 5 ]; then
|
||||
n=$($VALGRIND $Q $JQ -n --debug-dump-disasm '1+1' | wc -l)
|
||||
if [ $n -ne $nref ]; then
|
||||
echo "Constant expression folding for strings didn't work"
|
||||
exit 1
|
||||
fi
|
||||
n=`$VALGRIND $Q $JQ -n --debug-dump-disasm '1-1' | wc -l`
|
||||
if [ $n -ne 5 ]; then
|
||||
n=$($VALGRIND $Q $JQ -n --debug-dump-disasm '1-1' | wc -l)
|
||||
if [ $n -ne $nref ]; then
|
||||
echo "Constant expression folding for strings didn't work"
|
||||
exit 1
|
||||
fi
|
||||
n=`$VALGRIND $Q $JQ -n --debug-dump-disasm '2*3' | wc -l`
|
||||
if [ $n -ne 5 ]; then
|
||||
n=$($VALGRIND $Q $JQ -n --debug-dump-disasm '2*3' | wc -l)
|
||||
if [ $n -ne $nref ]; then
|
||||
echo "Constant expression folding for strings didn't work"
|
||||
exit 1
|
||||
fi
|
||||
n=`$VALGRIND $Q $JQ -n --debug-dump-disasm '9/3' | wc -l`
|
||||
if [ $n -ne 5 ]; then
|
||||
n=$($VALGRIND $Q $JQ -n --debug-dump-disasm '9/3' | wc -l)
|
||||
if [ $n -ne $nref ]; then
|
||||
echo "Constant expression folding for strings didn't work"
|
||||
exit 1
|
||||
fi
|
||||
n=`$VALGRIND $Q $JQ -n --debug-dump-disasm '9==3' | wc -l`
|
||||
if [ $n -ne 5 ]; then
|
||||
n=$($VALGRIND $Q $JQ -n --debug-dump-disasm '9==3' | wc -l)
|
||||
if [ $n -ne $nref ]; then
|
||||
echo "Constant expression folding for strings didn't work"
|
||||
exit 1
|
||||
fi
|
||||
n=`$VALGRIND $Q $JQ -n --debug-dump-disasm '9!=3' | wc -l`
|
||||
if [ $n -ne 5 ]; then
|
||||
n=$($VALGRIND $Q $JQ -n --debug-dump-disasm '9!=3' | wc -l)
|
||||
if [ $n -ne $nref ]; then
|
||||
echo "Constant expression folding for strings didn't work"
|
||||
exit 1
|
||||
fi
|
||||
n=`$VALGRIND $Q $JQ -n --debug-dump-disasm '9<=3' | wc -l`
|
||||
if [ $n -ne 5 ]; then
|
||||
n=$($VALGRIND $Q $JQ -n --debug-dump-disasm '9<=3' | wc -l)
|
||||
if [ $n -ne $nref ]; then
|
||||
echo "Constant expression folding for strings didn't work"
|
||||
exit 1
|
||||
fi
|
||||
n=`$VALGRIND $Q $JQ -n --debug-dump-disasm '9>=3' | wc -l`
|
||||
if [ $n -ne 5 ]; then
|
||||
n=$($VALGRIND $Q $JQ -n --debug-dump-disasm '9>=3' | wc -l)
|
||||
if [ $n -ne $nref ]; then
|
||||
echo "Constant expression folding for strings didn't work"
|
||||
exit 1
|
||||
fi
|
||||
@@ -100,14 +96,20 @@ cmp $d/out $d/expected
|
||||
cat > $d/expected <<EOF
|
||||
ignoring parse error: Unfinished abandoned text at EOF at line 1, column 4
|
||||
EOF
|
||||
printf '"foo' | $JQ -ce --seq . > $d/out 2>&1
|
||||
printf '"foo' | $JQ -c --seq . > $d/out 2>&1
|
||||
cmp $d/out $d/expected
|
||||
|
||||
# with -e option should give 4 here as there's no valid output after
|
||||
# ignoring parse errors with --seq.
|
||||
printf '"foo' | $JQ -ce --seq . > $d/out 2>&1 || ret=$?
|
||||
[ $ret -eq 4 ]
|
||||
cmp $d/out $d/expected
|
||||
|
||||
# Numeric values truncated by EOF are ignored
|
||||
cat > $d/expected <<EOF
|
||||
ignoring parse error: Unfinished abandoned text at EOF at line 1, column 1
|
||||
EOF
|
||||
printf '1' | $JQ -ce --seq . > $d/out 2>&1
|
||||
printf '1' | $JQ -c --seq . > $d/out 2>&1
|
||||
cmp $d/out $d/expected
|
||||
|
||||
cat > $d/expected <<EOF
|
||||
@@ -119,6 +121,21 @@ if printf '1\n' | $JQ -cen --seq '[inputs] == []' >/dev/null 2> $d/out; then
|
||||
fi
|
||||
cmp $d/out $d/expected
|
||||
|
||||
|
||||
## Test --exit-status
|
||||
data='{"i": 1}\n{"i": 2}\n{"i": 3}\n'
|
||||
echo "$data" | $JQ --exit-status 'select(.i==1)' > /dev/null 2>&1
|
||||
echo "$data" | $JQ --exit-status 'select(.i==2)' > /dev/null 2>&1
|
||||
echo "$data" | $JQ --exit-status 'select(.i==3)' > /dev/null 2>&1
|
||||
ret=0
|
||||
echo "$data" | $JQ --exit-status 'select(.i==4)' > /dev/null 2>&1 || ret=$?
|
||||
[ $ret -eq 4 ]
|
||||
ret=0
|
||||
echo "$data" | $JQ --exit-status 'select(.i==2) | false' > /dev/null 2>&1 || ret=$?
|
||||
[ $ret -eq 1 ]
|
||||
echo "$data" | $JQ --exit-status 'select(.i==2) | true' > /dev/null 2>&1
|
||||
|
||||
|
||||
# Regression test for #951
|
||||
printf '"a\n' > $d/input
|
||||
if $VALGRIND $Q $JQ -e . $d/input; then
|
||||
@@ -126,6 +143,14 @@ if $VALGRIND $Q $JQ -e . $d/input; then
|
||||
exit 2
|
||||
fi
|
||||
|
||||
# Regression test for #1534
|
||||
echo "[1,2,3,4]" > $d/expected
|
||||
printf "[1,2][3,4]" | $JQ -cs add > $d/out 2>&1
|
||||
cmp $d/out $d/expected
|
||||
printf "[1,2][3,4]\n" | $JQ -cs add > $d/out 2>&1
|
||||
cmp $d/out $d/expected
|
||||
|
||||
|
||||
## Test streaming parser
|
||||
|
||||
## If we add an option to stream to the `import ... as $symbol;` directive
|
||||
@@ -141,15 +166,15 @@ if which seq > /dev/null 2>&1; then
|
||||
# test very, very slow when run with valgrind, and the whole point
|
||||
# is to run it with valgrind.
|
||||
#
|
||||
#len=`wc -c < "$JQTESTDIR/torture/input0.json"`
|
||||
#len=$(wc -c < "$JQTESTDIR/torture/input0.json")
|
||||
if [ -z "$VALGRIND" ]; then
|
||||
start=1
|
||||
end=`wc -c < "$JQTESTDIR/torture/input0.json"`
|
||||
end=$(wc -c < "$JQTESTDIR/torture/input0.json")
|
||||
else
|
||||
start=120
|
||||
end=151
|
||||
fi
|
||||
for i in `seq $start $end`; do
|
||||
for i in $(seq $start $end); do
|
||||
dd "if=tests/torture/input0.json" bs=$i count=1 2>/dev/null |
|
||||
$VALGRIND $JQ -c . > $d/out0 2>$d/err || true
|
||||
if [ -n "$VALGRIND" ]; then
|
||||
@@ -174,7 +199,7 @@ fi
|
||||
|
||||
## Fuzz parser
|
||||
|
||||
## XXX With a `urandom` builtin we could move this test into tests/all.test
|
||||
## XXX With a $(urandom) builtin we could move this test into tests/all.test
|
||||
clean=false
|
||||
if dd if=/dev/urandom bs=16 count=1024 > $d/rand 2>/dev/null; then
|
||||
# Have a /dev/urandom, good
|
||||
@@ -193,12 +218,12 @@ clean=true
|
||||
|
||||
# Check handling of ~/.jq; these can't move into jq_test.c yet because
|
||||
# they depend on $HOME
|
||||
if [ "`HOME="$mods" $VALGRIND $Q $JQ -nr fg`" != foobar ]; then
|
||||
if [ "$(HOME="$mods" $VALGRIND $Q $JQ -nr fg)" != foobar ]; then
|
||||
echo "Bug #479 appears to be back" 1>&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if [ `HOME="$mods" $VALGRIND $Q $JQ --debug-dump-disasm -n fg | grep '^[a-z]' | wc -l` -gt 3 ]; then
|
||||
if [ $(HOME="$mods" $VALGRIND $Q $JQ --debug-dump-disasm -n fg | grep '^[a-z]' | wc -l) -gt 3 ]; then
|
||||
echo "Binding too many defs into program" 1>&2
|
||||
exit 1
|
||||
fi
|
||||
@@ -215,4 +240,91 @@ if ! $VALGRIND $Q $JQ -L tests/modules -ne 'import "test_bind_order" as check; c
|
||||
exit 1
|
||||
fi
|
||||
|
||||
## Halt
|
||||
|
||||
if ! $VALGRIND $Q $JQ -n halt; then
|
||||
echo "jq halt didn't work as expected" 1>&2
|
||||
exit 1
|
||||
fi
|
||||
if $VALGRIND $Q $VG_EXIT0 $JQ -n 'halt_error(1)'; then
|
||||
echo "jq halt_error(1) didn't work as expected" 1>&2
|
||||
exit 1
|
||||
elif [ $? -ne 1 ]; then
|
||||
echo "jq halt_error(1) had wrong error code" 1>&2
|
||||
exit 1
|
||||
fi
|
||||
if $VALGRIND $Q $VG_EXIT0 $JQ -n 'halt_error(11)'; then
|
||||
echo "jq halt_error(11) didn't work as expected" 1>&2
|
||||
exit 1
|
||||
elif [ $? -ne 11 ]; then
|
||||
echo "jq halt_error(11) had wrong error code" 1>&2
|
||||
exit 1
|
||||
fi
|
||||
if [ -n "$($VALGRIND $Q $JQ -n 'halt_error(1)' 2>&1)" ]; then
|
||||
echo "jq halt_error(1) had unexpected output" 1>&2
|
||||
exit 1
|
||||
fi
|
||||
if [ -n "$($VALGRIND $Q $JQ -n '"xyz\n"|halt_error(1)' 2>/dev/null)" ]; then
|
||||
echo "jq halt_error(1) had unexpected output on stdout" 1>&2
|
||||
exit 1
|
||||
fi
|
||||
if [ "$($VALGRIND $Q $JQ -n '"xyz\n"|halt_error(1)' 2>&1)" != xyz ]; then
|
||||
echo "jq halt_error(1) had unexpected output" 1>&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Check $JQ_COLORS
|
||||
$JQ -Ccn . > $d/color
|
||||
printf '\033[1;30mnull\033[0m\n' > $d/expect
|
||||
cmp $d/color $d/expect
|
||||
JQ_COLORS='4;31' $JQ -Ccn . > $d/color
|
||||
printf '\033[4;31mnull\033[0m\n' > $d/expect
|
||||
cmp $d/color $d/expect
|
||||
JQ_COLORS='1;30:0;31:0;32:0;33:0;34:1;35:1;36' \
|
||||
$JQ -Ccn '[{"a":true,"b":false},123,null]' > $d/color
|
||||
(
|
||||
printf '\033[1;35m[\033[1;36m{'
|
||||
printf '\033[0m\033[34;1m"a"\033['
|
||||
printf '0m\033[1;36m:\033[0m\033['
|
||||
printf '0;32mtrue\033[0m\033[1'
|
||||
printf ';36m,\033[0m\033[34;1m'
|
||||
printf '"b"\033[0m\033[1;36m:\033'
|
||||
printf '[0m\033[0;31mfalse\033'
|
||||
printf '[0m\033[1;36m\033[1;36'
|
||||
printf 'm}\033[0m\033[1;35m,\033['
|
||||
printf '0;33m123\033[0m\033[1;'
|
||||
printf '35m,\033[1;30mnull\033'
|
||||
printf '[0m\033[1;35m\033[1;35'
|
||||
printf 'm]\033[0m\n'
|
||||
) > $d/expect
|
||||
cmp $d/color $d/expect
|
||||
|
||||
# Check garbage in JQ_COLORS. We write each color sequence into a 16
|
||||
# char buffer that needs to hold ESC [ <color> m NUL, so each color
|
||||
# sequence can be no more than 12 chars (bytes). These emit a warning
|
||||
# on stderr.
|
||||
set -vx
|
||||
echo 'Failed to set $JQ_COLORS' > $d/expect_warning
|
||||
$JQ -Ccn '[{"a":true,"b":false},123,null]' > $d/expect
|
||||
JQ_COLORS='garbage;30:*;31:,;3^:0;$%:0;34:1;35:1;36' \
|
||||
$JQ -Ccn '[{"a":true,"b":false},123,null]' > $d/color 2>$d/warning
|
||||
cmp $d/color $d/expect
|
||||
cmp $d/warning $d/expect_warning
|
||||
JQ_COLORS='1234567890123456789;30:0;31:0;32:0;33:0;34:1;35:1;36' \
|
||||
$JQ -Ccn '[{"a":true,"b":false},123,null]' > $d/color 2>$d/warning
|
||||
cmp $d/color $d/expect
|
||||
cmp $d/warning $d/expect_warning
|
||||
JQ_COLORS='1;31234567890123456789:0;31:0;32:0;33:0;34:1;35:1;36' \
|
||||
$JQ -Ccn '[{"a":true,"b":false},123,null]' > $d/color 2>$d/warning
|
||||
cmp $d/color $d/expect
|
||||
cmp $d/warning $d/expect_warning
|
||||
JQ_COLORS='1234567890123456;1234567890123456:1234567890123456;1234567890123456:1234567890123456;1234567890123456:1234567890123456;1234567890123456:1234567890123456;1234567890123456:1234567890123456;1234567890123456:1234567890123456;1234567890123456' \
|
||||
$JQ -Ccn '[{"a":true,"b":false},123,null]' > $d/color 2>$d/warning
|
||||
cmp $d/color $d/expect
|
||||
cmp $d/warning $d/expect_warning
|
||||
JQ_COLORS="0123456789123:0123456789123:0123456789123:0123456789123:0123456789123:0123456789123:0123456789123:0123456789123:" \
|
||||
$JQ -Ccn '[{"a":true,"b":false},123,null]' > $d/color 2>$d/warning
|
||||
cmp $d/color $d/expect
|
||||
cmp $d/warning $d/expect_warning
|
||||
|
||||
exit 0
|
||||
+1
-1
@@ -2,7 +2,7 @@
|
||||
|
||||
. "${0%/*}/setup" "$@"
|
||||
|
||||
if [ "`$VALGRIND $Q $JQ -nf $JQTESTDIR/utf8-truncate.jq`" != "true" ]; then
|
||||
if [ "$($VALGRIND $Q $JQ -nf $JQTESTDIR/utf8-truncate.jq)" != "true" ]; then
|
||||
echo "UTF-8 byte sequences that span the jv_load_file read buffer are mangled"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
Reference in new issue
Block a user