Script 'mail_helper' called by obssrc Hello community, here is the log from the commit of package jo for openSUSE:Factory checked in at 2022-01-11 21:20:21 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Comparing /work/SRC/openSUSE:Factory/jo (Old) and /work/SRC/openSUSE:Factory/.jo.new.1892 (New) ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Package is "jo" Tue Jan 11 21:20:21 2022 rev:6 rq:945611 version:1.6 Changes: -------- --- /work/SRC/openSUSE:Factory/jo/jo.changes 2021-05-18 18:27:51.894592503 +0200 +++ /work/SRC/openSUSE:Factory/.jo.new.1892/jo.changes 2022-01-11 21:24:33.565182069 +0100 @@ -1,0 +2,20 @@ +Wed Jan 5 16:14:18 UTC 2022 - Martin Hauke <mar...@gmx.de> + +- Update to versino 1.6 + * FIX: repair tests broken by AUTHORS change + * FIX: repair make distcheck by removing copied _jo zsh functions + +------------------------------------------------------------------- +Tue Jan 4 11:59:53 UTC 2022 - Martin Hauke <mar...@gmx.de> + +- Udpate to version 1.5 + * NEW: replace asserts with human errors + * NEW: zsh completion + * FIX: several cleanups + * NEW: Meson build + * NEW: option to deduplicate keys + * NEW: Filter functionality + * FIX: file embedding + * FIX: add missing tests to Makefile.am + +------------------------------------------------------------------- Old: ---- jo-1.4.tar.gz New: ---- jo-1.6.tar.gz ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Other differences: ------------------ ++++++ jo.spec ++++++ --- /var/tmp/diff_new_pack.v4v0H9/_old 2022-01-11 21:24:33.957182345 +0100 +++ /var/tmp/diff_new_pack.v4v0H9/_new 2022-01-11 21:24:33.961182347 +0100 @@ -1,7 +1,7 @@ # # spec file for package jo # -# Copyright (c) 2021 SUSE LLC +# Copyright (c) 2022 SUSE LLC # # All modifications and additions to the file contributed by third parties # remain the property of their copyright owners, unless otherwise agreed @@ -17,7 +17,7 @@ Name: jo -Version: 1.4 +Version: 1.6 Release: 0 Summary: JSON output from a shell License: GPL-2.0-or-later AND MIT @@ -37,12 +37,21 @@ Group: Productivity/Text/Utilities Requires: %{name} = %{version} Requires: bash-completion -Supplements: (jo and bash-completion) +Supplements: (%{name} and bash-completion) BuildArch: noarch %description bash-completion Bash completion script for %{name}. +%package zsh-completion +Summary: ZSH completion for %{name} +Group: Productivity/Text/Utilities +Supplements: (%{name} and zsh) +BuildArch: noarch + +%description zsh-completion +zsh shell completions for %{name}. + %prep %setup -q @@ -54,6 +63,8 @@ %install %make_install +install -d %{buildroot}/%{_sysconfdir}/zsh_completion.d +mv %{buildroot}%{_datadir}/zsh/site-functions/_jo %{buildroot}%{_sysconfdir}/zsh_completion.d/%{name} %check %make_build check @@ -67,4 +78,8 @@ %files bash-completion %{_datadir}/bash-completion/completions/%{name}.bash +%files zsh-completion +%dir %{_sysconfdir}/zsh_completion.d +%config %{_sysconfdir}/zsh_completion.d/%{name} + %changelog ++++++ jo-1.4.tar.gz -> jo-1.6.tar.gz ++++++ diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/jo-1.4/.travis.yml new/jo-1.6/.travis.yml --- old/jo-1.4/.travis.yml 2020-07-18 18:20:38.000000000 +0200 +++ new/jo-1.6/.travis.yml 2022-01-05 16:59:37.000000000 +0100 @@ -1,6 +1,10 @@ os: - linux - osx + +arch: + - amd64 + - ppc64le sudo: false language: c compiler: diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/jo-1.4/AUTHORS new/jo-1.6/AUTHORS --- old/jo-1.4/AUTHORS 2020-07-18 18:20:38.000000000 +0200 +++ new/jo-1.6/AUTHORS 2022-01-05 16:59:37.000000000 +0100 @@ -1 +1,2 @@ Jan-Piet Mens <jpm...@gmail.com> +Adrian Ho <the.grom...@gmail.com> diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/jo-1.4/ChangeLog new/jo-1.6/ChangeLog --- old/jo-1.4/ChangeLog 2020-07-18 18:20:38.000000000 +0200 +++ new/jo-1.6/ChangeLog 2022-01-05 16:59:37.000000000 +0100 @@ -1,3 +1,22 @@ + +2022-01-05 1.6 + +- FIX: repair tests broken by AUTHORS change (#164) +- FIX: repair make distcheck by removing copied _jo zsh functions + +2022-01-04 1.5 + +- NEW: replace asserts with human errors (#162) +- NEW: zsh completion (#158) +- FIX: stdin filter on Windows (# +- FIX: several cleanups +- NEW: Meson build +- UPD: snap to newer base (#149) +- NEW: option to deduplicate keys (#143, #145) +- NEW: Filter functionality (#141) +- FIX: file embedding +- FIX: add missing tests to Makefile.am + 2020-07-18 1.4 - FIX: Coercion flag logic now permits getopt(3) double-dash diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/jo-1.4/Makefile.am new/jo-1.6/Makefile.am --- old/jo-1.4/Makefile.am 2020-07-18 18:20:38.000000000 +0200 +++ new/jo-1.6/Makefile.am 2022-01-05 16:59:37.000000000 +0100 @@ -10,17 +10,25 @@ bashcompdir = @bashcompdir@ dist_bashcomp_DATA = jo.bash +zshcompdir = $(datadir)/zsh/site-functions +dist_zshcomp_DATA = jo.zsh +install-data-hook: + mv -f $(DESTDIR)$(zshcompdir)/jo.zsh $(DESTDIR)$(zshcompdir)/_jo + +uninstall-local: + rm -f $(DESTDIR)$(zshcompdir)/_jo + if USE_PANDOC # Add targets to rebuild pages jo.1: jo.pandoc @test -n "$(PANDOC)" || \ { echo 'pandoc' not found during configure.; exit 1; } - $(PANDOC) -s -w man+simple_tables -o $@ $< + $(PANDOC) -s -w man -f markdown -o $@ $< jo.md: jo.pandoc @test -n "$(PANDOC)" || \ { echo 'pandoc' not found during configure.; exit 1; } - $(PANDOC) -s -w markdown+simple_tables -o $@ $< + $(PANDOC) -s -w markdown+simple_tables -f markdown -o $@ $< endif @@ -53,4 +61,17 @@ tests/jo.11.sh tests/jo.11.exp \ tests/jo.12.sh tests/jo.12.exp \ tests/jo.13.sh tests/jo.13.exp tests/jo-logo.png \ - tests/jo.14.sh tests/jo.14.exp + tests/jo.14.sh tests/jo.14.exp \ + tests/jo.15.sh tests/jo.15.exp \ + tests/jo.16.sh tests/jo.16.exp \ + tests/jo.17.sh tests/jo.17.exp tests/jo-creator.txt \ + tests/jo.18.sh tests/jo.18.exp \ + tests/jo.19.sh tests/jo.19.exp \ + tests/jo.20.sh tests/jo.20.exp \ + tests/jo.21.sh tests/jo.21.exp \ + tests/jo.22.sh tests/jo.22.exp \ + tests/jo.23.sh tests/jo.23.exp \ + tests/jo.24.sh tests/jo.24.exp \ + tests/jo.25.sh tests/jo.25.exp \ + tests/jo.26.sh tests/jo.26.exp \ + tests/jo.27.sh tests/jo.27.exp diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/jo-1.4/README.md new/jo-1.6/README.md --- old/jo-1.4/README.md 2020-07-18 18:20:38.000000000 +0200 +++ new/jo-1.6/README.md 2022-01-05 16:59:37.000000000 +0100 @@ -38,7 +38,7 @@ ## Build from Github -[](https://travis-ci.org/jpmens/jo) +[](https://travis-ci.com/github/jpmens/jo) To install from the repository, you will need a C compiler as well as a relatively recent version of _automake_ and _autoconf_. @@ -79,8 +79,10 @@ * [ArchLinux](https://aur.archlinux.org/packages/jo/) * [OpenBSD](http://openports.se/textproc/jo) * [FreeBSD](https://www.freshports.org/textproc/jo) +* [Guix](https://guix.gnu.org/packages/jo-1.4/) * [pkgsrc](http://pkgsrc.se/textproc/jo) * [repology.org](https://repology.org/metapackage/jo/versions) +* [Docker](https://hub.docker.com/repository/docker/jpmens/jo) ## See also diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/jo-1.4/configure.ac new/jo-1.6/configure.ac --- old/jo-1.4/configure.ac 2020-07-18 18:20:38.000000000 +0200 +++ new/jo-1.6/configure.ac 2022-01-05 16:59:37.000000000 +0100 @@ -1,5 +1,5 @@ AC_PREREQ([2.63]) -AC_INIT([jo], [1.4], [j...@mens.de]) +AC_INIT([jo], [1.6], [j...@mens.de]) AC_CONFIG_AUX_DIR([build-aux]) AC_CONFIG_SRCDIR([jo.c]) diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/jo-1.4/jo.1 new/jo-1.6/jo.1 --- old/jo-1.4/jo.1 2020-07-18 18:20:38.000000000 +0200 +++ new/jo-1.6/jo.1 2022-01-05 16:59:37.000000000 +0100 @@ -1,20 +1,28 @@ -.\"t -.\" Automatically generated by Pandoc 2.5 +'\" t +.\" Automatically generated by Pandoc 2.12 .\" .TH "JO" "1" "" "User Manuals" "" .hy .SH NAME .PP -jo \- JSON output from a shell +jo - JSON output from a shell .SH SYNOPSIS .PP -jo [\-p] [\-a] [\-B] [\-e] [\-v] [\-V] [\-d keydelim] [\[en]] [ -[\-s|\-n|\-b] word \&...] +jo [-p] [-a] [-B] [-D] [-e] [-n] [-v] [-V] [-d keydelim] [-f file] +[\[en]] [ [-s|-n|-b] word \&...] .SH DESCRIPTION .PP -\f[I]jo\f[R] creates a JSON string on \f[I]stdout\f[R] from _word_s -given it as arguments or read from \f[I]stdin\f[R]. -Without option \f[C]\-a\f[R] it generates an object whereby each +\f[I]jo\f[R] creates a JSON string on \f[I]stdout\f[R] from +\f[I]word\f[R]s given it as arguments or read from \f[I]stdin\f[R]. +If \f[C]-f\f[R] is specified, \f[I]jo\f[R] first loads the contents of +\f[I]file\f[R] as a JSON object or array, then modifies it with +subsequent \f[I]word\f[R]s before printing the final JSON string to +\f[I]stdout\f[R]. +\f[I]file\f[R] may be specified as \f[C]-\f[R] to read from +\f[I]jo\f[R]\[cq]s standard input; this takes precedence over reading +\f[I]word\f[R]s from \f[I]stdin\f[R]. +.PP +Without option \f[C]-a\f[R] it generates an object whereby each \f[I]word\f[R] is a \f[C]key=value\f[R] (or \f[C]key\[at]value\f[R]) pair with \f[I]key\f[R] being the JSON object element and \f[I]value\f[R] its value. @@ -22,8 +30,12 @@ create number (using \f[I]strtod(3)\f[R]), string, or null values in JSON. .PP +A missing or empty \f[I]value\f[R] normally results in an element whose +value is \f[C]null\f[R]. +If \f[C]-n\f[R] is specified, this element is not created. +.PP \f[I]jo\f[R] normally treats \f[I]key\f[R] as a literal string value. -If the \f[C]\-d\f[R] option is specified, \f[I]key\f[R] will be +If the \f[C]-d\f[R] option is specified, \f[I]key\f[R] will be interpreted as an \f[I]object path\f[R], whose individual components are separated by the first character of \f[I]keydelim\f[R]. .PP @@ -42,12 +54,12 @@ T{ \[at]file T}@T{ -substitute the contents of \f[I]file\f[R] as\-is +substitute the contents of \f[I]file\f[R] as-is T} T{ %file T}@T{ -substitute the contents of \f[I]file\f[R] in base64\-encoded form +substitute the contents of \f[I]file\f[R] in base64-encoded form T} T{ :file @@ -64,25 +76,24 @@ elements: if the value begins with \f[C]T\f[R], \f[C]t\f[R], or the numeric value is greater than zero, the result is \f[C]true\f[R], else \f[C]false\f[R]. -A missing or empty value behind the colon results in a \f[C]null\f[R] -JSON element. .PP -\f[I]jo\f[R] creates an array instead of an object when \f[C]\-a\f[R] is +\f[I]jo\f[R] creates an array instead of an object when \f[C]-a\f[R] is specified. .PP When the \f[C]:=\f[R] operator is used in a \f[I]word\f[R], the name to the right of \f[C]:=\f[R] is a file containing JSON which is parsed and assigned to the key left of the operator. -The file may be specified as \f[C]\-\f[R] to read from -\f[I]jo\f[R]\[cq]s standard input. +The file may be specified as \f[C]-\f[R] to read from \f[I]jo\f[R]\[cq]s +standard input. .SH TYPE COERCION .PP -\f[I]jo\f[R]\[cq]s type guesses can be overridden on a per\-word basis -by prefixing \f[I]word\f[R] with \f[C]\-s\f[R] for \f[I]string\f[R], -\f[C]\-n\f[R] for \f[I]number\f[R], or \f[C]\-b\f[R] for +\f[I]jo\f[R]\[cq]s type guesses can be overridden on a per-word basis by +prefixing \f[I]word\f[R] with \f[C]-s\f[R] for \f[I]string\f[R], +\f[C]-n\f[R] for \f[I]number\f[R], or \f[C]-b\f[R] for \f[I]boolean\f[R]. -The list of _word_s \f[I]must\f[R] be prefixed with \f[C]\-\-\f[R], to -indicate to \f[I]jo\f[R] that there are no more global options. +The list of \f[I]word\f[R]s \f[I]must\f[R] be prefixed with +\f[C]--\f[R], to indicate to \f[I]jo\f[R] that there are no more global +options. .PP Type coercion works as follows: .PP @@ -92,11 +103,11 @@ T{ word T}@T{ -\-s +-s T}@T{ -\-n +-n T}@T{ -\-b +-b T}@T{ default T} @@ -180,19 +191,19 @@ T} .TE .PP -Coercing a non\-number string to number outputs the \f[I]length\f[R] of +Coercing a non-number string to number outputs the \f[I]length\f[R] of the string. .PP -Coercing a non\-boolean string to boolean outputs \f[C]false\f[R] if the +Coercing a non-boolean string to boolean outputs \f[C]false\f[R] if the string is empty, \f[C]true\f[R] otherwise. .PP Type coercion only applies to \f[C]key=value\f[R] words, and individual -words in a \f[C]\-a\f[R] array. +words in a \f[C]-a\f[R] array. Coercing other words has no effect. .SH EXAMPLES .PP Create an object. -Note how the incorrectly\-formatted float value becomes a string: +Note how the incorrectly-formatted float value becomes a string: .IP .nf \f[C] @@ -201,11 +212,11 @@ \f[R] .fi .PP -Pretty\-print an array with a list of files in the current directory: +Pretty-print an array with a list of files in the current directory: .IP .nf \f[C] -$ jo \-p \-a * +$ jo -p -a * [ \[dq]Makefile\[dq], \[dq]README.md\[dq], @@ -225,7 +236,7 @@ .IP .nf \f[C] -$ jo \-p name=JP object=$(jo fruit=Orange hungry\[at]0 point=$(jo x=10 y=20 list=$(jo \-a 1 2 3 4 5)) number=17) sunday\[at]0 +$ jo -p name=JP object=$(jo fruit=Orange hungry\[at]0 point=$(jo x=10 y=20 list=$(jo -a 1 2 3 4 5)) number=17) sunday\[at]0 { \[dq]name\[dq]: \[dq]JP\[dq], \[dq]object\[dq]: { @@ -250,16 +261,16 @@ .fi .PP Booleans as strings or as boolean (pay particular attention to -\f[I]switch\f[R]; the \f[C]\-B\f[R] option disables the default -detection of the \[lq]\f[C]true\f[R]\[rq], \[lq]\f[C]false\f[R]\[rq], -and \[lq]\f[C]null\f[R]\[rq] strings): +\f[I]switch\f[R]; the \f[C]-B\f[R] option disables the default detection +of the \[lq]\f[C]true\f[R]\[rq], \[lq]\f[C]false\f[R]\[rq], and +\[lq]\f[C]null\f[R]\[rq] strings): .IP .nf \f[C] $ jo switch=true morning\[at]0 {\[dq]switch\[dq]:true,\[dq]morning\[dq]:false} -$ jo \-B switch=true morning\[at]0 +$ jo -B switch=true morning\[at]0 {\[dq]switch\[dq]:\[dq]true\[dq],\[dq]morning\[dq]:false} \f[R] .fi @@ -270,7 +281,7 @@ .IP .nf \f[C] -$ jo \-p name=Jane point[]=1 point[]=2 geo[lat]=10 geo[lon]=20 +$ jo -p name=Jane point[]=1 point[]=2 geo[lat]=10 geo[lon]=20 { \[dq]name\[dq]: \[dq]Jane\[dq], \[dq]point\[dq]: [ @@ -289,7 +300,7 @@ .IP .nf \f[C] -$ jo \-p \-d. name=Jane point[]=1 point[]=2 geo.lat=10 geo.lon=20 +$ jo -p -d. name=Jane point[]=1 point[]=2 geo.lat=10 geo.lon=20 { \[dq]name\[dq]: \[dq]Jane\[dq], \[dq]point\[dq]: [ @@ -304,11 +315,11 @@ \f[R] .fi .PP -Without \f[C]\-d\f[R], a different object is generated: +Without \f[C]-d\f[R], a different object is generated: .IP .nf \f[C] -$ jo \-p name=Jane point[]=1 point[]=2 geo.lat=10 geo.lon=20 +$ jo -p name=Jane point[]=1 point[]=2 geo.lat=10 geo.lon=20 { \[dq]name\[dq]: \[dq]Jane\[dq], \[dq]point\[dq]: [ @@ -329,7 +340,7 @@ {} $ MY_ARRAY=(a=1 b=2) -$ jo \-a \[dq]${MY_ARRAY[\[at]]}\[dq] < /dev/null +$ jo -a \[dq]${MY_ARRAY[\[at]]}\[dq] < /dev/null [\[dq]a=1\[dq],\[dq]b=2\[dq]] \f[R] .fi @@ -338,7 +349,7 @@ .IP .nf \f[C] -$ jo \-p \-\- \-s a=true b=true \-s c=123 d=123 \-b e=\[dq]1\[dq] \-b f=\[dq]true\[dq] \-n g=\[dq]This is a test\[dq] \-b h=\[dq]This is a test\[dq] +$ jo -p -- -s a=true b=true -s c=123 d=123 -b e=\[dq]1\[dq] -b f=\[dq]true\[dq] -n g=\[dq]This is a test\[dq] -b h=\[dq]This is a test\[dq] { \[dq]a\[dq]: \[dq]true\[dq], \[dq]b\[dq]: true, @@ -350,20 +361,20 @@ \[dq]h\[dq]: true } -$ jo \-a \-\- \-s 123 \-n \[dq]This is a test\[dq] \-b C_Rocks 456 +$ jo -a -- -s 123 -n \[dq]This is a test\[dq] -b C_Rocks 456 [\[dq]123\[dq],14,true,456] \f[R] .fi .PP Read element values from files: a value which starts with \f[C]\[at]\f[R] is read in plain whereas if it begins with a \f[C]%\f[R] -it will be base64\-encoded and if it starts with \f[C]:\f[R] the -contents are interpreted as JSON: +it will be base64-encoded and if it starts with \f[C]:\f[R] the contents +are interpreted as JSON: .IP .nf \f[C] $ jo program=jo authors=\[at]AUTHORS -{\[dq]program\[dq]:\[dq]jo\[dq],\[dq]authors\[dq]:\[dq]Jan\-Piet Mens <jpmens\[at]gmail.com>\[dq]} +{\[dq]program\[dq]:\[dq]jo\[dq],\[dq]authors\[dq]:\[dq]Jan-Piet Mens <jpmens\[at]gmail.com>\[dq]} $ jo filename=AUTHORS content=%AUTHORS {\[dq]filename\[dq]:\[dq]AUTHORS\[dq],\[dq]content\[dq]:\[dq]SmFuLVBpZXQgTWVucyA8anBtZW5zQGdtYWlsLmNvbT4K\[dq]} @@ -393,42 +404,72 @@ .IP .nf \f[C] -$ ls | jo \-a > child.json +$ ls | jo -a > child.json $ jo files:=child.json {\[dq]files\[dq]:[\[dq]AUTHORS\[dq],\[dq]COPYING\[dq],\[dq]ChangeLog\[dq] .... -$ ls *.c | jo \-a > source.json; ls *.h | jo \-a > headers.json -$ jo \-a :source.json :headers.json +$ ls *.c | jo -a > source.json; ls *.h | jo -a > headers.json +$ jo -a :source.json :headers.json [[\[dq]base64.c\[dq],\[dq]jo.c\[dq],\[dq]json.c\[dq]],[\[dq]base64.h\[dq],\[dq]json.h\[dq]]] \f[R] .fi +.PP +Add elements to existing JSON: +.IP +.nf +\f[C] +$ jo -f source.json 1 | jo -f - 2 3 +[\[dq]base64.c\[dq],\[dq]jo.c\[dq],\[dq]json.c\[dq],1,2,3] + +$ curl -s \[aq]https://noembed.com/embed?url=https://www.youtube.com/watch?v=dQw4w9WgXcQ\[aq] | jo -f - status=Rickrolled +{ ...., \[dq]type\[dq]:\[dq]video\[dq],\[dq]author_url\[dq]:\[dq]https://www.youtube.com/user/RickAstleyVEVO\[dq],\[dq]status\[dq]:\[dq]Rickrolled\[dq]} +\f[R] +.fi +.PP +Deduplicate object keys (\f[I]jo\f[R] appends duplicate object keys by +default): +.IP +.nf +\f[C] +$ jo a=1 b=2 a=3 +{\[dq]a\[dq]:1,\[dq]b\[dq]:2,\[dq]a\[dq]:3} +$ jo -D a=1 b=2 a=3 +{\[dq]a\[dq]:3,\[dq]b\[dq]:2} +\f[R] +.fi .SH OPTIONS .PP \f[I]jo\f[R] understands the following global options. .TP -.B \-a +-a Interpret the list of \f[I]words\f[R] as array values and produce an array instead of an object. .TP -.B \-B -By default \f[I]jo\f[R] interprets the strings \[lq]\f[C]true\f[R]\[rq] +-B +By default, \f[I]jo\f[R] interprets the strings \[lq]\f[C]true\f[R]\[rq] and \[lq]\f[C]false\f[R]\[rq] as boolean elements \f[C]true\f[R] and \f[C]false\f[R] respectively, and \[lq]\f[C]null\f[R]\[rq] as \f[C]null\f[R]. Disable with this option. .TP -.B \-e +-D +Deduplicate object keys. +.TP +-e Ignore empty stdin (i.e.\ don\[cq]t produce a diagnostic error when \f[I]stdin\f[R] is empty) .TP -.B \-p -Pretty\-print the JSON string on output instead of the terse one\-line +-n +Do not add keys with empty values. +.TP +-p +Pretty-print the JSON string on output instead of the terse one-line output it prints by default. .TP -.B \-v +-v Show version and exit. .TP -.B \-V +-V Show version as a JSON object and exit. .SH BUGS .PP @@ -459,7 +500,7 @@ This was designed thusly. .SH RETURN CODES .PP -\f[I]jo\f[R] exits with a code 0 on success and non\-zero on failure +\f[I]jo\f[R] exits with a code 0 on success and non-zero on failure after indicating what caused the failure. .SH AVAILABILITY .PP @@ -479,4 +520,4 @@ strtod(3) .SH AUTHOR .PP -Jan\-Piet Mens <http://jpmens.net> +Jan-Piet Mens <http://jpmens.net> diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/jo-1.4/jo.c new/jo-1.6/jo.c --- old/jo-1.4/jo.c 2020-07-18 18:20:38.000000000 +0200 +++ new/jo-1.6/jo.c 2022-01-05 16:59:37.000000000 +0100 @@ -36,7 +36,8 @@ #define FLAG_NOBOOL 0x04 #define FLAG_BOOLEAN 0x08 #define FLAG_NOSTDIN 0x10 -#define FLAG_MASK (FLAG_ARRAY | FLAG_PRETTY | FLAG_NOBOOL | FLAG_BOOLEAN | FLAG_NOSTDIN) +#define FLAG_SKIPNULLS 0x20 +#define FLAG_MASK (FLAG_ARRAY | FLAG_PRETTY | FLAG_NOBOOL | FLAG_BOOLEAN | FLAG_NOSTDIN | FLAG_SKIPNULLS) /* Size of buffer blocks for pipe slurping */ #define SLURP_BLOCK_SIZE 4096 @@ -100,12 +101,6 @@ } } -const char *maybe_stdin(const char* filename) -{ - return strcmp(filename, "-") ? filename : "/dev/fd/0"; -} - - char *slurp_file(const char* filename, size_t *out_len, bool fold_newlines) { char *buf; @@ -113,8 +108,10 @@ int ch; off_t buffer_len; FILE *fp; + bool use_stdin = strcmp(filename, "-") == 0; - if ((fp = fopen(maybe_stdin(filename), "r")) == NULL) { + if (use_stdin) fp = stdin; + else if ((fp = fopen(filename, "r")) == NULL) { perror(filename); errx(1, "Cannot open %s for reading", filename); } @@ -142,7 +139,7 @@ buf[i++] = ch; } } - fclose(fp); + if (!use_stdin) fclose(fp); buf[i] = 0; *out_len = i; return (buf); @@ -224,7 +221,7 @@ JsonTag type = flags_to_tag(flags); if (strlen(str) == 0) { - return jo_mknull(type); + return (flags & FLAG_SKIPNULLS) ? (JsonNode *)NULL : jo_mknull(type); } /* If str begins with a double quote, keep it a string */ @@ -339,13 +336,15 @@ int usage(char *prog) { - fprintf(stderr, "Usage: %s [-a] [-B] [-d keydelim] [-p] [-e] [-v] [-V] [--] [-s|-n|-b] [word...]\n", prog); + fprintf(stderr, "Usage: %s [-a] [-B] [-D] [-d keydelim] [-p] [-e] [-n] [-v] [-V] [-f file] [--] [-s|-n|-b] [word...]\n", prog); fprintf(stderr, "\tword is key=value or key@value\n"); fprintf(stderr, "\t-a creates an array of words\n"); - fprintf(stderr, "\t-B disable boolean true/false\n"); + fprintf(stderr, "\t-B disable boolean true/false/null detection\n"); + fprintf(stderr, "\t-D deduplicate object keys\n"); fprintf(stderr, "\t-d key will be object path separated by keydelim\n"); + fprintf(stderr, "\t-f load file as JSON object or array\n"); fprintf(stderr, "\t-p pretty-prints JSON on output\n"); - fprintf(stderr, "\t-e if stdin is empty do not wait for input quit\n"); + fprintf(stderr, "\t-e quit if stdin is empty do not wait for input\n"); fprintf(stderr, "\t-s coerce type guessing to string\n"); fprintf(stderr, "\t-b coerce type guessing to bool\n"); fprintf(stderr, "\t-n coerce type guessing to number\n"); @@ -365,12 +364,14 @@ * *baseop -> object node in which caller should insert "value" */ -bool resolve_nested(int flags, char **keyp, char key_delim, char *value, JsonNode **baseop) +bool resolve_nested(int flags, char **keyp, char key_delim, JsonNode *value, JsonNode **baseop) { char *member = NULL, *bo, *bc, *so; /* bracket open, close, sub-object */ JsonNode *op; int found = false; + (void)flags; + if (key_delim) { /* First construct nested object */ while ((so = strchr(*keyp, key_delim)) != NULL) { @@ -411,17 +412,9 @@ } if (member == NULL) { /* we're doing an array */ - if (flags & FLAG_BOOLEAN) { - json_append_element(op, boolnode(value)); - } else { - json_append_element(op, vnode(value, flags)); - } + json_append_element(op, value); } else { /* we're doing an object */ - if (flags & FLAG_BOOLEAN) { - json_append_member(op, member, boolnode(value)); - } else { - json_append_member(op, member, vnode(value, flags)); - } + json_append_member(op, member, value); } if (!found) { @@ -457,7 +450,8 @@ } *r = 0; /* Chop at ":=" */ - json_append_member(object, kv, o); + if (!resolve_nested(flags, &kv, key_delim, o, &object)) + json_append_member(object, kv, o); return (0); } @@ -465,20 +459,20 @@ return (-1); } - + JsonNode *val; if (p) { - if (p) { - *p = 0; + *p = 0; + val = vnode(p+1, flags); - if (!resolve_nested(flags, &kv, key_delim, p+1, &object)) - json_append_member(object, kv, vnode(p+1, flags)); - } + if (!resolve_nested(flags, &kv, key_delim, val, &object)) + json_append_member(object, kv, val); } else { if (q) { *q = 0; + val = boolnode(q+1); - if (!resolve_nested(flags | FLAG_BOOLEAN, &kv, key_delim, q+1, &object)) - json_append_member(object, kv, boolnode(q+1)); + if (!resolve_nested(flags | FLAG_BOOLEAN, &kv, key_delim, val, &object)) + json_append_member(object, kv, val); } } return (0); @@ -567,9 +561,9 @@ # define locale_free(p) free(p) #else # define utf8_from_locale(p, l) (p) -# define utf8_free(p) +# define utf8_free(p) do {} while (0) # define locale_from_utf8(p, l) (p) -# define locale_free(p) +# define locale_free(p) do {} while (0) #endif char *stringify(JsonNode *json, int flags) @@ -602,6 +596,8 @@ int c, key_delim = 0; bool showversion = false; char *kv, *js_string, *progname, buf[BUFSIZ], *p; + char *in_file = NULL, *in_str; + size_t in_len = 0; int ttyin = isatty(fileno(stdin)), ttyout = isatty(fileno(stdout)); int flags = 0; JsonNode *json, *op; @@ -614,7 +610,7 @@ progname = (progname = strrchr(*argv, '/')) ? progname + 1 : *argv; - while ((c = getopt(argc, argv, "aBd:hpevV")) != EOF) { + while ((c = getopt(argc, argv, "aBDd:f:hpenvV")) != EOF) { switch (c) { case 'a': flags |= FLAG_ARRAY; @@ -622,9 +618,15 @@ case 'B': flags |= FLAG_NOBOOL; break; + case 'D': + json_dedup_members(true); + break; case 'd': key_delim = optarg[0]; break; + case 'f': + in_file = optarg; + break; case 'h': usage(progname); return (0); @@ -634,6 +636,9 @@ case 'e': flags |= FLAG_NOSTDIN; break; + case 'n': + flags |= FLAG_SKIPNULLS; + break; case 'v': printf("jo %s\n", PACKAGE_VERSION); exit(0); @@ -653,7 +658,26 @@ argv += optind; pile = json_mkobject(); - json = (flags & FLAG_ARRAY) ? json_mkarray() : json_mkobject(); + if (in_file != NULL) { + if ((in_str = slurp_file(in_file, &in_len, false)) == NULL) { + errx(1, "Error reading file %s", in_file); + } + json = json_decode(in_str); + if (json) { + switch (json->tag) { + case JSON_ARRAY: + flags |= FLAG_ARRAY; + break; + case JSON_OBJECT: + break; + default: + errx(1, "Input JSON not an array or object: %s", stringify(json, flags)); + } + } else + json = (flags & FLAG_ARRAY) ? json_mkarray() : json_mkobject(); + } else { + json = (flags & FLAG_ARRAY) ? json_mkarray() : json_mkobject(); + } if (argc == 0) { if (flags & FLAG_NOSTDIN) { diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/jo-1.4/jo.md new/jo-1.6/jo.md --- old/jo-1.4/jo.md 2020-07-18 18:20:38.000000000 +0200 +++ new/jo-1.6/jo.md 2022-01-05 16:59:37.000000000 +0100 @@ -1,5 +1,5 @@ --- -title: 'JO(1) User Manuals' +title: JO(1) User Manuals --- NAME @@ -10,18 +10,27 @@ SYNOPSIS ======== -jo \[-p\] \[-a\] \[-B\] \[-e\] \[-v\] \[-V\] \[-d keydelim\] \[--\] \[ -\[-s\|-n\|-b\] word ...\] +jo \[-p\] \[-a\] \[-B\] \[-D\] \[-e\] \[-n\] \[-v\] \[-V\] \[-d +keydelim\] \[-f file\] \[--\] \[ \[-s\|-n\|-b\] word ...\] DESCRIPTION =========== -*jo* creates a JSON string on *stdout* from \_word\_s given it as -arguments or read from *stdin*. Without option `-a` it generates an -object whereby each *word* is a `key=value` (or `key@value`) pair with -*key* being the JSON object element and *value* its value. *jo* attempts -to guess the type of *value* in order to create number (using -*strtod(3)*), string, or null values in JSON. +*jo* creates a JSON string on *stdout* from *word*s given it as +arguments or read from *stdin*. If `-f` is specified, *jo* first loads +the contents of *file* as a JSON object or array, then modifies it with +subsequent *word*s before printing the final JSON string to *stdout*. +*file* may be specified as `-` to read from *jo*'s standard input; this +takes precedence over reading *word*s from *stdin*. + +Without option `-a` it generates an object whereby each *word* is a +`key=value` (or `key@value`) pair with *key* being the JSON object +element and *value* its value. *jo* attempts to guess the type of +*value* in order to create number (using *strtod(3)*), string, or null +values in JSON. + +A missing or empty *value* normally results in an element whose value is +`null`. If `-n` is specified, this element is not created. *jo* normally treats *key* as a literal string value. If the `-d` option is specified, *key* will be interpreted as an *object path*, whose @@ -31,19 +40,18 @@ *jo* normally treats *value* as a literal string value, unless it begins with one of the following characters: - value action - -------- --------------------------------------------------------------------- - @file substitute the contents of *file* as-is - \%file substitute the contents of *file* in base64-encoded form - :file interpret the contents of *file* as JSON, and substitute the result + value action + ------- --------------------------------------------------------------------- + @file substitute the contents of *file* as-is + %file substitute the contents of *file* in base64-encoded form + :file interpret the contents of *file* as JSON, and substitute the result Escape the special character with a backslash to prevent this interpretation. *jo* treats `key@value` specifically as boolean JSON elements: if the value begins with `T`, `t`, or the numeric value is greater than zero, -the result is `true`, else `false`. A missing or empty value behind the -colon results in a `null` JSON element. +the result is `true`, else `false`. *jo* creates an array instead of an object when `-a` is specified. @@ -57,7 +65,7 @@ *jo*'s type guesses can be overridden on a per-word basis by prefixing *word* with `-s` for *string*, `-n` for *number*, or `-b` for *boolean*. -The list of \_word\_s *must* be prefixed with `--`, to indicate to *jo* +The list of *word*s *must* be prefixed with `--`, to indicate to *jo* that there are no more global options. Type coercion works as follows: @@ -233,7 +241,7 @@ $ jo action="split window" vimcmd="\:split" {"action":"split window","vimcmd":":split"} -Read element values from a file in order to overcome ARG\_MAX limits +Read element values from a file in order to overcome ARG_MAX limits during object assignment: $ ls | jo -a > child.json @@ -244,6 +252,21 @@ $ jo -a :source.json :headers.json [["base64.c","jo.c","json.c"],["base64.h","json.h"]] +Add elements to existing JSON: + + $ jo -f source.json 1 | jo -f - 2 3 + ["base64.c","jo.c","json.c",1,2,3] + + $ curl -s 'https://noembed.com/embed?url=https://www.youtube.com/watch?v=dQw4w9WgXcQ' | jo -f - status=Rickrolled + { ...., "type":"video","author_url":"https://www.youtube.com/user/RickAstleyVEVO","status":"Rickrolled"} + +Deduplicate object keys (*jo* appends duplicate object keys by default): + + $ jo a=1 b=2 a=3 + {"a":1,"b":2,"a":3} + $ jo -D a=1 b=2 a=3 + {"a":3,"b":2} + OPTIONS ======= @@ -254,14 +277,20 @@ instead of an object. -B -: By default *jo* interprets the strings "`true`" and "`false`" as +: By default, *jo* interprets the strings "`true`" and "`false`" as boolean elements `true` and `false` respectively, and "`null`" as `null`. Disable with this option. +-D +: Deduplicate object keys. + -e : Ignore empty stdin (i.e.??don't produce a diagnostic error when *stdin* is empty) +-n +: Do not add keys with empty values. + -p : Pretty-print the JSON string on output instead of the terse one-line output it prints by default. diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/jo-1.4/jo.pandoc new/jo-1.6/jo.pandoc --- old/jo-1.4/jo.pandoc 2020-07-18 18:20:38.000000000 +0200 +++ new/jo-1.6/jo.pandoc 2022-01-05 16:59:37.000000000 +0100 @@ -6,15 +6,23 @@ # SYNOPSIS -jo [-p] [-a] [-B] [-e] [-v] [-V] [-d keydelim] [--] [ [-s|-n|-b] word ...] +jo [-p] [-a] [-B] [-D] [-e] [-n] [-v] [-V] [-d keydelim] [-f file] [--] [ [-s|-n|-b] word ...] # DESCRIPTION -*jo* creates a JSON string on _stdout_ from _word_s given it as arguments or read from _stdin_. Without -option `-a` it generates an object whereby each _word_ is a `key=value` (or `key@value`) +*jo* creates a JSON string on _stdout_ from *word*s given it as arguments or read from _stdin_. +If `-f` is specified, *jo* first loads the contents of _file_ as a JSON object or array, then +modifies it with subsequent *word*s before printing the final JSON string to _stdout_. +_file_ may be specified as `-` to read from _jo_'s standard input; this takes precedence over +reading *word*s from _stdin_. + +Without option `-a` it generates an object whereby each _word_ is a `key=value` (or `key@value`) pair with _key_ being the JSON object element and _value_ its value. *jo* attempts to guess the type of _value_ in order to create number (using _strtod(3)_), string, or null values in JSON. +A missing or empty _value_ normally results in an element whose value is `null`. If `-n` is specified, this +element is not created. + *jo* normally treats _key_ as a literal string value. If the `-d` option is specified, _key_ will be interpreted as an _object path_, whose individual components are separated by the first character of _keydelim_. @@ -29,8 +37,7 @@ Escape the special character with a backslash to prevent this interpretation. *jo* treats `key@value` specifically as boolean JSON elements: if the value begins with `T`, `t`, -or the numeric value is greater than zero, the result is `true`, else `false`. A missing or -empty value behind the colon results in a `null` JSON element. +or the numeric value is greater than zero, the result is `true`, else `false`. *jo* creates an array instead of an object when `-a` is specified. @@ -40,7 +47,7 @@ # TYPE COERCION *jo*'s type guesses can be overridden on a per-word basis by prefixing _word_ with `-s` for _string_, -`-n` for _number_, or `-b` for _boolean_. The list of _word_s *must* be prefixed with `--`, to indicate +`-n` for _number_, or `-b` for _boolean_. The list of *word*s *must* be prefixed with `--`, to indicate to *jo* that there are no more global options. Type coercion works as follows: @@ -216,22 +223,43 @@ $ jo -a :source.json :headers.json [["base64.c","jo.c","json.c"],["base64.h","json.h"]] +Add elements to existing JSON: + + $ jo -f source.json 1 | jo -f - 2 3 + ["base64.c","jo.c","json.c",1,2,3] + + $ curl -s 'https://noembed.com/embed?url=https://www.youtube.com/watch?v=dQw4w9WgXcQ' | jo -f - status=Rickrolled + { ...., "type":"video","author_url":"https://www.youtube.com/user/RickAstleyVEVO","status":"Rickrolled"} + +Deduplicate object keys (*jo* appends duplicate object keys by default): + + $ jo a=1 b=2 a=3 + {"a":1,"b":2,"a":3} + $ jo -D a=1 b=2 a=3 + {"a":3,"b":2} + # OPTIONS -*jo* understands the following global options. +*jo* understands the following global options. -a : Interpret the list of _words_ as array values and produce an array instead of an object. -B -: By default *jo* interprets the strings "`true`" and "`false`" as boolean elements +: By default, *jo* interprets the strings "`true`" and "`false`" as boolean elements `true` and `false` respectively, and "`null`" as `null`. Disable with this option. +-D +: Deduplicate object keys. + -e : Ignore empty stdin (i.e. don't produce a diagnostic error when *stdin* is empty) +-n +: Do not add keys with empty values. + -p : Pretty-print the JSON string on output instead of the terse one-line output it prints by default. diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/jo-1.4/jo.zsh new/jo-1.6/jo.zsh --- old/jo-1.4/jo.zsh 1970-01-01 01:00:00.000000000 +0100 +++ new/jo-1.6/jo.zsh 2022-01-05 16:59:37.000000000 +0100 @@ -0,0 +1,62 @@ +#compdef jo + +# Completion function for zsh +# Store this file in a directory listed in $fpath for it to be picked up +# by compinit. It needs to be named with an initial underscore, e.g. _jo + +local curcontext="$curcontext" +local -i aopt nm=$compstate[nmatches] +local -a expl line state state_descr +local -A opt_args + +_arguments -C -s -A "-*" \ + '(-h)-p[pretty-print JSON on output]' \ + '(-d -h)-a[create an array of words]' \ + '(-v -V -h)-B[disable interpretation of true/false/null strings]' \ + "(-v -V -h)-e[if stdin is empty don't wait for input - quit]" \ + '(- *)-v[show version information]' \ + '(-a -B -e -h -v *)-V[show version in JSON]' \ + '(-a -h -v -V)-d+[key will be object path separated by given delimiter]:key delimiter' \ + '(- *)-h[show usage information]' \ + '*::word:->words' + +if [[ -n $state ]]; then + aopt=$+opt_args[-a] + _arguments \ + '*-s[coerce type guessing to string]: :->words' \ + '*-b[coerce type guessing to bool]: :->words' \ + '*-n[coerce type guessing to number]: :->words' \ + '*: :->words' + + if (( aopt )); then + _message -e words 'array element' + elif compset -P 1 '*:='; then + _alternative 'files:file:_files' 'operators:stdin:(-)' + elif compset -P 1 '*='; then + if compset -P '[@%:]'; then + _files + else + _describe -t operators "file prefix" '( + @:substitute\ file\ as-is + %:substitute\ file\ in\ base64-encoded\ form + \\::substitute\ file\ as\ JSON + )' -S '' + _message -e values value + fi + elif compset -P 1 '?*@'; then + _description booleans expl 'boolean' + compadd -M 'm:{a-zA-Z}={A-Za-z} m:{10}={TF}' "$expl[@]" True False + else + if compset -P '[^-]*'; then + _describe -t suffixes suffix '( + @:boolean\ element + \=:value + \\:=:substitute\ JSON\ file + \[\]:array\ element + )' -S '' + fi + _message -e keys key + fi +fi + +[[ nm -ne compstate[nmatches] ]] diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/jo-1.4/json.c new/jo-1.6/json.c --- old/jo-1.4/json.c 2020-07-18 18:20:38.000000000 +0200 +++ new/jo-1.6/json.c 2022-01-05 16:59:37.000000000 +0100 @@ -34,6 +34,18 @@ exit(EXIT_FAILURE); \ } while (0) +#ifdef _WIN32 +# define failx(e, n, f, ...) if (!(e)) { \ + fprintf(stderr, "JSON_ERR: " f, __VA_ARGS__); \ + exit(n); \ + } +#else +# include <err.h> +# define failx(e, n, f, ...) if (!(e)) { \ + errx(n, "JSON_ERR: " f, __VA_ARGS__); \ + } +#endif + /* Sadly, strdup is not portable. */ static char *json_strdup(const char *str) { @@ -271,7 +283,12 @@ { unsigned char *o = (unsigned char*) out; - assert(unicode <= 0x10FFFF && !(unicode >= 0xD800 && unicode <= 0xDFFF)); + failx( + unicode <= 0x10FFFF && !(unicode >= 0xD800 && unicode <= 0xDFFF), + 1, + "Illegal Unicode codepoint 0x%08X found", + unicode + ); if (unicode <= 0x7F) { /* U+0000..U+007F */ @@ -323,7 +340,12 @@ { js_uchar_t n; - assert(unicode >= 0x10000 && unicode <= 0x10FFFF); + failx( + unicode >= 0x10000 && unicode <= 0x10FFFF, + 1, + "Cannot construct UTF-16 surrogate pair for Unicode codepoint 0x%08X", + unicode + ); n = unicode - 0x10000; *uc = ((n >> 10) & 0x3FF) | 0xD800; @@ -357,8 +379,11 @@ static JsonNode *mknode(JsonTag tag); static void append_node(JsonNode *parent, JsonNode *child); static void prepend_node(JsonNode *parent, JsonNode *child); +static void insert_node(JsonNode *parent, JsonNode *child); static void append_member(JsonNode *object, char *key, JsonNode *value); +static void (*append_member_node_fn)(JsonNode *parent, JsonNode *child) = append_node; + /* Assertion-friendly validity checks */ static bool tag_is_valid(unsigned int tag); static bool number_is_valid(const char *num); @@ -540,6 +565,7 @@ static void append_node(JsonNode *parent, JsonNode *child) { + if (!child) return; child->parent = parent; child->prev = parent->children.tail; child->next = NULL; @@ -553,6 +579,7 @@ static void prepend_node(JsonNode *parent, JsonNode *child) { + if (!child) return; child->parent = parent; child->prev = NULL; child->next = parent->children.head; @@ -564,15 +591,52 @@ parent->children.head = child; } +static void insert_node(JsonNode *parent, JsonNode *child) +{ + if (!child) return; + JsonNode *this = parent->children.head; + + while (this != NULL && strcmp(this->key, child->key)) + this = this->next; + + if (this != NULL) + { + /* we found a matching key, insert before this node */ + if (this->prev == NULL) + { + prepend_node(parent, child); + } + else + { + child->parent = parent; + child->next = this->next; + child->prev = this->prev; + this->prev->next = child; + this->prev = child; + } + json_delete(this); + } + else + append_node(parent, child); +} + static void append_member(JsonNode *object, char *key, JsonNode *value) { + if (!value) return; value->key = key; - append_node(object, value); + append_member_node_fn(object, value); } void json_append_element(JsonNode *array, JsonNode *element) { - assert(array->tag == JSON_ARRAY); + if (!element) return; + failx( + array->tag == JSON_ARRAY, + 1, + "Cannot append %s to non-array %s", + json_encode(element), + json_encode(array) + ); assert(element->parent == NULL); append_node(array, element); @@ -580,7 +644,14 @@ void json_prepend_element(JsonNode *array, JsonNode *element) { - assert(array->tag == JSON_ARRAY); + if (!element) return; + failx( + array->tag == JSON_ARRAY, + 1, + "Cannot append %s to non-array %s", + json_encode(element), + json_encode(array) + ); assert(element->parent == NULL); prepend_node(array, element); @@ -588,7 +659,15 @@ void json_append_member(JsonNode *object, const char *key, JsonNode *value) { - assert(object->tag == JSON_OBJECT); + if (!value) return; + failx( + object->tag == JSON_OBJECT, + 1, + "Cannot add {\"%s\":%s} to non-object %s", + key, + json_encode(value), + json_encode(object) + ); assert(value->parent == NULL); append_member(object, json_strdup(key), value); @@ -596,13 +675,26 @@ void json_prepend_member(JsonNode *object, const char *key, JsonNode *value) { - assert(object->tag == JSON_OBJECT); + if (!value) return; + failx( + object->tag == JSON_OBJECT, + 1, + "Cannot add {\"%s\":%s} to non-object %s", + key, + json_encode(value), + json_encode(object) + ); assert(value->parent == NULL); value->key = json_strdup(key); prepend_node(object, value); } +void json_dedup_members(bool b) +{ + append_member_node_fn = b ? insert_node : append_node; +} + void json_remove_from_parent(JsonNode *node) { JsonNode *parent = node->parent; @@ -1181,9 +1273,9 @@ #endif b += 6; } else { - *b++ = 0xEF; - *b++ = 0xBF; - *b++ = 0xBD; + *b++ = (char)0xEF; + *b++ = (char)0xBF; + *b++ = (char)0xBD; } s++; } else if (c < 0x1F || (c >= 0x80 && escape_unicode)) { diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/jo-1.4/json.h new/jo-1.6/json.h --- old/jo-1.4/json.h 2020-07-18 18:20:38.000000000 +0200 +++ new/jo-1.6/json.h 2022-01-05 16:59:37.000000000 +0100 @@ -101,6 +101,7 @@ void json_prepend_element(JsonNode *array, JsonNode *element); void json_append_member(JsonNode *object, const char *key, JsonNode *value); void json_prepend_member(JsonNode *object, const char *key, JsonNode *value); +void json_dedup_members(bool b); void json_remove_from_parent(JsonNode *node); diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/jo-1.4/meson.build new/jo-1.6/meson.build --- old/jo-1.4/meson.build 1970-01-01 01:00:00.000000000 +0100 +++ new/jo-1.6/meson.build 2022-01-05 16:59:37.000000000 +0100 @@ -0,0 +1,92 @@ +project('jo', 'c', + version: '1.6', + license: 'GPL-2.0-or-later', + meson_version: '>=0.57.0', + default_options: ['warning_level=3', 'optimization=2']) + + +PACKAGE_VERSION = meson.project_version() + +cc = meson.get_compiler('c') + +headers = [ + 'stddef.h', + 'stdint.h', + 'stdlib.h', + 'string.h', + 'unistd.h', + 'stdbool.h' +] + +functions = [ + 'strchr', + 'strrchr', + 'strlcpy', + 'strlcat', + 'snprintf', + 'pledge', + 'err', + 'errx' +] + +foreach h: headers +cc.has_header(h, required: true) +endforeach +foreach f: functions +add_project_arguments( + '-DHAVE_@0@='.format(f.to_upper()) + + cc.has_function(f).to_int().to_string(), + language: 'c') +endforeach + +add_project_arguments('-DPACKAGE_VERSION="@0@"'.format(PACKAGE_VERSION), + language: 'c') + + +pandoc = find_program('pandoc', required: false) +if not pandoc.found() +warning('pandoc not found, man pages rebuild will not be possible') +jo1 = 'jo.1' +else +pandoc_commands = [pandoc, '-s', '-w', 'man', '-f', 'markdown', '-o'] +jo1 = custom_target('jo.1', + output: 'jo.1', + input: 'jo.pandoc', + build_always_stale: true, + command: [pandoc_commands, '@OUTPUT@', '@INPUT@']).full_path() +run_command(pandoc_commands, + join_paths(meson.current_build_dir(), 'jo.1'), + join_paths(meson.current_source_dir(), 'jo.pandoc')) +custom_target('jo.md', + output: 'jo.md', + input: 'jo.pandoc', + build_always_stale: true, + command: [pandoc, '-s', '-w', 'markdown+simple_tables', '-f', 'markdown', '-o', '@OUTPUT@', '@INPUT@']) +endif + +install_man(jo1) + +bashcomp = dependency('bash-completion', required: false) +if bashcomp.found() +bashcompdir = bashcomp.get_variable(pkgconfig: 'completionsdir') +else +bashcompdir = join_paths(get_option('sysconfdir'), 'bash_completion.d') +endif + +install_data('jo.bash', install_dir: bashcompdir) + +m_dep = cc.find_library('m', required : false) + +executable('jo', + 'jo.c', + 'base64.c', + 'base64.h', + 'json.c', + dependencies: m_dep, + install: true) + +summary({'Prefix': get_option('prefix'), + 'C compiler': cc.get_id(), + 'Pandoc': pandoc, + 'Bash completion': join_paths(bashcompdir, 'jo.bash'), + }) diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/jo-1.4/rpm-build/jo.spec new/jo-1.6/rpm-build/jo.spec --- old/jo-1.4/rpm-build/jo.spec 2020-07-18 18:20:38.000000000 +0200 +++ new/jo-1.6/rpm-build/jo.spec 2022-01-05 16:59:37.000000000 +0100 @@ -1,5 +1,5 @@ Name: jo -Version: 1.4 +Version: 1.6 Release: 2%{?dist} Summary: jo is a small utility to create JSON objects diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/jo-1.4/snapcraft.yaml new/jo-1.6/snapcraft.yaml --- old/jo-1.4/snapcraft.yaml 2020-07-18 18:20:38.000000000 +0200 +++ new/jo-1.6/snapcraft.yaml 2022-01-05 16:59:37.000000000 +0100 @@ -1,16 +1,16 @@ name: jo -version: "1.4" +version: "1.6" summary: jo description: | This is jo, a small utility to create JSON objects or arrays. confinement: strict grade: stable -base: core +base: core20 apps: jo: - command: jo + command: usr/local/bin/jo plugs: [home, removable-media] parts: diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/jo-1.4/tests/jo-creator.txt new/jo-1.6/tests/jo-creator.txt --- old/jo-1.4/tests/jo-creator.txt 1970-01-01 01:00:00.000000000 +0100 +++ new/jo-1.6/tests/jo-creator.txt 2022-01-05 16:59:37.000000000 +0100 @@ -0,0 +1 @@ +Jan-Piet Mens <jpm...@gmail.com> diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/jo-1.4/tests/jo.08.sh new/jo-1.6/tests/jo.08.sh --- old/jo-1.4/tests/jo.08.sh 2020-07-18 18:20:38.000000000 +0200 +++ new/jo-1.6/tests/jo.08.sh 2022-01-05 16:59:37.000000000 +0100 @@ -1,2 +1,2 @@ # data from file: text -${JO:-jo} program="jo" authors=@${srcdir:=.}/AUTHORS +${JO:-jo} program="jo" authors=@${srcdir:=.}/tests/jo-creator.txt diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/jo-1.4/tests/jo.09.sh new/jo-1.6/tests/jo.09.sh --- old/jo-1.4/tests/jo.09.sh 2020-07-18 18:20:38.000000000 +0200 +++ new/jo-1.6/tests/jo.09.sh 2022-01-05 16:59:37.000000000 +0100 @@ -1,2 +1,2 @@ # data from file: base64-encoded -${JO:-jo} program="jo" authors=%${srcdir:=.}/AUTHORS +${JO:-jo} program="jo" authors=%${srcdir:=.}/tests/jo-creator.txt diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/jo-1.4/tests/jo.17.sh new/jo-1.6/tests/jo.17.sh --- old/jo-1.4/tests/jo.17.sh 2020-07-18 18:20:38.000000000 +0200 +++ new/jo-1.6/tests/jo.17.sh 2022-01-05 16:59:37.000000000 +0100 @@ -26,5 +26,5 @@ done # @/% file inclusions -${JO:-jo} -- -s s=@${srcdir:=.}/AUTHORS -n n=@${srcdir:=.}/AUTHORS -b b=@${srcdir:=.}/AUTHORS a=@${srcdir:=.}/AUTHORS -${JO:-jo} -- -s s=%${srcdir:=.}/AUTHORS -n n=%${srcdir:=.}/AUTHORS -b b=%${srcdir:=.}/AUTHORS a=%${srcdir:=.}/AUTHORS +${JO:-jo} -- -s s=@${srcdir:=.}/tests/jo-creator.txt -n n=@${srcdir:=.}/tests/jo-creator.txt -b b=@${srcdir:=.}/tests/jo-creator.txt a=@${srcdir:=.}/tests/jo-creator.txt +${JO:-jo} -- -s s=%${srcdir:=.}/tests/jo-creator.txt -n n=%${srcdir:=.}/tests/jo-creator.txt -b b=%${srcdir:=.}/tests/jo-creator.txt a=%${srcdir:=.}/tests/jo-creator.txt diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/jo-1.4/tests/jo.21.exp new/jo-1.6/tests/jo.21.exp --- old/jo-1.4/tests/jo.21.exp 2020-07-18 18:20:38.000000000 +0200 +++ new/jo-1.6/tests/jo.21.exp 2022-01-05 16:59:37.000000000 +0100 @@ -1 +1,2 @@ {"nested":{"a":1,"b":"val"}} +{"top":{"obj1":{"c":3,"d":"key"},"obj2":{"a":1,"b":"val"}}} diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/jo-1.4/tests/jo.21.sh new/jo-1.6/tests/jo.21.sh --- old/jo-1.4/tests/jo.21.sh 2020-07-18 18:20:38.000000000 +0200 +++ new/jo-1.6/tests/jo.21.sh 2022-01-05 16:59:37.000000000 +0100 @@ -4,4 +4,7 @@ ${JO:-jo} nested=:$$.1 +# nested json within object path +${JO:-jo} -d . top.obj1.c=3 top.obj1.d="key" top.obj2=:$$.1 + rm -f $$.1 diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/jo-1.4/tests/jo.23.exp new/jo-1.6/tests/jo.23.exp --- old/jo-1.4/tests/jo.23.exp 1970-01-01 01:00:00.000000000 +0100 +++ new/jo-1.6/tests/jo.23.exp 2022-01-05 16:59:37.000000000 +0100 @@ -0,0 +1,6 @@ +{"foo":null} +{} +{"foo":1,"bar":null,"baz":3} +{"foo":1,"baz":3} +{"list":[1,null]} +{"list":[1]} diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/jo-1.4/tests/jo.23.sh new/jo-1.6/tests/jo.23.sh --- old/jo-1.4/tests/jo.23.sh 1970-01-01 01:00:00.000000000 +0100 +++ new/jo-1.6/tests/jo.23.sh 2022-01-05 16:59:37.000000000 +0100 @@ -0,0 +1,10 @@ +# disable creation of null-valued keys + +${JO:-jo} foo= +${JO:-jo} -n foo= +${JO:-jo} foo=1 bar= baz=3 +${JO:-jo} -n foo=1 bar= baz=3 +nothing= +${JO:-jo} list[]=1 list[]=$nothing +${JO:-jo} -n list[]=1 list[]=$nothing + diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/jo-1.4/tests/jo.24.exp new/jo-1.6/tests/jo.24.exp --- old/jo-1.4/tests/jo.24.exp 1970-01-01 01:00:00.000000000 +0100 +++ new/jo-1.6/tests/jo.24.exp 2022-01-05 16:59:37.000000000 +0100 @@ -0,0 +1,8 @@ +[1,2,3,4,6,8] +{"a":1,"b":2,"c":42,"d":3} +{"a":1,"b":2,"c":42,"d":3,"stage":{"1":"a","2":"b"}} +{"a":1,"b":2,"c":42,"d":3,"stage":{"1":"a","2":"b"}} +{"a":1,"b":2,"c":42,"d":3,"stage":{"1":"a","2":"b"}} +{"a":1,"b":2,"stage":{"1":"a","2":"b"}} +{"people":"need people"} +{"people":"need people"} diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/jo-1.4/tests/jo.24.sh new/jo-1.6/tests/jo.24.sh --- old/jo-1.4/tests/jo.24.sh 1970-01-01 01:00:00.000000000 +0100 +++ new/jo-1.6/tests/jo.24.sh 2022-01-05 16:59:37.000000000 +0100 @@ -0,0 +1,28 @@ +# jo as filter + +# filter array +echo "[1,2,3,4]" | ${JO:-jo} -f - 6 8 + +# filter object +${JO:-jo} a=1 b=2 | ${JO:-jo} -f - c=42 d=3 + +# multi-stage pipeline +${JO:-jo} a=1 b=2 | ${JO:-jo} -f - c=42 d=3 | ${JO:-jo} -f - -d . stage.1=a stage.2=b + +# filter from file +tmp=/tmp/jo.filter.$$ +trap "rm -f $tmp; exit" 0 1 2 15 + +${JO:-jo} a=1 b=2 > $tmp +${JO:-jo} -f $tmp c=42 d=3 | ${JO:-jo} -f - -d . stage.1=a stage.2=b + +# take initial object from file, and mods from stdin +echo "c=42 +d=3" | ${JO:-jo} -f $tmp | ${JO:-jo} -f - -d . stage.1=a stage.2=b + +# this command should NOT output keys "c" and "d" +${JO:-jo} a=1 b=2 | ${JO:-jo} -f - c=42 d=3 | ${JO:-jo} -f $tmp -d . stage.1=a stage.2=b + +# filter non-collections (input basically ignored) +echo hi | tee $tmp | ${JO:-jo} -f - people="need people" +${JO:-jo} -f $tmp people="need people" diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/jo-1.4/tests/jo.25.exp new/jo-1.6/tests/jo.25.exp --- old/jo-1.4/tests/jo.25.exp 1970-01-01 01:00:00.000000000 +0100 +++ new/jo-1.6/tests/jo.25.exp 2022-01-05 16:59:37.000000000 +0100 @@ -0,0 +1,6 @@ +{"a":1,"b":2,"a":3} +{"a":3,"b":2} +{"stage":{"1":"a","2":"b","3":"c","2":"x","3":"y","4":"d","1":"h"},"down":"up"} +{"stage":{"1":"h","2":"x","3":"y","4":"d"},"down":"up"} +{"name":"aaa"} +{"name":"cba","another_name":"abc"} diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/jo-1.4/tests/jo.25.sh new/jo-1.6/tests/jo.25.sh --- old/jo-1.4/tests/jo.25.sh 1970-01-01 01:00:00.000000000 +0100 +++ new/jo-1.6/tests/jo.25.sh 2022-01-05 16:59:37.000000000 +0100 @@ -0,0 +1,14 @@ +# overwrite values of existing object keys + +${JO:-jo} a=1 b=2 a=3 +${JO:-jo} -D a=1 b=2 a=3 + +tmp=`${JO:-jo} 1=a 2=b 3=c` +${JO:-jo} -d . stage="$tmp" down=up stage.2=x stage\[3\]=y stage.4=d stage\[1\]=h +${JO:-jo} -D -d . stage="$tmp" down=up stage.2=x stage\[3\]=y stage.4=d stage\[1\]=h + +# dedup filter input too +tmpf=$$.json +trap 'rm -f "$tmpf"' 0 1 2 15 +${JO:-jo} name=aaa name=aaa | tee $tmpf | ${JO:-jo} -D -f - name=aaa +${JO:-jo} -D -f $tmpf another_name=abc name=cba diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/jo-1.4/tests/jo.26.exp new/jo-1.6/tests/jo.26.exp --- old/jo-1.4/tests/jo.26.exp 1970-01-01 01:00:00.000000000 +0100 +++ new/jo-1.6/tests/jo.26.exp 2022-01-05 16:59:37.000000000 +0100 @@ -0,0 +1 @@ +{"file":"stdin","jo":true} diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/jo-1.4/tests/jo.26.sh new/jo-1.6/tests/jo.26.sh --- old/jo-1.4/tests/jo.26.sh 1970-01-01 01:00:00.000000000 +0100 +++ new/jo-1.6/tests/jo.26.sh 2022-01-05 16:59:37.000000000 +0100 @@ -0,0 +1,2 @@ +# read from stdin +echo '{"file":"stdin", "jo": true}' | ${JO:-jo} -f - diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/jo-1.4/tests/jo.27.exp new/jo-1.6/tests/jo.27.exp --- old/jo-1.4/tests/jo.27.exp 1970-01-01 01:00:00.000000000 +0100 +++ new/jo-1.6/tests/jo.27.exp 2022-01-05 16:59:37.000000000 +0100 @@ -0,0 +1,6 @@ +{"b":[1,2]} +jo: JSON_ERR: Cannot add {"a":3} to non-object [1] +Test 2 should fail +{"d":{"m":10,"n":20}} +jo: JSON_ERR: Cannot append 20 to non-array {"m":10} +Test 4 should fail diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/jo-1.4/tests/jo.27.sh new/jo-1.6/tests/jo.27.sh --- old/jo-1.4/tests/jo.27.sh 1970-01-01 01:00:00.000000000 +0100 +++ new/jo-1.6/tests/jo.27.sh 2022-01-05 16:59:37.000000000 +0100 @@ -0,0 +1,6 @@ +# user-friendly errors +${JO:-jo} b[]=1 b[]=2 +${JO:-jo} b[]=1 b[a]=3 2>&1 || echo "Test 2 should fail" + +${JO:-jo} d[m]=10 d[n]=20 +${JO:-jo} d[m]=10 d[]=20 2>&1 || echo "Test 4 should fail"