device: renew dhcp leases on awake for software devices
[NetworkManager.git] / libnm / generate-setting-docs.py
1 #!/usr/bin/env python
2 # This library is free software; you can redistribute it and/or
3 # modify it under the terms of the GNU Lesser General Public
4 # License as published by the Free Software Foundation; either
5 # version 2 of the License, or (at your option) any later version.
6 #
7 # This library is distributed in the hope that it will be useful,
8 # but WITHOUT ANY WARRANTY; without even the implied warranty of
9 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
10 # Lesser General Public License for more details.
11 #
12 # You should have received a copy of the GNU Lesser General Public
13 # License along with this library; if not, write to the
14 # Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
15 # Boston, MA 02110-1301 USA.
16 #
17 # Copyright 2009 - 2014 Red Hat, Inc.
18
19 from __future__ import print_function
20
21 import gi
22 gi.require_version('NM', '1.0')
23 from gi.repository import NM, GObject
24 import argparse, datetime, re, sys
25 import xml.etree.ElementTree as ET
26
27 dbus_type_name_map = {
28     'b': 'boolean',
29     's': 'string',
30     'i': 'int32',
31     'u': 'uint32',
32     't': 'uint64',
33     'x': 'int64',
34     'y': 'byte',
35     'as': 'array of string',
36     'au': 'array of uint32',
37     'ay': 'byte array',
38     'a{ss}': 'dict of string to string',
39     'a{sv}': 'vardict',
40     'aau': 'array of array of uint32',
41     'aay': 'array of byte array',
42     'a(ayuay)': 'array of legacy IPv6 address struct',
43     'a(ayuayu)': 'array of legacy IPv6 route struct',
44 }
45
46 ns_map = {
47     'c':    'http://www.gtk.org/introspection/c/1.0',
48     'gi':   'http://www.gtk.org/introspection/core/1.0',
49     'glib': 'http://www.gtk.org/introspection/glib/1.0'
50 }
51 identifier_key = '{%s}identifier' % ns_map['c']
52 nick_key = '{%s}nick' % ns_map['glib']
53 symbol_prefix_key = '{%s}symbol-prefix' % ns_map['c']
54
55 constants = {
56     'TRUE': 'TRUE',
57     'FALSE': 'FALSE',
58     'G_MAXUINT32': 'G_MAXUINT32',
59     'NULL': 'NULL' }
60 setting_names = {}
61
62 def init_constants(girxml, settings):
63     for const in girxml.findall('./gi:namespace/gi:constant', ns_map):
64         cname = const.attrib['{%s}type' % ns_map['c']]
65         cvalue = const.attrib['value']
66         if const.find('./gi:type[@name="utf8"]', ns_map) is not None:
67             cvalue = '"%s"' % cvalue
68         constants[cname] = cvalue
69
70     for enum in girxml.findall('./gi:namespace/gi:enumeration', ns_map):
71         for enumval in enum.findall('./gi:member', ns_map):
72             cname = enumval.attrib[identifier_key]
73             cvalue = '%s (%s)' % (cname, enumval.attrib['value'])
74             constants[cname] = cvalue
75
76     for enum in girxml.findall('./gi:namespace/gi:bitfield', ns_map):
77         for enumval in enum.findall('./gi:member', ns_map):
78             cname = enumval.attrib[identifier_key]
79             cvalue = '%s (0x%x)' % (cname, int(enumval.attrib['value']))
80             constants[cname] = cvalue
81
82     for setting in settings:
83         setting_type_name = 'NM' + setting.attrib['name'];
84         setting_name_symbol = 'NM_' + setting.attrib[symbol_prefix_key].upper() + '_SETTING_NAME'
85         if constants.has_key(setting_name_symbol):
86             setting_name = constants[setting_name_symbol]
87             setting_names[setting_type_name] = setting_name
88
89 def get_prop_type(setting, pspec, propxml):
90     dbus_type = setting.get_dbus_property_type(pspec.name).dup_string()
91     prop_type = dbus_type_name_map[dbus_type]
92
93     if GObject.type_is_a(pspec.value_type, GObject.TYPE_ENUM) or GObject.type_is_a(pspec.value_type, GObject.TYPE_FLAGS):
94         prop_type = "%s (%s)" % (pspec.value_type.name, prop_type)
95
96     return prop_type
97
98 def get_docs(setting, pspec, propxml):
99     doc_xml = propxml.find('gi:doc', ns_map)
100     if doc_xml is None:
101         return None
102
103     doc = doc_xml.text
104     if 'deprecated' in propxml.attrib:
105         doc = doc + ' Deprecated: ' + propxml.attrib['deprecated']
106
107     doc = re.sub(r'\n\s*', r' ', doc)
108
109     # Expand constants
110     doc = re.sub(r'%([^%]\w*)', lambda match: constants[match.group(1)], doc)
111
112     # #NMSettingWired:mac-address -> "mac-address"
113     doc = re.sub(r'#[A-Za-z0-9_]*:([A-Za-z0-9_-]*)', r'"\1"', doc)
114
115     # #NMSettingWired setting -> "802-3-ethernet" setting
116     doc = re.sub(r'#([A-Z]\w*) setting', lambda match: setting_names[match.group(1)] + ' setting', doc)
117
118     # remaining gtk-doc cleanup
119     doc = doc.replace('%%', '%')
120     doc = doc.replace('<!-- -->', '')
121     doc = re.sub(r' Element-.ype:.*', '', doc)
122     doc = re.sub(r'#([A-Z]\w*)', r'\1', doc)
123
124     # Remove sentences that refer to functions
125     doc = re.sub(r'\.\s+[^.]*\w\(\)[^.]*\.', r'.', doc)
126
127     return doc
128
129 def get_default_value(setting, pspec, propxml):
130     default_value = setting.get_property(pspec.name.replace('-', '_'))
131     if default_value is None:
132         return default_value
133
134     value_type = get_prop_type(setting, pspec, propxml)
135     if value_type == 'string' and default_value != '' and pspec.name != 'name':
136         default_value = '"%s"' % default_value
137     elif value_type == 'gchar' and default_value != '':
138         default_value = "'%s'" % default_value
139     elif value_type == 'boolean':
140         default_value = str(default_value).upper()
141     elif value_type == 'byte array':
142         default_value = '[]'
143     elif str(default_value).startswith('<'):
144         default_value = None
145
146     return default_value
147
148 def escape(val):
149     return str(val).replace('"', '&quot;')
150
151 def usage():
152     print("Usage: %s --gir FILE --output FILE" % sys.argv[0])
153     exit()
154
155 parser = argparse.ArgumentParser()
156 parser.add_argument('-g', '--gir', metavar='FILE', help='NM-1.0.gir file')
157 parser.add_argument('-x', '--overrides', metavar='FILE', help='documentation overrides file')
158 parser.add_argument('-o', '--output', metavar='FILE', help='output file')
159
160 args = parser.parse_args()
161 if args.gir is None or args.output is None:
162     usage()
163
164 girxml = ET.parse(args.gir).getroot()
165 outfile = open(args.output, mode='w')
166
167 basexml = girxml.find('./gi:namespace/gi:class[@name="Setting"]', ns_map)
168 settings = girxml.findall('./gi:namespace/gi:class[@parent="Setting"]', ns_map)
169 # Hack. Need a better way to do this
170 ipxml = girxml.find('./gi:namespace/gi:class[@name="SettingIPConfig"]', ns_map)
171 settings.extend(girxml.findall('./gi:namespace/gi:class[@parent="SettingIPConfig"]', ns_map))
172 settings = sorted(settings, key=lambda setting: setting.attrib['{%s}symbol-prefix' % ns_map['c']])
173
174 init_constants(girxml, settings)
175
176 if args.overrides is not None:
177     overrides = ET.parse(args.overrides).getroot()
178
179 outfile.write("""<?xml version=\"1.0\"?>
180 <!DOCTYPE nm-setting-docs [
181 <!ENTITY quot "&#34;">
182 ]>
183 <nm-setting-docs>
184 """)
185
186 for settingxml in settings:
187     if settingxml.attrib.has_key('abstract'):
188         continue
189
190     new_func = NM.__getattr__(settingxml.attrib['name'])
191     setting = new_func()
192
193     outfile.write("  <setting name=\"%s\">\n" % setting.props.name)
194
195     setting_properties = { prop.name: prop for prop in GObject.list_properties(setting) }
196     if args.overrides is None:
197         setting_overrides = {}
198     else:
199         setting_overrides = { override.attrib['name']: override for override in overrides.findall('./setting[@name="%s"]/property' % setting.props.name) }
200
201     properties = sorted(set.union(set(setting_properties.keys()), set(setting_overrides.keys())))
202
203     for prop in properties:
204         value_type = None
205         value_desc = None
206         default_value = None
207
208         if prop in setting_properties:
209             pspec = setting_properties[prop]
210             propxml = settingxml.find('./gi:property[@name="%s"]' % pspec.name, ns_map)
211             if propxml is None:
212                 propxml = basexml.find('./gi:property[@name="%s"]' % pspec.name, ns_map)
213             if propxml is None:
214                 propxml = ipxml.find('./gi:property[@name="%s"]' % pspec.name, ns_map)
215
216             value_type = get_prop_type(setting, pspec, propxml)
217             value_desc = get_docs(setting, pspec, propxml)
218             default_value = get_default_value(setting, pspec, propxml)
219
220         if prop in setting_overrides:
221             override = setting_overrides[prop]
222             if override.attrib['format'] != '':
223                 value_type = override.attrib['format']
224             if override.attrib['description'] != '':
225                 value_desc = override.attrib['description']
226
227         if default_value is not None:
228             outfile.write("    <property name=\"%s\" type=\"%s\" default=\"%s\" description=\"%s\" />\n" %
229                           (prop, value_type, escape(default_value), escape(value_desc)))
230         else:
231             outfile.write("    <property name=\"%s\" type=\"%s\" description=\"%s\" />\n" %
232                           (prop, value_type, escape(value_desc)))
233
234     outfile.write("  </setting>\n")
235
236 outfile.write("</nm-setting-docs>\n")
237 outfile.close()