diff --git a/ChangeLog b/ChangeLog
index 1aae032..e2bc163 100644
--- a/ChangeLog
+++ b/ChangeLog
@@ -1,3 +1,10 @@
+2020-07-18 1.4
+
+- FIX: Coercion flag logic now permits getopt(3) double-dash
+- FIX: Documentation clarifies special characters
+- FIX: Jo builds on snap builds (#110)
+- FIX: Jo builds on systems with slightly older pkg-config (#107)
+
 
 2019-11-04 1.3
 - FIX: Escaped @ ("\@") is treated as "@" (#42, #103)
diff --git a/Dockerfile b/Dockerfile
index 0601171..74cfa3f 100644
--- a/Dockerfile
+++ b/Dockerfile
@@ -1,5 +1,5 @@
 FROM alpine AS builder
-RUN apk -U add automake autoconf build-base make
+RUN apk -U add automake autoconf build-base make pkgconf
 COPY . /src
 WORKDIR /src
 RUN autoreconf -i && ./configure && make check && make install
diff --git a/README.md b/README.md
index aec8a86..40e8023 100644
--- a/README.md
+++ b/README.md
@@ -27,8 +27,9 @@ It has a [manual](jo.md), and you can read [why I wrote jo](http://jpmens.net/20
 To build from [a release](https://github.com/jpmens/jo/releases) you will need a C compiler to install from a source tarball which you download from the [Releases page](https://github.com/jpmens/jo/releases).
 
 ```bash
-tar xvzf jo-1.0.tar.gz
-cd jo-1.0
+tar xvzf jo-1.3.tar.gz
+cd jo-1.3
+autoreconf -i
 ./configure
 make check
 make install
@@ -58,14 +59,16 @@ brew install jo
 
 ## Ubuntu
 
-To install on Ubuntu, use [this PPA](https://launchpad.net/~duggan/+archive/ubuntu/jo):
-
 ```
-apt-add-repository ppa:duggan/jo --yes
-apt-get update -q
 apt-get install jo
 ```
 
+## Gentoo
+
+```
+emerge jo
+```
+
 ## Snap
 
 Thanks to [Roger Light](https://twitter.com/ralight/status/1166023769623867398), _jo_ is available as a [snap package](https://snapcraft.io/jo). Use `snap install jo` from a Linux distro that supports snaps.
diff --git a/configure.ac b/configure.ac
index 9438bb9..35ebca8 100644
--- a/configure.ac
+++ b/configure.ac
@@ -1,5 +1,5 @@
 AC_PREREQ([2.63])
-AC_INIT([jo], [1.3], [jp@mens.de])
+AC_INIT([jo], [1.4], [jp@mens.de])
 AC_CONFIG_AUX_DIR([build-aux])
 AC_CONFIG_SRCDIR([jo.c])
 
@@ -16,6 +16,19 @@ AC_CHECK_HEADERS([stddef.h stdint.h stdlib.h string.h unistd.h stdbool.h])
 AC_FUNC_STRTOD
 AC_CHECK_FUNCS([strchr strrchr strlcpy strlcat snprintf pledge err errx])
 
+# backport PKG_CHECK_VAR from pkgconfig 0.29
+m4_ifndef([PKG_CHECK_VAR], [AC_DEFUN([PKG_CHECK_VAR],
+[AC_REQUIRE([PKG_PROG_PKG_CONFIG])dnl
+AC_ARG_VAR([$1], [value of $3 for $2, overriding pkg-config])dnl
+
+_PKG_CONFIG([$1], [variable="][$3]["], [$2])
+AS_VAR_COPY([$1], [pkg_cv_][$1])
+
+AS_VAR_IF([$1], [""], [$5], [$4])dnl
+])dnl PKG_CHECK_VAR
+])
+
+
 AM_INIT_AUTOMAKE([foreign -Wall])
 AM_SILENT_RULES([yes])
 AC_REQUIRE_AUX_FILE([tap-driver.sh])
diff --git a/debian/changelog b/debian/changelog
index 672055e..18aade0 100644
--- a/debian/changelog
+++ b/debian/changelog
@@ -1,3 +1,9 @@
+jo (1.4-1) UNRELEASED; urgency=medium
+
+  * New upstream release.
+
+ -- Debian Janitor <janitor@jelmer.uk>  Sun, 09 Aug 2020 07:00:23 +0000
+
 jo (1.3-2) unstable; urgency=medium
 
   * d/patches: don't use simple_tables extension for manual page
diff --git a/debian/patches/a93337f0104aa1e9eb4837442fb4214f7bffd9bb.patch b/debian/patches/a93337f0104aa1e9eb4837442fb4214f7bffd9bb.patch
index 52d27ad..5789837 100644
--- a/debian/patches/a93337f0104aa1e9eb4837442fb4214f7bffd9bb.patch
+++ b/debian/patches/a93337f0104aa1e9eb4837442fb4214f7bffd9bb.patch
@@ -10,10 +10,10 @@ Subject: [PATCH] remove unnecessary `+simple_tables` from man generation
  jo.md       |  12 +++----
  3 files changed, 59 insertions(+), 59 deletions(-)
 
-diff --git a/Makefile.am b/Makefile.am
-index 6660409..835f6a8 100644
---- a/Makefile.am
-+++ b/Makefile.am
+Index: jo/Makefile.am
+===================================================================
+--- jo.orig/Makefile.am
++++ jo/Makefile.am
 @@ -15,12 +15,12 @@ if USE_PANDOC
  jo.1: jo.pandoc
  	@test -n "$(PANDOC)" || \
diff --git a/jo.1 b/jo.1
index 7cb42e0..0134ac8 100644
--- a/jo.1
+++ b/jo.1
@@ -1,5 +1,5 @@
 .\"t
-.\" Automatically generated by Pandoc 1.16.0.2
+.\" Automatically generated by Pandoc 2.5
 .\"
 .TH "JO" "1" "" "User Manuals" ""
 .hy
@@ -8,46 +8,81 @@
 jo \- JSON output from a shell
 .SH SYNOPSIS
 .PP
-jo [\-p] [\-a] [\-B] [\-e] [\-v] [\-V] [\-d keydelim] [\-\-] [
-[\-s|\-n|\-b] word ...]
+jo [\-p] [\-a] [\-B] [\-e] [\-v] [\-V] [\-d keydelim] [\[en]] [
+[\-s|\-n|\-b] word \&...]
 .SH DESCRIPTION
 .PP
-\f[I]jo\f[] creates a JSON string on \f[I]stdout\f[] from _word_s given
-it as arguments or read from \f[I]stdin\f[].
-Without option \f[C]\-a\f[] it generates an object whereby each
-\f[I]word\f[] is a \f[C]key=value\f[] (or \f[C]key\@value\f[]) pair with
-\f[I]key\f[] being the JSON object element and \f[I]value\f[] its value.
-\f[I]jo\f[] attempts to guess the type of \f[I]value\f[] in order to
-create number (using \f[I]strtod(3)\f[]), string, or null values in
+\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]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.
+\f[I]jo\f[R] attempts to guess the type of \f[I]value\f[R] in order to
+create number (using \f[I]strtod(3)\f[R]), string, or null values in
 JSON.
 .PP
-\f[I]jo\f[] normally treats \f[I]key\f[] as a literal string value.
-If the \f[C]\-d\f[] option is specified, \f[I]key\f[] will be
-interpreted as an \f[I]object path\f[], whose individual components are
-separated by the first character of \f[I]keydelim\f[].
+\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
+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
-\f[I]jo\f[] treats \f[C]key\@value\f[] specifically as boolean JSON
-elements: if the value begins with \f[C]T\f[], \f[C]t\f[], or the
-numeric value is greater than zero, the result is \f[C]true\f[], else
-\f[C]false\f[].
-A missing or empty value behind the colon results in a \f[C]null\f[]
+\f[I]jo\f[R] normally treats \f[I]value\f[R] as a literal string value,
+unless it begins with one of the following characters:
+.PP
+.TS
+tab(@);
+l l.
+T{
+value
+T}@T{
+action
+T}
+_
+T{
+\[at]file
+T}@T{
+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
+T}
+T{
+:file
+T}@T{
+interpret the contents of \f[I]file\f[R] as JSON, and substitute the
+result
+T}
+.TE
+.PP
+Escape the special character with a backslash to prevent this
+interpretation.
+.PP
+\f[I]jo\f[R] treats \f[C]key\[at]value\f[R] specifically as boolean JSON
+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[] creates an array instead of an object when \f[C]\-a\f[] 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[] operator is used in a \f[I]word\f[], the name to
-the right of \f[C]:=\f[] is a file containing JSON which is parsed and
+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[] to read from \f[I]jo\f[]\[aq]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[]\[aq]s type guesses can be overridden on a per\-word basis by
-prefixing \f[I]word\f[] with \f[C]\-s\f[] for \f[I]string\f[],
-\f[C]\-n\f[] for \f[I]number\f[], or \f[C]\-b\f[] for \f[I]boolean\f[].
-The list of _word_s \f[I]must\f[] be prefixed with \f[C]\-\-\f[], to
-indicate to \f[I]jo\f[] that there are no more global options.
+\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.
 .PP
 Type coercion works as follows:
 .PP
@@ -69,90 +104,90 @@ _
 T{
 a=
 T}@T{
-"a":""
+\[lq]a\[rq]:\[dq]\[dq]
 T}@T{
-"a":0
+\[lq]a\[rq]:0
 T}@T{
-"a":false
+\[lq]a\[rq]:false
 T}@T{
-"a":null
+\[lq]a\[rq]:null
 T}
 T{
 a=string
 T}@T{
-"a":"string"
+\[lq]a\[rq]:\[lq]string\[rq]
 T}@T{
-"a":6
+\[lq]a\[rq]:6
 T}@T{
-"a":true
+\[lq]a\[rq]:true
 T}@T{
-"a":"string"
+\[lq]a\[rq]:\[lq]string\[rq]
 T}
 T{
-a="quoted"
+a=\[dq]quoted\[dq]
 T}@T{
-"a":""quoted""
+\[lq]a\[rq]:\[lq]\[dq]quoted\[dq]\[rq]
 T}@T{
-"a":8
+\[lq]a\[rq]:8
 T}@T{
-"a":true
+\[lq]a\[rq]:true
 T}@T{
-"a":""quoted""
+\[lq]a\[rq]:\[lq]\[dq]quoted\[dq]\[rq]
 T}
 T{
 a=12345
 T}@T{
-"a":"12345"
+\[lq]a\[rq]:\[lq]12345\[rq]
 T}@T{
-"a":12345
+\[lq]a\[rq]:12345
 T}@T{
-"a":true
+\[lq]a\[rq]:true
 T}@T{
-"a":12345
+\[lq]a\[rq]:12345
 T}
 T{
 a=true
 T}@T{
-"a":"true"
+\[lq]a\[rq]:\[lq]true\[rq]
 T}@T{
-"a":1
+\[lq]a\[rq]:1
 T}@T{
-"a":true
+\[lq]a\[rq]:true
 T}@T{
-"a":true
+\[lq]a\[rq]:true
 T}
 T{
 a=false
 T}@T{
-"a":"false"
+\[lq]a\[rq]:\[lq]false\[rq]
 T}@T{
-"a":0
+\[lq]a\[rq]:0
 T}@T{
-"a":false
+\[lq]a\[rq]:false
 T}@T{
-"a":false
+\[lq]a\[rq]:false
 T}
 T{
 a=null
 T}@T{
-"a":""
+\[lq]a\[rq]:\[dq]\[dq]
 T}@T{
-"a":0
+\[lq]a\[rq]:0
 T}@T{
-"a":false
+\[lq]a\[rq]:false
 T}@T{
-"a":null
+\[lq]a\[rq]:null
 T}
 .TE
 .PP
-Coercing a non\-number string to number outputs the \f[I]length\f[] 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[] if the
-string is empty, \f[C]true\f[] otherwise.
+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[] words, and individual
-words in a \f[C]\-a\f[] array.
+Type coercion only applies to \f[C]key=value\f[R] words, and individual
+words in a \f[C]\-a\f[R] array.
 Coercing other words has no effect.
 .SH EXAMPLES
 .PP
@@ -161,180 +196,196 @@ Note how the incorrectly\-formatted float value becomes a string:
 .IP
 .nf
 \f[C]
-$\ jo\ tst=1457081292\ lat=12.3456\ cc=FR\ badfloat=3.14159.26\ name="JP\ Mens"\ nada=\ coffee\@T
-{"tst":1457081292,"lat":12.3456,"cc":"FR","badfloat":"3.14159.26","name":"JP\ Mens","nada":null,"coffee":true}
-\f[]
+$ jo tst=1457081292 lat=12.3456 cc=FR badfloat=3.14159.26 name=\[dq]JP Mens\[dq] nada= coffee\[at]T
+{\[dq]tst\[dq]:1457081292,\[dq]lat\[dq]:12.3456,\[dq]cc\[dq]:\[dq]FR\[dq],\[dq]badfloat\[dq]:\[dq]3.14159.26\[dq],\[dq]name\[dq]:\[dq]JP Mens\[dq],\[dq]nada\[dq]:null,\[dq]coffee\[dq]:true}
+\f[R]
 .fi
 .PP
 Pretty\-print an array with a list of files in the current directory:
 .IP
 .nf
 \f[C]
-$\ jo\ \-p\ \-a\ *
+$ jo \-p \-a *
 [
-\ "Makefile",
-\ "README.md",
-\ "jo.1",
-\ "jo.c",
-\ "jo.pandoc",
-\ "json.c",
-\ "json.h"
+ \[dq]Makefile\[dq],
+ \[dq]README.md\[dq],
+ \[dq]jo.1\[dq],
+ \[dq]jo.c\[dq],
+ \[dq]jo.pandoc\[dq],
+ \[dq]json.c\[dq],
+ \[dq]json.h\[dq]
 ]
-\f[]
+\f[R]
 .fi
 .PP
 Create objects within objects; this works because if the first character
 of value is an open brace or a bracket we attempt to decode the
 remainder as JSON.
-Beware spaces in strings ...
+Beware spaces in strings \&...
 .IP
 .nf
 \f[C]
-$\ jo\ \-p\ name=JP\ object=$(jo\ fruit=Orange\ hungry\@0\ point=$(jo\ x=10\ y=20\ list=$(jo\ \-a\ 1\ 2\ 3\ 4\ 5))\ number=17)\ sunday\@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
 {
-\ "name":\ "JP",
-\ "object":\ {
-\ \ "fruit":\ "Orange",
-\ \ "hungry":\ false,
-\ \ "point":\ {
-\ \ \ "x":\ 10,
-\ \ \ "y":\ 20,
-\ \ \ "list":\ [
-\ \ \ \ 1,
-\ \ \ \ 2,
-\ \ \ \ 3,
-\ \ \ \ 4,
-\ \ \ \ 5
-\ \ \ ]
-\ \ },
-\ \ "number":\ 17
-\ },
-\ "sunday":\ false
+ \[dq]name\[dq]: \[dq]JP\[dq],
+ \[dq]object\[dq]: {
+  \[dq]fruit\[dq]: \[dq]Orange\[dq],
+  \[dq]hungry\[dq]: false,
+  \[dq]point\[dq]: {
+   \[dq]x\[dq]: 10,
+   \[dq]y\[dq]: 20,
+   \[dq]list\[dq]: [
+    1,
+    2,
+    3,
+    4,
+    5
+   ]
+  },
+  \[dq]number\[dq]: 17
+ },
+ \[dq]sunday\[dq]: false
 }
-\f[]
+\f[R]
 .fi
 .PP
 Booleans as strings or as boolean (pay particular attention to
-\f[I]switch\f[]; the \f[C]\-B\f[] option disables the default detection
-of the "\f[C]true\f[]", "\f[C]false\f[]", and "\f[C]null\f[]" 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\@0
-{"switch":true,"morning":false}
+$ jo switch=true morning\[at]0
+{\[dq]switch\[dq]:true,\[dq]morning\[dq]:false}
 
-$\ jo\ \-B\ switch=true\ morning\@0
-{"switch":"true","morning":false}
-\f[]
+$ jo \-B switch=true morning\[at]0
+{\[dq]switch\[dq]:\[dq]true\[dq],\[dq]morning\[dq]:false}
+\f[R]
 .fi
 .PP
 Elements (objects and arrays) can be nested.
-The following example nests an array called \f[I]point\f[] and an object
-named \f[I]geo\f[]:
+The following example nests an array called \f[I]point\f[R] and an
+object named \f[I]geo\f[R]:
 .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
 {
-\ \ \ "name":\ "Jane",
-\ \ \ "point":\ [
-\ \ \ \ \ \ 1,
-\ \ \ \ \ \ 2
-\ \ \ ],
-\ \ \ "geo":\ {
-\ \ \ \ \ \ "lat":\ 10,
-\ \ \ \ \ \ "lon":\ 20
-\ \ \ }
+   \[dq]name\[dq]: \[dq]Jane\[dq],
+   \[dq]point\[dq]: [
+      1,
+      2
+   ],
+   \[dq]geo\[dq]: {
+      \[dq]lat\[dq]: 10,
+      \[dq]lon\[dq]: 20
+   }
 }
-\f[]
+\f[R]
 .fi
 .PP
 The same example, using object paths:
 .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
 {
-\ \ \ "name":\ "Jane",
-\ \ \ "point":\ [
-\ \ \ \ \ \ 1,
-\ \ \ \ \ \ 2
-\ \ \ ],
-\ \ \ "geo":\ {
-\ \ \ \ \ \ "lat":\ 10,
-\ \ \ \ \ \ "lon":\ 20
-\ \ \ }
+   \[dq]name\[dq]: \[dq]Jane\[dq],
+   \[dq]point\[dq]: [
+      1,
+      2
+   ],
+   \[dq]geo\[dq]: {
+      \[dq]lat\[dq]: 10,
+      \[dq]lon\[dq]: 20
+   }
 }
-\f[]
+\f[R]
 .fi
 .PP
-Without \f[C]\-d\f[], 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
 {
-\ \ \ "name":\ "Jane",
-\ \ \ "point":\ [
-\ \ \ \ \ \ 1,
-\ \ \ \ \ \ 2
-\ \ \ ],
-\ \ \ "geo.lat":\ 10,
-\ \ \ "geo.lon":\ 20
+   \[dq]name\[dq]: \[dq]Jane\[dq],
+   \[dq]point\[dq]: [
+      1,
+      2
+   ],
+   \[dq]geo.lat\[dq]: 10,
+   \[dq]geo.lon\[dq]: 20
 }
-\f[]
+\f[R]
 .fi
 .PP
 Create empty objects or arrays, intentionally or potentially:
 .IP
 .nf
 \f[C]
-$\ jo\ <\ /dev/null
+$ jo < /dev/null
 {}
 
-$\ MY_ARRAY=(a=1\ b=2)
-$\ jo\ \-a\ "${MY_ARRAY[\@]}"\ <\ /dev/null
-["a=1","b=2"]
-\f[]
+$ MY_ARRAY=(a=1 b=2)
+$ jo \-a \[dq]${MY_ARRAY[\[at]]}\[dq] < /dev/null
+[\[dq]a=1\[dq],\[dq]b=2\[dq]]
+\f[R]
 .fi
 .PP
 Type coercion:
 .IP
 .nf
 \f[C]
-$\ jo\ \-p\ \-\-\ \-s\ a=true\ b=true\ \-s\ c=123\ d=123\ \-b\ e="1"\ \-b\ f="true"\ \-n\ g="This\ is\ a\ test"\ \-b\ h="This\ is\ a\ test"
+$ 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]
 {
-\ \ \ "a":\ "true",
-\ \ \ "b":\ true,
-\ \ \ "c":\ "123",
-\ \ \ "d":\ 123,
-\ \ \ "e":\ true,
-\ \ \ "f":\ true,
-\ \ \ "g":\ 14,
-\ \ \ "h":\ true
+   \[dq]a\[dq]: \[dq]true\[dq],
+   \[dq]b\[dq]: true,
+   \[dq]c\[dq]: \[dq]123\[dq],
+   \[dq]d\[dq]: 123,
+   \[dq]e\[dq]: true,
+   \[dq]f\[dq]: true,
+   \[dq]g\[dq]: 14,
+   \[dq]h\[dq]: true
 }
 
-$\ jo\ \-a\ \-\-\ \-s\ 123\ \-n\ "This\ is\ a\ test"\ \-b\ C_Rocks\ 456
-["123",14,true,456]
-\f[]
+$ 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:
+.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]}
+
+$ jo filename=AUTHORS content=%AUTHORS
+{\[dq]filename\[dq]:\[dq]AUTHORS\[dq],\[dq]content\[dq]:\[dq]SmFuLVBpZXQgTWVucyA8anBtZW5zQGdtYWlsLmNvbT4K\[dq]}
+
+$ jo nested=:nested.json
+{\[dq]nested\[dq]:{\[dq]field1\[dq]:123,\[dq]field2\[dq]:\[dq]abc\[dq]}}
+\f[R]
 .fi
 .PP
-Read element values from files: a value which starts with \f[C]\@\f[] is
-read in plain whereas if it begins with a \f[C]%\f[] it will be
-base64\-encoded and if it starts with \f[C]:\f[] the contents are
-interpreted as JSON:
+These characters can be escaped to avoid interpretation:
 .IP
 .nf
 \f[C]
-$\ jo\ program=jo\ authors=\@AUTHORS
-{"program":"jo","authors":"Jan\-Piet\ Mens\ <jpmens\@gmail.com>"}
+$ jo name=\[dq]JP Mens\[dq] twitter=\[aq]\[rs]\[at]jpmens\[aq]
+{\[dq]name\[dq]:\[dq]JP Mens\[dq],\[dq]twitter\[dq]:\[dq]\[at]jpmens\[dq]}
 
-$\ jo\ filename=AUTHORS\ content=%AUTHORS
-{"filename":"AUTHORS","content":"SmFuLVBpZXQgTWVucyA8anBtZW5zQGdtYWlsLmNvbT4K"}
+$ jo char=\[dq] \[dq] URIescape=\[rs]\[rs]%20
+{\[dq]char\[dq]:\[dq] \[dq],\[dq]URIescape\[dq]:\[dq]%20\[dq]}
 
-$\ jo\ nested=:nested.json
-{"nested":{"field1":123,"field2":"abc"}}
-\f[]
+$ jo action=\[dq]split window\[dq] vimcmd=\[dq]\[rs]:split\[dq]
+{\[dq]action\[dq]:\[dq]split window\[dq],\[dq]vimcmd\[dq]:\[dq]:split\[dq]}
+\f[R]
 .fi
 .PP
 Read element values from a file in order to overcome ARG_MAX limits
@@ -342,76 +393,65 @@ during object assignment:
 .IP
 .nf
 \f[C]
-$\ ls\ |\ jo\ \-a\ >\ child.json
-$\ jo\ files:=child.json
-{"files":["AUTHORS","COPYING","ChangeLog"\ ....
+$ 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
-[["base64.c","jo.c","json.c"],["base64.h","json.h"]]
-\f[]
+$ 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
 .SH OPTIONS
 .PP
-\f[I]jo\f[] understands the following global options.
+\f[I]jo\f[R] understands the following global options.
 .TP
 .B \-a
-Interpret the list of \f[I]words\f[] as array values and produce an
+Interpret the list of \f[I]words\f[R] as array values and produce an
 array instead of an object.
-.RS
-.RE
 .TP
 .B \-B
-By default \f[I]jo\f[] interprets the strings "\f[C]true\f[]" and
-"\f[C]false\f[]" as boolean elements \f[C]true\f[] and \f[C]false\f[]
-respectively, and "\f[C]null\f[]" as \f[C]null\f[].
+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.
-.RS
-.RE
 .TP
 .B \-e
-Ignore empty stdin (i.e.
-don\[aq]t produce a diagnostic error when \f[I]stdin\f[] is empty)
-.RS
-.RE
+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
 output it prints by default.
-.RS
-.RE
 .TP
 .B \-v
 Show version and exit.
-.RS
-.RE
 .TP
 .B \-V
 Show version as a JSON object and exit.
-.RS
-.RE
 .SH BUGS
 .PP
 Probably.
 .PP
-If a value given to \f[I]jo\f[] expands to empty in the shell, then
-\f[I]jo\f[] produces a \f[C]null\f[] in object mode, and might appear to
-hang in array mode; it is not hanging, rather it\[aq]s reading
-\f[I]stdin\f[].
+If a value given to \f[I]jo\f[R] expands to empty in the shell, then
+\f[I]jo\f[R] produces a \f[C]null\f[R] in object mode, and might appear
+to hang in array mode; it is not hanging, rather it\[cq]s reading
+\f[I]stdin\f[R].
 This is not a bug.
 .PP
 Numeric values are converted to numbers which can produce undesired
 results.
-If you quote a numeric value, \f[I]jo\f[] will make it a string.
+If you quote a numeric value, \f[I]jo\f[R] will make it a string.
 Compare the following:
 .IP
 .nf
 \f[C]
-$\ jo\ a=1.0
-{"a":1}
-$\ jo\ a=\\"1.0\\"
-{"a":"1.0"}
-\f[]
+$ jo a=1.0
+{\[dq]a\[dq]:1}
+$ jo a=\[rs]\[dq]1.0\[rs]\[dq]
+{\[dq]a\[dq]:\[dq]1.0\[dq]}
+\f[R]
 .fi
 .PP
 Omitting a closing bracket on a nested element causes a diagnostic
@@ -419,14 +459,14 @@ message to print, but the output contains garbage anyway.
 This was designed thusly.
 .SH RETURN CODES
 .PP
-\f[I]jo\f[] 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
 <http://github.com/jpmens/jo>
 .SH CREDITS
 .IP \[bu] 2
-This program uses \f[C]json.[ch]\f[], by Joseph A.
+This program uses \f[C]json.[ch]\f[R], by Joseph A.
 Adams.
 .SH SEE ALSO
 .IP \[bu] 2
diff --git a/jo.c b/jo.c
index aaa0541..182da20 100644
--- a/jo.c
+++ b/jo.c
@@ -50,14 +50,16 @@ static JsonNode *pile;		/* pile of nested objects/arrays */
 # define ftello	ftell
 #endif
 
+#define TAG_TO_FLAGS(tag) ((FLAG_MASK + 1) * (tag))
+#define TAG_FLAG_BOOL     (TAG_TO_FLAGS(JSON_BOOL))
+#define TAG_FLAG_STRING   (TAG_TO_FLAGS(JSON_STRING))
+#define TAG_FLAG_NUMBER   (TAG_TO_FLAGS(JSON_NUMBER))
+#define COERCE_MASK       (TAG_FLAG_BOOL | TAG_FLAG_STRING | TAG_FLAG_NUMBER)
+
 JsonTag flags_to_tag(int flags) {
 	return flags / (FLAG_MASK + 1);
 }
 
-int tag_to_flags(JsonTag tag) {
-	return (FLAG_MASK + 1) * tag;
-}
-
 void json_copy_to_object(JsonNode * obj, JsonNode * object_or_array, int clobber)
 {
 	JsonNode *node, *node_child, *obj_child;
@@ -666,27 +668,32 @@ int main(int argc, char **argv)
 		}
 	} else {
 		while ((kv = *argv++)) {
-			if (kv[0] == '-') {
+			if (kv[0] == '-' && !(flags & COERCE_MASK)) {
 				/* Set one-shot coerce flag */
 				switch (kv[1]) {
 					case 'b':
-						flags |= tag_to_flags(JSON_BOOL);
+						flags |= TAG_FLAG_BOOL;
 						break;
 					case 's':
-						flags |= tag_to_flags(JSON_STRING);
+						flags |= TAG_FLAG_STRING;
 						break;
 					case 'n':
-						flags |= tag_to_flags(JSON_NUMBER);
+						flags |= TAG_FLAG_NUMBER;
 						break;
 					default:
-						exit(usage(progname));
+						/* Treat as normal input */
+						p = utf8_from_locale(kv, -1);
+						append_kv(json, flags, key_delim, p);
+						utf8_free(p);
+						/* Reset any one-shot coerce flags */
+						flags &= ~(COERCE_MASK);
 				}
 			} else {
 				p = utf8_from_locale(kv, -1);
 				append_kv(json, flags, key_delim, p);
 				utf8_free(p);
 				/* Reset any one-shot coerce flags */
-				flags &= FLAG_MASK;
+				flags &= ~(COERCE_MASK);
 			}
 		}
 	}
diff --git a/jo.md b/jo.md
index 593ab1b..3c02344 100644
--- a/jo.md
+++ b/jo.md
@@ -11,7 +11,7 @@ SYNOPSIS
 ========
 
 jo \[-p\] \[-a\] \[-B\] \[-e\] \[-v\] \[-V\] \[-d keydelim\] \[--\] \[
-\[-s|-n|-b\] word ...\]
+\[-s\|-n\|-b\] word ...\]
 
 DESCRIPTION
 ===========
@@ -28,6 +28,18 @@ is specified, *key* will be interpreted as an *object path*, whose
 individual components are separated by the first character of
 *keydelim*.
 
+*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
+
+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
@@ -50,15 +62,15 @@ that there are no more global options.
 
 Type coercion works as follows:
 
-  word         -s               -n          -b          default
-  ------------ ---------------- ----------- ----------- ----------------
-  a=           "a":""           "a":0       "a":false   "a":null
-  a=string     "a":"string"     "a":6       "a":true    "a":"string"
-  a="quoted"   "a":""quoted""   "a":8       "a":true    "a":""quoted""
-  a=12345      "a":"12345"      "a":12345   "a":true    "a":12345
-  a=true       "a":"true"       "a":1       "a":true    "a":true
-  a=false      "a":"false"      "a":0       "a":false   "a":false
-  a=null       "a":""           "a":0       "a":false   "a":null
+  word           -s                 -n          -b          default
+  -------------- ------------------ ----------- ----------- ------------------
+  a=             "a":\"\"           "a":0       "a":false   "a":null
+  a=string       "a":"string"       "a":6       "a":true    "a":"string"
+  a=\"quoted\"   "a":"\"quoted\""   "a":8       "a":true    "a":"\"quoted\""
+  a=12345        "a":"12345"        "a":12345   "a":true    "a":12345
+  a=true         "a":"true"         "a":1       "a":true    "a":true
+  a=false        "a":"false"        "a":0       "a":false   "a":false
+  a=null         "a":\"\"           "a":0       "a":false   "a":null
 
 Coercing a non-number string to number outputs the *length* of the
 string.
@@ -210,6 +222,17 @@ it starts with `:` the contents are interpreted as JSON:
     $ jo nested=:nested.json
     {"nested":{"field1":123,"field2":"abc"}}
 
+These characters can be escaped to avoid interpretation:
+
+    $ jo name="JP Mens" twitter='\@jpmens'
+    {"name":"JP Mens","twitter":"@jpmens"}
+
+    $ jo char=" " URIescape=\\%20
+    {"char":" ","URIescape":"%20"}
+
+    $ jo action="split window" vimcmd="\:split"
+    {"action":"split window","vimcmd":":split"}
+
 Read element values from a file in order to overcome ARG\_MAX limits
 during object assignment:
 
@@ -236,7 +259,7 @@ OPTIONS
     `null`. Disable with this option.
 
 -e
-:   Ignore empty stdin (i.e. don't produce a diagnostic error when
+:   Ignore empty stdin (i.e. don't produce a diagnostic error when
     *stdin* is empty)
 
 -p
diff --git a/jo.pandoc b/jo.pandoc
index 753b879..5881fdd 100644
--- a/jo.pandoc
+++ b/jo.pandoc
@@ -18,6 +18,16 @@ guess the type of _value_ in order to create number (using _strtod(3)_), string,
 *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_.
 
+*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
+
+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.
@@ -185,6 +195,17 @@ Read element values from files: a value which starts with `@` is read in plain w
 	$ jo nested=:nested.json
 	{"nested":{"field1":123,"field2":"abc"}}
 
+These characters can be escaped to avoid interpretation:
+
+	$ jo name="JP Mens" twitter='\@jpmens'
+	{"name":"JP Mens","twitter":"@jpmens"}
+
+	$ jo char=" " URIescape=\\%20
+	{"char":" ","URIescape":"%20"}
+
+	$ jo action="split window" vimcmd="\:split"
+	{"action":"split window","vimcmd":":split"}
+
 Read element values from a file in order to overcome ARG_MAX limits during object assignment:
 
 	$ ls | jo -a > child.json
diff --git a/rpm-build/jo.spec b/rpm-build/jo.spec
index 79fa11d..3a29a5d 100644
--- a/rpm-build/jo.spec
+++ b/rpm-build/jo.spec
@@ -1,11 +1,11 @@
 Name:           jo
-Version:        1.1
-Release:        1%{?dist}
+Version:        1.4
+Release:        2%{?dist}
 Summary:        jo is a small utility to create JSON objects
 
 License:        GPL2
 URL:            https://github.com/jpmens/jo
-Source0:        https://github.com/jpmens/jo/releases/download/v%{version}/jo-%{version}.tar.gz
+Source0:        https://github.com/jpmens/jo/releases/download/%{version}/jo-%{version}.tar.gz
 
 BuildRequires:  autoconf
 BuildRequires:	pandoc
@@ -31,9 +31,22 @@ rm -rf $RPM_BUILD_ROOT
 %doc
 %{_bindir}/*
 %{_mandir}/man1/*
+%if 0%{?suse_version}
+%{_datadir}/bash-completion/completions
+%else
+%{_sysconfdir}/bash_completion.d/%{name}.bash
+%endif
 
 
 %changelog
+* Sat Jul 18 2020 JP Mens <jp@mens.de> 1.4
+- bump version -- see Changelog
+* Tue Apr 28 2020 Christian Albrecht <cal@albix.de> 1.3-2
+- Fix broken download url
+- Make bash completion work on RHEL based distros
+* Tue Apr 7 2020 Kilian Cavalotti <kilian@stanford.edu> 1.3-1
+- Bumped to 1.3 release version
+- Include bash-completion file in package
 * Thu May 18 2017 Fabian Arrotin <fabian.arrotin@arrfab.net> 1.1-1
 - Bumped to 1.1 release version
 * Wed Mar 15 2017 Fabian Arrotin <fabian.arrotin@arrfab.net> 1.0-1
diff --git a/snapcraft.yaml b/snapcraft.yaml
index 5002286..e4ac1dd 100644
--- a/snapcraft.yaml
+++ b/snapcraft.yaml
@@ -1,11 +1,12 @@
 name: jo
-version: "1.3"
+version: "1.4"
 summary: jo
 description: |
   This is jo, a small utility to create JSON objects or arrays.
 
 confinement: strict
 grade: stable
+base: core
 
 apps:
   jo:
@@ -17,3 +18,5 @@ parts:
     plugin: autotools
     source-type: git
     source: https://github.com/jpmens/jo
+    build-packages:
+      - pkg-config
diff --git a/tests/jo.17.exp b/tests/jo.17.exp
index b61d6ae..4ba4a4e 100644
--- a/tests/jo.17.exp
+++ b/tests/jo.17.exp
@@ -6,6 +6,10 @@
 {"s":"false","n":0,"b":false,"a":false}
 {"s":"","n":0,"b":false,"a":null}
 ["123",14,true,456]
+["-s",2,true]
+["--test","--toast"]
+{"--test":"--toast"}
+[true,"--toast","--test","--toast",6,"--toast"]
 {"s":false,"n":false,"b":false,"a":false}
 {"s":true,"n":true,"b":true,"a":true}
 {"s":"Jan-Piet Mens <jpmens@gmail.com>","n":"Jan-Piet Mens <jpmens@gmail.com>","b":"Jan-Piet Mens <jpmens@gmail.com>","a":"Jan-Piet Mens <jpmens@gmail.com>"}
diff --git a/tests/jo.17.sh b/tests/jo.17.sh
index 28b9855..cdbd5de 100644
--- a/tests/jo.17.sh
+++ b/tests/jo.17.sh
@@ -8,6 +8,16 @@ done
 # coerce array items
 ${JO:-jo} -a -- -s 123 -n "This is a test" -b C_Rocks 456
 
+# coercion flag strings should be usable as inputs, when they aren't flags
+${JO:-jo} -a -- -s -s -n -n -b -b
+
+# non-flag strings should be read as normal strings, even if they begin with "-"
+${JO:-jo} -a -- --test --toast
+${JO:-jo} -- --test=--toast
+
+# coercion is one-shot, so all "--toast" strings are normal input
+${JO:-jo} -a -- -b --test --toast -s --test --toast -n --test --toast
+
 ### These should NOT be coerced
 
 # @ booleans