11#!/usr/bin/env python
2+ # Based on NetworkManager-1.39.2/tools/generate-docs-nm-settings-docs-merge.py
23# SPDX-License-Identifier: LGPL-2.1-or-later
34import collections
45import textwrap
@@ -57,6 +58,7 @@ def write(msg: Any) -> None:
5758
5859###############################################################################
5960
61+ # Order of the connection types(settings_classes) in the profile dataclass:
6062_setting_name_order = [
6163 "connection" ,
6264 "ipv4" ,
@@ -112,6 +114,7 @@ def write(msg: Any) -> None:
112114 "wpan" ,
113115]
114116
117+ # List of modules for which we must generate an import of typing.List
115118list_modules = [
116119 "bridge" ,
117120 "bridge_port" ,
@@ -134,22 +137,50 @@ def write(msg: Any) -> None:
134137 "wireless_security" ,
135138]
136139
140+ # Order of special properties which must be first because they are not optional
141+ _property_name_order = [
142+ "connection.id" ,
143+ "connection.type" ,
144+ "connection.uuid" ,
145+ ]
146+
147+ def _property_name_order_idx (name : str ) -> int :
148+ """Return the sort index for the given connection setting property"""
149+ try :
150+ return _property_name_order .index (name )
151+ except ValueError :
152+ # Properties not in _property_name_order are sorted last and then by their name
153+ return len (_property_name_order )
154+
155+
156+ def key_fcn_property_name (n1 : str ) -> Tuple [int , str ]:
157+ """key function for sorted(), used to sort connection properties"""
158+ return (_property_name_order_idx (n1 ), n1 )
159+
137160
138161def _setting_name_order_idx (name : str ) -> int :
162+ """Return the sort index for the given connection type(settings_class)"""
139163 try :
140164 return _setting_name_order .index (name )
141165 except ValueError :
142166 return len (_setting_name_order )
143167
144168
145169def key_fcn_setting_name (n1 : str ) -> Tuple [int , str ]:
170+ """key function for sorted(), used to sort the settings_classes(connection types)"""
146171 return (_setting_name_order_idx (n1 ), n1 )
147172
148173
149174def iter_keys_of_dicts (
150- dicts : List [Dict [str , Any ]], key : Optional [Any ] = None
175+ dicts : List [Dict [str , Any ]], key : Optional [Any ] = None , prefix : Optional [ str ] = ""
151176) -> List [str ]:
152- keys = {k for d in dicts for k in d .keys ()}
177+ """Return a sorted list of settings_classes or connection properties
178+
179+ To support sorting the required properites of settings_classes first,
180+ the settingsname can be passed ad prefix argument to let the key function
181+ return the correct sort index for the given settingsname property.
182+ """
183+ keys = {f'{ prefix } { k } ' for d in dicts for k in d .keys ()}
153184 return sorted (keys , key = key )
154185
155186
@@ -185,6 +216,8 @@ def find_first_not_none(itr: List[Any]) -> Optional[Any]:
185216 return next ((i for i in itr if i is not None ), None )
186217
187218
219+ # The code quality of this function is poor(Sourcery says 5%), needs refactoring,
220+ # also see the rework in tools/generate-settings-dataclasses-jinja.py
188221def main (settings_xml_path : Path ) -> None :
189222 gl_input_files = [settings_xml_path ]
190223
@@ -194,25 +227,30 @@ def main(settings_xml_path: Path) -> None:
194227 for root in xml_roots ]
195228
196229 root_node = ElementTree .Element ("nm-setting-docs" )
197- # print("")
230+
231+ # Generate the file header
198232 license = "SPDX-License-Identifier: LGPL-2.1-or-later"
199233 script = ("This file was generated by "
200234 "tools/generate-settings-dataclasses.py" )
201235 header = f"""# { license } \n # { script } ,
202- # if possible, please make changes by also updating the script.
203- """
236+ # if possible, please make changes by also updating the script.\n """
204237 i = open ("sdbus_async/networkmanager/settings/__init__.py" , mode = "w" )
205238 p = open ("sdbus_async/networkmanager/settings/profile.py" , mode = "r" )
206239 profile_py = open ("sdbus_async/networkmanager/settings/profile.py" ).read ()
240+
241+ # define start and end markers for generating part of settings/profile.py:
207242 start_string = "# start of the generated list of settings classes\n "
208243 start_index = profile_py .index (start_string ) + len (start_string )
209- # end_string = " def to_dbus"
210244 end_string = " # end of the generated list of settings classes\n "
211245 end_index = profile_py .index (end_string )
212246 p = open ("sdbus_async/networkmanager/settings/profile.py" , mode = "w" )
247+
248+ # write the file headers
213249 i .write (header )
214250 p .write (profile_py [:start_index ])
215251 classes = []
252+
253+ # generate the connection type settings classes:
216254 for settingname in iter_keys_of_dicts (settings_roots ,
217255 key_fcn_setting_name ):
218256 settings = [d .get (settingname ) for d in settings_roots ]
@@ -235,6 +273,8 @@ def main(settings_xml_path: Path) -> None:
235273 i .write (f"from .{ module } import { classname } \n " )
236274 classes .append (classname )
237275 f = open (f"sdbus_async/networkmanager/settings/{ module } .py" , mode = "w" )
276+
277+ # Generate module type headers with the needed import statements
238278 f .write (header )
239279 f .write ("from __future__ import annotations\n " )
240280 f .write ("from dataclasses import dataclass, field\n " )
@@ -255,6 +295,7 @@ def main(settings_xml_path: Path) -> None:
255295 f .write ("from .datatypes import WireguardPeers as Peers\n " )
256296 f .write ("\n \n " )
257297
298+ # Generate the settings_class and it's entry in profile.py:
258299 setting_node = ElementTree .SubElement (root_node , "setting" )
259300 if module != "connection" :
260301 p .write (f" { module } : Optional[{ classname } ] = field(\n " )
@@ -265,10 +306,15 @@ def main(settings_xml_path: Path) -> None:
265306 f .write ("@dataclass\n " )
266307 f .write (f"class { classname } (NetworkManagerSettingsMixin):\n " )
267308 setting_node .set ("name" , settingname )
309+
310+ # generate the docstring of the new settings_class
268311 desc = node_get_attr (settings , "description" )
269312 f .write (' """' + desc + '"""\n \n ' )
270- # node_set_attr(setting_node, "alias", settings)
271- for property_name in iter_keys_of_dicts (properties ):
313+
314+ # Generate the attributes of the settings_class for this profile type:
315+
316+ for property in iter_keys_of_dicts (properties , key_fcn_property_name , f'{ settingname } .' ):
317+ property_name = property [len (settingname )+ 1 :]
272318 properties_attrs = [p .get (property_name ) for p in properties ]
273319 property_node = ElementTree .SubElement (setting_node , "property" )
274320 property_node .set ("name" , property_name )
@@ -306,7 +352,7 @@ def main(settings_xml_path: Path) -> None:
306352 f .write (f" default={ str (default ).title ()} ,\n )\n " )
307353 else :
308354 attribute_type = dbus_type_name_map [dbustype ]
309- optional = module != "bond"
355+ optional = property not in _property_name_order
310356 if optional :
311357 f .write (f" { attribute } : Optional[{ attribute_type } ]" )
312358 else :
@@ -329,21 +375,26 @@ def main(settings_xml_path: Path) -> None:
329375 if optional :
330376 f .write (f" default={ str (default ).title ()} ,\n " )
331377 f .write (" )\n " )
378+
379+ # Generate docstrings for attributes: Not stored by python,
380+ # but is parsed for generating documentation and be red by
381+ # developers when they lookup the attribute declaration:
332382 generate_descriptions_for_attributes = False
333383 if generate_descriptions_for_attributes :
334384 desc = node_get_attr (properties_attrs , "description" )
335385 wrapper = textwrap .TextWrapper (
336- width = 74 ,
386+ width = 82 ,
337387 initial_indent = " " ,
338388 subsequent_indent = " " ,
339389 )
340- lines = wrapper .wrap (text = f'"""{ desc } ' )
390+ lines = wrapper .wrap (text = f'"""{ desc } """ ' )
341391 if len (lines ) == 1 :
342392 print (lines [0 ] + '"""' )
343393 else :
344394 for line in lines :
345- f .write (line )
346- f .write (' """' )
395+ f .write (f'{ line } \n ' )
396+ # If the closing """ shall be on a new line, use:
397+ # f.write(' """\n')
347398 f .write ("" )
348399 i .write ('\n __all__ = (\n ' )
349400 for cls in classes :
0 commit comments