feat(xce-test-cli-ux): build JSON list-output helper library

listfmt_json_begin/new_row/add_string/add_number/finish wrap cJSON
(already linked via struct2json_dep) into a minimal array/object
lifecycle library with no generic "record" abstraction -- each command's
own row struct maps its fields directly when wired in later (task 3.4).
Proven via a real stdout-capture-then-reparse round trip, including a
733-character field with no truncation, and stderr staying empty.
1306/1306 controller suite passing (+9).

Task 2.2 of .kiro/specs/xce-test-cli-ux/tasks.md.
Requirements: 5.1, 5.3

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019jaMf9QEwpugH2W9KAuc7f
This commit is contained in:
Rongsong Shen 2026-09-09 12:13:12 +00:00
parent a2686516f7
commit 07d238eada
No known key found for this signature in database
GPG key ID: 5BCDAAE0123E9FE2
6 changed files with 464 additions and 2 deletions

View file

@ -0,0 +1,54 @@
#pragma once
/*
* listfmt_json.h -- shared cJSON array/object plumbing for xce_test's
* listing commands' JSON output mode (xce-test-cli-ux spec, task 2.2;
* Requirements 5.1, 5.3).
*
* This module owns ONLY the JSON array/object lifecycle: start an array,
* append a new row object to it, add string/number fields to a row, then
* print + free the whole array. It does NOT know about workerlist's/
* joblist's/reslist's own field sets -- each command's existing row
* struct already exists and is stable (design.md's Simplification lens),
* so this library introduces no generic "record"/"row" abstraction beyond
* the bare cJSON object handle itself. Wiring this into cmd_workerlist/
* cmd_joblist/cmd_reslist (their field mapping, their --json flag) is
* task 3.4, not this one.
*
* cJSON is already linked into xce_test (struct2json_dep, per
* research.md) and already used for job exdata blobs (test.c's
* build_job_envs()) -- this module reuses that exact same cJSON API
* surface, not a different one.
*/
#include <cjson/cJSON.h>
/* Creates a new, empty JSON array meant to hold one object per output
* row. The caller passes the returned array to listfmt_json_new_row()
* once per row, then to listfmt_json_finish() exactly once when done.
* Returns NULL on allocation failure. */
cJSON* listfmt_json_begin(void);
/* Adds a string field `key`: `val` to `row`. `val` is copied in full,
* regardless of its length -- no field is ever truncated or dropped for
* being "too long" (Requirement 5.3). A NULL `val` is stored as an
* explicit JSON null rather than silently omitted or crashing. No-op if
* `row` or `key` is NULL. */
void listfmt_json_add_string(cJSON* row, const char* key, const char* val);
/* Adds a numeric field `key`: `val` to `row`. No-op if `row` or `key` is
* NULL. */
void listfmt_json_add_number(cJSON* row, const char* key, double val);
/* Creates a new, empty JSON object, appends it to `array`, and returns
* that object so the caller can then call listfmt_json_add_string()/
* listfmt_json_add_number() on it for each of this row's fields. Returns
* NULL (leaving `array` unmodified) if `array` is NULL or the new object
* could not be allocated. */
cJSON* listfmt_json_new_row(cJSON* array);
/* Prints `array` to stdout as JSON -- command-RESULT data, per
* Requirement 3's stdout/stderr discipline, so it is written to stdout
* only, never stderr -- and then frees the entire cJSON structure
* (`array` and every row/field it holds). `array` must not be
* referenced again after this call. No-op if `array` is NULL. */
void listfmt_json_finish(cJSON* array);