mirror of
https://gitlab.freedesktop.org/NetworkManager/NetworkManager.git
synced 2025-12-20 10:30:04 +01:00
When we generate the manual page for nm-settings-nmcli, we run:
"/usr/bin/python" \
./tools/generate-docs-nm-settings-docs-merge.py \
--only-from-first \
man/nm-settings-docs-nmcli.xml \
src/nmcli/gen-metadata-nm-settings-nmcli.xml \
src/libnm-client-impl/nm-property-infos-nmcli.xml \
src/libnm-client-impl/nm-settings-docs-gir.xml
If "gen-metadata-nm-settings-nmcli.xml" contains either a <description>
or a <description-docbook>, then we must not continue searching the
other XML documents. The user provided an explicit override, and
fallback (search further) is wrong. Previously, we might take
<description> from the first file, and <description-docbook> from the
second file. As "man/nm-settings-nmcli.xsl" prefers
<description-docbook>, it takes the wrong text. Instead, as we search
the files during merge, we must prefer the first one.
Note that the change doesn't really matter anymore, because each XML
now must also contain both <description> and <description-docbook>.
There is an assertion for that.
Also, stop generating <deprecated-docbook>. First, it lacked the
important "since=" attribute and was necessary. Also, it's redundant and
does not contain anything interesting. So far, we don't need special
formatting for the deprecated message, and we likely never will.
Also, stop accepting or generating the "description=" attribute. This
should always be an XML element now.
372 lines
11 KiB
Python
Executable file
372 lines
11 KiB
Python
Executable file
#!/usr/bin/env python
|
|
# SPDX-License-Identifier: LGPL-2.1-or-later
|
|
#
|
|
# Copyright (C) 2009 - 2017 Red Hat, Inc.
|
|
#
|
|
from __future__ import print_function, unicode_literals
|
|
import xml.etree.ElementTree as ET
|
|
import argparse
|
|
import os
|
|
import gi
|
|
import re
|
|
|
|
gi.require_version("GIRepository", "2.0")
|
|
from gi.repository import GIRepository
|
|
|
|
try:
|
|
libs = os.environ["LD_LIBRARY_PATH"].split(":")
|
|
libs.reverse()
|
|
for lib in libs:
|
|
GIRepository.Repository.prepend_library_path(lib)
|
|
except AttributeError:
|
|
# An old GI version, that has no prepend_library_path
|
|
# It's alright, it probably interprets LD_LIBRARY_PATH
|
|
# correctly.
|
|
pass
|
|
except KeyError:
|
|
pass
|
|
|
|
gi.require_version("NM", "1.0")
|
|
from gi.repository import NM, GObject
|
|
|
|
dbus_type_name_map = {
|
|
"b": "boolean",
|
|
"s": "string",
|
|
"i": "int32",
|
|
"u": "uint32",
|
|
"t": "uint64",
|
|
"x": "int64",
|
|
"y": "byte",
|
|
"as": "array of string",
|
|
"au": "array of uint32",
|
|
"ay": "byte array",
|
|
"a{ss}": "dict of string to string",
|
|
"a{sv}": "vardict",
|
|
"aa{sv}": "array of vardict",
|
|
"aau": "array of array of uint32",
|
|
"aay": "array of byte array",
|
|
"a(ayuay)": "array of legacy IPv6 address struct",
|
|
"a(ayuayu)": "array of legacy IPv6 route struct",
|
|
}
|
|
|
|
ns_map = {
|
|
"c": "http://www.gtk.org/introspection/c/1.0",
|
|
"gi": "http://www.gtk.org/introspection/core/1.0",
|
|
"glib": "http://www.gtk.org/introspection/glib/1.0",
|
|
}
|
|
identifier_key = "{%s}identifier" % ns_map["c"]
|
|
nick_key = "{%s}nick" % ns_map["glib"]
|
|
symbol_prefix_key = "{%s}symbol-prefix" % ns_map["c"]
|
|
|
|
constants = {
|
|
"TRUE": "TRUE",
|
|
"FALSE": "FALSE",
|
|
"G_MAXUINT32": "G_MAXUINT32",
|
|
"NULL": "NULL",
|
|
}
|
|
setting_names = {}
|
|
|
|
|
|
def get_setting_name_define(setting):
|
|
n = setting.attrib[symbol_prefix_key]
|
|
if n and n.startswith("setting_"):
|
|
return n[8:].upper()
|
|
raise Exception('Unexpected symbol_prefix_key "%s"' % (n))
|
|
|
|
|
|
def init_constants(girxml, settings):
|
|
for const in girxml.findall("./gi:namespace/gi:constant", ns_map):
|
|
cname = const.attrib["{%s}type" % ns_map["c"]]
|
|
cvalue = const.attrib["value"]
|
|
if const.find('./gi:type[@name="utf8"]', ns_map) is not None:
|
|
cvalue = '"%s"' % cvalue
|
|
constants[cname] = cvalue
|
|
|
|
for enum in girxml.findall("./gi:namespace/gi:enumeration", ns_map):
|
|
for enumval in enum.findall("./gi:member", ns_map):
|
|
cname = enumval.attrib[identifier_key]
|
|
cvalue = "%s (%s)" % (cname, enumval.attrib["value"])
|
|
constants[cname] = cvalue
|
|
|
|
for enum in girxml.findall("./gi:namespace/gi:bitfield", ns_map):
|
|
for enumval in enum.findall("./gi:member", ns_map):
|
|
cname = enumval.attrib[identifier_key]
|
|
cvalue = "%s (0x%x)" % (cname, int(enumval.attrib["value"]))
|
|
constants[cname] = cvalue
|
|
|
|
for setting in settings:
|
|
setting_type_name = "NM" + setting.attrib["name"]
|
|
setting_name_symbol = (
|
|
"NM_SETTING_" + get_setting_name_define(setting) + "_SETTING_NAME"
|
|
)
|
|
if setting_name_symbol in constants:
|
|
setting_name = constants[setting_name_symbol]
|
|
setting_names[setting_type_name] = setting_name
|
|
|
|
|
|
def get_prop_type(setting, pspec):
|
|
dbus_type = setting.get_dbus_property_type(pspec.name).dup_string()
|
|
prop_type = dbus_type_name_map[dbus_type]
|
|
|
|
if GObject.type_is_a(pspec.value_type, GObject.TYPE_ENUM) or GObject.type_is_a(
|
|
pspec.value_type, GObject.TYPE_FLAGS
|
|
):
|
|
prop_type = "%s (%s)" % (pspec.value_type.name, prop_type)
|
|
|
|
return prop_type
|
|
|
|
|
|
def remove_prefix(line, prefix):
|
|
return line[len(prefix) :] if line.startswith(prefix) else line
|
|
|
|
|
|
def format_docs(doc_xml):
|
|
doc = doc_xml.text
|
|
|
|
# split docs into lines
|
|
lines = re.split("\n", doc)
|
|
# strip leading *char and strip white spaces
|
|
lines = [remove_prefix(l, "*").strip() for l in lines]
|
|
doc = ""
|
|
for l in lines:
|
|
if l:
|
|
doc += l + " "
|
|
else:
|
|
doc = doc.strip(" ") + "\n\n"
|
|
|
|
doc = doc.strip("\n ")
|
|
|
|
# Expand constants
|
|
doc = re.sub(r"%([^%]\w*)", lambda match: constants[match.group(1)], doc)
|
|
|
|
# #NMSettingWired:mac-address -> "mac-address"
|
|
doc = re.sub(r"#[A-Za-z0-9_]*:([A-Za-z0-9_-]*)", r'"\1"', doc)
|
|
|
|
# #NMSettingWired setting -> "802-3-ethernet" setting
|
|
doc = re.sub(
|
|
r"#([A-Z]\w*) setting",
|
|
lambda match: setting_names[match.group(1)] + " setting",
|
|
doc,
|
|
)
|
|
|
|
# remaining gtk-doc cleanup
|
|
doc = doc.replace("%%", "%")
|
|
doc = doc.replace("<!-- -->", "")
|
|
doc = re.sub(r" Element-.ype:.*", "", doc)
|
|
doc = re.sub(r"#([A-Z]\w*)", r"\1", doc)
|
|
|
|
# Remove sentences that refer to functions
|
|
if "FindProxyForURL()" in doc:
|
|
# FIXME: this would break the description for "proxy.pac-script"
|
|
# Work around. But really the entire approach here is flawed
|
|
# and needs improvement.
|
|
pass
|
|
else:
|
|
doc = re.sub(r"\.\s+[^.]*\w\(\)[^.]*\.", r".", doc)
|
|
|
|
return doc
|
|
|
|
|
|
def get_docs(propxml):
|
|
doc_xml = propxml.find("gi:doc", ns_map)
|
|
if doc_xml is None:
|
|
return None
|
|
else:
|
|
return format_docs(doc_xml)
|
|
|
|
|
|
def get_default_value(setting, pspec, propxml):
|
|
default_value = setting.get_property(pspec.name.replace("-", "_"))
|
|
if default_value is None:
|
|
return default_value
|
|
|
|
value_type = get_prop_type(setting, pspec)
|
|
if value_type == "string" and default_value != "" and pspec.name != "name":
|
|
default_value = '"%s"' % default_value
|
|
elif value_type == "boolean":
|
|
default_value = str(default_value).upper()
|
|
elif value_type == "byte array":
|
|
default_value = "[]"
|
|
elif str(default_value).startswith("<"):
|
|
default_value = None
|
|
elif str(default_value).startswith("["):
|
|
default_value = None
|
|
|
|
return default_value
|
|
|
|
|
|
def settings_sort_key(x):
|
|
x_prefix = x.attrib["{%s}symbol-prefix" % ns_map["c"]]
|
|
# always sort NMSettingConnection first
|
|
return (x_prefix != "setting_connection", x_prefix)
|
|
|
|
|
|
def create_desc_docbook(desc_docbook, description):
|
|
lines = re.split("\n", description)
|
|
|
|
paragraph = ET.SubElement(
|
|
desc_docbook,
|
|
"para",
|
|
)
|
|
|
|
for l in lines:
|
|
if not l:
|
|
# A blank line. This starts a new paragraph
|
|
paragraph = ET.SubElement(desc_docbook, "para")
|
|
continue
|
|
paragraph.text = l
|
|
|
|
|
|
def main(gir_path_str, output_path_str):
|
|
girxml = ET.parse(gir_path_str).getroot()
|
|
|
|
basexml = girxml.find('./gi:namespace/gi:class[@name="Setting"]', ns_map)
|
|
settings = girxml.findall('./gi:namespace/gi:class[@parent="Setting"]', ns_map)
|
|
# Hack. Need a better way to do this
|
|
ipxml = girxml.find('./gi:namespace/gi:class[@name="SettingIPConfig"]', ns_map)
|
|
settings.extend(
|
|
girxml.findall('./gi:namespace/gi:class[@parent="SettingIPConfig"]', ns_map)
|
|
)
|
|
settings = sorted(settings, key=settings_sort_key)
|
|
|
|
init_constants(girxml, settings)
|
|
|
|
nm_settings_docs_element = ET.Element("nm-setting-docs")
|
|
docs_gir = ET.ElementTree(nm_settings_docs_element)
|
|
|
|
for settingxml in settings:
|
|
if "abstract" in settingxml.attrib:
|
|
continue
|
|
|
|
new_func = NM.__getattr__(settingxml.attrib["name"])
|
|
setting = new_func()
|
|
|
|
class_desc = get_docs(settingxml)
|
|
if class_desc is None:
|
|
raise Exception(
|
|
"%s needs a gtk-doc block with one-line description"
|
|
% setting.props.name
|
|
)
|
|
setting_element = ET.SubElement(
|
|
nm_settings_docs_element,
|
|
"setting",
|
|
attrib={
|
|
"name": setting.props.name,
|
|
"description": class_desc,
|
|
"name_upper": get_setting_name_define(settingxml),
|
|
},
|
|
)
|
|
|
|
setting_properties = {
|
|
prop.name: prop
|
|
for prop in GObject.list_properties(setting)
|
|
if prop.name != "name"
|
|
}
|
|
|
|
for prop in sorted(setting_properties):
|
|
pspec = setting_properties[prop]
|
|
|
|
propxml = settingxml.find('./gi:property[@name="%s"]' % pspec.name, ns_map)
|
|
if propxml is None:
|
|
propxml = basexml.find('./gi:property[@name="%s"]' % pspec.name, ns_map)
|
|
if propxml is None:
|
|
propxml = ipxml.find('./gi:property[@name="%s"]' % pspec.name, ns_map)
|
|
|
|
value_type = get_prop_type(setting, pspec)
|
|
value_desc = get_docs(propxml)
|
|
default_value = get_default_value(setting, pspec, propxml)
|
|
|
|
if "deprecated" in propxml.attrib:
|
|
deprecated = True
|
|
deprecated_since = propxml.attrib["deprecated-version"]
|
|
deprecated_desc = format_docs(propxml.find("gi:doc-deprecated", ns_map))
|
|
else:
|
|
deprecated = False
|
|
|
|
prop_upper = prop.upper().replace("-", "_")
|
|
|
|
if value_desc is None:
|
|
raise Exception(
|
|
"%s.%s needs a documentation description"
|
|
% (setting.props.name, prop)
|
|
)
|
|
|
|
property_attributes = {
|
|
"name": prop,
|
|
"name_upper": prop_upper,
|
|
"type": value_type,
|
|
}
|
|
|
|
if default_value is not None:
|
|
property_attributes["default"] = str(default_value)
|
|
|
|
property_element = ET.SubElement(
|
|
setting_element,
|
|
"property",
|
|
attrib=property_attributes,
|
|
)
|
|
|
|
ET.SubElement(
|
|
property_element,
|
|
"description",
|
|
).text = value_desc
|
|
|
|
if value_desc:
|
|
description_docbook = ET.SubElement(
|
|
property_element,
|
|
"description-docbook",
|
|
)
|
|
|
|
create_desc_docbook(description_docbook, value_desc)
|
|
|
|
if deprecated:
|
|
ET.SubElement(
|
|
property_element,
|
|
"deprecated",
|
|
attrib={
|
|
"since": deprecated_since,
|
|
},
|
|
).text = deprecated_desc
|
|
|
|
# The text should only be one line. Otherwise, our simple "<deprecated>" element
|
|
# cannot be rendered nicely.
|
|
assert re.split("\n", deprecated_desc) == [deprecated_desc]
|
|
|
|
docs_gir.write(
|
|
output_path_str,
|
|
xml_declaration=True,
|
|
encoding="utf-8",
|
|
)
|
|
|
|
|
|
if __name__ == "__main__":
|
|
parser = argparse.ArgumentParser()
|
|
parser.add_argument(
|
|
"-l",
|
|
"--lib-path",
|
|
metavar="PATH",
|
|
action="append",
|
|
help="path to scan for shared libraries",
|
|
)
|
|
parser.add_argument(
|
|
"-g",
|
|
"--gir",
|
|
metavar="FILE",
|
|
help="NM-1.0.gir file",
|
|
required=True,
|
|
)
|
|
parser.add_argument(
|
|
"-o",
|
|
"--output",
|
|
metavar="FILE",
|
|
help="output file",
|
|
required=True,
|
|
)
|
|
|
|
args = parser.parse_args()
|
|
|
|
if args.lib_path:
|
|
for lib in args.lib_path:
|
|
GIRepository.Repository.prepend_library_path(lib)
|
|
|
|
main(args.gir, args.output)
|