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