]>
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):
23 'org.freedesktop.DBus.Peer',
24 'org.freedesktop.DBus.Introspectable',
25 'org.freedesktop.DBus.Properties',
28 def print_method(declarations
, elem
, *, prefix
, file, is_signal
=False):
29 name
= elem
.get('name')
30 klass
= 'signal' if is_signal
else 'method'
31 declarations
[klass
].append(name
)
33 print(f
'''{prefix}{name}(''', file=file, end
='')
34 lead
= ',\n' + prefix
+ ' ' * len(name
) + ' '
36 for num
, arg
in enumerate(elem
.findall('./arg')):
37 argname
= arg
.get('name')
41 print(f
'method {name}: argument {num+1} has no name', file=sys
.stderr
)
44 type = arg
.get('type')
46 direction
= arg
.get('direction')
47 print(f
'''{lead if num > 0 else ''}{direction:3} {type} {argname}''', file=file, end
='')
49 print(f
'''{lead if num > 0 else ''}{type} {argname}''', file=file, end
='')
51 print(f
');', file=file)
55 'write' : 'readwrite',
58 def value_ellipsis(type):
62 inner
= value_ellipsis(type[1:])
63 return f
"[{inner}{', ...' if inner != '...' else ''}]";
66 def print_property(declarations
, elem
, *, prefix
, file):
67 name
= elem
.get('name')
68 type = elem
.get('type')
69 access
= elem
.get('access')
71 declarations
['property'].append(name
)
73 # @org.freedesktop.DBus.Property.EmitsChangedSignal("false")
74 # @org.freedesktop.systemd1.Privileged("true")
75 # readwrite b EnableWallMessages = false;
77 for anno
in elem
.findall('./annotation'):
78 anno_name
= anno
.get('name')
79 anno_value
= anno
.get('value')
80 print(f
'''{prefix}@{anno_name}("{anno_value}")''', file=file)
82 access
= ACCESS_MAP
.get(access
, access
)
83 print(f
'''{prefix}{access} {type} {name} = {value_ellipsis(type)};''', file=file)
85 def print_interface(iface
, *, prefix
, file, print_boring
, only_interface
, declarations
):
86 name
= iface
.get('name')
88 is_boring
= (name
in BORING_INTERFACES
or
89 only_interface
is not None and name
!= only_interface
)
91 if is_boring
and print_boring
:
92 print(f
'''{prefix}interface {name} {{ ... }};''', file=file)
94 elif not is_boring
and not print_boring
:
95 print(f
'''{prefix}interface {name} {{''', file=file)
96 prefix2
= prefix
+ ' '
98 for num
, elem
in enumerate(iface
.findall('./method')):
100 print(f
'''{prefix2}methods:''', file=file)
101 print_method(declarations
, elem
, prefix
=prefix2
+ ' ', file=file)
103 for num
, elem
in enumerate(iface
.findall('./signal')):
105 print(f
'''{prefix2}signals:''', file=file)
106 print_method(declarations
, elem
, prefix
=prefix2
+ ' ', file=file, is_signal
=True)
108 for num
, elem
in enumerate(iface
.findall('./property')):
110 print(f
'''{prefix2}properties:''', file=file)
111 print_property(declarations
, elem
, prefix
=prefix2
+ ' ', file=file)
113 print(f
'''{prefix}}};''', file=file)
115 def document_has_elem_with_text(document
, elem
, item_repr
):
116 predicate
= f
".//{elem}" # [text() = 'foo'] doesn't seem supported :(
117 for loc
in document
.findall(predicate
):
118 if loc
.text
== item_repr
:
123 def check_documented(document
, declarations
):
125 for klass
, items
in declarations
.items():
127 if klass
== 'method':
129 item_repr
= f
'{item}()'
130 elif klass
== 'signal':
133 elif klass
== 'property':
137 assert False, (klass
, item
)
139 if not document_has_elem_with_text(document
, elem
, item_repr
):
141 print(f
'{klass} {item} is not documented :(')
142 missing
.append((klass
, item
))
146 def xml_to_text(destination
, xml
, *, only_interface
=None):
149 declarations
= collections
.defaultdict(list)
152 print(f
'''node {destination} {{''', file=file)
154 for print_boring
in [False, True]:
155 for iface
in xml
.findall('./interface'):
156 print_interface(iface
, prefix
=' ', file=file,
157 print_boring
=print_boring
,
158 only_interface
=only_interface
,
159 declarations
=declarations
)
160 name
= iface
.get('name')
161 if not name
in BORING_INTERFACES
:
162 interfaces
.append(name
)
164 print(f
'''}};''', file=file)
166 return file.getvalue(), declarations
, interfaces
168 def subst_output(document
, programlisting
):
169 executable
= programlisting
.get('executable', None)
170 if executable
is None:
173 executable
= programlisting
.get('executable')
174 node
= programlisting
.get('node')
175 interface
= programlisting
.get('interface')
177 argv
= [f
'{build_dir}/{executable}', f
'--bus-introspect={interface}']
178 print(f
'COMMAND: {shlex.join(argv)}')
181 out
= subprocess
.check_output(argv
, text
=True)
182 except FileNotFoundError
:
183 print(f
'{executable} not found, ignoring', file=sys
.stderr
)
186 xml
= etree
.fromstring(out
, parser
=PARSER
)
188 new_text
, declarations
, interfaces
= xml_to_text(node
, xml
, only_interface
=interface
)
189 programlisting
.text
= '\n' + new_text
+ ' '
192 missing
= check_documented(document
, declarations
)
193 parent
= programlisting
.getparent()
195 # delete old comments
197 if (child
.tag
== etree
.Comment
198 and 'Autogenerated' in child
.text
):
200 if (child
.tag
== etree
.Comment
201 and 'not documented' in child
.text
):
203 if (child
.tag
== "variablelist"
204 and child
.attrib
.get("generated",False) == "True"):
207 # insert pointer for systemd-directives generation
208 the_tail
= programlisting
.tail
#tail is erased by addnext, so save it here.
209 prev_element
= etree
.Comment("Autogenerated cross-references for systemd.directives, do not edit")
210 programlisting
.addnext(prev_element
)
211 programlisting
.tail
= the_tail
213 for interface
in interfaces
:
214 variablelist
= etree
.Element("variablelist")
215 variablelist
.attrib
['class'] = 'dbus-interface'
216 variablelist
.attrib
['generated'] = 'True'
217 variablelist
.attrib
['extra-ref'] = interface
219 prev_element
.addnext(variablelist
)
220 prev_element
.tail
= the_tail
221 prev_element
= variablelist
223 for decl_type
,decl_list
in declarations
.items():
224 for declaration
in decl_list
:
225 variablelist
= etree
.Element("variablelist")
226 variablelist
.attrib
['class'] = 'dbus-'+decl_type
227 variablelist
.attrib
['generated'] = 'True'
228 if decl_type
== 'method' :
229 variablelist
.attrib
['extra-ref'] = declaration
+ '()'
231 variablelist
.attrib
['extra-ref'] = declaration
233 prev_element
.addnext(variablelist
)
234 prev_element
.tail
= the_tail
235 prev_element
= variablelist
237 last_element
= etree
.Comment("End of Autogenerated section")
238 prev_element
.addnext(last_element
)
239 prev_element
.tail
= the_tail
240 last_element
.tail
= the_tail
242 # insert comments for undocumented items
243 for item
in reversed(missing
):
244 comment
= etree
.Comment(f
'{item[0]} {item[1]} is not documented!')
245 comment
.tail
= programlisting
.tail
246 parent
.insert(parent
.index(programlisting
) + 1, comment
)
249 src
= open(page
).read()
250 xml
= etree
.fromstring(src
, parser
=PARSER
)
252 # print('parsing {}'.format(name), file=sys.stderr)
253 if xml
.tag
!= 'refentry':
256 pls
= xml
.findall('.//programlisting')
258 subst_output(xml
, pl
)
260 out_text
= etree
.tostring(xml
, encoding
='unicode')
261 # massage format to avoid some lxml whitespace handling idiosyncrasies
262 # https://bugs.launchpad.net/lxml/+bug/526799
263 out_text
= (src
[:src
.find('<refentryinfo')] +
264 out_text
[out_text
.find('<refentryinfo'):] +
267 with
open(page
, 'w') as out
:
270 if __name__
== '__main__':
273 if pages
[0].startswith('--build-dir='):
274 build_dir
= pages
[0].partition('=')[2]
279 if not os
.path
.exists(f
'{build_dir}/systemd'):
280 exit(f
"{build_dir}/systemd doesn't exist. Use --build-dir=.")