man nm-setting-*: add "expand enumvals" capability to property-infos
In some cases, properties documentation might require to provide an explanation of each of the possible values that the property accepts. If the possible values are the variants of an enum, we can use the introspection data to get all the possible values for that enum and their descriptions. With that info, we can automatically generate the documentation with an always up to date list of accepted values. Add a new "expand enumvals" feature: it will convert a token with the format `#EnumName:*` to a list of all the possible values. For the docbook (description-docbook field in the XML), it is expanded to a bulleted list of all the values and their respective documentations. This feature is limited to the "property-infos" comments (those like ---nmcli---, ---dbus---, etc). This comments are used only to generate the nm-settings-* manual pages. For the documentation under the doc/ folder this is not needed: it's not supported by gtkdoc and, anyway, it's better to use just `#EnumName` that will generate an HTML link. Additionally, expansion of `%ENUM_VALUE` is now supported in the property-infos comments. Instead of expanding them in the same style than gtkdoc "ENUM_VALUE (num)", it is expanded in a format more suitable for the nm-setting-* manual pages: - for nmcli: value_nick (num) - others: num (value_nick) Also, fix typo in meson build file propery -> property.
This commit is contained in:
@@ -2088,7 +2088,7 @@ endif
|
||||
|
||||
libnm_docs_sources = $(src_libnm_core_impl_lib_c_settings_real)
|
||||
|
||||
src/libnm-client-impl/nm-property-infos-%.xml: tools/generate-docs-nm-property-infos.py src/libnm-core-impl/libnm-core-impl.la $(libnm_docs_sources)
|
||||
src/libnm-client-impl/nm-property-infos-%.xml: tools/generate-docs-nm-property-infos.py src/libnm-core-impl/libnm-core-impl.la src/libnm-client-impl/NM-1.0.gir $(libnm_docs_sources)
|
||||
$(AM_V_GEN) \
|
||||
"$(PYTHON)" \
|
||||
$(srcdir)/tools/generate-docs-nm-property-infos.py \
|
||||
|
@@ -169,9 +169,9 @@ if enable_introspection
|
||||
endif
|
||||
foreach info: infos
|
||||
t = custom_target(
|
||||
'nm-propery-infos-' + info + '.xml',
|
||||
input: libnm_core_settings_sources,
|
||||
output: 'nm-propery-infos-' + info + '.xml',
|
||||
'nm-property-infos-' + info + '.xml',
|
||||
input: [libnm_gir[0]] + libnm_core_settings_sources,
|
||||
output: 'nm-property-infos-' + info + '.xml',
|
||||
command: [
|
||||
python.path(),
|
||||
join_paths(meson.source_root(), 'tools', 'generate-docs-nm-property-infos.py'),
|
||||
|
@@ -14,6 +14,9 @@ class LineError(Exception):
|
||||
self.line_no = line_no
|
||||
|
||||
|
||||
enums = {}
|
||||
enumvals = {}
|
||||
|
||||
_dbg_level = 0
|
||||
try:
|
||||
_dbg_level = int(os.getenv("NM_DEBUG_GENERATE_DOCS", 0))
|
||||
@@ -52,6 +55,45 @@ def xnode_get_or_create(root_node, node_name, name):
|
||||
return node, created
|
||||
|
||||
|
||||
def init_enumvals(girxml):
|
||||
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",
|
||||
}
|
||||
type_key = "{%s}type" % ns_map["c"]
|
||||
identifier_key = "{%s}identifier" % ns_map["c"]
|
||||
nick_key = "{%s}nick" % ns_map["glib"]
|
||||
|
||||
for enum in girxml.findall("./gi:namespace/gi:enumeration", ns_map):
|
||||
enum_cname = enum.attrib[type_key]
|
||||
enums[enum_cname] = []
|
||||
for enumval in enum.findall("./gi:member", ns_map):
|
||||
cname = enumval.attrib[identifier_key]
|
||||
doc = enumval.find("./gi:doc", ns_map)
|
||||
|
||||
enums[enum_cname].append(cname)
|
||||
enumvals[cname] = {
|
||||
"value": enumval.attrib["value"],
|
||||
"nick": enumval.attrib.get(nick_key, cname),
|
||||
"doc": doc.text if doc is not None else None,
|
||||
}
|
||||
|
||||
for enum in girxml.findall("./gi:namespace/gi:bitfield", ns_map):
|
||||
enum_cname = enum.attrib[type_key]
|
||||
enums[enum_cname] = []
|
||||
for enumval in enum.findall("./gi:member", ns_map):
|
||||
cname = enumval.attrib[identifier_key]
|
||||
doc = enumval.find("./gi:doc", ns_map)
|
||||
|
||||
enums[enum_cname].append(cname)
|
||||
enumvals[cname] = {
|
||||
"value": "0x%x" % int(enumval.attrib["value"]),
|
||||
"nick": enumval.attrib.get(nick_key, cname),
|
||||
"doc": doc.text if doc is not None else None,
|
||||
}
|
||||
|
||||
|
||||
def get_setting_names(source_file):
|
||||
m = re.match(r"^(.*)/libnm-core-impl/(nm-setting-[^/]*)\.c$", source_file)
|
||||
assert m
|
||||
@@ -172,15 +214,50 @@ def write_data(tag, setting_node, line_no, parsed_data):
|
||||
else:
|
||||
assert False
|
||||
|
||||
|
||||
def expand_enumval(enumval_name, nick_first):
|
||||
if enumval_name not in enumvals:
|
||||
return enumval_name
|
||||
|
||||
enumval = enumvals[enumval_name]
|
||||
if nick_first:
|
||||
return "%s (%s)" % (enumval["nick"], enumval["value"])
|
||||
else:
|
||||
return "%s (%s)" % (enumval["value"], enumval["nick"])
|
||||
|
||||
|
||||
def expand_all_enumvals(enum, nick_first):
|
||||
assert enum in enums, "Enum name not found: %s" % enum
|
||||
return ", ".join(expand_enumval(val_name, nick_first) for val_name in enums[enum])
|
||||
|
||||
|
||||
def expand_all_enumvals_with_docs(enum, nick_first):
|
||||
assert enum in enums, "Enum name not found: %s" % enum
|
||||
|
||||
out_str = "<itemizedlist>"
|
||||
for enumval_name in enums[enum]:
|
||||
assert enumval_name in enumvals
|
||||
enumval = enumvals[enumval_name]
|
||||
out_str += "<listitem><para><literal>%s</literal>%s</para></listitem>" % (
|
||||
expand_enumval(enumval_name, nick_first),
|
||||
" - " + enumval["doc"] if enumval["doc"] else "",
|
||||
)
|
||||
out_str += "</itemizedlist>"
|
||||
|
||||
return out_str
|
||||
|
||||
|
||||
def format_descriptions(tag, parsed_data):
|
||||
if (
|
||||
parsed_data.get("description", None) is not None
|
||||
and parsed_data.get("description-docbook", None) is None
|
||||
):
|
||||
# we have a description, but no docbook. Generate one.
|
||||
node = ET.SubElement(property_node, "description-docbook")
|
||||
for l in re.split("\n", parsed_data["description"]):
|
||||
paragraph = ET.SubElement(node, "para")
|
||||
paragraph.text = l
|
||||
parsed_data["description-docbook"] = ""
|
||||
for line in parsed_data["description"].split("\n"):
|
||||
para = ET.Element("para")
|
||||
para.text = line
|
||||
parsed_data["description-docbook"] += ET.tostring(para, encoding="unicode")
|
||||
elif (
|
||||
parsed_data.get("description-docbook", None) is not None
|
||||
and parsed_data.get("description", None) is None
|
||||
@@ -188,10 +265,35 @@ def write_data(tag, setting_node, line_no, parsed_data):
|
||||
raise Exception(
|
||||
'Invalid configuration. When specifying "description-docbook:" there MUST be also a "description:"'
|
||||
)
|
||||
elif parsed_data.get("description", None) is None:
|
||||
return
|
||||
|
||||
# Expand enumvals expressions (%ENUM_VALUE and #EnumName:*)
|
||||
nick_first = tag == "nmcli"
|
||||
|
||||
kwd_first_line_re = re.compile(r"^ *\* ([-a-z0-9]+): (.*)$")
|
||||
kwd_more_line_re = re.compile(r"^ *\*( *)(.*?)\s*$")
|
||||
parsed_data["description"] = re.sub(
|
||||
r"#([A-Za-z0-9_]*):\*",
|
||||
lambda match: expand_all_enumvals(match.group(1), nick_first),
|
||||
parsed_data["description"],
|
||||
)
|
||||
|
||||
parsed_data["description"] = re.sub(
|
||||
r"%([^%]\w*)",
|
||||
lambda match: expand_enumval(match.group(1), nick_first),
|
||||
parsed_data["description"],
|
||||
)
|
||||
|
||||
parsed_data["description-docbook"] = re.sub(
|
||||
r"#([A-Za-z0-9_]*):\*",
|
||||
lambda match: expand_all_enumvals_with_docs(match.group(1), nick_first),
|
||||
parsed_data["description-docbook"],
|
||||
)
|
||||
|
||||
parsed_data["description-docbook"] = re.sub(
|
||||
r"%([^%]\w*)",
|
||||
lambda match: expand_enumval(match.group(1), nick_first),
|
||||
parsed_data["description-docbook"],
|
||||
)
|
||||
|
||||
|
||||
def parse_data(tag, line_no, lines):
|
||||
@@ -326,6 +428,7 @@ def process_setting(tag, root_node, source_file, setting_name):
|
||||
parsed_data = parse_data(tag, line_no_start, lines)
|
||||
if not parsed_data:
|
||||
raise Exception('invalid data: line %s, "%s"' % (line_no, lines))
|
||||
format_descriptions(tag, parsed_data)
|
||||
dbg("> > > property: %s" % (parsed_data["property"],))
|
||||
if _dbg_level > 1:
|
||||
for keyword in sorted(parsed_data.keys()):
|
||||
@@ -344,12 +447,14 @@ def process_setting(tag, root_node, source_file, setting_name):
|
||||
raise LineError(line_no_start, "Unterminated start tag")
|
||||
|
||||
|
||||
def process_settings_docs(tag, output, source_files):
|
||||
def process_settings_docs(tag, output, gir_file, source_files):
|
||||
|
||||
dbg("> tag:%s, output:%s" % (tag, output))
|
||||
|
||||
root_node = ET.Element("nm-setting-docs")
|
||||
|
||||
init_enumvals(ET.parse(gir_file).getroot())
|
||||
|
||||
for setting_name, source_file in get_file_infos(source_files):
|
||||
try:
|
||||
process_setting(tag, root_node, source_file, setting_name)
|
||||
@@ -369,11 +474,16 @@ def process_settings_docs(tag, output, source_files):
|
||||
|
||||
def main():
|
||||
if len(sys.argv) < 4:
|
||||
print("Usage: %s [tag] [output-xml-file] [srcfiles...]" % (sys.argv[0]))
|
||||
print(
|
||||
"Usage: %s [tag] [output-xml-file] [gir-file] [srcfiles...]" % (sys.argv[0])
|
||||
)
|
||||
exit(1)
|
||||
|
||||
process_settings_docs(
|
||||
tag=sys.argv[1], output=sys.argv[2], source_files=sys.argv[3:]
|
||||
tag=sys.argv[1],
|
||||
output=sys.argv[2],
|
||||
gir_file=sys.argv[3],
|
||||
source_files=sys.argv[4:],
|
||||
)
|
||||
|
||||
|
||||
|
Reference in New Issue
Block a user