Skip to content

Commit

Permalink
man: add checks for missing version information
Browse files Browse the repository at this point in the history
This adds a new script tools/check-version-history.py and a corresponding
test when building in developer mode. It checks manpages (except dbus
documentation which is handled by update-dbus-docs) for missing version
history information.

It also adds ignore lists based on version 183 (the version that our version
annotations go back to). These can be augmented if we want to ignore other
elements if it doesn't make sense for them to have version annotations.
  • Loading branch information
abderrahim committed Oct 1, 2023
1 parent a8b53f4 commit 3691e7f
Show file tree
Hide file tree
Showing 5 changed files with 702 additions and 0 deletions.
5 changes: 5 additions & 0 deletions man/meson.build
Original file line number Diff line number Diff line change
Expand Up @@ -229,6 +229,11 @@ if conf.get('BUILD_MODE_DEVELOPER') == 1
suite : 'dist',
args : ['--build-dir', project_build_root, '--test', dbus_docs],
depends : dbus_programs)

test('check-version-history',
check_version_history_py,
suite : 'dist',
args : source_xml_files)
endif

update_man_rules = custom_target(
Expand Down
1 change: 1 addition & 0 deletions meson.build
Original file line number Diff line number Diff line change
Expand Up @@ -1788,6 +1788,7 @@ conf.set10('ENABLE_UKIFY', want_ukify)

############################################################

check_version_history_py = find_program('tools/check-version-history.py')
elf2efi_py = find_program('tools/elf2efi.py')
export_dbus_interfaces_py = find_program('tools/dbus_exporter.py')
generate_gperfs = find_program('tools/generate-gperfs.py')
Expand Down
135 changes: 135 additions & 0 deletions tools/check-version-history.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,135 @@
#!/usr/bin/env python3
# SPDX-License-Identifier: LGPL-2.1-or-later

import os
import sys
import lxml.etree as tree

_parser = tree.XMLParser(resolve_entities=False)
tree.set_default_parser(_parser)


def find_undocumented_functions(pages, ignorelist):
undocumented = []
for page in pages:
filename = os.path.basename(page)
pagetree = tree.parse(page)

assert pagetree.getroot().tag == "refentry"

hist_section = pagetree.find("refsect1[title='History']")
for func in pagetree.findall("//funcprototype/funcdef/function"):
path = f"/refsynopsisdiv/funcsynopsis/funcprototype/funcdef/function[.='{func.text}']"
assert pagetree.findall(path) == [func]

if (
hist_section is None
or hist_section.find(f"para/function[.='{func.text}()']") is None
):
if func.text not in ignorelist:
undocumented.append((filename, func.text))
return undocumented


def construct_path(element):
tag = element.tag

if tag == "refentry":
return ""

predicate = ""
if tag == "varlistentry":
text = "".join(element.find("term").itertext())
predicate = f'[term="{text}"]'
elif tag.startswith("refsect"):
text = "".join(element.find("title").itertext())
predicate = f'[title="{text}"]'
elif tag == "variablelist":
varlists = element.getparent().findall(tag)
if len(varlists) > 1:
predicate = f"[{varlists.index(element)+1}]"

return construct_path(element.getparent()) + "/" + tag + predicate


def find_undocumented_commands(pages, ignorelist):
undocumented = []
for page in pages:
filename = os.path.basename(page)

pagetree = tree.parse(page)
if pagetree.getroot().tag != "refentry":
continue

for varlistentry in pagetree.findall("*//variablelist/varlistentry"):
path = construct_path(varlistentry)

assert pagetree.findall(path) == [varlistentry]

listitem = varlistentry.find("listitem")
parent = listitem if listitem is not None else varlistentry

rev = parent.getchildren()[-1]
if rev.get("href") != "version-info.xml":
if (filename, path) not in ignorelist:
undocumented.append((filename, path))
return undocumented


def process_pages(pages):
command_pages = []
function_pages = []

for page in pages:
filename = os.path.basename(page)
if filename.startswith("org.freedesktop."): # dbus
continue

if (
filename.startswith("sd_")
or filename.startswith("sd-")
or filename.startswith("udev_")
):
function_pages.append(page)
continue

command_pages.append(page)

undocumented_commands = find_undocumented_commands(
command_pages, command_ignorelist
)
undocumented_functions = find_undocumented_functions(
function_pages, function_ignorelist
)

return undocumented_commands, undocumented_functions


if __name__ == "__main__":
with open(os.path.join(os.path.dirname(__file__), "command_ignorelist")) as f:
command_ignorelist = []
for l in f.read().splitlines():
if l.startswith("#"):
continue
fname, path = l.split(" ", 1)
path = path.replace("\\n", "\n")
command_ignorelist.append((fname, path))
with open(os.path.join(os.path.dirname(__file__), "function_ignorelist")) as f:
function_ignorelist = f.read().splitlines()

undocumented_commands, undocumented_functions = process_pages(sys.argv[1:])

if undocumented_commands or undocumented_functions:
for filename, func in undocumented_functions:
print(
f"Function {func}() in {filename} isn't documented in the History section."
)
for filename, path in undocumented_commands:
print(filename, path, "is undocumented")
if undocumented_commands:
print(
"Hint: if you reorganized this part of the documentation, "
"please update tools/commands_ignorelist."
)

sys.exit(1)
Loading

0 comments on commit 3691e7f

Please sign in to comment.