diff options
| author | Linus Torvalds <torvalds@linux-foundation.org> | 2025-12-03 11:34:28 -0800 |
|---|---|---|
| committer | Linus Torvalds <torvalds@linux-foundation.org> | 2025-12-03 11:34:28 -0800 |
| commit | f96163865a1346b199cc38e827269296f0f24ab0 (patch) | |
| tree | 8d1bd64f49f689eb2c66dd6e50144fc7b8bb27a5 /Documentation/sphinx/kernel_include.py | |
| parent | a619fe35ab41fded440d3762d4fbad84ff86a4d4 (diff) | |
| parent | 464257baf99200d1be1c053f15aa617056361e81 (diff) | |
Merge tag 'docs-6.19' of git://git.lwn.net/linux
Pull documentation updates from Jonathan Corbet:
"This has been another busy cycle for documentation, with a lot of
build-system thrashing. That work should slow down from here on out.
- The various scripts and tools for documentation were spread out in
several directories; now they are (almost) all coalesced under
tools/docs/. The holdout is the kernel-doc script, which cannot be
easily moved without some further thought.
- As the amount of Python code increases, we are accumulating modules
that are imported by multiple programs. These modules have been
pulled together under tools/lib/python/ -- at least, for
documentation-related programs. There is other Python code in the
tree that might eventually want to move toward this organization.
- The Perl kernel-doc.pl script has been removed. It is no longer
used by default, and nobody has missed it, least of all anybody who
actually had to look at it.
- The docs build was controlled by a complex mess of makefilese that
few dared to touch. Mauro has moved that logic into a new program
(tools/docs/sphinx-build-wrapper) that, with any luck at all, will
be far easier to understand and maintain.
- The get_feat.pl program, used to access information under
Documentation/features/, has been rewritten in Python, bringing an
end to the use of Perl in the docs subsystem.
- The top-level README file has been reorganized into a more
reader-friendly presentation.
- A lot of Chinese translation additions
- Typo fixes and documentation updates as usual"
* tag 'docs-6.19' of git://git.lwn.net/linux: (164 commits)
docs: makefile: move rustdoc check to the build wrapper
README: restructure with role-based documentation and guidelines
docs: kdoc: various fixes for grammar, spelling, punctuation
docs: kdoc_parser: use '@' for Excess enum value
docs: submitting-patches: Clarify that removal of Acks needs explanation too
docs: kdoc_parser: add data/function attributes to ignore
docs: MAINTAINERS: update Mauro's files/paths
docs/zh_CN: Add wd719x.rst translation
docs/zh_CN: Add libsas.rst translation
get_feat.pl: remove it, as it got replaced by get_feat.py
Documentation/sphinx/kernel_feat.py: use class directly
tools/docs/get_feat.py: convert get_feat.pl to Python
Documentation/admin-guide: fix typo and comment in cscope example
docs/zh_CN: Add data-integrity.rst translation
docs/zh_CN: Add blk-mq.rst translation
docs/zh_CN: Add block/index.rst translation
docs/zh_CN: Update the Chinese translation of kbuild.rst
docs: bring some order to our Python module hierarchy
docs: Move the python libraries to tools/lib/python
Documentation/kernel-parameters: Move the kernel build options
...
Diffstat (limited to 'Documentation/sphinx/kernel_include.py')
| -rwxr-xr-x | Documentation/sphinx/kernel_include.py | 112 |
1 files changed, 95 insertions, 17 deletions
diff --git a/Documentation/sphinx/kernel_include.py b/Documentation/sphinx/kernel_include.py index f94412cd17c9..626762ff6af3 100755 --- a/Documentation/sphinx/kernel_include.py +++ b/Documentation/sphinx/kernel_include.py @@ -87,6 +87,8 @@ import os.path import re import sys +from difflib import get_close_matches + from docutils import io, nodes, statemachine from docutils.statemachine import ViewList from docutils.parsers.rst import Directive, directives @@ -95,15 +97,17 @@ from docutils.parsers.rst.directives.body import CodeBlock, NumberLines from sphinx.util import logging srctree = os.path.abspath(os.environ["srctree"]) -sys.path.insert(0, os.path.join(srctree, "tools/docs/lib")) +sys.path.insert(0, os.path.join(srctree, "tools/lib/python")) -from parse_data_structs import ParseDataStructs +from kdoc.parse_data_structs import ParseDataStructs __version__ = "1.0" logger = logging.getLogger(__name__) RE_DOMAIN_REF = re.compile(r'\\ :(ref|c:type|c:func):`([^<`]+)(?:<([^>]+)>)?`\\') RE_SIMPLE_REF = re.compile(r'`([^`]+)`') +RE_LINENO_REF = re.compile(r'^\s*-\s+LINENO_(\d+):\s+(.*)') +RE_SPLIT_DOMAIN = re.compile(r"(.*)\.(.*)") def ErrorString(exc): # Shamelessly stolen from docutils return f'{exc.__class__.__name}: {exc}' @@ -212,14 +216,16 @@ class KernelInclude(Directive): - a TOC table containing cross references. """ parser = ParseDataStructs() - parser.parse_file(path) if 'exception-file' in self.options: source_dir = os.path.dirname(os.path.abspath( self.state_machine.input_lines.source( self.lineno - self.state_machine.input_offset - 1))) exceptions_file = os.path.join(source_dir, self.options['exception-file']) - parser.process_exceptions(exceptions_file) + else: + exceptions_file = None + + parser.parse_file(path, exceptions_file) # Store references on a symbol dict to be used at check time if 'warn-broken' in self.options: @@ -242,23 +248,32 @@ class KernelInclude(Directive): # TOC output is a ReST file, not a literal. So, we can add line # numbers - rawtext = parser.gen_toc() + startline = self.options.get('start-line', None) + endline = self.options.get('end-line', None) - include_lines = statemachine.string2lines(rawtext, tab_width, - convert_whitespace=True) + relpath = os.path.relpath(path, srctree) - # Append line numbers data + result = ViewList() + for line in parser.gen_toc().split("\n"): + match = RE_LINENO_REF.match(line) + if not match: + result.append(line, path) + continue - startline = self.options.get('start-line', None) + ln, ref = match.groups() + ln = int(ln) - result = ViewList() - if startline and startline > 0: - offset = startline - 1 - else: - offset = 0 + # Filter line range if needed + if startline and (ln < startline): + continue + + if endline and (ln > endline): + continue - for ln, line in enumerate(include_lines, start=offset): - result.append(line, path, ln) + # Sphinx numerates starting with zero, but text editors + # and other tools start from one + realln = ln + 1 + result.append(f"- {ref}: {relpath}#{realln}", path, ln) self.state_machine.insert_input(result, path) @@ -388,6 +403,63 @@ class KernelInclude(Directive): # ============================================================================== reported = set() +DOMAIN_INFO = {} +all_refs = {} + +def fill_domain_info(env): + """ + Get supported reference types for each Sphinx domain and C namespaces + """ + if DOMAIN_INFO: + return + + for domain_name, domain_instance in env.domains.items(): + try: + object_types = list(domain_instance.object_types.keys()) + DOMAIN_INFO[domain_name] = object_types + except AttributeError: + # Ignore domains that we can't retrieve object types, if any + pass + + for domain in DOMAIN_INFO.keys(): + domain_obj = env.get_domain(domain) + for name, dispname, objtype, docname, anchor, priority in domain_obj.get_objects(): + ref_name = name.lower() + + if domain == "c": + if '.' in ref_name: + ref_name = ref_name.split(".")[-1] + + if not ref_name in all_refs: + all_refs[ref_name] = [] + + all_refs[ref_name].append(f"\t{domain}:{objtype}:`{name}` (from {docname})") + +def get_suggestions(app, env, node, + original_target, original_domain, original_reftype): + """Check if target exists in the other domain or with different reftypes.""" + original_target = original_target.lower() + + # Remove namespace if present + if original_domain == "c": + if '.' in original_target: + original_target = original_target.split(".")[-1] + + suggestions = [] + + # If name exists, propose exact name match on different domains + if original_target in all_refs: + return all_refs[original_target] + + # If not found, get a close match, using difflib. + # Such method is based on Ratcliff-Obershelp Algorithm, which seeks + # for a close match within a certain distance. We're using the defaults + # here, e.g. cutoff=0.6, proposing 3 alternatives + matches = get_close_matches(original_target, all_refs.keys()) + for match in matches: + suggestions += all_refs[match] + + return suggestions def check_missing_refs(app, env, node, contnode): """Check broken refs for the files it creates xrefs""" @@ -404,11 +476,13 @@ def check_missing_refs(app, env, node, contnode): if node.source not in xref_files: return None + fill_domain_info(env) + target = node.get('reftarget', '') domain = node.get('refdomain', 'std') reftype = node.get('reftype', '') - msg = f"can't link to: {domain}:{reftype}:: {target}" + msg = f"Invalid xref: {domain}:{reftype}:`{target}`" # Don't duplicate warnings data = (node.source, msg) @@ -416,6 +490,10 @@ def check_missing_refs(app, env, node, contnode): return None reported.add(data) + suggestions = get_suggestions(app, env, node, target, domain, reftype) + if suggestions: + msg += ". Possible alternatives:\n" + '\n'.join(suggestions) + logger.warning(msg, location=node, type='ref', subtype='missing') return None |