chiark / gitweb /
Prep v239: Add missing updates that evaded migration.
[elogind.git] / tools / make-directive-index.py
1 #!/usr/bin/env python3
2 #  -*- Mode: python; coding: utf-8; indent-tabs-mode: nil -*- */
3 # SPDX-License-Identifier: LGPL-2.1+
4
5 import sys
6 import collections
7 import re
8 from xml_helper import xml_parse, xml_print, tree
9 from copy import deepcopy
10
11 TEMPLATE = '''\
12 <refentry id="elogind.directives" conditional="HAVE_PYTHON">
13
14         <refentryinfo>
15                 <title>elogind.directives</title>
16                 <productname>elogind</productname>
17         </refentryinfo>
18
19         <refmeta>
20                 <refentrytitle>elogind.directives</refentrytitle>
21                 <manvolnum>7</manvolnum>
22         </refmeta>
23
24         <refnamediv>
25                 <refname>elogind.directives</refname>
26                 <refpurpose>Index of configuration directives</refpurpose>
27         </refnamediv>
28
29         <refsect1>
30                 <title>Unit directives</title>
31
32                 <para>Directives for configuring units, used in unit
33                 files.</para>
34
35                 <variablelist id='unit-directives' />
36         </refsect1>
37
38         <refsect1>
39                 <title>Options on the kernel command line</title>
40
41                 <para>Kernel boot options for configuring the behaviour of the
42                 elogind process.</para>
43
44                 <variablelist id='kernel-commandline-options' />
45         </refsect1>
46
47         <refsect1>
48                 <title>Environment variables</title>
49
50                 <para>Environment variables understood by the elogind
51                 manager and other programs.</para>
52
53                 <variablelist id='environment-variables' />
54         </refsect1>
55
56         <refsect1>
57                 <title>UDEV directives</title>
58
59                 <para>Directives for configuring elogind units through the
60                 udev database.</para>
61
62                 <variablelist id='udev-directives' />
63         </refsect1>
64
65         <refsect1>
66                 <title>Network directives</title>
67
68                 <para>Directives for configuring network links through the
69                 net-setup-link udev builtin and networks through
70                 elogind-networkd.</para>
71
72                 <variablelist id='network-directives' />
73         </refsect1>
74
75         <refsect1>
76                 <title>Journal fields</title>
77
78                 <para>Fields in the journal events with a well known meaning.</para>
79
80                 <variablelist id='journal-directives' />
81         </refsect1>
82
83         <refsect1>
84                 <title>PAM configuration directives</title>
85
86                 <para>Directives for configuring PAM behaviour.</para>
87
88                 <variablelist id='pam-directives' />
89         </refsect1>
90
91         <refsect1>
92                 <title><filename>/etc/crypttab</filename> and
93                 <filename>/etc/fstab</filename> options</title>
94
95                 <para>Options which influence mounted filesystems and
96                 encrypted volumes.</para>
97
98                 <variablelist id='fstab-options' />
99         </refsect1>
100
101         <refsect1>
102                 <title>System manager directives</title>
103
104                 <para>Directives for configuring the behaviour of the
105                 elogind process.</para>
106
107                 <variablelist id='elogind-directives' />
108         </refsect1>
109
110         <refsect1>
111                 <title>command line options</title>
112
113                 <para>Command-line options accepted by programs in the
114                 elogind suite.</para>
115
116                 <variablelist id='options' />
117         </refsect1>
118
119         <refsect1>
120                 <title>Constants</title>
121
122                 <para>Various constant used and/or defined by elogind.</para>
123
124                 <variablelist id='constants' />
125         </refsect1>
126
127         <refsect1>
128                 <title>Miscellaneous options and directives</title>
129
130                 <para>Other configuration elements which don't fit in
131                 any of the above groups.</para>
132
133                 <variablelist id='miscellaneous' />
134         </refsect1>
135
136         <refsect1>
137                 <title>Files and directories</title>
138
139                 <para>Paths and file names referred to in the
140                 documentation.</para>
141
142                 <variablelist id='filenames' />
143         </refsect1>
144
145         <refsect1>
146                 <title>Colophon</title>
147                 <para id='colophon' />
148         </refsect1>
149 </refentry>
150 '''
151
152 COLOPHON = '''\
153 This index contains {count} entries in {sections} sections,
154 referring to {pages} individual manual pages.
155 '''
156
157 def _extract_directives(directive_groups, formatting, page):
158     t = xml_parse(page)
159     section = t.find('./refmeta/manvolnum').text
160     pagename = t.find('./refmeta/refentrytitle').text
161
162     storopt = directive_groups['options']
163     for variablelist in t.iterfind('.//variablelist'):
164         klass = variablelist.attrib.get('class')
165         storvar = directive_groups[klass or 'miscellaneous']
166         # <option>s go in OPTIONS, unless class is specified
167         for xpath, stor in (('./varlistentry/term/varname', storvar),
168                             ('./varlistentry/term/option',
169                              storvar if klass else storopt)):
170             for name in variablelist.iterfind(xpath):
171                 text = re.sub(r'([= ]).*', r'\1', name.text).rstrip()
172                 stor[text].append((pagename, section))
173                 if text not in formatting:
174                     # use element as formatted display
175                     if name.text[-1] in '= ':
176                         name.clear()
177                     else:
178                         name.tail = ''
179                     name.text = text
180                     formatting[text] = name
181
182     storfile = directive_groups['filenames']
183     for xpath, absolute_only in (('.//refsynopsisdiv//filename', False),
184                                  ('.//refsynopsisdiv//command', False),
185                                  ('.//filename', True)):
186         for name in t.iterfind(xpath):
187             if absolute_only and not (name.text and name.text.startswith('/')):
188                 continue
189             if name.attrib.get('noindex'):
190                 continue
191             name.tail = ''
192             if name.text:
193                 if name.text.endswith('*'):
194                     name.text = name.text[:-1]
195                 if not name.text.startswith('.'):
196                     text = name.text.partition(' ')[0]
197                     if text != name.text:
198                         name.clear()
199                         name.text = text
200                     if text.endswith('/'):
201                         text = text[:-1]
202                     storfile[text].append((pagename, section))
203                     if text not in formatting:
204                         # use element as formatted display
205                         formatting[text] = name
206             else:
207                 text = ' '.join(name.itertext())
208                 storfile[text].append((pagename, section))
209                 formatting[text] = name
210
211     storfile = directive_groups['constants']
212     for name in t.iterfind('.//constant'):
213         if name.attrib.get('noindex'):
214             continue
215         name.tail = ''
216         if name.text.startswith('('): # a cast, strip it
217             name.text = name.text.partition(' ')[2]
218         storfile[name.text].append((pagename, section))
219         formatting[name.text] = name
220
221 def _make_section(template, name, directives, formatting):
222     varlist = template.find(".//*[@id='{}']".format(name))
223     for varname, manpages in sorted(directives.items()):
224         entry = tree.SubElement(varlist, 'varlistentry')
225         term = tree.SubElement(entry, 'term')
226         display = deepcopy(formatting[varname])
227         term.append(display)
228
229         para = tree.SubElement(tree.SubElement(entry, 'listitem'), 'para')
230
231         b = None
232         for manpage, manvolume in sorted(set(manpages)):
233             if b is not None:
234                 b.tail = ', '
235             b = tree.SubElement(para, 'citerefentry')
236             c = tree.SubElement(b, 'refentrytitle')
237             c.text = manpage
238             c.attrib['target'] = varname
239             d = tree.SubElement(b, 'manvolnum')
240             d.text = manvolume
241         entry.tail = '\n\n'
242
243 def _make_colophon(template, groups):
244     count = 0
245     pages = set()
246     for group in groups:
247         count += len(group)
248         for pagelist in group.values():
249             pages |= set(pagelist)
250
251     para = template.find(".//para[@id='colophon']")
252     para.text = COLOPHON.format(count=count,
253                                 sections=len(groups),
254                                 pages=len(pages))
255
256 def _make_page(template, directive_groups, formatting):
257     """Create an XML tree from directive_groups.
258
259     directive_groups = {
260        'class': {'variable': [('manpage', 'manvolume'), ...],
261                  'variable2': ...},
262        ...
263     }
264     """
265     for name, directives in directive_groups.items():
266         _make_section(template, name, directives, formatting)
267
268     _make_colophon(template, directive_groups.values())
269
270     return template
271
272 def make_page(*xml_files):
273     "Extract directives from xml_files and return XML index tree."
274     template = tree.fromstring(TEMPLATE)
275     names = [vl.get('id') for vl in template.iterfind('.//variablelist')]
276     directive_groups = {name:collections.defaultdict(list)
277                         for name in names}
278     formatting = {}
279     for page in xml_files:
280         try:
281             _extract_directives(directive_groups, formatting, page)
282         except Exception:
283             raise ValueError("failed to process " + page)
284
285     return _make_page(template, directive_groups, formatting)
286
287 if __name__ == '__main__':
288     with open(sys.argv[1], 'wb') as f:
289         f.write(xml_print(make_page(*sys.argv[2:])))