]>
git.ipfire.org Git - thirdparty/systemd.git/blob - tools/update-dbus-docs.py
2 # SPDX-License-Identifier: LGPL-2.1+
10 from lxml
import etree
12 PARSER
= etree
.XMLParser(no_network
=True,
13 remove_comments
=False,
15 resolve_entities
=False)
19 class NoCommand(Exception):
22 def find_command(lines
):
24 for num
, line
in enumerate(lines
):
25 # skip empty leading line
26 if num
== 0 and not line
:
28 cont
= line
.endswith('\\')
30 line
= line
[:-1].rstrip()
31 acc
.append(line
if not acc
else line
.lstrip())
34 joined
= ' '.join(acc
)
35 if not joined
.startswith('$ '):
37 return joined
[2:], lines
[:num
+1] + [''], lines
[-1]
40 'org.freedesktop.DBus.Peer',
41 'org.freedesktop.DBus.Introspectable',
42 'org.freedesktop.DBus.Properties',
45 def print_method(declarations
, elem
, *, prefix
, file, is_signal
=False):
46 name
= elem
.get('name')
47 klass
= 'signal' if is_signal
else 'method'
48 declarations
[klass
].append(name
)
50 print(f
'''{prefix}{name}(''', file=file, end
='')
51 lead
= ',\n' + prefix
+ ' ' * len(name
) + ' '
53 for num
, arg
in enumerate(elem
.findall('./arg')):
54 argname
= arg
.get('name')
58 print(f
'method {name}: argument {num+1} has no name', file=sys
.stderr
)
61 type = arg
.get('type')
63 direction
= arg
.get('direction')
64 print(f
'''{lead if num > 0 else ''}{direction:3} {type} {argname}''', file=file, end
='')
66 print(f
'''{lead if num > 0 else ''}{type} {argname}''', file=file, end
='')
68 print(f
');', file=file)
72 'write' : 'readwrite',
75 def value_ellipsis(type):
79 inner
= value_ellipsis(type[1:])
80 return f
"[{inner}{', ...' if inner != '...' else ''}]";
83 def print_property(declarations
, elem
, *, prefix
, file):
84 name
= elem
.get('name')
85 type = elem
.get('type')
86 access
= elem
.get('access')
88 declarations
['property'].append(name
)
90 # @org.freedesktop.DBus.Property.EmitsChangedSignal("false")
91 # @org.freedesktop.systemd1.Privileged("true")
92 # readwrite b EnableWallMessages = false;
94 for anno
in elem
.findall('./annotation'):
95 anno_name
= anno
.get('name')
96 anno_value
= anno
.get('value')
97 print(f
'''{prefix}@{anno_name}("{anno_value}")''', file=file)
99 access
= ACCESS_MAP
.get(access
, access
)
100 print(f
'''{prefix}{access} {type} {name} = {value_ellipsis(type)};''', file=file)
102 def print_interface(iface
, *, prefix
, file, print_boring
, only_interface
, declarations
):
103 name
= iface
.get('name')
105 is_boring
= (name
in BORING_INTERFACES
or
106 only_interface
is not None and name
!= only_interface
)
108 if is_boring
and print_boring
:
109 print(f
'''{prefix}interface {name} {{ ... }};''', file=file)
111 elif not is_boring
and not print_boring
:
112 print(f
'''{prefix}interface {name} {{''', file=file)
113 prefix2
= prefix
+ ' '
115 for num
, elem
in enumerate(iface
.findall('./method')):
117 print(f
'''{prefix2}methods:''', file=file)
118 print_method(declarations
, elem
, prefix
=prefix2
+ ' ', file=file)
120 for num
, elem
in enumerate(iface
.findall('./signal')):
122 print(f
'''{prefix2}signals:''', file=file)
123 print_method(declarations
, elem
, prefix
=prefix2
+ ' ', file=file, is_signal
=True)
125 for num
, elem
in enumerate(iface
.findall('./property')):
127 print(f
'''{prefix2}properties:''', file=file)
128 print_property(declarations
, elem
, prefix
=prefix2
+ ' ', file=file)
130 print(f
'''{prefix}}};''', file=file)
132 def document_has_elem_with_text(document
, elem
, item_repr
):
133 predicate
= f
".//{elem}" # [text() = 'foo'] doesn't seem supported :(
134 for loc
in document
.findall(predicate
):
135 if loc
.text
== item_repr
:
140 def check_documented(document
, declarations
):
142 for klass
, items
in declarations
.items():
144 if klass
== 'method':
146 item_repr
= f
'{item}()'
147 elif klass
== 'signal':
150 elif klass
== 'property':
154 assert False, (klass
, item
)
156 if not document_has_elem_with_text(document
, elem
, item_repr
):
158 print(f
'{klass} {item} is not documented :(')
159 missing
.append((klass
, item
))
163 def xml_to_text(destination
, xml
, *, only_interface
=None):
166 declarations
= collections
.defaultdict(list)
169 print(f
'''node {destination} {{''', file=file)
171 for print_boring
in [False, True]:
172 for iface
in xml
.findall('./interface'):
173 print_interface(iface
, prefix
=' ', file=file,
174 print_boring
=print_boring
,
175 only_interface
=only_interface
,
176 declarations
=declarations
)
177 name
= iface
.get('name')
178 if not name
in BORING_INTERFACES
:
179 interfaces
.append(name
)
181 print(f
'''}};''', file=file)
183 return file.getvalue(), declarations
, interfaces
185 def subst_output(document
, programlisting
):
187 cmd
, prefix_lines
, footer
= find_command(programlisting
.text
.splitlines())
191 only_interface
= programlisting
.get('interface', None)
193 argv
= shlex
.split(cmd
)
195 print(f
'COMMAND: {shlex.join(argv)}')
197 object_idx
= argv
.index('--object-path')
198 object_path
= argv
[object_idx
+ 1]
201 out
= subprocess
.check_output(argv
, text
=True)
202 except subprocess
.CalledProcessError
:
203 print('command failed, ignoring', file=sys
.stderr
)
206 xml
= etree
.fromstring(out
, parser
=PARSER
)
208 new_text
, declarations
, interfaces
= xml_to_text(object_path
, xml
, only_interface
=only_interface
)
210 programlisting
.text
= '\n'.join(prefix_lines
) + '\n' + new_text
+ footer
213 missing
= check_documented(document
, declarations
)
214 parent
= programlisting
.getparent()
216 # delete old comments
218 if (child
.tag
== etree
.Comment
219 and 'Autogenerated' in child
.text
):
221 if (child
.tag
== etree
.Comment
222 and 'not documented' in child
.text
):
224 if (child
.tag
== "variablelist"
225 and child
.attrib
.get("generated",False) == "True"):
228 # insert pointer for systemd-directives generation
229 the_tail
= programlisting
.tail
#tail is erased by addnext, so save it here.
230 prev_element
= etree
.Comment("Autogenerated cross-references for systemd.directives, do not edit")
231 programlisting
.addnext(prev_element
)
232 programlisting
.tail
= the_tail
234 for interface
in interfaces
:
235 variablelist
= etree
.Element("variablelist")
236 variablelist
.attrib
['class'] = 'dbus-interface'
237 variablelist
.attrib
['generated'] = 'True'
238 variablelist
.attrib
['extra-ref'] = interface
240 prev_element
.addnext(variablelist
)
241 prev_element
.tail
= the_tail
242 prev_element
= variablelist
244 for decl_type
,decl_list
in declarations
.items():
245 for declaration
in decl_list
:
246 variablelist
= etree
.Element("variablelist")
247 variablelist
.attrib
['class'] = 'dbus-'+decl_type
248 variablelist
.attrib
['generated'] = 'True'
249 if decl_type
== 'method' :
250 variablelist
.attrib
['extra-ref'] = declaration
+ '()'
252 variablelist
.attrib
['extra-ref'] = declaration
254 prev_element
.addnext(variablelist
)
255 prev_element
.tail
= the_tail
256 prev_element
= variablelist
258 last_element
= etree
.Comment("End of Autogenerated section")
259 prev_element
.addnext(last_element
)
260 prev_element
.tail
= the_tail
261 last_element
.tail
= the_tail
263 # insert comments for undocumented items
264 for item
in reversed(missing
):
265 comment
= etree
.Comment(f
'{item[0]} {item[1]} is not documented!')
266 comment
.tail
= programlisting
.tail
267 parent
.insert(parent
.index(programlisting
) + 1, comment
)
270 src
= open(page
).read()
271 xml
= etree
.fromstring(src
, parser
=PARSER
)
273 # print('parsing {}'.format(name), file=sys.stderr)
274 if xml
.tag
!= 'refentry':
277 pls
= xml
.findall('.//programlisting')
279 subst_output(xml
, pl
)
281 out_text
= etree
.tostring(xml
, encoding
='unicode')
282 # massage format to avoid some lxml whitespace handling idiosyncrasies
283 # https://bugs.launchpad.net/lxml/+bug/526799
284 out_text
= (src
[:src
.find('<refentryinfo')] +
285 out_text
[out_text
.find('<refentryinfo'):] +
288 with
open(page
, 'w') as out
:
291 if __name__
== '__main__':