gHggPo+jmHXbbcCl8sz7cbjPQ5rHDe861MDVjgXf+TPAdsDSUfAvXpOB3wMQCKViZJboYRA7g= Received: from CH0PR03CA0095.namprd03.prod.outlook.com (2603:10b6:610:cd::10) by DS0PR05MB11116.namprd05.prod.outlook.com (2603:10b6:8:298::12) with Microsoft SMTP Server (version=TLS1_2, cipher=TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384) id 15.20.9228.10; Tue, 14 Oct 2025 21:32:09 +0000 Received: from DS2PEPF0000343D.namprd02.prod.outlook.com (2603:10b6:610:cd:cafe::48) by CH0PR03CA0095.outlook.office365.com (2603:10b6:610:cd::10) with Microsoft SMTP Server (version=TLS1_3, cipher=TLS_AES_256_GCM_SHA384) id 15.20.9228.10 via Frontend Transport; Tue, 14 Oct 2025 21:32:09 +0000 X-MS-Exchange-Authentication-Results: spf=softfail (sender IP is 66.129.239.15) smtp.mailfrom=juniper.net; dkim=none (message not signed) header.d=none;dmarc=fail action=oreject header.from=juniper.net; Received-SPF: SoftFail (protection.outlook.com: domain of transitioning juniper.net discourages use of 66.129.239.15 as permitted sender) Received: from p-exchfe-eqx-02.jnpr.net (66.129.239.15) by DS2PEPF0000343D.mail.protection.outlook.com (10.167.18.40) with Microsoft SMTP Server (version=TLS1_2, cipher=TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384) id 15.20.9228.7 via Frontend Transport; Tue, 14 Oct 2025 21:32:09 +0000 Received: from p-exchbe-eqx-01.jnpr.net (10.104.9.14) by p-exchfe-eqx-02.jnpr.net (10.104.9.17) with Microsoft SMTP Server (version=TLS1_2, cipher=TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384) id 15.2.1544.14; Tue, 14 Oct 2025 16:32:08 -0500 Received: from p-mailhub01.juniper.net (10.104.20.6) by p-exchbe-eqx-01.jnpr.net (10.104.9.14) with Microsoft SMTP Server (version=TLS1_2, cipher=TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384) id 15.2.1544.14 via Frontend Transport; Tue, 14 Oct 2025 16:32:08 -0500 Received: from idleski.juniper.net (idleski.juniper.net [172.25.4.10]) by p-mailhub01.juniper.net (8.14.4/8.11.3) with ESMTP id 59ELW7Xt028410; Tue, 14 Oct 2025 14:32:08 -0700 (envelope-from phil@juniper.net) Received: from [172.25.4.153] (localhost [127.0.0.1]) by idleski.juniper.net (8.18.1/8.18.1) with ESMTP id 59ELXOQZ000667; Tue, 14 Oct 2025 17:33:24 -0400 (EDT) (envelope-from phil@juniper.net) From: Phil Shafer To: Kirk McKusick CC: , , Subject: Re: git: 0d4642a67e59 - main - Add --libxo support for geom status and list sub commands. Date: Tue, 14 Oct 2025 17:32:06 -0400 X-Mailer: MailMate (1.14r5937) Message-ID: In-Reply-To: <202510132116.59DLGGdS009938@gitrepo.freebsd.org> References: <202510132116.59DLGGdS009938@gitrepo.freebsd.org> List-Id: Commit messages for all branches of the src repository List-Archive: https://lists.freebsd.org/archives/dev-commits-src-all List-Help: List-Post: List-Subscribe: List-Unsubscribe: X-BeenThere: dev-commits-src-all@freebsd.org Sender: owner-dev-commits-src-all@FreeBSD.org MIME-Version: 1.0 Content-Type: text/plain Content-Transfer-Encoding: quoted-printable X-EOPAttributedMessage: 0 X-MS-PublicTrafficType: Email X-MS-TrafficTypeDiagnostic: DS2PEPF0000343D:EE_|DS0PR05MB11116:EE_ X-MS-Office365-Filtering-Correlation-Id: 7b8187f5-6d32-4acd-4db9-08de0b692182 X-MS-Exchange-SenderADCheck: 1 X-MS-Exchange-AntiSpam-Relay: 0 X-Microsoft-Antispam: BCL:0;ARA:13230040|376014|36860700013|1800799024|82310400026; X-Microsoft-Antispam-Message-Info: =?us-ascii?Q?fRhDpVWpxMlm3SZN+d6FEyqzJYS3rBttLuv3d5+XNQ6owzXt04FsV6mRG6ow?= =?us-ascii?Q?du4yXLZa+QjS7fpfYrMjPeCaacmK/wSfos7fzH4cpBpSTZDBMtcNCafKehU/?= =?us-ascii?Q?Ivfqj68LwJzVpAnX/sUeGIFT/8MXBD6qmStk5a34dEwN/TN9v/HNJ9CuN9mD?= =?us-ascii?Q?hnCGlJvuBTdFKlRxS+qRa+GGHF7zP88W2oafGSPKSZ1af82p+P8v3g8lNEPa?= =?us-ascii?Q?kQ8yoOY1IednWWIYTp47Af/LHDBD3TQk+g0YYBchXFrSwSTaqnRviXOx6cPg?= =?us-ascii?Q?2AbGpCvsaROPl+HcHRNNhuzeiEvdQXhd+4p/3BmLPLmr1UsumrYnbJtIV4ok?= =?us-ascii?Q?DcYaaRA3QgnOk37rI+AeS3OjthgzAM+qabrZ/jpnglvNf+5OMeVCmT6O3JKy?= =?us-ascii?Q?ClKsSfTZZPvLnEIxYXd/q0CBMrG43TafHeTo8qaW1bzld3wEwjIMjy2Lxnjg?= =?us-ascii?Q?vbgltWiBh7XIDGPoZOSyKIxWnTUdJZcozXGoqraeXufIqBTEB2IZ3XFyNFd1?= =?us-ascii?Q?MOhZZGYR1JSbS4jScfOYD6og8CeS2NfSAimM15zXSEvs9xVRcmwa5EKeb8BA?= =?us-ascii?Q?YyzGAOpvGpVsr48YVWGN6ggMdeUYcRUuY7T1lJK+RVXbjjx6R/egWFfulKcN?= =?us-ascii?Q?esVlPFdkcpc+WTmgBxEb3l+6Y7YD6FT14KWvbRIBUWKl+HY/G7jQTAc/I2lQ?= =?us-ascii?Q?3+bK9Itwb/Mqyl/JkgbsOm1AJfl0Rx6J2tH5RJZzvl1DmIl/oSHOvjDa2KCj?= =?us-ascii?Q?OhG82vAwuJTQu0ngx6jku2LZDPmJYhGIDABzPqYLFgH9tnjNY8LD6Cvn0gqP?= =?us-ascii?Q?8HEBJboOQM0UkBXvkOMm5s1yw4xVDqwPaN5BZ6ViKvsyfqhKmhhfjJjYI/uo?= =?us-ascii?Q?sHwJqKPbi79h96HFyrHhOalDSA9XCm+R6JLNdDXBbOxD2wdf91qUrGOOXzHm?= =?us-ascii?Q?BrLVYvntatvwu50XQmk7TrKFujjCgUpNnEZM0TlZIBe71aYYqiLS1h5Y7p//?= =?us-ascii?Q?oM9dwccBo4jO+njVMclsgSsNt3nFOjudo7yFc/j9tWmmnXx+m6p5CYdKouB8?= =?us-ascii?Q?ONrbKdFWr+rL2FVv3ccoINdXTir/c7NLy+MK3mAuEXNzA7Nw6oyNbXUafghO?= =?us-ascii?Q?b8WiWkOd6XZKUlPosy9HlBEHgUeGK9mUAZHV9mF65i3J+e41mW77Qv5jPSaO?= =?us-ascii?Q?cCisLWZoMQfAEpH+KtCsUqka6aK1LMwxj0fY1ZwHoVT97Gu6TBt7gXtugpuO?= =?us-ascii?Q?FePkyTBnSUAyl+alEth9U9OdcySdrkw0+G3o4QVqJ6vw0GBFnRtO6tnbzxom?= =?us-ascii?Q?K6KvP6+4WInrzuv1Rgntxv59Y1MPXAE2zu6+mjAWDQmjKzV68F4OVg7MAyLO?= =?us-ascii?Q?xau505Ll+Rk0WlScDS69QdF3EkMpzZYSQomGcRGiugEYLgdzeQJnRwCHoij3?= =?us-ascii?Q?tYuhl2HFYCWV1ZCfZZ2epyZJ7Gg9M4O782eWRLCRaIVejfVIvq0zBgut6qYm?= =?us-ascii?Q?uuCqGQn+Xba52B8=3D?= X-Forefront-Antispam-Report: CIP:66.129.239.15;CTRY:US;LANG:en;SCL:1;SRV:;IPV:CAL;SFV:NSPM;H:p-exchfe-eqx-02.jnpr.net;PTR:InfoDomainNonexistent;CAT:NONE;SFS:(13230040)(376014)(36860700013)(1800799024)(82310400026);DIR:OUT;SFP:1101; X-OriginatorOrg: juniper.net X-MS-Exchange-CrossTenant-OriginalArrivalTime: 14 Oct 2025 21:32:09.3962 (UTC) X-MS-Exchange-CrossTenant-Network-Message-Id: 7b8187f5-6d32-4acd-4db9-08de0b692182 X-MS-Exchange-CrossTenant-Id: bea78b3c-4cdb-4130-854a-1d193232e5f4 X-MS-Exchange-CrossTenant-OriginalAttributedTenantConnectingIp: TenantId=bea78b3c-4cdb-4130-854a-1d193232e5f4;Ip=[66.129.239.15];Helo=[p-exchfe-eqx-02.jnpr.net] X-MS-Exchange-CrossTenant-AuthSource: DS2PEPF0000343D.namprd02.prod.outlook.com X-MS-Exchange-CrossTenant-AuthAs: Anonymous X-MS-Exchange-CrossTenant-FromEntityHeader: HybridOnPrem X-MS-Exchange-Transport-CrossTenantHeadersStamped: DS0PR05MB11116 X-Proofpoint-GUID: 70fRAvpwG50AVVEvk-UX1ThZ6-M236I8 X-Proofpoint-ORIG-GUID: 70fRAvpwG50AVVEvk-UX1ThZ6-M236I8 X-Proofpoint-Spam-Details-Enc: AW1haW4tMjUxMDE0MDEzNiBTYWx0ZWRfX5rJ06ja76K25 GRQ5Fmj0jTiQA6qTRpkiH3Fhs5RyMJa3Emz0YHhA7aW1Pz8mCdMeSzJ4QQZzQtZOn3O8aGOIQfL HZMISZ6iMebLFCNisXzJXG0s9Rlc7/3/xYe8VpDr9C++gZ+1Dgil1eW4PHu6gNC2vcuQq84kg04 sWzmJg3x8R56H7OmCIl7hBm9dXSXJw6JWdcON0n225tUfRI6o9BIKT3ACCLlDAElRwmsoub3fgL 9NQ/EW5DQ2q2eHMz8Zlx6UX3tru5OtaCjdwd/PHdcyh4WZphq+NXqRkzfkwE1SKuTGapAJYOPnr mssmc62kVFE+B6uCt8Yt7NYvB9/MnToxz4R2dSNz2u3UKjHsHuHg4tyFcG6F8+DPH50LoUhvFoj LcMuRCg4PYia0fayeVG28oUqaewBvQ== X-Authority-Analysis: v=2.4 cv=Wq0m8Nfv c=1 sm=1 tr=0 ts=68eec15c cx=c_pps a=BKMVb8u7xwlFrvPG8etDZw==:117 a=YQU41r7WENJiSYrYYNJVsQ==:17 a=6eWqkTHjU83fiwn7nKZWdM+Sl24=:19 a=x6icFKpwvdMA:10 a=s63m1ICgrNkA:10 a=rhJc5-LppCAA:10 a=VkNPw1HP01LnGYTKEx00:22 a=UXIAUNObAAAA:8 a=GIgwrTwaEVm9zTZ1qiwA:9 a=pT8Wv8JN1uoA:10 a=a1s67YnXd6TbAZZNj1wK:22 a=poXaRoVlC6wW9_mwW8W4:22 X-Proofpoint-Virus-Version: vendor=baseguard engine=ICAP:2.0.293,Aquarius:18.0.1121,Hydra:6.1.9,FMLib:17.12.80.40 definitions=2025-10-14_04,2025-10-13_01,2025-03-28_01 X-Proofpoint-Spam-Details: rule=outbound_spam_notspam policy=outbound_spam score=0 phishscore=0 malwarescore=0 spamscore=0 suspectscore=0 impostorscore=0 clxscore=1011 priorityscore=1501 lowpriorityscore=0 bulkscore=0 adultscore=0 classifier=typeunknown authscore=0 authtc= authcc= route=outbound adjust=0 reason=mlx scancount=1 engine=8.19.0-2510020000 definitions=main-2510140136 X-Spamd-Bar: ---- X-Spamd-Result: default: False [-4.56 / 15.00]; NEURAL_HAM_MEDIUM(-1.00)[-1.000]; ARC_ALLOW(-1.00)[microsoft.com:s=arcselector10001:i=1]; NEURAL_HAM_LONG(-1.00)[-1.000]; NEURAL_HAM_SHORT(-0.96)[-0.959]; R_MISSING_CHARSET(0.50)[]; DMARC_POLICY_ALLOW(-0.50)[juniper.net,reject]; R_SPF_ALLOW(-0.20)[+ip4:67.231.152.164]; R_DKIM_ALLOW(-0.20)[juniper.net:s=PPS1017]; MIME_GOOD(-0.10)[text/plain]; RCVD_IN_DNSWL_LOW(-0.10)[67.231.152.164:from]; FREEFALL_USER(0.00)[phil]; RCVD_IN_DNSWL_NONE(0.00)[52.101.53.59:received]; FROM_HAS_DN(0.00)[]; TO_DN_SOME(0.00)[]; MIME_TRACE(0.00)[0:+]; FROM_EQ_ENVFROM(0.00)[]; DKIM_TRACE(0.00)[juniper.net:+,juniper.net:~]; DKIM_MIXED(0.00)[]; RCVD_TLS_LAST(0.00)[]; TO_MATCH_ENVRCPT_ALL(0.00)[]; R_DKIM_PERMFAIL(0.00)[juniper.net:s=selector1]; MLMMJ_DEST(0.00)[dev-commits-src-main@freebsd.org,dev-commits-src-all@freebsd.org]; RCPT_COUNT_THREE(0.00)[4]; MID_RHS_MATCH_FROM(0.00)[]; RCVD_COUNT_SEVEN(0.00)[9] X-Rspamd-Queue-Id: 4cmS9923x2z3PQm On 13 Oct 2025, at 17:16, Kirk McKusick wrote: > static void > -list_one_provider(struct gprovider *pp, const char *prefix) > +list_one_provider(struct gprovider *pp, const char *padding) > { > struct gconfig *conf; > char buf[5]; > > - printf("Name: %s\n", pp->lg_name); > + xo_emit("{Lcw:Name}{:Name}\n", pp->lg_name); > humanize_number(buf, sizeof(buf), (int64_t)pp->lg_mediasize, ""= , > HN_AUTOSCALE, HN_B | HN_NOSPACE | HN_DECIMAL); Tag names should be lower case, for consistency and ease of use: s/:Name/= :name/ (This applies throughout) > - printf("%sMediasize: %jd (%s)\n", prefix, (intmax_t)pp->lg_medi= asize, > - buf); > - printf("%sSectorsize: %u\n", prefix, pp->lg_sectorsize); > + xo_emit("{P:/%s}{Lcw:Mediasize}{:Mediasize/%jd} ({N:/%s})\n", > + padding, (intmax_t)pp->lg_mediasize, buf); > + xo_emit("{P:/%s}{Lcw:Sectorsize}{:Sectorsize/%u} \n", > + padding, pp->lg_sectorsize); Using the "humanize" features will allow human-readable numbers in human-= friendly styles (text, html) and real numbers in encoding styles (xml and= json). https://libxo.readthedocs.io/en/latest/field-modifiers.html?highlight=3Dh= umanize#field-modifiers-1 Modifiers ~~~~~~~~~~~~~~~ Field modifiers are flags which modify the way content emitted for particular output styles: =3D=3D=3D =3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D =3D=3D=3D=3D=3D= =3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D= =3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D M Name Description =3D=3D=3D =3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D =3D=3D=3D=3D=3D= =3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D= =3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D ... h humanize (hn) Format large numbers in human-readable style \ hn-space Humanize: Place space between numeric and unit \ hn-decimal Humanize: Add a decimal digit, if number < 10 \ hn-1000 Humanize: Use 1000 as divisor instead of 1024 Hmm.... looks like I'm lacking a flag for HN_B, sadly. Not sure how I mi= ssed that. I'll open a PR to add it. If you want the full numbers, you can use the "e" flag to restrict emitti= ng values to encodings styles. > if (pp->lg_stripesize > 0 || pp->lg_stripeoffset > 0) { > - printf("%sStripesize: %ju\n", prefix, pp->lg_stripesize= ); > - printf("%sStripeoffset: %ju\n", prefix, pp->lg_stripeof= fset); > + xo_emit("{P:/%s}{Lcw:Stripesize}{Stripesize/%ju}\n", > + padding, pp->lg_stripesize); > + xo_emit("{P:/%s}{Lcw:Stripeoffset}{Stripeoffset/%ju}\n"= , > + padding, pp->lg_stripeoffset); Missing some colons before the tag names. Testing with the "warn" flag s= hould point this out, if the code gets hit. Style-wise, using hyphens ("{:strip-size/%ju}", "{:strip-offset/%ju}") he= lps make readable tag names. > - printf("%sMode: %s\n", prefix, pp->lg_mode); > + xo_emit("{P:/%s}{Lcw:Mode}{Mode}\n", padding, pp->lg_mode); {:mode} > @@ -940,27 +958,36 @@ list_one_geom(struct ggeom *gp) > struct gconfig *conf; > unsigned n; > > - printf("Geom name: %s\n", gp->lg_name); > + xo_emit("{Lcw:Geom name}{:Name}\n", gp->lg_name); > LIST_FOREACH(conf, &gp->lg_config, lg_config) { > - printf("%s: %s\n", conf->lg_name, conf->lg_val); > + xo_emit("{Lcwa:}{a:}\n", conf->lg_name, conf->lg_name, > + conf->lg_val); > } > if (!LIST_EMPTY(&gp->lg_provider)) { > - printf("Providers:\n"); > + xo_open_list("Providers"); Style-wise, list and container names should also be lower case. > + xo_emit("{Tc:Providers}\n"); > n =3D 1; > LIST_FOREACH(pp, &gp->lg_provider, lg_provider) { > - printf("%u. ", n++); > + xo_emit("{T:/%u} ", n++); > + xo_open_instance("provider"); > list_one_provider(pp, " "); > + xo_close_instance("provider"); > } > + xo_close_list("Providers"); > } > if (!LIST_EMPTY(&gp->lg_consumer)) { > - printf("Consumers:\n"); > + xo_open_list("Consumers"); > + xo_emit("{Tc:Consumers}\n"); > n =3D 1; > LIST_FOREACH(cp, &gp->lg_consumer, lg_consumer) { > - printf("%u. ", n++); > + xo_emit("{T:/%u} ", n++); > + xo_open_instance("consumer"); > list_one_consumer(cp, " "); > + xo_close_instance("consumer"); > } > + xo_close_list("Consumers"); > } For simple lists like this, use the "l" flag here, since it will yield mo= re useable JSON (dropping the open/close list/instance): https://libxo.readthedocs.io/en/latest/field-modifiers.html?highlight=3Dl= eaf#the-leaf-list-modifier-l The Leaf-List Modifier ({l:}) +++++++++++++++++++++++++++++ .. index:: Field Modifiers; Leaf-List The leaf-list modifier is used to distinguish lists where each instance consists of only a single value. In XML, these are rendered as single elements, where JSON renders them as arrays:: EXAMPLE: for (i =3D 0; i < num_users; i++) { xo_emit("Member {l:user}\\n", user[i].u_name); } XML: phil pallavi JSON: "user": [ "phil", "pallavi" ] The name of the field must match the name of the leaf list. FWIW, there's some example code showing the differences between list and = leaf-list rendering in XML and JSON appended below. > @@ -1038,14 +1067,20 @@ std_list(struct gctl_req *req, unsigned flags _= _unused) > "an instance named '%s'.", > gclass_name, name); > } > + xo_open_container("Geom"); > list_one_geom(gp); > + xo_close_container("Geom"); > } > } else { > + xo_open_list("Geoms"); > LIST_FOREACH(gp, &classp->lg_geom, lg_geom) { > if (LIST_EMPTY(&gp->lg_provider) && !all) > continue; > + xo_open_instance("geom"); > list_one_geom(gp); > + xo_close_instance("geom"); > } > + xo_close_list("Geoms"); Same here ("l:"). > + xo_open_instance("status"); > LIST_FOREACH(cp, &gp->lg_consumer, lg_consumer) { > - component =3D status_one_consumer(cp); > - if (component =3D=3D NULL) > + cstate =3D status_one_consumer(cp, "state"); > + csyncr =3D status_one_consumer(cp, "synchronized"); > + if (cstate =3D=3D NULL && csyncr =3D=3D NULL) > continue; > + if (!gotone || script) { > + if (!gotone) { > + xo_emit("{:name/%*s} {:status/%*s} ",= > + name_len, name, status_len, status)= ; > + } else { > + xo_emit("{d:name/%*s} {d:status/%*s} = ", > + name_len, name, status_len, status)= ; > + } > + xo_open_list("components"); > + } > + > + xo_open_instance("components"); > + if (cstate !=3D NULL && csyncr !=3D NULL) { > + xo_emit("{P:/%*s}{:compontent} ({:state}, {:syn= chronized})\n", > + len, "", cp->lg_provider->lg_name, cstate, csyn= cr); > + } else if (cstate !=3D NULL) { > + xo_emit("{P:/%*s}{:compontent} ({:state})\n", > + len, "", cp->lg_provider->lg_name, cstate); > + } else { > + xo_emit("{P:/%*s}{:compontent} ({:synchronized}= )\n", > + len, "", cp->lg_provider->lg_name, csyncr); > + } Extra "n"s in the tag names: s/compontent/component/ > + xo_close_instance("components"); Do these instances have keys? Or is this a simple list (leaf-list)? > gotone =3D 1; > - printf("%*s %*s %s\n", name_len, name, status_len, st= atus, > - component); > - if (!script) > - name =3D status =3D ""; > + if (!len && !script) > + len =3D name_len + status_len + 4; > } > if (!gotone) { > - printf("%*s %*s %s\n", name_len, name, status_len, st= atus, > - "N/A"); > + xo_emit("{:name/%*s} {:status/%*s} ", name_len, name,= status_len, status); > + xo_open_list("components"); > + xo_open_instance("components"); > + xo_emit("{P:/%*s}{d:compontent}\n", len, "", "N/A"); > + xo_close_instance("components"); > } > + xo_close_list("components"); > + xo_close_instance("status"); Avoid the close_list call if you didn't call open_list. libxo's internal= state transition FSM will generally catch this (and warn you), but it's = better to avoid it. Otherwise looks good. Thanks, Phil ----------------- Bock % cat /tmp/foo.c #include const char *user[] =3D { "alice", "bob", "carl", "doug", NULL }; const int num_users =3D 4; int main (int argc, char **argv) { int i; argc =3D xo_parse_args(argc, argv); if (argc < 0) return (argc); int yes =3D (argc > 1); xo_open_container("top"); if (yes) { for (i =3D 0; i < num_users; i++) { xo_emit("Member {l:user}\n", user[i]); } } else { xo_open_list("test"); for (i =3D 0; i < num_users; i++) { xo_open_instance("test"); xo_emit("Member {k:user}\n", user[i]); xo_close_instance("test"); } xo_close_list("test"); } xo_close_container("top"); xo_finish(); exit(0); } Bock % cc -I work/root/include/ -L work/root/lib -lxo -o /tmp/foo /tmp/fo= o.c Bock % /tmp/foo Member alice Member bob Member carl Member doug Bock % /tmp/foo --libxo:XP alice bob carl doug Bock % /tmp/foo --libxo:XP yes alice bob carl doug Bock % /tmp/foo --libxo:JP { "top": { "test": [ { "user": "alice" }, { "user": "bob" }, { "user": "carl" }, { "user": "doug" } ] } } Bock % /tmp/foo --libxo:JP yes { "top": { "user": [ "alice", "bob", "carl", "doug" ] } }