mirror of
https://gitlab.freedesktop.org/NetworkManager/NetworkManager.git
synced 2026-05-16 17:38:08 +02:00
docs: add "expand enumvals" capability to properties documenation
Having to type manually all the accepted values in each property's documentation is very error prone: you can make typos and you will probably forget to always update them when new possible values are added. If the possible values are the variant 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. Also, for nmcli documentation we want to show the pretty nickname of each value because it's the more user friendly way for users to enter the data. C names like NM_IP_TUNNEL_MODE_IPIP have no value here so they mustn't be shown, and the pretty nickname "ipip" must be shown instead. With this commit, the special string "#EnumName:*" can be used in docs and it will be expanded to the complete list of accepted values. If the docs that are being generated are for nmcli, pretty nicknames will be used instead of C names. It is also desirable to show a description of what each value means in nmcli man pages. To do this, the special string "#EnumName:**" can be used. It will expand the same way than "#EnumName:*" in the "description" field of the property, but it will expand to a formatted list with all the values and their explanations in the "description-docbook" field. The "description-docbook" field is currently only used for the nm-settings-nmcli man page. To expand a single enum value in the proper format (i.e. using nicknames in nmcli docs), use the string "%ENUM_VALUE". However, this feature has an important limitation: it can only be used for man/nm-settings-*.5 files generation, and the expansion only happens in the special tagged comments (like ---nmcli---), not in the normal gtkdoc comments. There are 2 reasons: 1. Any other documentation, other than man/nm-settings-*.5, is generated directly with gtkdoc, that doesn't support this expansion feature. 2. The gtkdoc comment applies to everything, either nmcli or dbus, keyfile, etc. We can't format the expanded text in a proper way without knowing where it's going to be used.
This commit is contained in:
parent
f930c76a0a
commit
8460040df1
2 changed files with 131 additions and 10 deletions
|
|
@ -170,7 +170,7 @@ if enable_introspection
|
|||
foreach info: infos
|
||||
t = custom_target(
|
||||
'nm-propery-infos-' + info + '.xml',
|
||||
input: libnm_core_settings_sources,
|
||||
input: [libnm_gir[0]] + libnm_core_settings_sources,
|
||||
output: 'nm-propery-infos-' + info + '.xml',
|
||||
command: [
|
||||
python.path(),
|
||||
|
|
|
|||
|
|
@ -8,6 +8,10 @@ import collections
|
|||
import xml.etree.ElementTree as ET
|
||||
|
||||
|
||||
enums = {}
|
||||
enumvals = {}
|
||||
|
||||
|
||||
class LineError(Exception):
|
||||
def __init__(self, line_no, msg):
|
||||
Exception.__init__(self, msg)
|
||||
|
|
@ -52,6 +56,53 @@ 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]
|
||||
num_val = enumval.attrib["value"]
|
||||
doc = enumval.find("./gi:doc", ns_map)
|
||||
doc = doc.text if doc is not None else None
|
||||
nick = enumval.attrib.get(nick_key)
|
||||
nick = "%s (%s)" % (nick, num_val) if nick else None
|
||||
|
||||
enums[enum_cname].append(cname)
|
||||
enumvals[cname] = {
|
||||
"cvalue": "%s (%s)" % (cname, num_val),
|
||||
"nick": nick,
|
||||
"doc": doc,
|
||||
}
|
||||
|
||||
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]
|
||||
num_val = int(enumval.attrib["value"])
|
||||
doc = enumval.find("./gi:doc", ns_map)
|
||||
doc = doc.text if doc is not None else None
|
||||
nick = enumval.attrib.get(nick_key)
|
||||
nick = "%s (0x%x)" % (nick, num_val) if nick else None
|
||||
|
||||
enums[enum_cname].append(cname)
|
||||
enumvals[cname] = {
|
||||
"cvalue": "%s (0x%x)" % (cname, num_val),
|
||||
"nick": nick,
|
||||
"doc": doc,
|
||||
}
|
||||
|
||||
|
||||
def get_setting_names(source_file):
|
||||
m = re.match(r"^(.*)/libnm-core-impl/(nm-setting-[^/]*)\.c$", source_file)
|
||||
assert m
|
||||
|
|
@ -171,15 +222,46 @@ def write_data(tag, setting_node, line_no, parsed_data):
|
|||
else:
|
||||
assert False
|
||||
|
||||
|
||||
def expand_enumval(enumval_name, use_nicks):
|
||||
if enumval_name not in enumvals:
|
||||
return enumval_name
|
||||
enumval = enumvals[enumval_name]
|
||||
return enumval["nick"] if use_nicks and enumval["nick"] else enumval["cvalue"]
|
||||
|
||||
|
||||
def expand_all_enumvals(enum, use_nicks):
|
||||
assert enum in enums
|
||||
return ", ".join(expand_enumval(val_name, use_nicks) for val_name in enums[enum])
|
||||
|
||||
|
||||
def expand_all_enumvals_with_docs(enum, use_nicks):
|
||||
assert enum in enums
|
||||
|
||||
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, use_nicks),
|
||||
" - " + 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
|
||||
|
|
@ -187,10 +269,41 @@ 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, #EnumName:** and #EnumName:*)
|
||||
use_nicks = 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_]*):\*{1,2}",
|
||||
lambda match: expand_all_enumvals(match.group(1), use_nicks),
|
||||
parsed_data["description"],
|
||||
)
|
||||
|
||||
parsed_data["description"] = re.sub(
|
||||
r"%([^%]\w*)",
|
||||
lambda match: expand_enumval(match.group(1), use_nicks),
|
||||
parsed_data["description"],
|
||||
)
|
||||
|
||||
parsed_data["description-docbook"] = re.sub(
|
||||
r"#([A-Za-z0-9_]*):\*\*",
|
||||
lambda match: expand_all_enumvals_with_docs(match.group(1), use_nicks),
|
||||
parsed_data["description-docbook"],
|
||||
)
|
||||
|
||||
parsed_data["description-docbook"] = re.sub(
|
||||
r"#([A-Za-z0-9_]*):\*",
|
||||
lambda match: expand_all_enumvals(match.group(1), use_nicks),
|
||||
parsed_data["description-docbook"],
|
||||
)
|
||||
|
||||
parsed_data["description-docbook"] = re.sub(
|
||||
r"%([^%]\w*)",
|
||||
lambda match: expand_enumval(match.group(1), use_nicks),
|
||||
parsed_data["description-docbook"],
|
||||
)
|
||||
|
||||
|
||||
def parse_data(tag, line_no, lines):
|
||||
|
|
@ -325,6 +438,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()):
|
||||
|
|
@ -343,12 +457,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)
|
||||
|
|
@ -368,11 +484,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:],
|
||||
)
|
||||
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue