diff --git a/doc/json.n b/doc/json.n index 55567c9..a44572e 100644 --- a/doc/json.n +++ b/doc/json.n @@ -4,15 +4,15 @@ '\" See the file "LICENSE" for information on usage and redistribution '\" of this file, and for a DISCLAIMER OF ALL WARRANTIES. '\" -.TH json n 0.14.0 rl_json "RubyLane/JSON Package Commands" +.TH json n 0.15.0 rl_json "RubyLane/JSON Package Commands" .so man.macros .BS '\" Note: do not modify the .SH NAME line immediately below! .SH NAME -json \- Parse, manipulate and produce JSON documents +json \- Parse, manipulate and produce JSON documents .SH SYNOPSIS .nf -\fBpackage require rl_json\fR ?\fB0.14.0\fR? +\fBpackage require rl_json\fR ?\fB0.15.0\fR? \fBjson get\fR ?\fB-default\fR \fIdefaultValue\fR? \fIjsonValue\fR ?\fIkey ...\fR? \fBjson extract\fR ?\fB-default\fR \fIdefaultValue\fR? \fIjsonValue\fR ?\fIkey ...\fR? @@ -28,9 +28,10 @@ json \- Parse, manipulate and produce JSON documents \fBjson boolean\fR \fIvalue\fR \fBjson object\fR \fI?key value ?key value ...??\fR \fBjson array\fR \fIelem ...\fR +\fBjson autoarray\fR \fIvalue ...\fR \fBjson bool\fR \fIvalue\fR \fBjson normalize\fR \fIjsonValue\fR -\fBjson pretty\fR ?\fB-intent\fR \fIindent\fR? \fIjsonValue\fR ?\fIkey ...\fR? +\fBjson pretty\fR ?\fB-indent\fR \fIindent\fR? ?\fB-compact\fR? ?\fB-arrays\fR \fImode\fR? \fIjsonValue\fR ?\fIkey ...\fR? \fBjson template\fR \fIjsonValue\fR ?\fIdictionary\fR? \fBjson isnull\fR \fIjsonValue\fR ?\fIkey ...\fR? \fBjson type\fR \fIjsonValue\fR ?\fIkey ...\fR? @@ -170,6 +171,26 @@ Return a JSON array containing each of the elements given. \fIelem\fR is a list of two elements, the first being the type {string, number, boolean, null, object, array, json}, and the second being the value. .TP +\fBjson autoarray \fI?value ...?\fR +. +Return a JSON array containing each of the values given, with automatic type detection. +Unlike \fBjson array\fR which requires explicit type specification, \fBjson autoarray\fR +automatically determines the appropriate JSON type for each value: +.RS +.IP \(bu 3 +Values exactly matching "true" or "false" (case-sensitive) are converted to JSON booleans. +.IP \(bu 3 +Values that can be parsed as valid JSON numbers are converted to JSON numbers. +.IP \(bu 3 +All other values are converted to JSON strings. +.RE +.PP +For example: +.CS + json autoarray 1 2.5 true false "hello world" 42 + # Returns: [1,2.5,true,false,"hello world",42] +.CE +.TP \fBjson foreach \fIvarList1 jsonValue1\fR ?\fIvarList2 jsonValue2 ...\fR? \fIscript\fR . Evaluate \fIscript\fR in a loop in a similar way to the \fBforeach\fR command. @@ -232,12 +253,38 @@ Return a version of the input \fIjsonValue\fR, i.e., with all optional whitespace trimmed. .TP -\fBjson pretty\fR ?\fB-indent\fR \fIindent\fR? \fIjsonValue\fR ?\fIkey ...\fR? +\fBjson pretty\fR ?\fB-indent\fR \fIindent\fR? ?\fB-compact\fR? ?\fB-arrays\fR \fImode\fR? \fIjsonValue\fR ?\fIkey ...\fR? . Returns a pretty-printed string representation of \fIjsonValue\fR, found by following the path of \fIkey\fRs. Useful for debugging or inspecting the -structure of JSON data. If \fB-indent\fR is supplied, use \fIindent\fR for -each level of indent, otherwise default to four spaces. +structure of JSON data. +.RS +.PP +The following options control the formatting: +.TP +\fB-indent\fR \fIindent\fR +. +Use \fIindent\fR for each level of indent. Defaults to four spaces if not specified. +.TP +\fB-compact\fR +. +Return a compact, single-line representation with no extra whitespace. This is equivalent +to \fBjson normalize\fR but provided for convenience when using other pretty options. +When this option is used, \fB-indent\fR and \fB-arrays\fR are ignored. +.TP +\fB-arrays\fR \fImode\fR +. +Control how arrays are formatted. \fImode\fR must be one of: +.RS +.IP \fBinline\fR 10 +All arrays are formatted on a single line: [1,2,3] +.IP \fBmultiline\fR 10 +All arrays are formatted with one element per line. +.RE +.PP +If not specified, arrays with 3 or fewer elements are formatted inline, while larger +arrays are formatted with one element per line. +.RE .TP \fBjson decode \fIbytes\fR ?\fIencoding\fR? . diff --git a/generic/api.c b/generic/api.c index 5319e21..ed97463 100644 --- a/generic/api.c +++ b/generic/api.c @@ -31,10 +31,10 @@ int JSON_NewJNumberObj(Tcl_Interp* interp, Tcl_Obj* number, Tcl_Obj** new) //{{{ int JSON_NewJBooleanObj(Tcl_Interp* interp, Tcl_Obj* boolean, Tcl_Obj** new) //{{{ { struct interp_cx* l = Tcl_GetAssocData(interp, "rl_json", NULL); - int bool; + int boolVal; - TEST_OK(Tcl_GetBooleanFromObj(interp, boolean, &bool)); - replace_tclobj(new, bool ? l->json_true : l->json_false); + TEST_OK(Tcl_GetBooleanFromObj(interp, boolean, &boolVal)); + replace_tclobj(new, boolVal ? l->json_true : l->json_false); return TCL_OK; } @@ -937,7 +937,7 @@ int JSON_Normalize(Tcl_Interp* interp, Tcl_Obj* obj, Tcl_Obj** normalized) //{{{ } //}}} -int JSON_Pretty(Tcl_Interp* interp, Tcl_Obj* obj, Tcl_Obj* indent, Tcl_Obj** prettyString) //{{{ +int JSON_Pretty(Tcl_Interp* interp, Tcl_Obj* obj, Tcl_Obj* indent, int nopadding, int compact, int arrays_inline, Tcl_Obj** prettyString) //{{{ { int retval = TCL_OK; Tcl_DString ds; @@ -945,6 +945,13 @@ int JSON_Pretty(Tcl_Interp* interp, Tcl_Obj* obj, Tcl_Obj* indent, Tcl_Obj** pre Tcl_Obj* pad = NULL; struct interp_cx* l = Tcl_GetAssocData(interp, "rl_json", NULL); + // Handle compact mode - just normalize (remove all whitespace) + if (compact) { + retval = JSON_Normalize(interp, obj, prettyString); + return retval; + } + + // Normal pretty printing with formatting options if (indent == NULL) { replace_tclobj(&lindent, get_string(l, " ", 4)); indent = lindent; @@ -952,7 +959,7 @@ int JSON_Pretty(Tcl_Interp* interp, Tcl_Obj* obj, Tcl_Obj* indent, Tcl_Obj** pre replace_tclobj(&pad, l->tcl_empty); Tcl_DStringInit(&ds); - retval = json_pretty(interp, obj, indent, pad, &ds); + retval = json_pretty(interp, obj, indent, nopadding, pad, arrays_inline, &ds); if (retval == TCL_OK) replace_tclobj(prettyString, Tcl_NewStringObj(Tcl_DStringValue(&ds), Tcl_DStringLength(&ds))); diff --git a/generic/rl_json.c b/generic/rl_json.c index 34aec51..1b83150 100644 --- a/generic/rl_json.c +++ b/generic/rl_json.c @@ -1339,7 +1339,7 @@ static int foreach(Tcl_Interp* interp, int objc, Tcl_Obj *const objv[], enum col } //}}} -int json_pretty(Tcl_Interp* interp, Tcl_Obj* json, Tcl_Obj* indent, Tcl_Obj* pad, Tcl_DString* ds) //{{{ +int json_pretty(Tcl_Interp* interp, Tcl_Obj* json, Tcl_Obj* indent, int nopadding, Tcl_Obj* pad, int arrays_inline, Tcl_DString* ds) //{{{ { int pad_len, next_pad_len, count; enum json_types type; @@ -1377,15 +1377,19 @@ int json_pretty(Tcl_Interp* interp, Tcl_Obj* json, Tcl_Obj* indent, Tcl_Obj* pad TEST_OK_LABEL(finally, retval, Tcl_DictObjFirst(interp, val, &search, &k, &v, &done)); - for (; !done; Tcl_DictObjNext(&search, &k, &v, &done)) { - Tcl_GetStringFromObj(k, &k_len); - if (k_len <= 20 && k_len > max) - max = k_len; - } - Tcl_DictObjDone(&search); + // keep the default behaviour, if wanted add the -nopadding option + // and the output will be condensed + if (!nopadding) { + for (; !done; Tcl_DictObjNext(&search, &k, &v, &done)) { + Tcl_GetStringFromObj(k, &k_len); + if (k_len <= 20 && k_len > max) + max = k_len; + } + Tcl_DictObjDone(&search); - if (max > 20) - max = 20; // If this cap is changed be sure to adjust the key_pad_buf length above + if (max > 20) + max = 20; // If this cap is changed be sure to adjust the key_pad_buf length above + } replace_tclobj(&next_pad, Tcl_DuplicateObj(pad)); Tcl_AppendObjToObj(next_pad, indent); @@ -1401,11 +1405,14 @@ int json_pretty(Tcl_Interp* interp, Tcl_Obj* json, Tcl_Obj* indent, Tcl_Obj* pad append_json_string(&scx, k); Tcl_DStringAppend(ds, ": ", 2); - Tcl_GetStringFromObj(k, &k_len); - if (k_len < max) - Tcl_DStringAppend(ds, key_pad_buf, max-k_len); - - if (json_pretty(interp, v, indent, next_pad, ds) != TCL_OK) { + // keep the default behaviour, if wanted add the -nopadding option + if (!nopadding) { + Tcl_GetStringFromObj(k, &k_len); + if (k_len < max) + Tcl_DStringAppend(ds, key_pad_buf, max-k_len); + } + + if (json_pretty(interp, v, indent, nopadding, next_pad, arrays_inline, ds) != TCL_OK) { Tcl_DictObjDone(&search); retval = TCL_ERROR; goto finally; @@ -1429,6 +1436,7 @@ int json_pretty(Tcl_Interp* interp, Tcl_Obj* json, Tcl_Obj* indent, Tcl_Obj* pad { int i, oc; Tcl_Obj** ov; + int force_inline, force_multiline, should_inline; TEST_OK_LABEL(finally, retval, Tcl_ListObjGetElements(interp, val, &oc, &ov)); @@ -1436,20 +1444,38 @@ int json_pretty(Tcl_Interp* interp, Tcl_Obj* json, Tcl_Obj* indent, Tcl_Obj* pad Tcl_AppendObjToObj(next_pad, indent); next_pad_str = Tcl_GetStringFromObj(next_pad, &next_pad_len); + // Determine array formatting: inline vs multiline + force_inline = (arrays_inline == 1); + force_multiline = (arrays_inline == 0); + // Auto heuristic: small arrays (<=3 elements) inline by default + should_inline = (!force_multiline) && (force_inline || oc <= 3); + if (oc == 0) { Tcl_DStringAppend(ds, "[]", 2); + } else if (should_inline) { + // Inline format: [1,2,3] + Tcl_DStringAppend(ds, "[", 1); + count = 0; + for (i=0; ijson_true)); + } else if (len == 5 && strcmp(str, "false") == 0) { + replace_tclobj(&elem, JSON_NewJvalObj(JSON_BOOL, l->json_false)); + } else { + // Try to parse as a number + int is_number = (force_json_number(interp, l, objv[i], &forced) == TCL_OK); + + if (is_number) { + // It's a valid JSON number + replace_tclobj(&elem, JSON_NewJvalObj(JSON_NUMBER, forced)); + release_tclobj(&forced); + } else { + // Default to string + // Clear any error message from failed number conversion + Tcl_ResetResult(interp); + replace_tclobj(&elem, JSON_NewJvalObj(JSON_STRING, objv[i])); + } + } + + TEST_OK_LABEL(finally, retval, Tcl_ListObjAppendElement(interp, val, elem)); + } + Tcl_SetObjResult(interp, JSON_NewJvalObj(JSON_ARRAY, val)); + +finally: + release_tclobj(&elem); + release_tclobj(&val); + release_tclobj(&forced); + return retval; +} + //}}} static int jsonDecode(ClientData cdata, Tcl_Interp* interp, int objc, Tcl_Obj *const objv[]) //{{{ { @@ -3181,18 +3255,36 @@ static int jsonPretty(ClientData cdata, Tcl_Interp* interp, int objc, Tcl_Obj *c Tcl_Obj* indent = NULL; Tcl_Obj* target = NULL; int argbase = 1; + int compact = 0; + int nopadding = 0; + int arrays_inline = -1; // -1 = default/auto, 0 = multiline, 1 = inline static const char* opts[] = { "-indent", + "-compact", + "-nopadding", + "-arrays", "--", // Unnecessary for this case, but supported for convention NULL }; enum { OPT_INDENT, + OPT_COMPACT, + OPT_NOPADDING, + OPT_ARRAYS, OPT_END_OPTIONS }; + static const char* array_modes[] = { + "inline", + "multiline", + NULL + }; + enum { + ARRAYS_INLINE, + ARRAYS_MULTILINE + }; enum {A_cmd, A_VAL, A_args}; - CHECK_MIN_ARGS_LABEL(finally, code, "pretty ?-indent indent? json_val ?key ...?"); + CHECK_MIN_ARGS_LABEL(finally, code, "pretty ?-indent indent? ?-compact? ?-nopadding? ?-arrays inline|multiline? json_val ?key ...?"); // Consume any leading options {{{ while (argbase < objc) { @@ -3214,18 +3306,40 @@ static int jsonPretty(ClientData cdata, Tcl_Interp* interp, int objc, Tcl_Obj *c argbase += 2; break; + case OPT_COMPACT: + compact = 1; + argbase++; + break; + + case OPT_NOPADDING: + nopadding = 1; + argbase++; + break; + + case OPT_ARRAYS: { + int array_mode; + if (objc - argbase < 2) { + Tcl_SetErrorCode(interp, "TCL", "ARGUMENT", "MISSING", NULL); + THROW_ERROR_LABEL(finally, code, "missing argument to \"-arrays\""); + } + TEST_OK_LABEL(finally, code, Tcl_GetIndexFromObj(interp, objv[argbase+1], array_modes, "array mode", TCL_EXACT, &array_mode)); + arrays_inline = (array_mode == ARRAYS_INLINE) ? 1 : 0; + argbase += 2; + break; + } + case OPT_END_OPTIONS: argbase++; goto endoptions; default: - THROW_ERROR_LABEL(finally, code, "Unhandled get option idx"); + THROW_ERROR_LABEL(finally, code, "Unhandled pretty option idx"); } } endoptions: if (objc == argbase) { - Tcl_WrongNumArgs(interp, 1, objv, "?-default defaultValue? json_val ?key ...?"); + Tcl_WrongNumArgs(interp, 1, objv, "?-indent indent? ?-compact? ?-nopadding? ?-arrays inline|multiline? json_val ?key ...?"); code = TCL_ERROR; goto finally; } @@ -3237,7 +3351,7 @@ static int jsonPretty(ClientData cdata, Tcl_Interp* interp, int objc, Tcl_Obj *c replace_tclobj(&target, objv[argbase]); } - TEST_OK_LABEL(finally, code, JSON_Pretty(interp, target, indent, &pretty)); + TEST_OK_LABEL(finally, code, JSON_Pretty(interp, target, indent, nopadding, compact, arrays_inline, &pretty)); Tcl_SetObjResult(interp, pretty); @@ -3602,6 +3716,7 @@ static int jsonNRObj(ClientData cdata, Tcl_Interp* interp, int objc, Tcl_Obj *co "boolean", "object", "array", + "autoarray", "decode", @@ -3643,6 +3758,7 @@ static int jsonNRObj(ClientData cdata, Tcl_Interp* interp, int objc, Tcl_Obj *co M_BOOLEAN, M_OBJECT, M_ARRAY, + M_AUTOARRAY, M_DECODE, // Debugging M_FREE_CACHE, @@ -3678,6 +3794,7 @@ static int jsonNRObj(ClientData cdata, Tcl_Interp* interp, int objc, Tcl_Obj *co case M_BOOLEAN: return jsonBoolean(cdata, interp, objc-1, objv+1); case M_OBJECT: return jsonObject(cdata, interp, objc-1, objv+1); case M_ARRAY: return jsonArray(cdata, interp, objc-1, objv+1); + case M_AUTOARRAY: return jsonAutoArray(cdata, interp, objc-1, objv+1); case M_DECODE: return jsonDecode(cdata, interp, objc-1, objv+1); case M_ISNULL: return jsonIsNull(cdata, interp, objc-1, objv+1); case M_TEMPLATE: return jsonTemplate(cdata, interp, objc-1, objv+1); @@ -4128,6 +4245,7 @@ DLLEXPORT int Rl_json_Init(Tcl_Interp* interp) //{{{ Tcl_CreateObjCommand(interp, ENS "boolean", jsonBoolean, l, NULL); Tcl_CreateObjCommand(interp, ENS "object", jsonObject, l, NULL); Tcl_CreateObjCommand(interp, ENS "array", jsonArray, l, NULL); + Tcl_CreateObjCommand(interp, ENS "autoarray", jsonAutoArray, l, NULL); Tcl_CreateObjCommand(interp, ENS "decode", jsonDecode, l, NULL); Tcl_CreateObjCommand(interp, ENS "isnull", jsonIsNull, l, NULL); Tcl_CreateObjCommand(interp, ENS "template", jsonTemplate, l, NULL); diff --git a/generic/rl_json.decls b/generic/rl_json.decls index 9354f00..b4d908e 100644 --- a/generic/rl_json.decls +++ b/generic/rl_json.decls @@ -87,7 +87,7 @@ declare 24 generic { int JSON_Normalize(Tcl_Interp* interp, Tcl_Obj* obj, Tcl_Obj** normalized) } declare 25 generic { - int JSON_Pretty(Tcl_Interp* interp, Tcl_Obj* obj, Tcl_Obj* indent, Tcl_Obj** prettyString) + int JSON_Pretty(Tcl_Interp* interp, Tcl_Obj* obj, Tcl_Obj* indent, int nopadding, int compact, int arrays_inline, Tcl_Obj** prettyString) } declare 26 generic { int JSON_Template(Tcl_Interp* interp, Tcl_Obj* template, Tcl_Obj* dict, Tcl_Obj** res) diff --git a/generic/rl_jsonDecls.h b/generic/rl_jsonDecls.h index 1ec3823..73e2082 100644 --- a/generic/rl_jsonDecls.h +++ b/generic/rl_jsonDecls.h @@ -87,7 +87,8 @@ EXTERN int JSON_Normalize(Tcl_Interp*interp, Tcl_Obj*obj, Tcl_Obj**normalized); /* 25 */ EXTERN int JSON_Pretty(Tcl_Interp*interp, Tcl_Obj*obj, - Tcl_Obj*indent, Tcl_Obj**prettyString); + Tcl_Obj*indent, int nopadding, int compact, + int arrays_inline, Tcl_Obj**prettyString); /* 26 */ EXTERN int JSON_Template(Tcl_Interp*interp, Tcl_Obj*template, Tcl_Obj*dict, Tcl_Obj**res); @@ -118,7 +119,7 @@ EXTERN int JSON_Valid(Tcl_Interp*interp, Tcl_Obj*json, int*valid, enum extensions extensions, struct parse_error*details); -typedef struct Rl_jsonStubs { +typedef struct TcljsonStubs { int magic; void *hooks; @@ -147,7 +148,7 @@ typedef struct Rl_jsonStubs { int (*jSON_Set) (Tcl_Interp*interp, Tcl_Obj*obj, Tcl_Obj* path /* can be NULL */, Tcl_Obj*replacement); /* 22 */ int (*jSON_Unset) (Tcl_Interp*interp, Tcl_Obj*obj, Tcl_Obj* path /* can be NULL */); /* 23 */ int (*jSON_Normalize) (Tcl_Interp*interp, Tcl_Obj*obj, Tcl_Obj**normalized); /* 24 */ - int (*jSON_Pretty) (Tcl_Interp*interp, Tcl_Obj*obj, Tcl_Obj*indent, Tcl_Obj**prettyString); /* 25 */ + int (*jSON_Pretty) (Tcl_Interp*interp, Tcl_Obj*obj, Tcl_Obj*indent, int nopadding, int compact, int arrays_inline, Tcl_Obj**prettyString); /* 25 */ int (*jSON_Template) (Tcl_Interp*interp, Tcl_Obj*template, Tcl_Obj*dict, Tcl_Obj**res); /* 26 */ int (*jSON_IsNULL) (Tcl_Interp*interp, Tcl_Obj*obj, Tcl_Obj* path /* can be NULL */, int*isnull); /* 27 */ int (*jSON_Type) (Tcl_Interp*interp, Tcl_Obj*obj, Tcl_Obj* path /* can be NULL */, enum json_types*type); /* 28 */ @@ -156,6 +157,7 @@ typedef struct Rl_jsonStubs { int (*jSON_Decode) (Tcl_Interp*interp, Tcl_Obj*bytes, Tcl_Obj*encoding, Tcl_Obj**decodedstring); /* 31 */ int (*jSON_Foreach) (Tcl_Interp*interp, Tcl_Obj*iterators, JSON_ForeachBody*body, enum collecting_mode collect, Tcl_Obj**res, ClientData cdata); /* 32 */ int (*jSON_Valid) (Tcl_Interp*interp, Tcl_Obj*json, int*valid, enum extensions extensions, struct parse_error*details); /* 33 */ + } Rl_jsonStubs; extern const Rl_jsonStubs *rl_jsonStubsPtr; diff --git a/generic/rl_jsonInt.h b/generic/rl_jsonInt.h index 56867a5..7cb495d 100644 --- a/generic/rl_jsonInt.h +++ b/generic/rl_jsonInt.h @@ -1,6 +1,10 @@ #ifndef _RL_JSONINT #define _RL_JSONINT +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + #include "rl_json.h" #include "tclstuff.h" #include @@ -248,7 +252,7 @@ int apply_template_actions(Tcl_Interp* interp, Tcl_Obj* template, Tcl_Obj* actio int build_template_actions(Tcl_Interp* interp, Tcl_Obj* template, Tcl_Obj** actions); int convert_to_tcl(Tcl_Interp* interp, Tcl_Obj* obj, Tcl_Obj** out); int resolve_path(Tcl_Interp* interp, Tcl_Obj* src, Tcl_Obj *const pathv[], int pathc, Tcl_Obj** target, const int exists, const int modifiers, Tcl_Obj* def); -int json_pretty(Tcl_Interp* interp, Tcl_Obj* json, Tcl_Obj* indent, Tcl_Obj* pad, Tcl_DString* ds); +int json_pretty(Tcl_Interp* interp, Tcl_Obj* json, Tcl_Obj* indent, int nopadding, Tcl_Obj* pad, int arrays_inline, Tcl_DString* ds); void foreach_state_free(struct foreach_state* state); #define TEMPLATE_TYPE(s, len, out) \ diff --git a/generic/rl_jsonStubInit.c b/generic/rl_jsonStubInit.c index 4b65f93..d981bb0 100644 --- a/generic/rl_jsonStubInit.c +++ b/generic/rl_jsonStubInit.c @@ -38,7 +38,7 @@ const Rl_jsonStubs rl_jsonStubs = { JSON_Keys, /* 30 */ JSON_Decode, /* 31 */ JSON_Foreach, /* 32 */ - JSON_Valid, /* 33 */ + JSON_Valid /* 33 */ }; /* !END!: Do not edit above this line. */ diff --git a/tests/helpers.tcl b/tests/helpers.tcl index 13ae0cb..d5ce82e 100644 --- a/tests/helpers.tcl +++ b/tests/helpers.tcl @@ -154,19 +154,14 @@ proc _compare_json {opts j1 j2 {path {}}} { #<<< #>>> proc compare_json args { #<<< - parse_args $args { - -subset {-default none} - j1 {} - j2 {} - } opts - - try { - _compare_json $opts [dict get $opts j1] [dict get $opts j2] - } trap {RL TEST JSON_MISMATCH} {errmsg options} { - return $errmsg - } on ok {} { - return match - } + set opts [list -subset none j1 [lindex $args 0] j2 [lindex $args 1]] + try { + _compare_json $opts [dict get $opts j1] [dict get $opts j2] + } trap {RL TEST JSON_MISMATCH} {errmsg options} { + return $errmsg + } on ok {} { + return match + } } #>>> diff --git a/tests/number.test b/tests/number.test index 5d5eda8..bf4ac1c 100644 --- a/tests/number.test +++ b/tests/number.test @@ -239,14 +239,14 @@ test number-2.3 {json number, not a number} -body { #<<< list $code $r [dict get $o -errorcode] } -cleanup { unset -nocomplain code r o -} -result [list 1 {can't use non-numeric string "foo" as operand of "+"} {ARITH DOMAIN {non-numeric string}}] +} -result [list 1 {can't use non-numeric string as operand of "+"} {ARITH DOMAIN {non-numeric string}}] #>>> test number-2.4 {json number, not a number: empty string} -body { #<<< set code [catch {json number ""} r o] list $code $r [dict get $o -errorcode] } -cleanup { unset -nocomplain code r o -} -result [list 1 {can't use empty string "" as operand of "+"} {ARITH DOMAIN {empty string}}] +} -result [list 1 {can't use empty string as operand of "+"} {ARITH DOMAIN {empty string}}] #>>> ::tcltest::cleanupTests diff --git a/tests/pretty.test b/tests/pretty.test index 1bd6e51..162e5c2 100644 --- a/tests/pretty.test +++ b/tests/pretty.test @@ -4,10 +4,11 @@ if {"::tcltest" ni [namespace children]} { } package require rl_json -package require parse_args -namespace path {::rl_json ::parse_args} +namespace path {::rl_json} -test pretty-1.1 {Basic pretty-print} -body { #<<< +test pretty-jsonPretty-1.1 {} -body {json pretty -indent} -returnCodes error -result {missing argument to "-indent"} -errorCode {TCL ARGUMENT MISSING} +#>>> +test pretty-1.2 {Basic pretty-print} -body { #<<< json pretty {{"foo":null,"empty":{},"emptyarr":[],"hello, world":"bar","This is a much longer key":["str",123,123.4,true,false,null,{"inner": "obj"}]}} } -cleanup { unset -nocomplain o @@ -29,7 +30,7 @@ test pretty-1.1 {Basic pretty-print} -body { #<<< ] }} #>>> -test pretty-1.2 {Basic pretty-print, different indent} -body { #<<< +test pretty-1.3 {Basic pretty-print, different indent} -body { #<<< json pretty -indent " " {{"foo":null,"empty":{},"emptyarr":[],"hello, world":"bar","This is a much longer key":["str",123,123.4,true,false,null,{"inner": "obj"}]}} } -result {{ "foo": null, @@ -49,11 +50,15 @@ test pretty-1.2 {Basic pretty-print, different indent} -body { #<<< ] }} #>>> -test pretty-2.1 {too few args} -body { #<<< +test pretty-jsonPretty-2.1 {} -body {json pretty -- 1} -result 1 +#>>> +test pretty-2.2 {too few args} -body { #<<< json pretty -} -returnCodes error -result {wrong # args: should be "pretty pretty ?-indent indent? json_val ?key ...?"} +} -returnCodes error -result {wrong # args: should be "pretty pretty ?-indent indent? ?-compact? ?-nopadding? ?-arrays inline|multiline? json_val ?key ...?"} +#>>> +test pretty-jsonPretty-3.1 {} -body {json pretty -indent { }} -returnCodes error -result {wrong # args: should be "pretty ?-indent indent? ?-compact? ?-nopadding? ?-arrays inline|multiline? json_val ?key ...?"} -errorCode {TCL WRONGARGS} #>>> -test pretty-3.1 {path} -body { #<<< +test pretty-3.2 {path} -body { #<<< json pretty {{"foo":null,"empty":{},"emptyarr":[],"hello, world":"bar","This is a much longer key":["str",123,123.4,true,false,null,{"inner": "obj"}]}} {This is a much longer key} } -result {[ "str", @@ -67,11 +72,220 @@ test pretty-3.1 {path} -body { #<<< } ]} #>>> -test pretty-3.2 {bad path} -body { #<<< +test pretty-3.3 {bad path} -body { #<<< json pretty {{"foo":null,"empty":{},"emptyarr":[],"hello, world":"bar","This is a much longer key":["str",123,123.4,true,false,null,{"inner": "obj"}]}} bad } -returnCodes error -result {Path element 2: "bad" not found} -errorCode NONE #>>> - +test pretty-jsonPretty-4.1 {} -body {json pretty bad} -returnCodes error -result {Error parsing JSON value: Illegal character at offset 0} -errorCode {RL JSON PARSE {Illegal character} bad 0} +#>>> +test pretty-4.2 {compact output} -body { + json pretty -compact {{"foo":"bar","array":[1,2,3]}} +} -result {{"foo":"bar","array":[1,2,3]}} +#>>> +test pretty-jsonPretty-5.1 {} -body {json pretty -bad} -returnCodes error -result {bad option "-bad": must be *} -errorCode {TCL LOOKUP INDEX option -bad} -match glob +#>>> +test pretty-5.2 {arrays inline mode} -body { + json pretty -arrays inline {{"arr":[1,2,3,4,5]}} +} -result {{ + "arr": [1,2,3,4,5] +}} +#>>> +test pretty-5.3 {arrays multiline mode} -body { + json pretty -arrays multiline {{"arr":[1,2,3]}} +} -result {{ + "arr": [ + 1, + 2, + 3 + ] +}} +#>>> +test pretty-5.4 {arrays default auto mode - small inline} -body { + json pretty {{"small":[1,2,3]}} +} -result {{ + "small": [1,2,3] +}} +#>>> +test pretty-5.5 {arrays default auto mode - large multiline} -body { + json pretty {{"large":[1,2,3,4,5]}} +} -result {{ + "large": [ + 1, + 2, + 3, + 4, + 5 + ] +}} +#>>> +test pretty-6.1 {combine -indent with -arrays inline} -body { + json pretty -indent " " -arrays inline {{"arr":[1,2,3,4,5]}} +} -result {{ + "arr": [1,2,3,4,5] +}} +#>>> +test pretty-6.2 {combine -indent with -arrays multiline} -body { + json pretty -indent " " -arrays multiline {{"arr":[1,2,3]}} +} -result {{ + "arr": [ + 1, + 2, + 3 + ] +}} +#>>> +test pretty-6.3 {tab indent with arrays inline} -body { + json pretty -indent "\t" -arrays inline {{"data":[10,20,30]}} +} -result {{ + "data": [10,20,30] +}} +#>>> +test pretty-6.4 {tab indent with arrays multiline} -body { + json pretty -indent "\t" -arrays multiline {{"data":[10,20]}} +} -result {{ + "data": [ + 10, + 20 + ] +}} +#>>> +test pretty-7.1 {compact overrides indent} -body { + json pretty -compact -indent " " {{"foo":"bar"}} +} -result {{"foo":"bar"}} +#>>> +test pretty-7.2 {compact overrides arrays inline} -body { + json pretty -compact -arrays inline {{"arr":[1,2,3]}} +} -result {{"arr":[1,2,3]}} +#>>> +test pretty-7.3 {compact overrides arrays multiline} -body { + json pretty -compact -arrays multiline {{"arr":[1,2,3,4,5]}} +} -result {{"arr":[1,2,3,4,5]}} +#>>> +test pretty-7.4 {compact with all options} -body { + json pretty -compact -indent "\t" -arrays multiline {{"x":[1,2],"y":{"z":"w"}}} +} -result {{"x":[1,2],"y":{"z":"w"}}} +#>>> +test pretty-8.1 {nested objects with custom indent} -body { + json pretty -indent " " {{"outer":{"inner":{"deep":"value"}}}} +} -result {{ + "outer": { + "inner": { + "deep": "value" + } + } +}} +#>>> +test pretty-8.2 {nested arrays with multiline} -body { + json pretty -arrays multiline {{"matrix":[[1,2],[3,4]]}} +} -result {{ + "matrix": [ + [ + 1, + 2 + ], + [ + 3, + 4 + ] + ] +}} +#>>> +test pretty-8.3 {nested arrays with inline} -body { + json pretty -arrays inline {{"matrix":[[1,2],[3,4]]}} +} -result {{ + "matrix": [[1,2],[3,4]] +}} +#>>> +test pretty-8.4 {mixed nested structures} -body { + json pretty -indent " " {{"users":[{"name":"Alice","age":30},{"name":"Bob","age":25}]}} +} -result {{ + "users": [{ + "name": "Alice", + "age": 30 + },{ + "name": "Bob", + "age": 25 + }] +}} +#>>> +test pretty-9.1 {empty array with default} -body { + json pretty {{"empty":[]}} +} -result {{ + "empty": [] +}} +#>>> +test pretty-9.2 {empty array with arrays inline} -body { + json pretty -arrays inline {{"empty":[]}} +} -result {{ + "empty": [] +}} +#>>> +test pretty-9.3 {empty array with arrays multiline} -body { + json pretty -arrays multiline {{"empty":[]}} +} -result {{ + "empty": [] +}} +#>>> +test pretty-9.4 {empty object} -body { + json pretty {{"empty":{}}} +} -result {{ + "empty": {} +}} +#>>> +test pretty-10.1 {complex structure default formatting} -body { + json pretty {{"api":{"version":"1.0","endpoints":["/user","/data","/login","/logout"]}}} +} -result {{ + "api": { + "version": "1.0", + "endpoints": [ + "/user", + "/data", + "/login", + "/logout" + ] + } +}} +#>>> +test pretty-10.2 {complex structure with indent and inline arrays} -body { + json pretty -indent "\t" -arrays inline {{"config":{"debug":true,"ports":[8080,8081,8082],"name":"server"}}} +} -result {{ + "config": { + "debug": true, + "ports": [8080,8081,8082], + "name": "server" + } +}} +#>>> +test pretty-10.3 {multiple data types} -body { + json pretty {{"string":"text","number":42,"bool":true,"null":null,"array":[1,2]}} +} -result {{ + "string": "text", + "number": 42, + "bool": true, + "null": null, + "array": [1,2] +}} +#>>> +test pretty-11.1 {single space indent} -body { + json pretty -indent " " {{"a":"b"}} +} -result {{ + "a": "b" +}} +#>>> +test pretty-11.2 {large indent} -body { + json pretty -indent " " {{"x":"y"}} +} -result {{ + "x": "y" +}} +#>>> +test pretty-11.3 {three space indent with nested} -body { + json pretty -indent " " {{"outer":{"inner":"val"}}} +} -result {{ + "outer": { + "inner": "val" + } +}} +#>>> test debug-1.1 {Basic debug pretty-print} -body { #<<< json debug {{"foo":null,"empty":{},"emptyarr":[],"hello, world":"bar","This is a much longer key":["str",123,123.4,true,false,null,{"inner": "obj"}]}} } -match regexp -result {^\(0x[0-9a-fA-F]+\[[0-9]+\]+/0x[0-9a-fA-F]+\[[0-9]+\]+ [a-z ]+\){ @@ -91,14 +305,6 @@ test debug-1.1 {Basic debug pretty-print} -body { #<<< } \] }$} -#>>> - -# Coverage golf -test pretty-jsonPretty-1.1 {} -body {json pretty -indent} -returnCodes error -result {missing argument to "-indent"} -errorCode {TCL ARGUMENT MISSING} -test pretty-jsonPretty-2.1 {} -body {json pretty -- 1} -result 1 -test pretty-jsonPretty-3.1 {} -body {json pretty -indent { }} -returnCodes error -result {wrong # args: should be "pretty ?-default defaultValue? json_val ?key ...?"} -errorCode {TCL WRONGARGS} -test pretty-jsonPretty-4.1 {} -body {json pretty bad} -returnCodes error -result {Error parsing JSON value: Illegal character at offset 0} -errorCode {RL JSON PARSE {Illegal character} bad 0} -test pretty-jsonPretty-5.1 {} -body {json pretty -bad} -returnCodes error -result {bad option "-bad": must be *} -errorCode {TCL LOOKUP INDEX option -bad} -match glob json free_cache diff --git a/tests/set.test b/tests/set.test index ce5d737..8da79bb 100644 --- a/tests/set.test +++ b/tests/set.test @@ -4,8 +4,7 @@ if {"::tcltest" ni [namespace children]} { } package require rl_json -package require parse_args -namespace path {::rl_json ::parse_args} +namespace path {::rl_json} source [file join [file dirname [info script]] helpers.tcl] diff --git a/tests/unset.test b/tests/unset.test index 1f0dc34..beb76d9 100644 --- a/tests/unset.test +++ b/tests/unset.test @@ -4,8 +4,7 @@ if {"::tcltest" ni [namespace children]} { } package require rl_json -package require parse_args -namespace path {::rl_json ::parse_args} +namespace path {::rl_json} source [file join [file dirname [info script]] helpers.tcl]