]> git.ipfire.org Git - thirdparty/systemd.git/blob - tools/make-directive-index.py
NEWS: finalize for v256~rc3
[thirdparty/systemd.git] / tools / make-directive-index.py
1 #!/usr/bin/env python3
2 # SPDX-License-Identifier: LGPL-2.1-or-later
3
4 import collections
5 import re
6 import sys
7 from copy import deepcopy
8
9 from xml_helper import tree, xml_parse, xml_print
10
11 COLOPHON = '''\
12 This index contains {count} entries in {sections} sections,
13 referring to {pages} individual manual pages.
14 '''
15
16 def _extract_directives(directive_groups, formatting, page):
17 t = xml_parse(page)
18 section = t.find('./refmeta/manvolnum').text
19 pagename = t.find('./refmeta/refentrytitle').text
20
21 storopt = directive_groups['options']
22 for variablelist in t.iterfind('.//variablelist'):
23 klass = variablelist.attrib.get('class')
24 searchpath = variablelist.attrib.get('xpath','./varlistentry/term/varname')
25 storvar = directive_groups[klass or 'miscellaneous']
26 # <option>s go in OPTIONS, unless class is specified
27 for xpath, stor in ((searchpath, storvar),
28 ('./varlistentry/term/option',
29 storvar if klass else storopt)):
30 for name in variablelist.iterfind(xpath):
31 text = re.sub(r'([= ]).*', r'\1', name.text).rstrip()
32 if text.startswith('-'):
33 # for options, merge options with and without mandatory arg
34 text = text.partition('=')[0]
35 stor[text].append((pagename, section))
36 if text not in formatting:
37 # use element as formatted display
38 if name.text[-1] in "= '":
39 name.clear()
40 else:
41 name.tail = ''
42 name.text = text
43 formatting[text] = name
44 extra = variablelist.attrib.get('extra-ref')
45 if extra:
46 stor[extra].append((pagename, section))
47 if extra not in formatting:
48 elt = tree.Element("varname")
49 elt.text= extra
50 formatting[extra] = elt
51
52 storfile = directive_groups['filenames']
53 for xpath, absolute_only in (('.//refsynopsisdiv//filename', False),
54 ('.//refsynopsisdiv//command', False),
55 ('.//filename', True)):
56 for name in t.iterfind(xpath):
57 if absolute_only and not (name.text and name.text.startswith('/')):
58 continue
59 if name.attrib.get('index') == 'false':
60 continue
61 name.tail = ''
62 if name.text:
63 if name.text.endswith('*'):
64 name.text = name.text[:-1]
65 if not name.text.startswith('.'):
66 text = name.text.partition(' ')[0]
67 if text != name.text:
68 name.clear()
69 name.text = text
70 if text.endswith('/'):
71 text = text[:-1]
72 storfile[text].append((pagename, section))
73 if text not in formatting:
74 # use element as formatted display
75 formatting[text] = name
76 else:
77 text = ' '.join(name.itertext())
78 storfile[text].append((pagename, section))
79 formatting[text] = name
80
81 for name in t.iterfind('.//constant'):
82 if name.attrib.get('index') == 'false':
83 continue
84 name.tail = ''
85 if name.text.startswith('('): # a cast, strip it
86 name.text = name.text.partition(' ')[2]
87 klass = name.attrib.get('class') or 'constants'
88 storfile = directive_groups[klass]
89 storfile[name.text].append((pagename, section))
90 formatting[name.text] = name
91
92 storfile = directive_groups['specifiers']
93 for name in t.iterfind(".//table[@class='specifiers']//entry/literal"):
94 if name.text[0] != '%' or name.getparent().text is not None:
95 continue
96 if name.attrib.get('index') == 'false':
97 continue
98 storfile[name.text].append((pagename, section))
99 formatting[name.text] = name
100 for name in t.iterfind(".//literal[@class='specifiers']"):
101 storfile[name.text].append((pagename, section))
102 formatting[name.text] = name
103
104 def _make_section(template, name, directives, formatting):
105 varlist = template.find(f".//*[@id='{name}']")
106 for varname, manpages in sorted(directives.items()):
107 entry = tree.SubElement(varlist, 'varlistentry')
108 term = tree.SubElement(entry, 'term')
109 display = deepcopy(formatting[varname])
110 term.append(display)
111
112 para = tree.SubElement(tree.SubElement(entry, 'listitem'), 'para')
113
114 b = None
115 for manpage, manvolume in sorted(set(manpages)):
116 if b is not None:
117 b.tail = ', '
118 b = tree.SubElement(para, 'citerefentry')
119 c = tree.SubElement(b, 'refentrytitle')
120 c.text = manpage
121 c.attrib['target'] = varname
122 d = tree.SubElement(b, 'manvolnum')
123 d.text = manvolume
124 entry.tail = '\n\n'
125
126 def _make_colophon(template, groups):
127 count = 0
128 pages = set()
129 for group in groups:
130 count += len(group)
131 for pagelist in group.values():
132 pages |= set(pagelist)
133
134 para = template.find(".//para[@id='colophon']")
135 para.text = COLOPHON.format(count=count,
136 sections=len(groups),
137 pages=len(pages))
138
139 def _make_page(template, directive_groups, formatting):
140 """Create an XML tree from directive_groups.
141
142 directive_groups = {
143 'class': {'variable': [('manpage', 'manvolume'), ...],
144 'variable2': ...},
145 ...
146 }
147 """
148 for name, directives in directive_groups.items():
149 _make_section(template, name, directives, formatting)
150
151 _make_colophon(template, directive_groups.values())
152
153 return template
154
155 def make_page(template_path, xml_files):
156 "Extract directives from xml_files and return XML index tree."
157 template = xml_parse(template_path)
158 names = [vl.get('id') for vl in template.iterfind('.//variablelist')]
159 directive_groups = {name:collections.defaultdict(list)
160 for name in names}
161 formatting = {}
162 for page in xml_files:
163 try:
164 _extract_directives(directive_groups, formatting, page)
165 except Exception as e:
166 raise ValueError("failed to process " + page) from e
167
168 return _make_page(template, directive_groups, formatting)
169
170 if __name__ == '__main__':
171 with open(sys.argv[1], 'wb') as f:
172 _template_path = sys.argv[2]
173 _xml_files = sys.argv[3:]
174 _xml = make_page(_template_path, _xml_files)
175 f.write(xml_print(_xml))