File: rst_find_reference.py

package info (click to toggle)
blender-doc 4.3-1
  • links: PTS, VCS
  • area: main
  • in suites: forky, sid, trixie
  • size: 253,604 kB
  • sloc: python: 13,030; javascript: 322; makefile: 113; sh: 107
file content (186 lines) | stat: -rwxr-xr-x 5,077 bytes parent folder | download
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
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
#!/usr/bin/env python3
# Apache License, Version 2.0

"""
Utility that takes a reference and returns a file:line:column.

Useful for editors/IDE's to be able to jump to the reference under the cursor.

Notes:

- This command will only ever write an absolute file:line:column to the stdout.
- Errors always output to the stderr and return exit code 1.
"""
import os
import re

RST_EXT = (".rst", ".txt")


def rst_files(path):
    for dirpath, dirnames, filenames in os.walk(path):
        if dirpath.startswith("."):
            continue
        for filename in filenames:
            if filename.startswith("."):
                continue
            ext = os.path.splitext(filename)[1]
            if ext.lower() == ".rst":
                yield os.path.join(dirpath, filename)


def find_vcs_root(test, dirs=(".svn", ".git"), default=None):
    import os
    prev, test = None, os.path.abspath(test)
    while prev != test:
        if any(os.path.isdir(os.path.join(test, d)) for d in dirs):
            return test
        prev, test = test, os.path.abspath(os.path.join(test, os.pardir))
    return default


def find_rst_root(test, files=("index.rst", "index.txt", "contents.rst", "contents.txt"), default=None):
    import os
    prev, test = None, os.path.abspath(test)
    test_found = default
    while prev != test:
        if any(os.path.exists(os.path.join(test, fn)) for fn in files):
            test_found = test
        prev, test = test, os.path.abspath(os.path.join(test, os.pardir))
    return test_found


re_find_references = re.compile(r"(^[ \t]*\.\.\s+_)([a-zA-Z0-9_\-]+):", re.MULTILINE)


def find_references(fn, data, find_ref):
    """
    Use to find instances of the :ref: role.
    """
    for g in re_find_references.finditer(data):
        if g[2] == find_ref:
            start = g.start()
            return data.count('\n', 0, start), len(g[1])
    return None, None


def create_argparse():
    import argparse

    usage_text = __doc__

    parser = argparse.ArgumentParser(
        prog="rst_lookup_reference",
        description=usage_text,
    )

    parser.add_argument(
        "--path",
        dest="path",
        help=(
            "Path to operate use as a reference when finding the root. "
            "Typically the path of the file containing what you want to find is sufficient. "
            "Use the current working directory when not passed."
        ),
    )

    parser.add_argument(
        "--find",
        dest="find",
        metavar='FIND',
        required=True,
        default=1, type=str,
        help=(
            "Text referring to a role, eg:\n"
            "  :ref:`My Reference <some-reference>`"
            "  :doc:`My Doc </path/to/document>`"
        ),
    )

    return parser


def main(argv=None):

    import sys
    import os
    import re

    if argv is None:
        argv = sys.argv[1:]

    parser = create_argparse()
    args = parser.parse_args(argv)

    # rst_root = find_vcs_root(args.path)
    rst_path = args.path or os.getcwd()
    if os.path.isdir(rst_path):
        rst_cwd = rst_path
    else:
        rst_cwd = os.path.dirname(rst_path)
    rst_cwd = os.path.abspath(rst_cwd)
    rst_root = find_rst_root(rst_cwd)
    if not rst_root:
        return

    re_role_match_brackets = r":([a-zA-Z0-9_]+):`[^<]*<([^>]+)>`"
    re_role_match = r":([a-zA-Z0-9_]+):`([^`]+)`"
    match = re.match(re_role_match_brackets, args.find)
    if match is None:
        match = re.match(re_role_match, args.find)

    if match is None:
        print("Could not match:", re_role_match, file=sys.stderr)
        sys.exit(1)

    role_id, role_data = match.groups()
    del match

    line = col = None
    if role_id == "ref":
        for fn in rst_files(rst_root):
            if os.path.exists(fn):
                with open(fn, 'r', encoding='utf-8') as f_handle:
                    data = f_handle.read()
                    line, col = find_references(fn, data, role_data)
                    if line is not None:
                        break
        if line is None:
            print("Could not find reference:", repr(role_data), "in", repr(rst_root), file=sys.stderr)
            sys.exit(1)

    elif role_id == "doc":
        if role_data.startswith("/"):
            # Absolute path.
            fn_noext = os.path.join(rst_root, role_data[1:])
        else:
            # Relative path.
            fn_noext = os.path.join(rst_cwd, role_data)

        fn = None
        fn_attempts = []
        for ext in RST_EXT:
            fn_test = fn_noext + ext
            if os.path.exists(fn_test):
                fn = fn_test
                break
            fn_attempts.append(fn_test)

        if fn is None:
            print("Could not find files:", repr(fn_attempts), file=sys.stderr)
            sys.exit(1)

        line = 1
        col = 0
    else:
        # TODO:
        # - term
        print("Role", role_id, "not handled!", file=sys.stderr)
        sys.exit(1)

    assert (line is not None)
    print("%s:%d:%d" % (fn, line, col))


if __name__ == "__main__":
    main()