From owner-freebsd-arch@FreeBSD.ORG Fri Jul 25 04:49:29 2014 Return-Path: Delivered-To: arch@freebsd.org Received: from mx1.freebsd.org (mx1.freebsd.org [IPv6:2001:1900:2254:206a::19:1]) (using TLSv1 with cipher ADH-AES256-SHA (256/256 bits)) (No client certificate requested) by hub.freebsd.org (Postfix) with ESMTPS id 5E6F5C10; Fri, 25 Jul 2014 04:49:29 +0000 (UTC) Received: from na01-bn1-obe.outbound.protection.outlook.com (mail-bn1lp0141.outbound.protection.outlook.com [207.46.163.141]) (using TLSv1 with cipher ECDHE-RSA-AES256-SHA (256/256 bits)) (Client CN "mail.protection.outlook.com", Issuer "MSIT Machine Auth CA 2" (verified OK)) by mx1.freebsd.org (Postfix) with ESMTPS id 884FF200B; Fri, 25 Jul 2014 04:49:27 +0000 (UTC) Received: from BY2PR05CA008.namprd05.prod.outlook.com (10.242.32.38) by CO2PR05MB730.namprd05.prod.outlook.com (10.141.228.15) with Microsoft SMTP Server (TLS) id 15.0.990.7; Fri, 25 Jul 2014 04:49:25 +0000 Received: from BN1BFFO11FD003.protection.gbl (2a01:111:f400:7c10::1:172) by BY2PR05CA008.outlook.office365.com (2a01:111:e400:2c2a::38) with Microsoft SMTP Server (TLS) id 15.0.990.7 via Frontend Transport; Fri, 25 Jul 2014 04:49:24 +0000 Received: from P-EMF02-SAC.jnpr.net (66.129.239.16) by BN1BFFO11FD003.mail.protection.outlook.com (10.58.144.66) with Microsoft SMTP Server (TLS) id 15.0.980.11 via Frontend Transport; Fri, 25 Jul 2014 04:49:24 +0000 Received: from magenta.juniper.net (172.17.27.123) by P-EMF02-SAC.jnpr.net (172.24.192.21) with Microsoft SMTP Server (TLS) id 14.3.146.0; Thu, 24 Jul 2014 21:49:23 -0700 Received: from chaos.jnpr.net (chaos.jnpr.net [172.21.16.28]) by magenta.juniper.net (8.11.3/8.11.3) with ESMTP id s6P4nLn65071; Thu, 24 Jul 2014 21:49:22 -0700 (PDT) (envelope-from sjg@juniper.net) Received: from chaos (localhost [127.0.0.1]) by chaos.jnpr.net (Postfix) with ESMTP id 9F0D3580A2; Thu, 24 Jul 2014 21:49:21 -0700 (PDT) To: Subject: XML Output: libxo - provide single API to output TXT, XML, JSON and HTML X-Mailer: MH-E 7.82+cvs; nmh 1.3; GNU Emacs 22.3.1 Date: Thu, 24 Jul 2014 21:49:21 -0700 From: Simon Gerraty Message-ID: <20140725044921.9F0D3580A2@chaos.jnpr.net> MIME-Version: 1.0 Content-Type: text/plain X-EOPAttributedMessage: 0 X-Forefront-Antispam-Report: CIP:66.129.239.16; CTRY:US; IPV:NLI; IPV:NLI; EFV:NLI; SFV:NSPM; SFS:(6009001)(199002)(377424004)(51704005)(189002)(164054003)(80022001)(87936001)(81542001)(68736004)(74662001)(106466001)(46102001)(64706001)(4396001)(62966002)(21056001)(79102001)(104166001)(6806004)(50466002)(97736001)(2351001)(86362001)(48376002)(93546004)(101356003)(84676001)(92566001)(50226001)(33656002)(88136002)(77156001)(105596002)(90896003)(69596002)(95666004)(57986006)(229853001)(83322001)(99396002)(87286001)(110136001)(47776003)(102836001)(85852003)(76482001)(92726001)(50986999)(81342001)(83072002)(20776003)(81156004)(107046002)(89996001)(74502001)(44976005)(93916002)(70486001)(77982001)(19580395003)(31966008)(85306003)(76506005)(102176002)(42262001); DIR:OUT; SFP:; SCL:1; SRVR:CO2PR05MB730; H:P-EMF02-SAC.jnpr.net; FPR:; MLV:sfv; PTR:InfoDomainNonexistent; MX:1; LANG:en; X-Microsoft-Antispam: BCL:0;PCL:0;RULEID: X-Forefront-PRVS: 02830F0362 Received-SPF: SoftFail (: domain of transitioning juniper.net discourages use of 66.129.239.16 as permitted sender) Authentication-Results: spf=softfail (sender IP is 66.129.239.16) smtp.mailfrom=sjg@juniper.net; X-OriginatorOrg: juniper.net Cc: sjg@freebsd.org, marcel@freebsd.org, phil@juniper.net X-BeenThere: freebsd-arch@freebsd.org X-Mailman-Version: 2.1.18 Precedence: list List-Id: Discussion related to FreeBSD architecture List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , X-List-Received-Date: Fri, 25 Jul 2014 04:49:29 -0000 Hi, At a vendor summit a few years ago I asked about whether anyone but us (Juniper) would be interested in the ablity to have standard BSD apps output XML. I was actually surprised by the amount of interest expressed. I've occasionally nagged our UI team ever since for a clean and simple API that we could contribute to address this. We now have a what I think is a viable candidate and we'd like to take the next steps towards contributing it and converting at least a few apps. Not only does it handle TXT and XML output but JSON and HTML as well, and very rich HTML at that. With some slick javascript - you can do amazing things with the level of detail you can get out of this sort of thing. The API is of necessity a bit more complex than just printf(3). Considering the level of functionality available though it is a good tradeoff. The main open issue (assuming this functionality is still desired) is support of wide charachters. We figure the worst case solution is a sed(1) script to generate the wide version of the API from the normal one, but perhaps simply always using UTF8 would be a better solution? Thanks --sjg The following from Phil provides some idea of the functionality available and the API. The one shown here uses the default output handle (stdout), but there are variants that allow multiple output handles. >Here's some sample output (from my libxo-ified "w"): > >(This uses an environment variable to trigger the mode and options: > T X J and H are the modes (text, xml, json, html); > P means pretty print (indent, newlines) > I means print help (datatype, description), if provided (there aren't for "w") > x means print xpath to the data) > >% foreach i ( T XP JP HP HPIx ) > echo === $i === > env LIBXO_OPTIONS=$i ./xtest -n | head -10 > end >=== T === > 6:47PM up 18 days, 2:01, 9 user%s, load averages: 0.00, 0.00, 0.00 >USER TTY FROM LOGIN@ IDLE WHAT >phil pts/0 76.182.32.73 5:09PM 33 /bin/sh >phil pts/1 76.182.32.73 05Jul14 2 /usr/bin/perl /u/phil/bin/plum ( >phil pts/2 76.182.32.73 05Jul14 1 /bin/tcsh >phil pts/3 76.182.32.73 05Jul14 2days ssh dent >phil pts/4 76.182.32.73 Tue02PM 2days ssh svl-junos-d026.juniper.net >phil pts/5 76.182.32.73 Wed01AM 2days telnet man-o-war 2006 >phil pts/6 76.182.32.73 Fri10PM 2days ssh 198.85.229.65 >phil pts/7 76.182.32.73 Fri10PM 2days ssh zap >=== XP === > > 6:47PM > 18 days > 2:01 > 9 > 0.00 > 0.00 > 0.00 > > >=== JP === >"uptime-information": { > "time-of-day": " 6:47PM", > "uptime": "18 days", > "uptime": 2:01, > "users": 9, > "load-average-1": 0.00, > "load-average-5": 0.00, > "load-average-15": 0.00, > "user-table": { > "user-entry": [ >=== HP === >
>
6:47PM
>
>
up
>
>
18 days
>
,
>
>
2:01
>
,
>=== HPIx === >
>
6:47PM
>
>
up
>
>
18 days
>
,
>
>
2:01
>
,
> >Thanks, > Phil > >----- > >FWIW: here's the diff for "w". I don't have "wchar_t" support >yet, so I just undid it for now. > > >diff -rbu /usr/src/usr.bin/w/pr_time.c ./pr_time.c >--- /usr/src/usr.bin/w/pr_time.c 2010-12-21 12:09:25.000000000 -0500 >+++ ./pr_time.c 2014-07-21 17:12:19.000000000 -0400 >@@ -55,10 +55,10 @@ > int > pr_attime(time_t *started, time_t *now) > { >- static wchar_t buf[256]; >+ static char buf[256]; > struct tm tp, tm; > time_t diff; >- wchar_t *fmt; >+ char *fmt; > int len, width, offset = 0; > > tp = *localtime(started); >@@ -67,7 +67,7 @@ > > /* If more than a week, use day-month-year. */ > if (diff > 86400 * 7) >- fmt = L"%d%b%y"; >+ fmt = "%d%b%y"; > > /* If not today, use day-hour-am/pm. */ > else if (tm.tm_mday != tp.tm_mday || >@@ -75,23 +75,23 @@ > tm.tm_year != tp.tm_year) { > /* The line below does not take DST into consideration */ > /* else if (*now / 86400 != *started / 86400) { */ >- fmt = use_ampm ? L"%a%I%p" : L"%a%H"; >+ fmt = use_ampm ? "%a%I%p" : "%a%H"; > } > > /* Default is hh:mm{am,pm}. */ > else { >- fmt = use_ampm ? L"%l:%M%p" : L"%k:%M"; >+ fmt = use_ampm ? "%l:%M%p" : "%k:%M"; > } > >- (void)wcsftime(buf, sizeof(buf), fmt, &tp); >- len = wcslen(buf); >- width = wcswidth(buf, len); >+ (void)strftime(buf, sizeof(buf), fmt, &tp); >+ len = strlen(buf); >+ width = len; > if (len == width) >- (void)wprintf(L"%-7.7ls", buf); >+ xo_emit("{:login-time/%-7.7s}", buf); > else if (width < 7) >- (void)wprintf(L"%ls%.*s", buf, 7 - width, " "); >+ xo_emit("{:login-time/%s}%.*s", buf, 7 - width, " "); > else { >- (void)wprintf(L"%ls", buf); >+ xo_emit("{:login-time/%s}", buf); > offset = width - 7; > } > return (offset); >@@ -108,7 +108,7 @@ > /* If idle more than 36 hours, print as a number of days. */ > if (idle >= 36 * 3600) { > int days = idle / 86400; >- (void)printf(" %dday%s ", days, days > 1 ? "s" : " " ); >+ xo_emit(" {:idle/%dday%s} ", days, days > 1 ? "s" : " " ); > if (days >= 100) > return (2); > if (days >= 10) >@@ -117,15 +117,15 @@ > > /* If idle more than an hour, print as HH:MM. */ > else if (idle >= 3600) >- (void)printf(" %2d:%02d ", >+ xo_emit(" {:idle/%2d:%02d/} ", > (int)(idle / 3600), (int)((idle % 3600) / 60)); > > else if (idle / 60 == 0) >- (void)printf(" - "); >+ xo_emit(" - "); > > /* Else print the minutes idle. */ > else >- (void)printf(" %2d ", (int)(idle / 60)); >+ xo_emit(" {:idle/%2d} ", (int)(idle / 60)); > > return (0); /* not idle longer than 9 days */ > } >diff -rbu /usr/src/usr.bin/w/w.c ./w.c >--- /usr/src/usr.bin/w/w.c 2010-12-21 12:09:25.000000000 -0500 >+++ ./w.c 2014-07-21 18:13:50.000000000 -0400 >@@ -86,6 +86,7 @@ > #include > #include > #include >+#include > > #include "extern.h" > >@@ -260,9 +261,12 @@ > } > (void)fclose(ut); > >+ xo_open_container("uptime-information"); >+ > if (header || wcmd == 0) { > pr_header(&now, nusers); > if (wcmd == 0) { >+ xo_close_container("uptime-information"); > (void)kvm_close(kd); > exit(0); > } >@@ -274,11 +278,11 @@ > #define HEADER_WHAT "WHAT\n" > #define WUSED (UT_NAMESIZE + UT_LINESIZE + W_DISPHOSTSIZE + \ > sizeof(HEADER_LOGIN_IDLE) + 3) /* header width incl. spaces */ >- (void)printf("%-*.*s %-*.*s %-*.*s %s", >+ xo_emit("{T:/%-*.*s} {T:/%-*.*s} " >+ "{T:/%-*.*s} {T:LOGIN@} {T:IDLE} {T:WHAT}\n", > UT_NAMESIZE, UT_NAMESIZE, HEADER_USER, > UT_LINESIZE, UT_LINESIZE, HEADER_TTY, >- W_DISPHOSTSIZE, W_DISPHOSTSIZE, HEADER_FROM, >- HEADER_LOGIN_IDLE HEADER_WHAT); >+ W_DISPHOSTSIZE, W_DISPHOSTSIZE, HEADER_FROM); > } > > if ((kp = kvm_getprocs(kd, KERN_PROC_ALL, 0, &nentries)) == NULL) >@@ -347,6 +351,9 @@ > } > } > >+ xo_open_container("user-table"); >+ xo_open_list("user-entry"); >+ > for (ep = ehead; ep != NULL; ep = ep->next) { > char host_buf[UT_HOSTSIZE + 1]; > struct sockaddr_storage ss; >@@ -356,6 +363,8 @@ > time_t t; > int isaddr; > >+ xo_open_instance("user-entry"); >+ > host_buf[UT_HOSTSIZE] = '\0'; > strncpy(host_buf, ep->utmp.ut_host, UT_HOSTSIZE); > p = *host_buf ? host_buf : "-"; >@@ -388,6 +397,9 @@ > p = buf; > } > if (dflag) { >+ xo_open_container("process-table"); >+ xo_open_list("process-entry"); >+ > for (dkp = ep->dkp; dkp != NULL; dkp = debugproc(dkp)) { > const char *ptr; > >@@ -395,23 +407,37 @@ > dkp->ki_comm, MAXCOMLEN); > if (ptr == NULL) > ptr = "-"; >- (void)printf("\t\t%-9d %s\n", >+ xo_open_instance("process-entry"); >+ xo_emit("\t\t{:process-id/%-9d/%d} " >+ "{:command/%s}\n", > dkp->ki_pid, ptr); >+ xo_close_instance("process-entry"); > } >+ xo_close_list("process-entry"); >+ xo_close_container("process-table"); > } >- (void)printf("%-*.*s %-*.*s %-*.*s ", >+ xo_emit("{:user/%-*.*s/%@**@s} {:tty/%-*.*s/%@**@s} " >+ "{:from/%-*.*s/%@**@s} ", > UT_NAMESIZE, UT_NAMESIZE, ep->utmp.ut_name, > UT_LINESIZE, UT_LINESIZE, > strncmp(ep->utmp.ut_line, "tty", 3) && > strncmp(ep->utmp.ut_line, "cua", 3) ? > ep->utmp.ut_line : ep->utmp.ut_line + 3, > W_DISPHOSTSIZE, W_DISPHOSTSIZE, *p ? p : "-"); >+ > t = _time_to_time32(ep->utmp.ut_time); > longattime = pr_attime(&t, &now); > longidle = pr_idle(ep->idle); >- (void)printf("%.*s\n", argwidth - longidle - longattime, >- ep->args); >+ xo_emit("{:command/%.*s/%@*@s}\n", >+ argwidth - longidle - longattime, ep->args); >+ >+ xo_close_instance("user-entry"); > } >+ >+ xo_close_list("user-entry"); >+ xo_close_container("user-table"); >+ xo_close_container("uptime-information"); >+ > (void)kvm_close(kd); > exit(0); > } >@@ -430,7 +456,7 @@ > */ > if (strftime(buf, sizeof(buf), > use_ampm ? "%l:%M%p" : "%k:%M", localtime(nowp)) != 0) >- (void)printf("%s ", buf); >+ xo_emit("{:time-of-day/%s} ", buf); > /* > * Print how long system has been up. > */ >@@ -444,35 +470,45 @@ > uptime %= 3600; > mins = uptime / 60; > secs = uptime % 60; >- (void)printf(" up"); >+ xo_emit(" up"); >+ xo_attr("seconds", "%lu", (unsigned long) tp.tv_sec); > if (days > 0) >- (void)printf(" %d day%s,", days, days > 1 ? "s" : ""); >+ xo_emit(" {:uptime/%d day%s},", >+ days, days > 1 ? "s" : ""); > if (hrs > 0 && mins > 0) >- (void)printf(" %2d:%02d,", hrs, mins); >+ xo_emit(" {:uptime/%2d:%02d},", hrs, mins); > else if (hrs > 0) >- (void)printf(" %d hr%s,", hrs, hrs > 1 ? "s" : ""); >+ xo_emit(" {:uptime/%d hr%s},", >+ hrs, hrs > 1 ? "s" : ""); > else if (mins > 0) >- (void)printf(" %d min%s,", mins, mins > 1 ? "s" : ""); >+ xo_emit(" {:uptime/%d min%s},", >+ mins, mins > 1 ? "s" : ""); > else >- (void)printf(" %d sec%s,", secs, secs > 1 ? "s" : ""); >+ xo_emit(" {:uptime/%d sec%s},", >+ secs, secs > 1 ? "s" : ""); > } > > /* Print number of users logged in to system */ >- (void)printf(" %d user%s", nusers, nusers == 1 ? "" : "s"); >+ xo_emit(" {:users/%d} user%s", nusers, nusers == 1 ? "" : "s"); > > /* > * Print 1, 5, and 15 minute load averages. > */ > if (getloadavg(avenrun, sizeof(avenrun) / sizeof(avenrun[0])) == -1) >- (void)printf(", no load average information available\n"); >+ xo_emit(", no load average information available\n"); > else { >- (void)printf(", load averages:"); >+ static const char *format[] = { >+ " {:load-average-1/%.2f}", >+ " {:load-average-5/%.2f}", >+ " {:load-average-15/%.2f}", >+ }; >+ xo_emit(", load averages:"); > for (i = 0; i < (int)(sizeof(avenrun) / sizeof(avenrun[0])); i++) { > if (use_comma && i > 0) >- (void)printf(","); >- (void)printf(" %.2f", avenrun[i]); >+ xo_emit(","); >+ xo_emit(format[i], avenrun[i]); > } >- (void)printf("\n"); >+ xo_emit("\n"); > } > } > >@@ -493,10 +529,9 @@ > usage(int wcmd) > { > if (wcmd) >- (void)fprintf(stderr, >- "usage: w [-dhin] [-M core] [-N system] [user ...]\n"); >+ xo_error("usage: w [-dhin] [-M core] [-N system] [user ...]\n"); > else >- (void)fprintf(stderr, "usage: uptime\n"); >+ xo_error("usage: uptime\n"); > exit(1); > } > >