Skip to content

Commit

Permalink
Implement type coercion
Browse files Browse the repository at this point in the history
  • Loading branch information
gromgit committed May 18, 2017
1 parent 9e7390a commit ebb8ade
Show file tree
Hide file tree
Showing 7 changed files with 386 additions and 21 deletions.
4 changes: 2 additions & 2 deletions Makefile.am
Original file line number Diff line number Diff line change
Expand Up @@ -12,12 +12,12 @@ if USE_PANDOC
jo.1: jo.pandoc
@test -n "$(PANDOC)" || \
{ echo 'pandoc' not found during configure.; exit 1; }
$(PANDOC) -s -w man -o $@ $<
$(PANDOC) -s -w man+simple_tables -o $@ $<

jo.md: jo.pandoc
@test -n "$(PANDOC)" || \
{ echo 'pandoc' not found during configure.; exit 1; }
$(PANDOC) -s -w markdown -o $@ $<
$(PANDOC) -s -w markdown+simple_tables -o $@ $<

endif

Expand Down
141 changes: 138 additions & 3 deletions jo.1
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
.\" Automatically generated by Pandoc 1.16.0.2
.\"t
.\" Automatically generated by Pandoc 1.19.2.1
.\"
.TH "JO" "1" "" "User Manuals" ""
.hy
Expand All @@ -7,7 +8,7 @@
jo \- JSON output from a shell
.SH SYNOPSIS
.PP
jo [\-p] [\-a] [\-B] [\-v] [\-V] [word ...]
jo [\-p] [\-a] [\-B] [\-v] [\-V] [\-\-] [ [\-s|\-n|\-b] word ...]
.SH DESCRIPTION
.PP
\f[I]jo\f[] creates a JSON string on \f[I]stdout\f[] from _word_s given
Expand All @@ -32,6 +33,119 @@ specified.
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
assigned to the key left of the operator.
.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.
.PP
Type coercion works as follows:
.PP
.TS
tab(@);
l l l l l.
T{
word
T}@T{
\-s
T}@T{
\-n
T}@T{
\-b
T}@T{
default
T}
_
T{
a=
T}@T{
"a":""
T}@T{
"a":0
T}@T{
"a":false
T}@T{
"a":null
T}
T{
a=string
T}@T{
"a":"string"
T}@T{
"a":6
T}@T{
"a":true
T}@T{
"a":"string"
T}
T{
a="quoted"
T}@T{
"a":""quoted""
T}@T{
"a":8
T}@T{
"a":true
T}@T{
"a":""quoted""
T}
T{
a=12345
T}@T{
"a":"12345"
T}@T{
"a":12345
T}@T{
"a":true
T}@T{
"a":12345
T}
T{
a=true
T}@T{
"a":"true"
T}@T{
"a":1
T}@T{
"a":true
T}@T{
"a":true
T}
T{
a=false
T}@T{
"a":"false"
T}@T{
"a":0
T}@T{
"a":false
T}@T{
"a":false
T}
T{
a=null
T}@T{
"a":""
T}@T{
"a":0
T}@T{
"a":false
T}@T{
"a":null
T}
.TE
.PP
Coercing a non\-number string to number outputs the \f[I]length\f[] 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.
.PP
Type coercion only applies to \f[C]key=value\f[] words, and individual
words in a \f[C]\-a\f[] array.
Coercing other words has no effect.
.SH EXAMPLES
.PP
Create an object.
Expand Down Expand Up @@ -127,6 +241,27 @@ $\ jo\ \-p\ name=Jane\ point[]=1\ point[]=2\ geo[lat]=10\ geo[lon]=20
\f[]
.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"
{
\ \ \ "a":\ "true",
\ \ \ "b":\ true,
\ \ \ "c":\ "123",
\ \ \ "d":\ 123,
\ \ \ "e":\ true,
\ \ \ "f":\ true,
\ \ \ "g":\ 14,
\ \ \ "h":\ true
}

$\ jo\ \-a\ \-\-\ \-s\ 123\ \-n\ "This\ is\ a\ test"\ \-b\ C_Rocks\ 456
["123",14,true,456]
\f[]
.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:
Expand All @@ -153,7 +288,7 @@ $\ jo\ files:=child.json
.fi
.SH OPTIONS
.PP
\f[I]jo\f[] understands the following options.
\f[I]jo\f[] understands the following global options.
.TP
.B \-a
Interpret the list of \f[I]words\f[] as array values and produce an
Expand Down
116 changes: 106 additions & 10 deletions jo.c
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@
#define FLAG_PRETTY 0x02
#define FLAG_NOBOOL 0x04
#define FLAG_BOOLEAN 0x08
#define FLAG_MASK (FLAG_ARRAY | FLAG_PRETTY | FLAG_NOBOOL | FLAG_BOOLEAN)

static JsonNode *pile; /* pile of nested objects/arrays */

Expand All @@ -45,6 +46,14 @@ static JsonNode *pile; /* pile of nested objects/arrays */
# define ftello ftell
#endif

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;
Expand Down Expand Up @@ -105,15 +114,83 @@ char *slurp_file(FILE *fp, size_t *out_len, bool fold_newlines)
return (buf);
}

JsonNode *jo_mknull(JsonTag type) {
switch (type) {
case JSON_STRING:
return json_mkstring("");
break;
case JSON_NUMBER:
return json_mknumber(0);
break;
case JSON_BOOL:
return json_mkbool(false);
break;
default:
return json_mknull();
break;
}
}

JsonNode *jo_mkbool(bool b, JsonTag type) {
switch (type) {
case JSON_STRING:
return json_mkstring(b ? "true" : "false");
break;
case JSON_NUMBER:
return json_mknumber(b ? 1 : 0);
break;
default:
return json_mkbool(b);
break;
}
}

JsonNode *jo_mkstring(char *str, JsonTag type) {
switch (type) {
case JSON_NUMBER:
/* Length of string */
return json_mknumber(strlen(str));
break;
case JSON_BOOL:
/* True if not empty */
return json_mkbool(strlen(str) > 0);
break;
default:
return json_mkstring(str);
break;
}
}

JsonNode *jo_mknumber(char *str, JsonTag type) {
/* ASSUMPTION: str already tested as valid number */
double n = strtod(str, NULL);

switch (type) {
case JSON_STRING:
/* Just return the original representation */
return json_mkstring(str);
break;
case JSON_BOOL:
return json_mkbool(n != 0);
break;
default:
/* ASSUMPTION: str already tested as valid number */
return json_mknumber(n);
break;
}
}

/*
* Attempt to "sniff" the type of data in `str' and return
* a JsonNode of the correct JSON type.
*/

JsonNode *vnode(char *str, int flags)
{
JsonTag type = flags_to_tag(flags);

if (strlen(str) == 0) {
return json_mknull();
return jo_mknull(type);
}

/* If str begins with a double quote, keep it a string */
Expand All @@ -126,23 +203,23 @@ JsonNode *vnode(char *str, int flags)
*bp = 0; /* Chop closing double quote */
return json_mkstring(str + 1);
#endif
return json_mkstring(str);
return jo_mkstring(str, type);
}

char *endptr;
double num = strtod(str, &endptr);

if (!*endptr && isfinite(num)) {
return json_mknumber(num);
return jo_mknumber(str, type);
}

if (!(flags & FLAG_NOBOOL)) {
if (strcmp(str, "true") == 0) {
return json_mkbool(true);
return jo_mkbool(true, type);
} else if (strcmp(str, "false") == 0) {
return json_mkbool(false);
return jo_mkbool(false, type);
} else if (strcmp(str, "null") == 0) {
return json_mknull();
return jo_mknull(type);
}
}

Expand Down Expand Up @@ -196,7 +273,7 @@ JsonNode *vnode(char *str, int flags)
return (obj);
}

return json_mkstring(str);
return jo_mkstring(str, type);
}

/*
Expand Down Expand Up @@ -523,9 +600,28 @@ int main(int argc, char **argv)
}
} else {
while ((kv = *argv++)) {
p = utf8_from_locale(kv, -1);
append_kv(json, flags, p);
utf8_free(p);
if (kv[0] == '-') {
/* Set one-shot coerce flag */
switch (kv[1]) {
case 'b':
flags |= tag_to_flags(JSON_BOOL);
break;
case 's':
flags |= tag_to_flags(JSON_STRING);
break;
case 'n':
flags |= tag_to_flags(JSON_NUMBER);
break;
default:
exit(usage(progname));
}
} else {
p = utf8_from_locale(kv, -1);
append_kv(json, flags, p);
utf8_free(p);
/* Reset any one-shot coerce flags */
flags &= FLAG_MASK;
}
}
}

Expand Down
Loading

0 comments on commit ebb8ade

Please sign in to comment.