summaryrefslogtreecommitdiffstats
path: root/tools/check-version-history.py
blob: d4a87df3555f7927b9624de65381d8299449a6ed (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
#!/usr/bin/env python3
# SPDX-License-Identifier: LGPL-2.1-or-later

import os
import sys

try:
    import lxml.etree as tree
except ImportError as e:
    print(str(e), file=sys.stderr)
    sys.exit(77)

_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)