diff --git a/.gse b/.gse new file mode 100644 index 0000000..37cced2 --- /dev/null +++ b/.gse @@ -0,0 +1,33 @@ +{ + "sync": { + "enable": true, + "pull": [ + { + "origin": "codeberg", + "branch": "wip" + } + ], + "push": [ + { + "origin": "codeberg", + "branch": "wip" + } + ] + }, + "template": { + "enable": true, + "origin": "fork", + "branch": "master" + }, + "pull": { + "enable": true + }, + "push": { + "enable": true + }, + "remotes": { + "codeberg": "https://codeberg.org/tcltk/fork-rljson", + "github": "https://github.com/teclabat/fork-rljson", + "fork": "https://github.com/RubyLane/rl_json" + } +} diff --git a/README.md b/README.md index e731bfd..57f5712 100644 --- a/README.md +++ b/README.md @@ -21,9 +21,11 @@ json - Parse, manipulate and produce JSON documents **json boolean** *value* **json object** ?*key value* ?*key value …*?? **json array** *elem …* +**json autoarray** ?*value …*? +**json autoobject** ?*key value …*? **json bool** *value* **json normalize** *jsonValue* -**json pretty** ?**-indent** *indent*? *jsonValue* ?*key …*? +**json pretty** ?**-indent** *indent*? ?**-compact**? ?**-nopadding**? ?**-arrays** **inline**|**multiline**? *jsonValue* ?*key …*? **json template** *jsonValue* ?*dictionary*? **json isnull** *jsonValue* ?*key …*? **json type** *jsonValue* ?*key …*? @@ -160,6 +162,49 @@ Return a JSON array containing each of the elements given. *elem* is a list of two elements, the first being the type {string, number, boolean, null, object, array, json}, and the second being the value. +**json autoarray** ?*value …*? +Return a JSON array built from the supplied *value* arguments, with JSON +type detected automatically for each one: + +- Exact string `true` (case-sensitive) → JSON boolean `true` +- Exact string `false` (case-sensitive) → JSON boolean `false` +- Any value that Tcl can interpret as a number → JSON number (Tcl formats + such as `0x1F`, `007`, `0b1010` are converted to their canonical decimal + equivalents) +- Everything else → JSON string + +This is a convenient shorthand for `json array` that avoids having to +specify a type for every element: + +``` tcl +json autoarray 1 2.5 true false "hello" 42 +# → [1,2.5,true,false,"hello",42] + +json autoarray 0x1F 007 +# → [31,7] + +json autoarray True FALSE +# → ["True","FALSE"] (case mismatch → strings) +``` + +**json autoobject** ?*key value …*? +Return a JSON object built from the supplied *key*–*value* pairs, with +JSON type detected automatically for each value (using the same rules as +**json autoarray**). Keys are always treated as strings. + +An odd number of arguments is an error. Duplicate keys are allowed; the +last value for a given key wins. + +``` tcl +json autoobject name "Alice" age 30 active true score 95.5 +# → {"name":"Alice","age":30,"active":true,"score":95.5} + +json autoobject 123 "numeric key" +# → {"123":"numeric key"} + +json autoobject key ;# error: wrong # args +``` + **json foreach** *varList1 jsonValue1* ?*varList2 jsonValue2 …*? *script* Evaluate *script* in a loop in a similar way to the **foreach** command. In each iteration, the values stored in the iterator variables in each @@ -212,11 +257,52 @@ path of *key*s. Return a “normalized” version of the input *jsonValue*, i.e., with all optional whitespace trimmed. -**json pretty** ?**-indent** *indent*? *jsonValue* ?*key …*? +**json pretty** ?**-indent** *indent*? ?**-compact**? ?**-nopadding**? ?**-arrays** **inline**|**multiline**? *jsonValue* ?*key …*? Returns a pretty-printed string representation of *jsonValue*, found by following the path of *key*s. Useful for debugging or inspecting the -structure of JSON data. If **-indent** is supplied, use *indent* for -each level of indent, otherwise default to four spaces. +structure of JSON data. + +Options: + +**-indent** *indent* +Use *indent* for each level of indentation. Defaults to four spaces. + +**-compact** +Return a compact, single-line representation with all optional whitespace +removed (equivalent to **json normalize**). All other formatting options +are ignored when **-compact** is set. + +**-nopadding** +Suppress the automatic alignment of object keys. By default, short keys +in an object are right-padded with spaces so that all values line up +(up to a cap of 20 characters). **-nopadding** disables this alignment. + +**-arrays** **inline**|**multiline** +Control how arrays are formatted: + +- `inline` — always render arrays on a single line: `[1,2,3]` +- `multiline` — always render each array element on its own line +- *(default)* — arrays with three or fewer elements are rendered inline; + larger arrays are rendered multiline + +``` tcl +json pretty {{"foo":null,"arr":[1,2,3]}} +# → +# { +# "foo": null, +# "arr": [1,2,3] +# } + +json pretty -compact {{"foo":"bar","arr":[1,2,3]}} +# → {"foo":"bar","arr":[1,2,3]} + +json pretty -nopadding -arrays multiline {{"foo":1,"longkey":2}} +# → +# { +# "foo": 1, +# "longkey": 2 +# } +``` **json decode** *bytes* ?*encoding*? Rl_json operates on characters, as returned from Tcl’s diff --git a/TECLABCHANGES.md b/TECLABCHANGES.md new file mode 100644 index 0000000..115aaa2 --- /dev/null +++ b/TECLABCHANGES.md @@ -0,0 +1,278 @@ +# TECLAB Changes: upstream rl_json v0.17 + +This document describes all changes made by TERMA/TECLAB on top of the upstream +[RubyLane/rl_json](https://github.com/RubyLane/rl_json) release v0.17. + +## Commit History (TECLAB additions) + +``` +(pending) add TECLABCHANGES.md and update README.md +b16828a adding autoarray, autoobject and pretty enhancements +85fd158 clean tests for tcl86 builds on msys2 +``` + +--- + +## 1. Windows / MinGW64 Build Fix + +### Compile error: `srandom()` / `random()` not available on MinGW64 + +**File:** `teabase/names.c` + +MinGW64 with GCC 15 uses the Windows UCRT which does not expose `srandom()` and +`random()` (POSIX BSD extensions) even when `_POSIX_C_SOURCE` is defined. + +**Fix:** Added an `#ifdef __MINGW32__` compatibility block after the includes: + +```c +#ifdef __MINGW32__ +/* MinGW64 UCRT does not expose random()/srandom() even with _POSIX_C_SOURCE */ +#define srandom(seed) srand((unsigned int)(seed)) +#define random() ((long)rand()) +#endif +``` + +Linux builds continue using `random()`/`srandom()` unchanged. + +--- + +## 2. Windows Test Suite Fix + +### Test failures due to cp1252 encoding on Windows + +**File:** `tests/all.tcl` + +On Windows, `tclsh` defaults to the system encoding (`cp1252`). When tcltest +spawns subprocess instances for each test file, those subprocesses also start +with `cp1252`. Test files contain non-ASCII literals (Japanese characters etc.) +encoded as UTF-8. Reading them as cp1252 causes encoding mismatches between the +expected and actual test results. + +**Symptoms (before fix):** +- `\u` escape decoding tests failing (expected `は` read as 3 cp1252 bytes) +- `?length` returning byte count instead of character count +- Character offset in parse errors off by 6 (bytes vs chars) + +**Fix:** Added encoding setup and forced single-process mode in `all.tcl` for +non-UTF-8 systems, placed before the `runAllTests` call: + +```tcl +# On Windows, tcltest's subprocess mode spawns fresh tclsh processes that +# inherit the system encoding (cp1252) rather than utf-8. Force single-process +# mode on non-utf-8 systems so test files are sourced in-process after the +# encoding is switched. +if {[encoding system] ne "utf-8"} { + encoding system utf-8 + fconfigure stdout -encoding utf-8 + fconfigure stderr -encoding utf-8 +} +... +configure ... {*}[expr {$_singleproc_for_encoding ? {-singleproc 1} : {}}] ... +``` + +Linux/Mac builds (already UTF-8) are unaffected. + +--- + +## 3. New `json autoarray` Command + +Creates a JSON array from Tcl values with automatic type detection, eliminating +the need for explicit type specification. + +**Syntax:** +```tcl +json autoarray ?value ...? +``` + +**Type detection (applied to each value):** +1. Exact `"true"` (case-sensitive) → JSON boolean `true` +2. Exact `"false"` (case-sensitive) → JSON boolean `false` +3. Valid JSON number (via `force_json_number`) → JSON number +4. Anything else → JSON string + +**Examples:** +```tcl +json autoarray 1 2.5 true false "hello" 42 +# → [1,2.5,true,false,"hello",42] + +json autoarray 0x1F 007 0b1010 +# → [31,7,10] (Tcl number formats converted to canonical JSON) + +json autoarray True FALSE +# → ["True","FALSE"] (not exact match → strings) +``` + +**Files:** +- `generic/rl_json.c`: `jsonAutoArray()` implementation; dispatch table wiring +- `tests/autoarray.test`: 33 new tests +- `README.md`: synopsis entry and full command description added + +--- + +## 4. New `json autoobject` Command + +Creates a JSON object from key-value pairs with automatic type detection for +values, complementing `json autoarray`. + +**Syntax:** +```tcl +json autoobject ?key value ...? +``` + +**Features:** +- Requires an even number of arguments (key-value pairs); odd count → error +- Keys are always JSON strings +- Values undergo the same automatic type detection as `json autoarray` +- Duplicate keys: last value wins (standard Tcl dict behaviour) + +**Examples:** +```tcl +json autoobject name "Alice" age 30 active true score 95.5 +# → {"name":"Alice","age":30,"active":true,"score":95.5} + +json autoobject key ;# error: wrong # args +``` + +**Files:** +- `generic/rl_json.c`: `jsonAutoObject()` implementation; dispatch table wiring +- `tests/autoobject.test`: 31 new tests +- `README.md`: synopsis entry and full command description added + +--- + +## 5. Enhanced `json pretty` Command + +Upstream v0.17 removed three options that existed in the TECLAB fork. They have +been restored. + +**Full syntax:** +```tcl +json pretty ?-indent indent? ?-compact? ?-nopadding? ?-arrays inline|multiline? json_val ?key ...? +``` + +### Option: `-compact` + +Returns a single-line compact representation with no extra whitespace (equivalent +to `json normalize`). + +```tcl +json pretty -compact {{"foo":"bar","arr":[1,2,3]}} +# → {"foo":"bar","arr":[1,2,3]} +``` + +### Option: `-nopadding` + +Suppresses the automatic alignment padding of object keys. + +```tcl +# Default (keys aligned): +json pretty {{"foo":1,"longerkey":2}} +# → { +# "foo": 1, +# "longerkey": 2 +# } + +# With -nopadding: +json pretty -nopadding {{"foo":1,"longerkey":2}} +# → { +# "foo": 1, +# "longerkey": 2 +# } +``` + +### Option: `-arrays inline|multiline` + +Controls array formatting: + +| Mode | Behaviour | +|------|-----------| +| `inline` | All arrays on one line: `[1,2,3,4,5]` | +| `multiline` | Each element on its own line | +| *(default)* | Arrays with ≤3 elements inline; larger arrays multiline | + +```tcl +json pretty -arrays inline {[1,2,3,4,5]} ;# → [1,2,3,4,5] +json pretty -arrays multiline {[1,2,3]} ;# → each on its own line +``` + +**Files:** +- `generic/rl_json.c`: `jsonPretty()` option parsing; `json_pretty()` and + `json_pretty_dbg()` body updated (nopadding gate, inline/multiline array logic) +- `generic/api.c`: `JSON_Pretty()` — signature extended to 7 params; compact + fast-path added +- `generic/rl_jsonInt.h`: `json_pretty()` forward declaration updated +- `generic/rl_jsonDecls.h`: `JSON_Pretty` stub declaration and struct field updated +- `generic/rl_json.decls`: Stubs source declaration updated +- `tests/pretty.test`: 18 new tests; 2 existing tests updated for new option set +- `README.md`: synopsis and per-option documentation updated + +--- + +## API Changes + +### Modified C API + +**`JSON_Pretty()`** — signature extended: + +```c +/* Upstream v0.17: */ +int JSON_Pretty(Tcl_Interp* interp, Tcl_Obj* obj, Tcl_Obj* indent, + Tcl_Obj** prettyString) + +/* TECLAB: */ +int JSON_Pretty(Tcl_Interp* interp, Tcl_Obj* obj, Tcl_Obj* indent, + int nopadding, int compact, int arrays_inline, + Tcl_Obj** prettyString) +``` + +**`json_pretty()`** — internal function signature extended: + +```c +/* Upstream v0.17: */ +int json_pretty(Tcl_Interp* interp, Tcl_Obj* json, Tcl_Obj* indent, + Tcl_Obj* pad, Tcl_DString* ds) + +/* TECLAB: */ +int json_pretty(Tcl_Interp* interp, Tcl_Obj* json, Tcl_Obj* indent, + int nopadding, Tcl_Obj* pad, int arrays_inline, + Tcl_DString* ds) +``` + +### New Tcl Commands + +```tcl +json autoarray ?value ...? +json autoobject ?key value ...? +``` + +### Enhanced Tcl Commands + +```tcl +json pretty ?-indent indent? ?-compact? ?-nopadding? ?-arrays inline|multiline? json_val ?key ...? +``` + +--- + +## Backward Compatibility + +| Change | Impact | +|--------|--------| +| `JSON_Pretty()` C signature | **Breaking** for C extension code calling this directly | +| `json_pretty()` internal signature | Internal only; no external impact | +| New Tcl commands `autoarray`/`autoobject` | Additive; no impact on existing scripts | +| Pretty new options | Additive; defaults preserve previous behaviour | +| Test suite encoding fix | No user-visible change; fixes false failures on Windows | + +--- + +## Documentation and Test Summary + +| File | Status | Notes | +|------|--------|-------| +| `README.md` | Updated | autoarray, autoobject sections added; pretty options documented | +| `TECLABCHANGES.md` | New | This file | +| `tests/autoarray.test` | New | 33 tests | +| `tests/autoobject.test` | New | 31 tests | +| `tests/pretty.test` | Updated | +18 new tests, 2 updated for new option set | +| `tests/all.tcl` | Updated | UTF-8 encoding + singleproc fix for Windows | +| `teabase/names.c` | Updated | MinGW64 compile fix | diff --git a/generic/api.c b/generic/api.c index d069e40..eca6780 100644 --- a/generic/api.c +++ b/generic/api.c @@ -943,7 +943,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; @@ -951,6 +951,9 @@ 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); + if (compact) + return JSON_Normalize(interp, obj, prettyString); + if (indent == NULL) { replace_tclobj(&lindent, get_string(l, " ", 4)); indent = lindent; @@ -958,7 +961,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 1d4b77e..2082767 100644 --- a/generic/rl_json.c +++ b/generic/rl_json.c @@ -120,7 +120,7 @@ static const char *extension_str[] = { static int new_json_value_from_list(Tcl_Interp* interp, int objc, Tcl_Obj *const objv[], Tcl_Obj** res); static int NRforeach_next_loop_bottom(ClientData cdata[], Tcl_Interp* interp, int retcode); -static int json_pretty_dbg(Tcl_Interp* interp, Tcl_Obj* json, Tcl_Obj* indent, Tcl_Obj* pad, Tcl_DString* ds); +static int json_pretty_dbg(Tcl_Interp* interp, Tcl_Obj* json, Tcl_Obj* indent, int nopadding, Tcl_Obj* pad, int arrays_inline, Tcl_DString* ds); static int _setdir(Tcl_Interp* interp) //{{{ { @@ -1622,7 +1622,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) //{{{ { Tcl_Size pad_len, next_pad_len, count; enum json_types type; @@ -1659,17 +1659,19 @@ int json_pretty(Tcl_Interp* interp, Tcl_Obj* json, Tcl_Obj* indent, Tcl_Obj* pad break; } - TEST_OK_LABEL(finally, retval, Tcl_DictObjFirst(interp, val, &search, &k, &v, &done)); + if (!nopadding) { + 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); + 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); @@ -1685,11 +1687,13 @@ 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 (!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, next_pad, ds) != TCL_OK) { + if (json_pretty(interp, v, indent, nopadding, next_pad, arrays_inline, ds) != TCL_OK) { Tcl_DictObjDone(&search); retval = TCL_ERROR; goto finally; @@ -1713,21 +1717,36 @@ int json_pretty(Tcl_Interp* interp, Tcl_Obj* json, Tcl_Obj* indent, Tcl_Obj* pad { Tcl_Size i, oc; Tcl_Obj** ov; + int should_inline; TEST_OK_LABEL(finally, retval, Tcl_ListObjGetElements(interp, val, &oc, &ov)); + if (oc == 0) { + Tcl_DStringAppend(ds, "[]", 2); + break; + } + + // arrays_inline: 1=force inline, 0=force multiline, -1=auto (≤3 inline) + should_inline = (arrays_inline == 1) || (arrays_inline == -1 && oc <= 3); + replace_tclobj(&next_pad, Tcl_DuplicateObj(pad)); Tcl_AppendObjToObj(next_pad, indent); next_pad_str = Tcl_GetStringFromObj(next_pad, &next_pad_len); - if (oc == 0) { - Tcl_DStringAppend(ds, "[]", 2); + if (should_inline) { + Tcl_DStringAppend(ds, "[", 1); + for (i=0; i max) - max = k_len; - } - Tcl_DictObjDone(&search); + 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); @@ -1830,11 +1851,13 @@ static int json_pretty_dbg(Tcl_Interp* interp, Tcl_Obj* json, Tcl_Obj* indent, T 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 (!nopadding) { + Tcl_GetStringFromObj(k, &k_len); + if (k_len < max) + Tcl_DStringAppend(ds, key_pad_buf, max-k_len); + } - if (json_pretty_dbg(interp, v, indent, next_pad, ds) != TCL_OK) { + if (json_pretty_dbg(interp, v, indent, nopadding, next_pad, arrays_inline, ds) != TCL_OK) { Tcl_DictObjDone(&search); retval = TCL_ERROR; goto finally; @@ -1858,21 +1881,35 @@ static int json_pretty_dbg(Tcl_Interp* interp, Tcl_Obj* json, Tcl_Obj* indent, T { Tcl_Size i, oc; Tcl_Obj** ov; + int should_inline; TEST_OK_LABEL(finally, retval, Tcl_ListObjGetElements(interp, val, &oc, &ov)); + if (oc == 0) { + Tcl_DStringAppend(ds, "[]", 2); + break; + } + + should_inline = (arrays_inline == 1) || (arrays_inline == -1 && oc <= 3); + replace_tclobj(&next_pad, Tcl_DuplicateObj(pad)); Tcl_AppendObjToObj(next_pad, indent); next_pad_str = Tcl_GetStringFromObj(next_pad, &next_pad_len); - if (oc == 0) { - Tcl_DStringAppend(ds, "[]", 2); + if (should_inline) { + Tcl_DStringAppend(ds, "[", 1); + for (i=0; ijson_true)); + } else if (len == 5 && strcmp(str, "false") == 0) { + replace_tclobj(&elem, JSON_NewJvalObj(JSON_BOOL, l->json_false)); + } else { + int is_number = (force_json_number(interp, l, objv[i], &forced) == TCL_OK); + if (is_number) { + replace_tclobj(&elem, JSON_NewJvalObj(JSON_NUMBER, forced)); + release_tclobj(&forced); + } else { + 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 jsonAutoObject(ClientData cdata, Tcl_Interp* interp, int objc, Tcl_Obj *const objv[]) //{{{ +{ + struct interp_cx* l = (struct interp_cx*)cdata; + int i, retval = TCL_OK; + Tcl_Obj* elem = NULL; + Tcl_Obj* dict = NULL; + Tcl_Obj* forced = NULL; + const char* str; + Tcl_Size len; + + if ((objc - 1) % 2 != 0) { + Tcl_SetObjResult(interp, Tcl_ObjPrintf("wrong # args: should be \"json autoobject ?key value ...?\"")); + Tcl_SetErrorCode(interp, "TCL", "WRONGARGS", NULL); + retval = TCL_ERROR; + goto finally; + } + + replace_tclobj(&dict, Tcl_NewDictObj()); + + for (i=1; ijson_true)); + } else if (len == 5 && strcmp(str, "false") == 0) { + replace_tclobj(&elem, JSON_NewJvalObj(JSON_BOOL, l->json_false)); + } else { + int is_number = (force_json_number(interp, l, objv[i+1], &forced) == TCL_OK); + if (is_number) { + replace_tclobj(&elem, JSON_NewJvalObj(JSON_NUMBER, forced)); + release_tclobj(&forced); + } else { + Tcl_ResetResult(interp); + replace_tclobj(&elem, JSON_NewJvalObj(JSON_STRING, objv[i+1])); + } + } + + TEST_OK_LABEL(finally, retval, Tcl_DictObjPut(interp, dict, objv[i], elem)); + } + Tcl_SetObjResult(interp, JSON_NewJvalObj(JSON_OBJECT, dict)); + +finally: + release_tclobj(&elem); + release_tclobj(&dict); + release_tclobj(&forced); + return retval; +} + //}}} static int jsonPretty(ClientData cdata, Tcl_Interp* interp, int objc, Tcl_Obj *const objv[]) //{{{ { @@ -3583,18 +3711,33 @@ 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=auto (≤3 inline), 0=multiline, 1=inline static const char* opts[] = { "-indent", - "--", // Unnecessary for this case, but supported for convention + "-compact", + "-nopadding", + "-arrays", + "--", 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) { @@ -3602,7 +3745,7 @@ static int jsonPretty(ClientData cdata, Tcl_Interp* interp, int objc, Tcl_Obj *c enum json_types type = JSON_GetJSONType(objv[argbase]); if (type != JSON_UNDEF) break; // Arg is already a JSON value, stop consuming options - const char* str = Tcl_GetString(objv[argbase]); // If it's not a native json value we will need this string rep to parse for the next step, so potientially regenerating the stringrep here isn't a concern + const char* str = Tcl_GetString(objv[argbase]); if (str[0] != '-') break; // Not an option TEST_OK_LABEL(finally, code, Tcl_GetIndexFromObj(interp, objv[argbase], opts, "option", TCL_EXACT, &optidx)); @@ -3616,18 +3759,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; } @@ -3639,7 +3804,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); @@ -3780,7 +3945,7 @@ static int jsonDebug(ClientData cdata, Tcl_Interp* interp, int objc, Tcl_Obj *co ); replace_tclobj(&pad, l->tcl_empty); - TEST_OK_LABEL(finally, retval, json_pretty_dbg(interp, objv[A_VAL], indent, pad, &ds)); + TEST_OK_LABEL(finally, retval, json_pretty_dbg(interp, objv[A_VAL], indent, 0, pad, -1, &ds)); Tcl_DStringResult(interp, &ds); finally: @@ -3994,6 +4159,8 @@ static int jsonNRObj(ClientData cdata, Tcl_Interp* interp, int objc, Tcl_Obj *co "lmap", "amap", "omap", + "autoarray", + "autoobject", "pretty", "valid", "debug", @@ -4038,6 +4205,8 @@ static int jsonNRObj(ClientData cdata, Tcl_Interp* interp, int objc, Tcl_Obj *co M_LMAP, M_AMAP, M_OMAP, + M_AUTOARRAY, + M_AUTOOBJECT, M_PRETTY, M_VALID, M_DEBUG, @@ -4094,6 +4263,8 @@ static int jsonNRObj(ClientData cdata, Tcl_Interp* interp, int objc, Tcl_Obj *co case M_LMAP: return jsonNRLmap(cdata, interp, objc-1, objv+1); case M_AMAP: return jsonNRAmap(cdata, interp, objc-1, objv+1); case M_OMAP: return jsonNROmap(cdata, interp, objc-1, objv+1); + case M_AUTOARRAY: return jsonAutoArray(cdata, interp, objc-1, objv+1); + case M_AUTOOBJECT: return jsonAutoObject(cdata, interp, objc-1, objv+1); case M_PRETTY: return jsonPretty(cdata, interp, objc-1, objv+1); case M_VALID: return jsonValid(cdata, interp, objc-1, objv+1); case M_DEBUG: return jsonDebug(cdata, interp, objc-1, objv+1); @@ -4571,6 +4742,8 @@ DLLEXPORT int Rl_json_Init(Tcl_Interp* interp) //{{{ Tcl_ListObjAppendElement(NULL, subcommands, Tcl_NewStringObj("lmap", -1)); Tcl_ListObjAppendElement(NULL, subcommands, Tcl_NewStringObj("amap", -1)); Tcl_ListObjAppendElement(NULL, subcommands, Tcl_NewStringObj("omap", -1)); + Tcl_ListObjAppendElement(NULL, subcommands, Tcl_NewStringObj("autoarray", -1)); + Tcl_ListObjAppendElement(NULL, subcommands, Tcl_NewStringObj("autoobject", -1)); Tcl_ListObjAppendElement(NULL, subcommands, Tcl_NewStringObj("free_cache", -1)); Tcl_ListObjAppendElement(NULL, subcommands, Tcl_NewStringObj("nop", -1)); Tcl_ListObjAppendElement(NULL, subcommands, Tcl_NewStringObj("pretty", -1)); @@ -4608,6 +4781,8 @@ DLLEXPORT int Rl_json_Init(Tcl_Interp* interp) //{{{ Tcl_NRCreateCommand(interp, ENS "lmap", jsonLmap, jsonNRLmap, l, NULL); Tcl_NRCreateCommand(interp, ENS "amap", jsonAmap, jsonNRAmap, l, NULL); Tcl_NRCreateCommand(interp, ENS "omap", jsonOmap, jsonNROmap, l, NULL); + Tcl_CreateObjCommand(interp, ENS "autoarray", jsonAutoArray, l, NULL); + Tcl_CreateObjCommand(interp, ENS "autoobject", jsonAutoObject, l, NULL); Tcl_CreateObjCommand(interp, ENS "free_cache", jsonFreeCache, l, NULL); Tcl_CreateObjCommand(interp, ENS "nop", jsonNop, l, NULL); Tcl_CreateObjCommand(interp, ENS "pretty", jsonPretty, l, NULL); diff --git a/generic/rl_json.decls b/generic/rl_json.decls index 0310d50..00fbf86 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 cec323f..273a761 100644 --- a/generic/rl_jsonDecls.h +++ b/generic/rl_jsonDecls.h @@ -90,7 +90,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); @@ -156,7 +157,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 */ diff --git a/generic/rl_jsonInt.h b/generic/rl_jsonInt.h index e97811a..c015a05 100644 --- a/generic/rl_jsonInt.h +++ b/generic/rl_jsonInt.h @@ -320,7 +320,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[], Tcl_Size 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/tests/all.tcl b/tests/all.tcl index 53c1d77..df9b2cc 100644 --- a/tests/all.tcl +++ b/tests/all.tcl @@ -14,7 +14,15 @@ package prefer latest package require tcltest 2.5 namespace import ::tcltest::* -configure -testdir [file normalize [file dirname [info script]]] {*}$argv +# On Windows, tcltest's subprocess mode (default) spawns fresh tclsh processes +# that inherit the system encoding (cp1252) rather than the utf-8 encoding set +# below. Force single-process mode on non-utf-8 systems so that test files are +# sourced in-process after the encoding is switched. +set _singleproc_for_encoding [expr {[encoding system] ne "utf-8"}] +configure -testdir [file normalize [file dirname [info script]]] \ + {*}[expr {$_singleproc_for_encoding ? {-singleproc 1} : {}}] \ + {*}$argv +unset _singleproc_for_encoding if {[singleProcess]} { interp debug {} -frame 1 @@ -237,6 +245,16 @@ if {$checkmem && [llength [info commands memory]] == 1} { proc memtest args {tailcall ::tcltest::test {*}$args} } +# On Windows, tclsh defaults to the system encoding (e.g. cp1252). +# Test files are UTF-8; force UTF-8 before runAllTests sources them so +# non-ASCII literals (Japanese characters etc.) are read correctly. +# The old all.tcl achieved this via explicit "source -encoding utf-8". +if {[encoding system] ne "utf-8"} { + encoding system utf-8 + fconfigure stdout -encoding utf-8 + fconfigure stderr -encoding utf-8 +} + set ErrorOnFailures [info exists env(ERROR_ON_FAILURES)] unset -nocomplain env(ERROR_ON_FAILURES) if {[runAllTests] && $ErrorOnFailures} {exit 1} diff --git a/tests/autoarray.test b/tests/autoarray.test new file mode 100644 index 0000000..ea2c015 --- /dev/null +++ b/tests/autoarray.test @@ -0,0 +1,231 @@ +source [file join [file dirname [info script]] common.tcl]; runtests { + +# Basic functionality tests +test autoarray-1.1 {Empty autoarray} -body { #<<< + json autoarray +} -result {[]} +#>>> +test autoarray-1.2 {Single string} -body { #<<< + json autoarray "hello" +} -result {["hello"]} +#>>> +test autoarray-1.3 {Single number - integer} -body { #<<< + json autoarray 42 +} -result {[42]} +#>>> +test autoarray-1.4 {Single number - float} -body { #<<< + json autoarray 3.14 +} -result {[3.14]} +#>>> +test autoarray-1.5 {Single boolean - true} -body { #<<< + json autoarray true +} -result {[true]} +#>>> +test autoarray-1.6 {Single boolean - false} -body { #<<< + json autoarray false +} -result {[false]} +#>>> + +# Mixed type arrays +test autoarray-2.1 {Mixed types - numbers and strings} -body { #<<< + json autoarray 1 "hello" 2 "world" +} -result {[1,"hello",2,"world"]} +#>>> +test autoarray-2.2 {Mixed types - all types} -body { #<<< + json autoarray 42 "hello" true 3.14 false "world" +} -result {[42,"hello",true,3.14,false,"world"]} +#>>> +test autoarray-2.3 {Mixed types with spaces in strings} -body { #<<< + json autoarray 1 "hello world" 2 "foo bar" +} -result {[1,"hello world",2,"foo bar"]} +#>>> + +# Number format tests +test autoarray-3.1 {Negative integer} -body { #<<< + json autoarray -42 +} -result {[-42]} +#>>> +test autoarray-3.2 {Negative float} -body { #<<< + json autoarray -3.14 +} -result {[-3.14]} +#>>> +test autoarray-3.3 {Zero} -body { #<<< + json autoarray 0 +} -result {[0]} +#>>> +test autoarray-3.4 {Large integer} -body { #<<< + json autoarray 9223372036854775807 +} -result {[9223372036854775807]} +#>>> + +# Boolean case sensitivity tests +test autoarray-4.1 {Boolean false case - True (not a boolean)} -body { #<<< + json autoarray True +} -result {["True"]} +#>>> +test autoarray-4.2 {Boolean false case - TRUE (not a boolean)} -body { #<<< + json autoarray TRUE +} -result {["TRUE"]} +#>>> +test autoarray-4.3 {Boolean false case - False (not a boolean)} -body { #<<< + json autoarray False +} -result {["False"]} +#>>> +test autoarray-4.4 {Boolean false case - FALSE (not a boolean)} -body { #<<< + json autoarray FALSE +} -result {["FALSE"]} +#>>> +test autoarray-4.5 {Exact boolean - true} -body { #<<< + json autoarray true +} -result {[true]} +#>>> +test autoarray-4.6 {Exact boolean - false} -body { #<<< + json autoarray false +} -result {[false]} +#>>> + +# Tcl number format handling +test autoarray-5.1 {Octal number with leading zeros} -body { #<<< + json autoarray 007 +} -result {[7]} +#>>> +test autoarray-5.2 {Number with plus sign} -body { #<<< + json autoarray +42 +} -result {[42]} +#>>> +test autoarray-5.3 {Hex number format} -body { #<<< + json autoarray 0x1F +} -result {[31]} +#>>> +test autoarray-5.4 {Binary number format} -constraints binary_literals -body { #<<< + json autoarray 0b1010 +} -result {[10]} +#>>> + +# Empty and whitespace strings +test autoarray-6.1 {Empty string} -body { #<<< + json autoarray "" +} -result {[""]} +#>>> +test autoarray-6.2 {String with only spaces} -body { #<<< + json autoarray " " +} -result {[" "]} +#>>> +test autoarray-6.3 {String "true" with leading space is a string} -body { #<<< + json autoarray " true" +} -result {[" true"]} +#>>> + +# Special string values +test autoarray-7.1 {String "null" is a string, not null} -body { #<<< + json autoarray null +} -result {["null"]} +#>>> +test autoarray-7.2 {String "undefined"} -body { #<<< + json autoarray undefined +} -result {["undefined"]} +#>>> + +# Multiple identical values +test autoarray-8.1 {Multiple true values} -body { #<<< + json autoarray true true true +} -result {[true,true,true]} +#>>> +test autoarray-8.2 {Multiple false values} -body { #<<< + json autoarray false false false +} -result {[false,false,false]} +#>>> +test autoarray-8.3 {Multiple same numbers} -body { #<<< + json autoarray 42 42 42 +} -result {[42,42,42]} +#>>> + +# Real-world usage examples +test autoarray-9.1 {Array of coordinates} -body { #<<< + json autoarray 10.5 20.3 30.1 40.9 +} -result {[10.5,20.3,30.1,40.9]} +#>>> +test autoarray-9.2 {Array of names} -body { #<<< + json autoarray "Alice" "Bob" "Charlie" +} -result {["Alice","Bob","Charlie"]} +#>>> +test autoarray-9.3 {Array of flags} -body { #<<< + json autoarray true false true true false +} -result {[true,false,true,true,false]} +#>>> + +# Comparison with json array command +test autoarray-10.1 {Compare autoarray vs array - strings} -body { #<<< + set auto [json autoarray "hello" "world"] + set manual [json array {string hello} {string world}] + expr {$auto eq $manual} +} -cleanup { + unset -nocomplain auto manual +} -result {1} +#>>> +test autoarray-10.2 {Compare autoarray vs array - numbers} -body { #<<< + set auto [json autoarray 1 2 3] + set manual [json array {number 1} {number 2} {number 3}] + expr {$auto eq $manual} +} -cleanup { + unset -nocomplain auto manual +} -result {1} +#>>> +test autoarray-10.3 {Compare autoarray vs array - booleans} -body { #<<< + set auto [json autoarray true false] + set manual [json array {boolean true} {boolean false}] + expr {$auto eq $manual} +} -cleanup { + unset -nocomplain auto manual +} -result {1} +#>>> + +# Type verification via json type +test autoarray-11.1 {Number type preserved} -body { #<<< + set arr [json autoarray 42] + json type $arr 0 +} -cleanup { + unset -nocomplain arr +} -result {number} +#>>> +test autoarray-11.2 {String type preserved} -body { #<<< + set arr [json autoarray "hello"] + json type $arr 0 +} -cleanup { + unset -nocomplain arr +} -result {string} +#>>> +test autoarray-11.3 {Boolean type preserved} -body { #<<< + set arr [json autoarray true] + json type $arr 0 +} -cleanup { + unset -nocomplain arr +} -result {boolean} +#>>> + +# String escaping +test autoarray-12.1 {String with quotes} -body { #<<< + json autoarray {He said "hello"} +} -result {["He said \"hello\""]} +#>>> +test autoarray-12.2 {String with backslashes} -body { #<<< + json autoarray {C:\path\to\file} +} -result {["C:\\path\\to\\file"]} +#>>> + +# Large arrays +test autoarray-13.1 {Array with 100 numbers} -body { #<<< + set nums [lmap i [lrepeat 100 0] {expr {$i + 1}}] + set result [json autoarray {*}$nums] + json length $result +} -cleanup { + unset -nocomplain nums result i +} -result {100} +#>>> + +} +# Local Variables: +# mode: tcl +# tab-width: 4 +# End: +# vim: ft=tcl foldmethod=marker foldmarker=<<<,>>> ts=4 shiftwidth=4 diff --git a/tests/autoobject.test b/tests/autoobject.test new file mode 100644 index 0000000..141c013 --- /dev/null +++ b/tests/autoobject.test @@ -0,0 +1,215 @@ +source [file join [file dirname [info script]] common.tcl]; runtests { + +# Basic functionality tests +test autoobject-1.1 {Empty autoobject} -body { #<<< + json autoobject +} -result {{}} +#>>> +test autoobject-1.2 {Single key-value pair - string} -body { #<<< + json autoobject name "Alice" +} -result {{"name":"Alice"}} +#>>> +test autoobject-1.3 {Single key-value pair - number} -body { #<<< + json autoobject age 30 +} -result {{"age":30}} +#>>> +test autoobject-1.4 {Single key-value pair - boolean true} -body { #<<< + json autoobject active true +} -result {{"active":true}} +#>>> +test autoobject-1.5 {Single key-value pair - boolean false} -body { #<<< + json autoobject enabled false +} -result {{"enabled":false}} +#>>> +test autoobject-1.6 {Multiple key-value pairs - mixed types} -body { #<<< + json autoobject name "Bob" age 25 active true score 95.5 +} -result {{"name":"Bob","age":25,"active":true,"score":95.5}} +#>>> + +# Argument validation tests +test autoobject-2.1 {Odd number of arguments - single arg} -body { #<<< + json autoobject key +} -returnCodes error -match glob -result {wrong # args: should be "json autoobject ?key value ...?"} +#>>> +test autoobject-2.2 {Odd number of arguments - three args} -body { #<<< + json autoobject key1 value1 key2 +} -returnCodes error -match glob -result {wrong # args: should be "json autoobject ?key value ...?"} +#>>> +test autoobject-2.3 {Odd number of arguments - five args} -body { #<<< + json autoobject a 1 b 2 c +} -returnCodes error -match glob -result {wrong # args: should be "json autoobject ?key value ...?"} +#>>> + +# Value type detection tests +test autoobject-3.1 {String values} -body { #<<< + json autoobject key1 "hello" key2 "world" +} -result {{"key1":"hello","key2":"world"}} +#>>> +test autoobject-3.2 {Integer values} -body { #<<< + json autoobject a 1 b 42 c -100 +} -result {{"a":1,"b":42,"c":-100}} +#>>> +test autoobject-3.3 {Float values} -body { #<<< + json autoobject pi 3.14159 e 2.71828 +} -result {{"pi":3.14159,"e":2.71828}} +#>>> +test autoobject-3.4 {Boolean true (exact match)} -body { #<<< + json autoobject flag true +} -result {{"flag":true}} +#>>> +test autoobject-3.5 {Boolean false (exact match)} -body { #<<< + json autoobject flag false +} -result {{"flag":false}} +#>>> +test autoobject-3.6 {Boolean case sensitivity - True is a string} -body { #<<< + json autoobject flag True +} -result {{"flag":"True"}} +#>>> +test autoobject-3.7 {Boolean case sensitivity - TRUE is a string} -body { #<<< + json autoobject flag TRUE +} -result {{"flag":"TRUE"}} +#>>> +test autoobject-3.8 {Boolean case sensitivity - False is a string} -body { #<<< + json autoobject flag False +} -result {{"flag":"False"}} +#>>> +test autoobject-3.9 {Boolean case sensitivity - FALSE is a string} -body { #<<< + json autoobject flag FALSE +} -result {{"flag":"FALSE"}} +#>>> + +# Key handling tests +test autoobject-4.1 {String keys} -body { #<<< + json autoobject name "Alice" city "Paris" +} -result {{"name":"Alice","city":"Paris"}} +#>>> +test autoobject-4.2 {Numeric-looking keys are strings} -body { #<<< + json autoobject 123 "value1" 456 "value2" +} -result {{"123":"value1","456":"value2"}} +#>>> +test autoobject-4.3 {Empty string key} -body { #<<< + json autoobject "" "empty key" +} -result {{"":"empty key"}} +#>>> +test autoobject-4.4 {Duplicate keys - last value wins} -body { #<<< + json autoobject name "Alice" age 25 name "Bob" +} -result {{"name":"Bob","age":25}} +#>>> +test autoobject-4.5 {Keys with special characters} -body { #<<< + json autoobject "first-name" "Alice" "last.name" "Smith" +} -result {{"first-name":"Alice","last.name":"Smith"}} +#>>> + +# Mixed type tests +test autoobject-5.1 {Mixed strings, numbers, booleans} -body { #<<< + json autoobject name "Alice" age 30 active true score 95.5 verified false count 10 +} -result {{"name":"Alice","age":30,"active":true,"score":95.5,"verified":false,"count":10}} +#>>> +test autoobject-5.2 {Empty string value} -body { #<<< + json autoobject key "" +} -result {{"key":""}} +#>>> +test autoobject-5.3 {String "null" is a string} -body { #<<< + json autoobject key null +} -result {{"key":"null"}} +#>>> + +# Equivalence tests +test autoobject-6.1 {Compare autoobject vs manual object - strings} -body { #<<< + set auto [json autoobject name "Alice" city "Paris"] + set manual [json object name {string "Alice"} city {string "Paris"}] + expr {$auto eq $manual} +} -cleanup { + unset -nocomplain auto manual +} -result {1} +#>>> +test autoobject-6.2 {Compare autoobject vs manual object - numbers} -body { #<<< + set auto [json autoobject age 30 score 95.5] + set manual [json object age {number 30} score {number 95.5}] + expr {$auto eq $manual} +} -cleanup { + unset -nocomplain auto manual +} -result {1} +#>>> +test autoobject-6.3 {Compare autoobject vs manual object - booleans} -body { #<<< + set auto [json autoobject active true verified false] + set manual [json object active {boolean true} verified {boolean false}] + expr {$auto eq $manual} +} -cleanup { + unset -nocomplain auto manual +} -result {1} +#>>> + +# Edge cases +test autoobject-7.1 {Zero value} -body { #<<< + json autoobject count 0 +} -result {{"count":0}} +#>>> +test autoobject-7.2 {Values with quotes} -body { #<<< + json autoobject quote {He said "hello"} +} -result {{"quote":"He said \"hello\""}} +#>>> +test autoobject-7.3 {Values with backslashes} -body { #<<< + json autoobject path {C:\Windows\System32} +} -result {{"path":"C:\\Windows\\System32"}} +#>>> + +# Tcl number format handling +test autoobject-8.1 {Octal format converts to decimal} -body { #<<< + json autoobject val 007 +} -result {{"val":7}} +#>>> +test autoobject-8.2 {Hex format converts to decimal} -body { #<<< + json autoobject val 0x1F +} -result {{"val":31}} +#>>> +test autoobject-8.3 {Binary format converts to decimal} -constraints binary_literals -body { #<<< + json autoobject val 0b1010 +} -result {{"val":10}} +#>>> + +# Type verification +test autoobject-9.1 {Result is JSON object type} -body { #<<< + json type [json autoobject name "Alice"] +} -result {object} +#>>> +test autoobject-9.2 {Empty result is JSON object type} -body { #<<< + json type [json autoobject] +} -result {object} +#>>> + +# Key extraction +test autoobject-10.1 {Extract value by key} -body { #<<< + set obj [json autoobject name "Alice" age 30] + json get $obj name +} -cleanup { + unset -nocomplain obj +} -result {Alice} +#>>> +test autoobject-10.2 {Get all keys} -body { #<<< + set obj [json autoobject name "Alice" age 30 active true] + lsort [json get $obj ?keys] +} -cleanup { + unset -nocomplain obj +} -result {active age name} +#>>> + +# Large object +test autoobject-11.1 {Large object with many keys} -body { #<<< + set pairs {} + for {set i 0} {$i < 50} {incr i} { + lappend pairs "key$i" $i + } + set obj [json autoobject {*}$pairs] + json get $obj ?size +} -cleanup { + unset -nocomplain pairs obj i +} -result {50} +#>>> + +} +# Local Variables: +# mode: tcl +# tab-width: 4 +# End: +# vim: ft=tcl foldmethod=marker foldmarker=<<<,>>> ts=4 shiftwidth=4 diff --git a/tests/pretty.test b/tests/pretty.test index 44a76f2..000acc3 100644 --- a/tests/pretty.test +++ b/tests/pretty.test @@ -44,7 +44,7 @@ test pretty-1.2 {Basic pretty-print, different indent} -body { #<<< #>>> test pretty-2.1 {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-3.1 {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} @@ -89,9 +89,94 @@ 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-3.1 {} -body {json pretty -indent { }} -returnCodes error -match glob -result {wrong # args: should be "pretty ?-indent indent? *json_val*"} -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 +test pretty-jsonPretty-6.1 {} -body {json pretty -arrays} -returnCodes error -result {missing argument to "-arrays"} -errorCode {TCL ARGUMENT MISSING} +test pretty-jsonPretty-7.1 {} -body {json pretty -arrays bad {1}} -returnCodes error -result {bad array mode "bad": must be inline or multiline} -errorCode {TCL LOOKUP INDEX {array mode} bad} + +# -compact option +test pretty-compact-1.1 {-compact produces single-line JSON} -body { #<<< + json pretty -compact {{"foo":"bar","arr":[1,2,3]}} +} -result {{"foo":"bar","arr":[1,2,3]}} +#>>> +test pretty-compact-1.2 {-compact with nested structure} -body { #<<< + json pretty -compact {{"a":{"b":{"c":1}}}} +} -result {{"a":{"b":{"c":1}}}} +#>>> +test pretty-compact-1.3 {-compact with array} -body { #<<< + json pretty -compact {[1,2,3,4,5]} +} -result {[1,2,3,4,5]} +#>>> + +# -nopadding option +test pretty-nopadding-1.1 {-nopadding suppresses key alignment} -body { #<<< + json pretty -nopadding {{"foo":1,"x":2,"longerkey":3}} +} -result {{ + "foo": 1, + "x": 2, + "longerkey": 3 +}} +#>>> +test pretty-nopadding-1.2 {default (no -nopadding) aligns keys} -body { #<<< + json pretty {{"foo":1,"x":2}} +} -result {{ + "foo": 1, + "x": 2 +}} +#>>> + +# -arrays option +test pretty-arrays-1.1 {-arrays inline forces all arrays on one line} -body { #<<< + json pretty -arrays inline {[1,2,3,4,5]} +} -result {[1,2,3,4,5]} +#>>> +test pretty-arrays-1.2 {-arrays multiline forces each element on its own line} -body { #<<< + json pretty -arrays multiline {[1,2,3]} +} -result {[ + 1, + 2, + 3 +]} +#>>> +test pretty-arrays-1.3 {default auto: ≤3 elements inline} -body { #<<< + json pretty {[1,2,3]} +} -result {[1,2,3]} +#>>> +test pretty-arrays-1.4 {default auto: >3 elements multiline} -body { #<<< + json pretty {[1,2,3,4]} +} -result {[ + 1, + 2, + 3, + 4 +]} +#>>> +test pretty-arrays-1.5 {-arrays inline with object containing arrays} -body { #<<< + json pretty -arrays inline {{"arr":[1,2,3,4,5],"short":[1,2]}} +} -result {{ + "arr": [1,2,3,4,5], + "short": [1,2] +}} +#>>> +test pretty-arrays-1.6 {-arrays multiline with object} -body { #<<< + json pretty -arrays multiline {{"arr":[1,2]}} +} -result {{ + "arr": [ + 1, + 2 + ] +}} +#>>> + +# Combined options +test pretty-combined-1.1 {-nopadding -arrays inline} -body { #<<< + json pretty -nopadding -arrays inline {{"foo":1,"arr":[1,2,3,4]}} +} -result {{ + "foo": 1, + "arr": [1,2,3,4] +}} +#>>> json free_cache