Re: git: 0d4642a67e59 - main - Add --libxo support for geom status and list sub commands.

From: Phil Shafer <phil_at_juniper.net>
Date: Tue, 14 Oct 2025 21:32:06 UTC
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_mediasize,
> -           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=humanize#field-modifiers-1

		Modifiers
		~~~~~~~~~~~~~~~

		Field modifiers are flags which modify the way content emitted for
		particular output styles:

		  === =============== ===================================================
		   M   Name            Description
		  === =============== ===================================================
		...
		   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 missed that.  I'll open a PR to add it.

If you want the full numbers, you can use the "e" flag to restrict emitting 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_stripeoffset);
> +               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 should point this out, if the code gets hit.

Style-wise, using hyphens ("{:strip-size/%ju}", "{:strip-offset/%ju}") helps 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 = 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 = 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 more useable JSON (dropping the open/close list/instance):

https://libxo.readthedocs.io/en/latest/field-modifiers.html?highlight=leaf#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 = 0; i < num_users; i++) {
		            xo_emit("Member {l:user}\\n", user[i].u_name);
		        }
		    XML:
		        <user>phil</user>
		        <user>pallavi</user>
		    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 = status_one_consumer(cp);
> -               if (component == NULL)
> +               cstate = status_one_consumer(cp, "state");
> +               csyncr = status_one_consumer(cp, "synchronized");
> +               if (cstate == NULL && csyncr == 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 != NULL && csyncr != NULL) {
> +                       xo_emit("{P:/%*s}{:compontent} ({:state}, {:synchronized})\n",
> +                       len, "", cp->lg_provider->lg_name, cstate, csyncr);
> +               } else if (cstate != 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 = 1;
> -               printf("%*s  %*s  %s\n", name_len, name, status_len, status,
> -                   component);
> -               if (!script)
> -                       name = status = "";
> +               if (!len && !script)
> +                       len = name_len + status_len + 4;
>         }
>         if (!gotone) {
> -               printf("%*s  %*s  %s\n", name_len, name, status_len, status,
> -                   "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 <libxo/xo.h>

const char *user[] = { "alice", "bob", "carl", "doug", NULL };
const int num_users = 4;

int
main (int argc, char **argv)
{
    int i;

    argc = xo_parse_args(argc, argv);
    if (argc < 0)
        return (argc);

    int yes = (argc > 1);

    xo_open_container("top");

    if (yes) {
        for (i = 0; i < num_users; i++) {
            xo_emit("Member {l:user}\n", user[i]);
        }
    } else {
        xo_open_list("test");

        for (i = 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/foo.c
Bock % /tmp/foo
Member alice
Member bob
Member carl
Member doug
Bock % /tmp/foo --libxo:XP
<top>
  <test>
    <user>alice</user>
  </test>
  <test>
    <user>bob</user>
  </test>
  <test>
    <user>carl</user>
  </test>
  <test>
    <user>doug</user>
  </test>
</top>
Bock % /tmp/foo --libxo:XP yes
<top>
  <user>alice</user>
  <user>bob</user>
  <user>carl</user>
  <user>doug</user>
</top>
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"
    ]
  }
}