commit 1ec538a
shrub
·
2026-06-28 17:14:25 +0000 UTC
parent 62c173c
add -m flag for mdoc emission
8 files changed,
+377,
-4
+2,
-1
1@@ -26,11 +26,12 @@ rule install
2 description = install proto
3
4 build parse.o: cc parse.c
5+build mdoc.o: cc mdoc.c
6 build proto.o: cc proto.c
7 build protocol.o: cc protocol.c
8 build util.o: cc util.c
9
10-build proto: link parse.o proto.o protocol.o util.o
11+build proto: link parse.o mdoc.o proto.o protocol.o util.o
12 build all: phony proto
13 build install: install proto
14
+275,
-0
1@@ -0,0 +1,275 @@
2+#include <stdbool.h>
3+#include <ctype.h>
4+#include <stdio.h>
5+#include <stdlib.h>
6+#include <string.h>
7+
8+#include "mdoc.h"
9+#include "util.h"
10+
11+static bool has_description(const struct description *);
12+static void mdoc_macro(const char *);
13+static void mdoc_text(const char *);
14+static void mdoc_textline(const char *, size_t);
15+static void mdoc_paragraphs(const struct description *, bool);
16+static void mdoc_message_signature(const struct message *);
17+static void mdoc_message_args(const struct message *);
18+static void mdoc_message_detail(const struct message *);
19+static void mdoc_enum_detail(const struct enumdef *);
20+static void mdoc_interface_detail(const struct interface *);
21+static char *mdoc_title(const char *);
22+
23+static bool
24+has_description(const struct description *desc)
25+{
26+ return (desc->summary && desc->summary[0]) ||
27+ (desc->text && desc->text[0]);
28+}
29+
30+static void
31+mdoc_macro(const char *s)
32+{
33+ printf("%s\n", s);
34+}
35+
36+static void
37+mdoc_text(const char *s)
38+{
39+ for (; *s; ++s) {
40+ if (*s == '\\')
41+ fputs("\\e", stdout);
42+ else
43+ putchar(*s);
44+ }
45+}
46+
47+static void
48+mdoc_textline(const char *s, size_t len)
49+{
50+ size_t i;
51+
52+ if (!s || len == 0) {
53+ putchar('\n');
54+ return;
55+ }
56+ if (s[0] == '.' || s[0] == '\'')
57+ fputs("\\&", stdout);
58+ for (i = 0; i < len; ++i) {
59+ if (s[i] == '\\')
60+ fputs("\\e", stdout);
61+ else
62+ putchar(s[i]);
63+ }
64+ putchar('\n');
65+}
66+
67+static void
68+mdoc_paragraphs(const struct description *desc, bool leadpp)
69+{
70+ const char *line, *next;
71+
72+ if (!desc->summary || !desc->summary[0]) {
73+ if (!desc->text || !desc->text[0])
74+ return;
75+ if (leadpp)
76+ mdoc_macro(".Pp");
77+ } else {
78+ if (leadpp)
79+ mdoc_macro(".Pp");
80+ fputs(".D1 ", stdout);
81+ mdoc_text(desc->summary);
82+ putchar('\n');
83+ if (!desc->text || !desc->text[0])
84+ return;
85+ mdoc_macro(".Pp");
86+ }
87+ line = desc->text;
88+ while (*line) {
89+ next = strchr(line, '\n');
90+ if (!next)
91+ next = line + strlen(line);
92+ mdoc_textline(line, (size_t)(next - line));
93+ line = *next ? next + 1 : next;
94+ }
95+}
96+
97+static void
98+mdoc_message_signature(const struct message *msg)
99+{
100+ size_t i;
101+
102+ printf(".Fo %s\n", msg->name);
103+ for (i = 0; i < msg->nargs; ++i) {
104+ const struct argument *arg = &msg->args[i];
105+
106+ fputs(".Fa \"", stdout);
107+ if (arg->nullable)
108+ fputc('?', stdout);
109+ if (arg->iface && arg->iface[0] &&
110+ (strcmp(arg->type, "object") == 0 || strcmp(arg->type, "new_id") == 0))
111+ mdoc_text(arg->iface);
112+ else
113+ mdoc_text(arg->type);
114+ fputc(' ', stdout);
115+ mdoc_text(arg->name);
116+ fputs("\"\n", stdout);
117+ }
118+ fputs(".Fc", stdout);
119+ {
120+ const struct argument *creator = message_creator_arg(msg);
121+
122+ if (creator) {
123+ fputs(" \"creates ", stdout);
124+ mdoc_text(creator->iface);
125+ fputc('"', stdout);
126+ }
127+ }
128+ if (msg->destructor)
129+ fputs(" \"destructor\"", stdout);
130+ putchar('\n');
131+}
132+
133+static void
134+mdoc_message_args(const struct message *msg)
135+{
136+ size_t i;
137+ bool any;
138+
139+ any = false;
140+ for (i = 0; i < msg->nargs; ++i) {
141+ if (msg->args[i].summary && msg->args[i].summary[0]) {
142+ any = true;
143+ break;
144+ }
145+ }
146+ if (!any)
147+ return;
148+ mdoc_macro(".Bl -tag -width Ds");
149+ for (i = 0; i < msg->nargs; ++i) {
150+ const struct argument *arg = &msg->args[i];
151+
152+ if (!arg->summary || !arg->summary[0])
153+ continue;
154+ fputs(".It Va ", stdout);
155+ mdoc_text(arg->name);
156+ putchar('\n');
157+ mdoc_textline(arg->summary, strlen(arg->summary));
158+ }
159+ mdoc_macro(".El");
160+}
161+
162+static void
163+mdoc_message_detail(const struct message *msg)
164+{
165+ mdoc_macro(".It");
166+ mdoc_message_signature(msg);
167+ mdoc_paragraphs(&msg->desc, true);
168+ mdoc_message_args(msg);
169+}
170+
171+static void
172+mdoc_enum_detail(const struct enumdef *en)
173+{
174+ size_t i;
175+
176+ mdoc_macro(".It");
177+ fputs(".Sy ", stdout);
178+ mdoc_text(en->name);
179+ putchar('\n');
180+ mdoc_paragraphs(&en->desc, true);
181+ if (en->nentries == 0)
182+ return;
183+ mdoc_macro(".Bl -tag -width Ds");
184+ for (i = 0; i < en->nentries; ++i) {
185+ const struct enumentry *entry = &en->entries[i];
186+
187+ fputs(".It Dv ", stdout);
188+ mdoc_text(entry->name);
189+ fputs(" = ", stdout);
190+ mdoc_text(entry->value);
191+ putchar('\n');
192+ if (entry->summary && entry->summary[0])
193+ mdoc_textline(entry->summary, strlen(entry->summary));
194+ mdoc_paragraphs(&entry->desc, true);
195+ }
196+ mdoc_macro(".El");
197+}
198+
199+static void
200+mdoc_interface_detail(const struct interface *iface)
201+{
202+ size_t i;
203+
204+ fputs(".Ss ", stdout);
205+ mdoc_text(iface->name);
206+ putchar('\n');
207+ fputs(".Sy Version:\\ ", stdout);
208+ printf("%d\n", iface->version);
209+ mdoc_paragraphs(&iface->desc, true);
210+
211+ if (iface->nrequests > 0) {
212+ mdoc_macro(".Pp");
213+ mdoc_macro(".D1 Requests");
214+ mdoc_macro(".Bl -item");
215+ for (i = 0; i < iface->nrequests; ++i)
216+ mdoc_message_detail(&iface->requests[i]);
217+ mdoc_macro(".El");
218+ }
219+ if (iface->nevents > 0) {
220+ mdoc_macro(".Pp");
221+ mdoc_macro(".D1 Events");
222+ mdoc_macro(".Bl -item");
223+ for (i = 0; i < iface->nevents; ++i)
224+ mdoc_message_detail(&iface->events[i]);
225+ mdoc_macro(".El");
226+ }
227+ if (iface->nenums > 0) {
228+ mdoc_macro(".Pp");
229+ mdoc_macro(".D1 Enums");
230+ mdoc_macro(".Bl -item");
231+ for (i = 0; i < iface->nenums; ++i)
232+ mdoc_enum_detail(&iface->enums[i]);
233+ mdoc_macro(".El");
234+ }
235+}
236+
237+static char *
238+mdoc_title(const char *name)
239+{
240+ size_t i, len;
241+ char *title;
242+
243+ len = strlen(name);
244+ title = xmalloc(len + 1);
245+ for (i = 0; i < len; ++i)
246+ title[i] = (char)toupper((unsigned char)name[i]);
247+ title[len] = '\0';
248+ return title;
249+}
250+
251+void
252+print_protocol_mdoc(const struct protocol *proto)
253+{
254+ size_t i;
255+ char *title;
256+
257+ title = mdoc_title(proto->name);
258+ mdoc_macro(".\\\" generated by proto from shrubtools!!!!.");
259+ mdoc_macro(".\\\" srcdump.net/shrub/shrubtools");
260+ mdoc_macro(".Dd $Mdocdate$");
261+ printf(".Dt %s 7\n", title);
262+ mdoc_macro(".Os");
263+ mdoc_macro(".Sh NAME");
264+ fputs(".Nm ", stdout);
265+ mdoc_text(proto->name);
266+ putchar('\n');
267+ mdoc_macro(".Nd protocol specification");
268+ if (has_description(&proto->desc)) {
269+ mdoc_macro(".Sh DESCRIPTION");
270+ mdoc_paragraphs(&proto->desc, false);
271+ }
272+ mdoc_macro(".Sh INTERFACES");
273+ for (i = 0; i < proto->nifaces; ++i)
274+ mdoc_interface_detail(&proto->ifaces[i]);
275+ free(title);
276+}
+8,
-0
1@@ -0,0 +1,8 @@
2+#ifndef MDOC_H
3+#define MDOC_H
4+
5+#include "protocol.h"
6+
7+void print_protocol_mdoc(const struct protocol *);
8+
9+#endif
+31,
-0
1@@ -12,6 +12,7 @@ enum parse_scope {
2 SCOPE_INTERFACE,
3 SCOPE_MESSAGE,
4 SCOPE_ENUM,
5+ SCOPE_ENTRY,
6 SCOPE_DESCRIPTION,
7 };
8
9@@ -22,6 +23,7 @@ struct parser {
10 struct interface *iface;
11 struct message *msg;
12 struct enumdef *en;
13+ struct enumentry *entry;
14 enum parse_scope stack[32];
15 size_t depth;
16 struct description *desc;
17@@ -215,11 +217,14 @@ onstart(void *data, const char *name, const char **attrs)
18 atype = attr(attrs, "type");
19 iface = attr(attrs, "interface");
20 allow_null = attr(attrs, "allow-null");
21+ summary = attr(attrs, "summary");
22 if (!aname || !atype)
23 die("%s:%lu: arg missing name/type",
24 p->path, XML_GetCurrentLineNumber(p->xml));
25 message_add_arg(p->msg, aname, atype, iface,
26 allow_null && strcmp(allow_null, "true") == 0);
27+ if (summary)
28+ p->msg->args[p->msg->nargs - 1].summary = xstrdup(summary);
29 return;
30 }
31
32@@ -234,9 +239,28 @@ onstart(void *data, const char *name, const char **attrs)
33 return;
34 }
35
36+ if (strcmp(name, "entry") == 0 && inscope(p, SCOPE_ENUM)) {
37+ const char *value;
38+
39+ aname = attr(attrs, "name");
40+ value = attr(attrs, "value");
41+ summary = attr(attrs, "summary");
42+ if (!aname || !value)
43+ die("%s:%lu: entry missing name/value",
44+ p->path, XML_GetCurrentLineNumber(p->xml));
45+ enum_add_entry(p->en, aname, value);
46+ p->entry = enum_last_entry(p->en);
47+ if (summary)
48+ p->entry->summary = xstrdup(summary);
49+ pushscope(p, SCOPE_ENTRY);
50+ return;
51+ }
52+
53 if (strcmp(name, "description") == 0) {
54 if (inscope(p, SCOPE_MESSAGE))
55 p->desc = &p->msg->desc;
56+ else if (inscope(p, SCOPE_ENTRY))
57+ p->desc = &p->entry->desc;
58 else if (inscope(p, SCOPE_ENUM))
59 p->desc = &p->en->desc;
60 else if (inscope(p, SCOPE_INTERFACE))
61@@ -274,6 +298,13 @@ onend(void *data, const char *name)
62 }
63 return;
64 }
65+ if (strcmp(name, "entry") == 0) {
66+ if (inscope(p, SCOPE_ENTRY)) {
67+ p->entry = NULL;
68+ popscope(p);
69+ }
70+ return;
71+ }
72 if (strcmp(name, "description") == 0) {
73 if (inscope(p, SCOPE_DESCRIPTION)) {
74 free(p->desc->text);
+14,
-2
1@@ -6,6 +6,7 @@
2 #include <string.h>
3
4 #include "parse.h"
5+#include "mdoc.h"
6 #include "protocol.h"
7 #include "util.h"
8
9@@ -34,7 +35,7 @@ static void print_description(const struct description *, const char *);
10 static void
11 usage(FILE *fp)
12 {
13- fprintf(fp, "usage: %s [-h] FILE [INTERFACE]\n", argv0);
14+ fprintf(fp, "usage: %s [-h] [-m] FILE [INTERFACE]\n", argv0);
15 }
16
17 static const char *
18@@ -253,18 +254,24 @@ main(int argc, char *argv[])
19 struct interface *iface;
20 const char *ifname;
21 char *path;
22+ bool mflag;
23 int i;
24
25 memset(&proto, 0, sizeof(proto));
26 argv0 = progname(argv[0], "proto");
27 path = NULL;
28 ifname = NULL;
29+ mflag = false;
30
31 for (i = 1; i < argc; ++i) {
32 if (strcmp(argv[i], "-h") == 0) {
33 usage(stdout);
34 return 0;
35 }
36+ if (strcmp(argv[i], "-m") == 0) {
37+ mflag = true;
38+ continue;
39+ }
40 if (argv[i][0] == '-' && argv[i][1] != '\0') {
41 usage(stderr);
42 return 2;
43@@ -289,6 +296,9 @@ main(int argc, char *argv[])
44 path = resolvepath(path);
45 parsefile(path, &proto);
46
47+ if (mflag && ifname)
48+ die("-m cannot be used with an interface name");
49+
50 iface = NULL;
51 if (ifname) {
52 iface = proto_find_interface(&proto, ifname);
53@@ -296,7 +306,9 @@ main(int argc, char *argv[])
54 die("unknown interface: %s", ifname);
55 }
56
57- if (iface)
58+ if (mflag)
59+ print_protocol_mdoc(&proto);
60+ else if (iface)
61 print_interface_summary(&proto, iface);
62 else
63 print_protocol_summary(&proto);
+32,
-0
1@@ -77,6 +77,7 @@ message_add_arg(struct message *msg, const char *name, const char *type, const c
2 arg->name = xstrdup(name);
3 arg->type = xstrdup(type);
4 arg->iface = iface ? xstrdup(iface) : NULL;
5+ arg->summary = NULL;
6 arg->nullable = nullable;
7 arg->new_id = strcmp(type, "new_id") == 0;
8 }
9@@ -100,6 +101,27 @@ iface_last_enum(struct interface *iface)
10 return &iface->enums[iface->nenums - 1];
11 }
12
13+void
14+enum_add_entry(struct enumdef *en, const char *name, const char *value)
15+{
16+ if (en->nentries == en->entrycap) {
17+ en->entrycap = en->entrycap ? en->entrycap * 2 : 8;
18+ en->entries = xreallocarray(en->entries, en->entrycap, sizeof(en->entries[0]));
19+ }
20+ memset(&en->entries[en->nentries], 0, sizeof(en->entries[0]));
21+ en->entries[en->nentries].name = xstrdup(name);
22+ en->entries[en->nentries].value = xstrdup(value);
23+ ++en->nentries;
24+}
25+
26+struct enumentry *
27+enum_last_entry(struct enumdef *en)
28+{
29+ if (en->nentries == 0)
30+ return NULL;
31+ return &en->entries[en->nentries - 1];
32+}
33+
34 const struct argument *
35 message_creator_arg(const struct message *msg)
36 {
37@@ -163,6 +185,7 @@ free_message(struct message *msg)
38 free(msg->args[i].name);
39 free(msg->args[i].type);
40 free(msg->args[i].iface);
41+ free(msg->args[i].summary);
42 }
43 free(msg->args);
44 }
45@@ -185,8 +208,17 @@ proto_free(struct protocol *proto)
46 free_message(&iface->events[j]);
47 for (j = 0; j < iface->nenums; ++j)
48 {
49+ size_t k;
50+
51 free(iface->enums[j].name);
52 free_description(&iface->enums[j].desc);
53+ for (k = 0; k < iface->enums[j].nentries; ++k) {
54+ free(iface->enums[j].entries[k].name);
55+ free(iface->enums[j].entries[k].value);
56+ free(iface->enums[j].entries[k].summary);
57+ free_description(&iface->enums[j].entries[k].desc);
58+ }
59+ free(iface->enums[j].entries);
60 }
61 free(iface->requests);
62 free(iface->events);
+13,
-0
1@@ -13,6 +13,7 @@ struct argument {
2 char *name;
3 char *type;
4 char *iface;
5+ char *summary;
6 bool nullable;
7 bool new_id;
8 };
9@@ -22,9 +23,19 @@ struct description {
10 char *text;
11 };
12
13+struct enumentry {
14+ char *name;
15+ char *value;
16+ char *summary;
17+ struct description desc;
18+};
19+
20 struct enumdef {
21 char *name;
22 struct description desc;
23+ struct enumentry *entries;
24+ size_t nentries;
25+ size_t entrycap;
26 };
27
28 struct message {
29@@ -72,6 +83,8 @@ struct message *iface_add_message(struct interface *, enum message_dir, const ch
30 void message_add_arg(struct message *, const char *, const char *, const char *, bool);
31 void iface_add_enum(struct interface *, const char *);
32 struct enumdef *iface_last_enum(struct interface *);
33+void enum_add_entry(struct enumdef *, const char *, const char *);
34+struct enumentry *enum_last_entry(struct enumdef *);
35 void proto_resolve(struct protocol *);
36 void proto_free(struct protocol *);
37
+2,
-1
1@@ -1,7 +1,7 @@
2 proto
3 -----
4
5-proto is a small command-line tool for inspecting wayland protocol xml files in a nice human-readable way. i was kinda sick of having a browser open just for browsing wayland.app. it searches your system paths, so you can do stuff like 'proto xdg-shell' or 'proto wayland' and it knows where to look. it can also read local files, but you need to give it the path. you can also provide an interface as an argument, and it will give you the rundown on that.
6+proto is a small command-line tool for inspecting wayland protocol xml files in a nice human-readable way. i was kinda sick of having a browser open just for browsing wayland.app. it searches your system paths, so you can do stuff like 'proto xdg-shell' or 'proto wayland' and it knows where to look. it can also read local files, but you need to give it the path. you can also provide an interface as an argument, and it will give you the rundown on that. with `-m`, it emits an mdoc manpage for the whole protocol.
7
8 build:
9 ninja
10@@ -10,6 +10,7 @@ build:
11 examples
12 proto xdg-shell
13 proto xdg-shell xdg_wm_base
14+ proto -m xdg-shell
15 proto files/my-local-wayland-protocol.xml
16
17 dependencies: