Import QEMU upstream snapshot d2e570c
Upstream: https://gitlab.com/qemu-project/qemu.git Upstream-Commit: d2e570cc0f97b936902a5b1b86b73c0f5998b475
This commit is contained in:
commit
cf256aa081
11315 files changed
+3598369
No files matched your search
@@ -0,0 +1,294 @@
|
||||
#!/usr/bin/env python3
|
||||
# SPDX-License-Identifier: GPL-2.0
|
||||
# Copyright(c) 2025: Mauro Carvalho Chehab <[email protected]>.
|
||||
#
|
||||
# pylint: disable=R0903,R0913,R0914,R0917
|
||||
|
||||
"""
|
||||
Parse lernel-doc tags on multiple kernel source files.
|
||||
"""
|
||||
|
||||
import argparse
|
||||
import logging
|
||||
import os
|
||||
import re
|
||||
|
||||
from kdoc_parser import KernelDoc
|
||||
from kdoc_output import OutputFormat
|
||||
|
||||
|
||||
class GlobSourceFiles:
|
||||
"""
|
||||
Parse C source code file names and directories via an Interactor.
|
||||
"""
|
||||
|
||||
def __init__(self, srctree=None, valid_extensions=None):
|
||||
"""
|
||||
Initialize valid extensions with a tuple.
|
||||
|
||||
If not defined, assume default C extensions (.c and .h)
|
||||
|
||||
It would be possible to use python's glob function, but it is
|
||||
very slow, and it is not interactive. So, it would wait to read all
|
||||
directories before actually do something.
|
||||
|
||||
So, let's use our own implementation.
|
||||
"""
|
||||
|
||||
if not valid_extensions:
|
||||
self.extensions = (".c", ".h")
|
||||
else:
|
||||
self.extensions = valid_extensions
|
||||
|
||||
self.srctree = srctree
|
||||
|
||||
def _parse_dir(self, dirname):
|
||||
"""Internal function to parse files recursively"""
|
||||
|
||||
with os.scandir(dirname) as obj:
|
||||
for entry in obj:
|
||||
name = os.path.join(dirname, entry.name)
|
||||
|
||||
if entry.is_dir(follow_symlinks=False):
|
||||
yield from self._parse_dir(name)
|
||||
|
||||
if not entry.is_file():
|
||||
continue
|
||||
|
||||
basename = os.path.basename(name)
|
||||
|
||||
if not basename.endswith(self.extensions):
|
||||
continue
|
||||
|
||||
yield name
|
||||
|
||||
def parse_files(self, file_list, file_not_found_cb):
|
||||
"""
|
||||
Define an iterator to parse all source files from file_list,
|
||||
handling directories if any
|
||||
"""
|
||||
|
||||
if not file_list:
|
||||
return
|
||||
|
||||
for fname in file_list:
|
||||
if self.srctree:
|
||||
f = os.path.join(self.srctree, fname)
|
||||
else:
|
||||
f = fname
|
||||
|
||||
if os.path.isdir(f):
|
||||
yield from self._parse_dir(f)
|
||||
elif os.path.isfile(f):
|
||||
yield f
|
||||
elif file_not_found_cb:
|
||||
file_not_found_cb(fname)
|
||||
|
||||
|
||||
class KernelFiles():
|
||||
"""
|
||||
Parse kernel-doc tags on multiple kernel source files.
|
||||
|
||||
There are two type of parsers defined here:
|
||||
- self.parse_file(): parses both kernel-doc markups and
|
||||
EXPORT_SYMBOL* macros;
|
||||
- self.process_export_file(): parses only EXPORT_SYMBOL* macros.
|
||||
"""
|
||||
|
||||
def warning(self, msg):
|
||||
"""Ancillary routine to output a warning and increment error count"""
|
||||
|
||||
self.config.log.warning(msg)
|
||||
self.errors += 1
|
||||
|
||||
def error(self, msg):
|
||||
"""Ancillary routine to output an error and increment error count"""
|
||||
|
||||
self.config.log.error(msg)
|
||||
self.errors += 1
|
||||
|
||||
def parse_file(self, fname):
|
||||
"""
|
||||
Parse a single Kernel source.
|
||||
"""
|
||||
|
||||
# Prevent parsing the same file twice if results are cached
|
||||
if fname in self.files:
|
||||
return
|
||||
|
||||
doc = KernelDoc(self.config, fname)
|
||||
export_table, entries = doc.parse_kdoc()
|
||||
|
||||
self.export_table[fname] = export_table
|
||||
|
||||
self.files.add(fname)
|
||||
self.export_files.add(fname) # parse_kdoc() already check exports
|
||||
|
||||
self.results[fname] = entries
|
||||
|
||||
def process_export_file(self, fname):
|
||||
"""
|
||||
Parses EXPORT_SYMBOL* macros from a single Kernel source file.
|
||||
"""
|
||||
|
||||
# Prevent parsing the same file twice if results are cached
|
||||
if fname in self.export_files:
|
||||
return
|
||||
|
||||
doc = KernelDoc(self.config, fname)
|
||||
export_table = doc.parse_export()
|
||||
|
||||
if not export_table:
|
||||
self.error(f"Error: Cannot check EXPORT_SYMBOL* on {fname}")
|
||||
export_table = set()
|
||||
|
||||
self.export_table[fname] = export_table
|
||||
self.export_files.add(fname)
|
||||
|
||||
def file_not_found_cb(self, fname):
|
||||
"""
|
||||
Callback to warn if a file was not found.
|
||||
"""
|
||||
|
||||
self.error(f"Cannot find file {fname}")
|
||||
|
||||
def __init__(self, verbose=False, out_style=None,
|
||||
werror=False, wreturn=False, wshort_desc=False,
|
||||
wcontents_before_sections=False,
|
||||
logger=None):
|
||||
"""
|
||||
Initialize startup variables and parse all files
|
||||
"""
|
||||
|
||||
if not verbose:
|
||||
verbose = bool(os.environ.get("KBUILD_VERBOSE", 0))
|
||||
|
||||
if out_style is None:
|
||||
out_style = OutputFormat()
|
||||
|
||||
if not werror:
|
||||
kcflags = os.environ.get("KCFLAGS", None)
|
||||
if kcflags:
|
||||
match = re.search(r"(\s|^)-Werror(\s|$)/", kcflags)
|
||||
if match:
|
||||
werror = True
|
||||
|
||||
# reading this variable is for backwards compat just in case
|
||||
# someone was calling it with the variable from outside the
|
||||
# kernel's build system
|
||||
kdoc_werror = os.environ.get("KDOC_WERROR", None)
|
||||
if kdoc_werror:
|
||||
werror = kdoc_werror
|
||||
|
||||
# Some variables are global to the parser logic as a whole as they are
|
||||
# used to send control configuration to KernelDoc class. As such,
|
||||
# those variables are read-only inside the KernelDoc.
|
||||
self.config = argparse.Namespace
|
||||
|
||||
self.config.verbose = verbose
|
||||
self.config.werror = werror
|
||||
self.config.wreturn = wreturn
|
||||
self.config.wshort_desc = wshort_desc
|
||||
self.config.wcontents_before_sections = wcontents_before_sections
|
||||
|
||||
if not logger:
|
||||
self.config.log = logging.getLogger("kernel-doc")
|
||||
else:
|
||||
self.config.log = logger
|
||||
|
||||
self.config.warning = self.warning
|
||||
|
||||
self.config.src_tree = os.environ.get("SRCTREE", None)
|
||||
|
||||
# Initialize variables that are internal to KernelFiles
|
||||
|
||||
self.out_style = out_style
|
||||
|
||||
self.errors = 0
|
||||
self.results = {}
|
||||
|
||||
self.files = set()
|
||||
self.export_files = set()
|
||||
self.export_table = {}
|
||||
|
||||
def parse(self, file_list, export_file=None):
|
||||
"""
|
||||
Parse all files
|
||||
"""
|
||||
|
||||
glob = GlobSourceFiles(srctree=self.config.src_tree)
|
||||
|
||||
for fname in glob.parse_files(file_list, self.file_not_found_cb):
|
||||
self.parse_file(fname)
|
||||
|
||||
for fname in glob.parse_files(export_file, self.file_not_found_cb):
|
||||
self.process_export_file(fname)
|
||||
|
||||
def out_msg(self, fname, name, arg):
|
||||
"""
|
||||
Return output messages from a file name using the output style
|
||||
filtering.
|
||||
|
||||
If output type was not handled by the styler, return None.
|
||||
"""
|
||||
|
||||
# NOTE: we can add rules here to filter out unwanted parts,
|
||||
# although OutputFormat.msg already does that.
|
||||
|
||||
return self.out_style.msg(fname, name, arg)
|
||||
|
||||
def msg(self, enable_lineno=False, export=False, internal=False,
|
||||
symbol=None, nosymbol=None, no_doc_sections=False,
|
||||
filenames=None, export_file=None):
|
||||
"""
|
||||
Interacts over the kernel-doc results and output messages,
|
||||
returning kernel-doc markups on each interaction
|
||||
"""
|
||||
|
||||
self.out_style.set_config(self.config)
|
||||
|
||||
if not filenames:
|
||||
filenames = sorted(self.results.keys())
|
||||
|
||||
glob = GlobSourceFiles(srctree=self.config.src_tree)
|
||||
|
||||
for fname in filenames:
|
||||
function_table = set()
|
||||
|
||||
if internal or export:
|
||||
if not export_file:
|
||||
export_file = [fname]
|
||||
|
||||
for f in glob.parse_files(export_file, self.file_not_found_cb):
|
||||
function_table |= self.export_table[f]
|
||||
|
||||
if symbol:
|
||||
for s in symbol:
|
||||
function_table.add(s)
|
||||
|
||||
self.out_style.set_filter(export, internal, symbol, nosymbol,
|
||||
function_table, enable_lineno,
|
||||
no_doc_sections)
|
||||
|
||||
msg = ""
|
||||
if fname not in self.results:
|
||||
self.config.log.warning("No kernel-doc for file %s", fname)
|
||||
continue
|
||||
|
||||
symbols = self.results[fname]
|
||||
self.out_style.set_symbols(symbols)
|
||||
|
||||
for arg in symbols:
|
||||
m = self.out_msg(fname, arg.name, arg)
|
||||
|
||||
if m is None:
|
||||
ln = arg.get("ln", 0)
|
||||
dtype = arg.get('type', "")
|
||||
|
||||
self.config.log.warning("%s:%d Can't handle %s",
|
||||
fname, ln, dtype)
|
||||
else:
|
||||
msg += m
|
||||
|
||||
if msg:
|
||||
yield fname, msg
|
||||
@@ -0,0 +1,43 @@
|
||||
# SPDX-License-Identifier: GPL-2.0
|
||||
#
|
||||
# A class that will, eventually, encapsulate all of the parsed data that we
|
||||
# then pass into the output modules.
|
||||
#
|
||||
|
||||
class KdocItem:
|
||||
def __init__(self, name, fname, type, start_line, **other_stuff):
|
||||
self.name = name
|
||||
self.fname = fname
|
||||
self.type = type
|
||||
self.declaration_start_line = start_line
|
||||
self.sections = {}
|
||||
self.sections_start_lines = {}
|
||||
self.parameterlist = []
|
||||
self.parameterdesc_start_lines = []
|
||||
self.parameterdescs = {}
|
||||
self.parametertypes = {}
|
||||
#
|
||||
# Just save everything else into our own dict so that the output
|
||||
# side can grab it directly as before. As we move things into more
|
||||
# structured data, this will, hopefully, fade away.
|
||||
#
|
||||
self.other_stuff = other_stuff
|
||||
|
||||
def get(self, key, default = None):
|
||||
return self.other_stuff.get(key, default)
|
||||
|
||||
def __getitem__(self, key):
|
||||
return self.get(key)
|
||||
|
||||
#
|
||||
# Tracking of section and parameter information.
|
||||
#
|
||||
def set_sections(self, sections, start_lines):
|
||||
self.sections = sections
|
||||
self.section_start_lines = start_lines
|
||||
|
||||
def set_params(self, names, descs, types, starts):
|
||||
self.parameterlist = names
|
||||
self.parameterdescs = descs
|
||||
self.parametertypes = types
|
||||
self.parameterdesc_start_lines = starts
|
||||
@@ -0,0 +1,824 @@
|
||||
#!/usr/bin/env python3
|
||||
# SPDX-License-Identifier: GPL-2.0
|
||||
# Copyright(c) 2025: Mauro Carvalho Chehab <[email protected]>.
|
||||
#
|
||||
# pylint: disable=C0301,R0902,R0911,R0912,R0913,R0914,R0915,R0917
|
||||
|
||||
"""
|
||||
Implement output filters to print kernel-doc documentation.
|
||||
|
||||
The implementation uses a virtual base class (OutputFormat) which
|
||||
contains dispatches to virtual methods, and some code to filter
|
||||
out output messages.
|
||||
|
||||
The actual implementation is done on one separate class per each type
|
||||
of output. Currently, there are output classes for ReST and man/troff.
|
||||
"""
|
||||
|
||||
import os
|
||||
import re
|
||||
from datetime import datetime
|
||||
|
||||
from kdoc_parser import KernelDoc, type_param
|
||||
from kdoc_re import KernRe
|
||||
|
||||
|
||||
function_pointer = KernRe(r"([^\(]*\(\*)\s*\)\s*\(([^\)]*)\)", cache=False)
|
||||
|
||||
# match expressions used to find embedded type information
|
||||
type_constant = KernRe(r"\b``([^\`]+)``\b", cache=False)
|
||||
type_constant2 = KernRe(r"\%([-_*\w]+)", cache=False)
|
||||
type_func = KernRe(r"(\w+)\(\)", cache=False)
|
||||
type_param_ref = KernRe(r"([\!~\*]?)\@(\w*((\.\w+)|(->\w+))*(\.\.\.)?)", cache=False)
|
||||
|
||||
# Special RST handling for func ptr params
|
||||
type_fp_param = KernRe(r"\@(\w+)\(\)", cache=False)
|
||||
|
||||
# Special RST handling for structs with func ptr params
|
||||
type_fp_param2 = KernRe(r"\@(\w+->\S+)\(\)", cache=False)
|
||||
|
||||
type_env = KernRe(r"(\$\w+)", cache=False)
|
||||
type_enum = KernRe(r"#(enum\s*([_\w]+))", cache=False)
|
||||
type_struct = KernRe(r"#(struct\s*([_\w]+))", cache=False)
|
||||
type_typedef = KernRe(r"#(([A-Z][_\w]*))", cache=False)
|
||||
type_union = KernRe(r"#(union\s*([_\w]+))", cache=False)
|
||||
type_member = KernRe(r"#([_\w]+)(\.|->)([_\w]+)", cache=False)
|
||||
type_fallback = KernRe(r"((?!))", cache=False) # this never matches
|
||||
type_member_func = type_member + KernRe(r"\(\)", cache=False)
|
||||
|
||||
|
||||
class OutputFormat:
|
||||
"""
|
||||
Base class for OutputFormat. If used as-is, it means that only
|
||||
warnings will be displayed.
|
||||
"""
|
||||
|
||||
# output mode.
|
||||
OUTPUT_ALL = 0 # output all symbols and doc sections
|
||||
OUTPUT_INCLUDE = 1 # output only specified symbols
|
||||
OUTPUT_EXPORTED = 2 # output exported symbols
|
||||
OUTPUT_INTERNAL = 3 # output non-exported symbols
|
||||
|
||||
# Virtual member to be overridden at the inherited classes
|
||||
highlights = []
|
||||
|
||||
def __init__(self):
|
||||
"""Declare internal vars and set mode to OUTPUT_ALL"""
|
||||
|
||||
self.out_mode = self.OUTPUT_ALL
|
||||
self.enable_lineno = None
|
||||
self.nosymbol = {}
|
||||
self.symbol = None
|
||||
self.function_table = None
|
||||
self.config = None
|
||||
self.no_doc_sections = False
|
||||
|
||||
self.data = ""
|
||||
|
||||
def set_config(self, config):
|
||||
"""
|
||||
Setup global config variables used by both parser and output.
|
||||
"""
|
||||
|
||||
self.config = config
|
||||
|
||||
def set_filter(self, export, internal, symbol, nosymbol, function_table,
|
||||
enable_lineno, no_doc_sections):
|
||||
"""
|
||||
Initialize filter variables according to the requested mode.
|
||||
|
||||
Only one choice is valid between export, internal and symbol.
|
||||
|
||||
The nosymbol filter can be used on all modes.
|
||||
"""
|
||||
|
||||
self.enable_lineno = enable_lineno
|
||||
self.no_doc_sections = no_doc_sections
|
||||
self.function_table = function_table
|
||||
|
||||
if symbol:
|
||||
self.out_mode = self.OUTPUT_INCLUDE
|
||||
elif export:
|
||||
self.out_mode = self.OUTPUT_EXPORTED
|
||||
elif internal:
|
||||
self.out_mode = self.OUTPUT_INTERNAL
|
||||
else:
|
||||
self.out_mode = self.OUTPUT_ALL
|
||||
|
||||
if nosymbol:
|
||||
self.nosymbol = set(nosymbol)
|
||||
|
||||
|
||||
def highlight_block(self, block):
|
||||
"""
|
||||
Apply the RST highlights to a sub-block of text.
|
||||
"""
|
||||
|
||||
for r, sub in self.highlights:
|
||||
block = r.sub(sub, block)
|
||||
|
||||
return block
|
||||
|
||||
def out_warnings(self, args):
|
||||
"""
|
||||
Output warnings for identifiers that will be displayed.
|
||||
"""
|
||||
|
||||
for log_msg in args.warnings:
|
||||
self.config.warning(log_msg)
|
||||
|
||||
def check_doc(self, name, args):
|
||||
"""Check if DOC should be output"""
|
||||
|
||||
if self.no_doc_sections:
|
||||
return False
|
||||
|
||||
if name in self.nosymbol:
|
||||
return False
|
||||
|
||||
if self.out_mode == self.OUTPUT_ALL:
|
||||
self.out_warnings(args)
|
||||
return True
|
||||
|
||||
if self.out_mode == self.OUTPUT_INCLUDE:
|
||||
if name in self.function_table:
|
||||
self.out_warnings(args)
|
||||
return True
|
||||
|
||||
return False
|
||||
|
||||
def check_declaration(self, dtype, name, args):
|
||||
"""
|
||||
Checks if a declaration should be output or not based on the
|
||||
filtering criteria.
|
||||
"""
|
||||
|
||||
if name in self.nosymbol:
|
||||
return False
|
||||
|
||||
if self.out_mode == self.OUTPUT_ALL:
|
||||
self.out_warnings(args)
|
||||
return True
|
||||
|
||||
if self.out_mode in [self.OUTPUT_INCLUDE, self.OUTPUT_EXPORTED]:
|
||||
if name in self.function_table:
|
||||
return True
|
||||
|
||||
if self.out_mode == self.OUTPUT_INTERNAL:
|
||||
if dtype != "function":
|
||||
self.out_warnings(args)
|
||||
return True
|
||||
|
||||
if name not in self.function_table:
|
||||
self.out_warnings(args)
|
||||
return True
|
||||
|
||||
return False
|
||||
|
||||
def msg(self, fname, name, args):
|
||||
"""
|
||||
Handles a single entry from kernel-doc parser
|
||||
"""
|
||||
|
||||
self.data = ""
|
||||
|
||||
dtype = args.type
|
||||
|
||||
if dtype == "doc":
|
||||
self.out_doc(fname, name, args)
|
||||
return self.data
|
||||
|
||||
if not self.check_declaration(dtype, name, args):
|
||||
return self.data
|
||||
|
||||
if dtype == "function":
|
||||
self.out_function(fname, name, args)
|
||||
return self.data
|
||||
|
||||
if dtype == "enum":
|
||||
self.out_enum(fname, name, args)
|
||||
return self.data
|
||||
|
||||
if dtype == "typedef":
|
||||
self.out_typedef(fname, name, args)
|
||||
return self.data
|
||||
|
||||
if dtype in ["struct", "union"]:
|
||||
self.out_struct(fname, name, args)
|
||||
return self.data
|
||||
|
||||
# Warn if some type requires an output logic
|
||||
self.config.log.warning("doesn't know how to output '%s' block",
|
||||
dtype)
|
||||
|
||||
return None
|
||||
|
||||
# Virtual methods to be overridden by inherited classes
|
||||
# At the base class, those do nothing.
|
||||
def set_symbols(self, symbols):
|
||||
"""Get a list of all symbols from kernel_doc"""
|
||||
|
||||
def out_doc(self, fname, name, args):
|
||||
"""Outputs a DOC block"""
|
||||
|
||||
def out_function(self, fname, name, args):
|
||||
"""Outputs a function"""
|
||||
|
||||
def out_enum(self, fname, name, args):
|
||||
"""Outputs an enum"""
|
||||
|
||||
def out_typedef(self, fname, name, args):
|
||||
"""Outputs a typedef"""
|
||||
|
||||
def out_struct(self, fname, name, args):
|
||||
"""Outputs a struct"""
|
||||
|
||||
|
||||
class RestFormat(OutputFormat):
|
||||
"""Consts and functions used by ReST output"""
|
||||
|
||||
highlights = [
|
||||
(type_constant, r"``\1``"),
|
||||
(type_constant2, r"``\1``"),
|
||||
|
||||
# Note: need to escape () to avoid func matching later
|
||||
(type_member_func, r":c:type:`\1\2\3\\(\\) <\1>`"),
|
||||
(type_member, r":c:type:`\1\2\3 <\1>`"),
|
||||
(type_fp_param, r"**\1\\(\\)**"),
|
||||
(type_fp_param2, r"**\1\\(\\)**"),
|
||||
(type_func, r"\1()"),
|
||||
(type_enum, r":c:type:`\1 <\2>`"),
|
||||
(type_struct, r":c:type:`\1 <\2>`"),
|
||||
(type_typedef, r":c:type:`\1 <\2>`"),
|
||||
(type_union, r":c:type:`\1 <\2>`"),
|
||||
|
||||
# in rst this can refer to any type
|
||||
(type_fallback, r":c:type:`\1`"),
|
||||
(type_param_ref, r"**\1\2**")
|
||||
]
|
||||
blankline = "\n"
|
||||
|
||||
sphinx_literal = KernRe(r'^[^.].*::$', cache=False)
|
||||
sphinx_cblock = KernRe(r'^\.\.\ +code-block::', cache=False)
|
||||
|
||||
def __init__(self):
|
||||
"""
|
||||
Creates class variables.
|
||||
|
||||
Not really mandatory, but it is a good coding style and makes
|
||||
pylint happy.
|
||||
"""
|
||||
|
||||
super().__init__()
|
||||
self.lineprefix = ""
|
||||
|
||||
def print_lineno(self, ln):
|
||||
"""Outputs a line number"""
|
||||
|
||||
if self.enable_lineno and ln is not None:
|
||||
ln += 1
|
||||
self.data += f".. LINENO {ln}\n"
|
||||
|
||||
def output_highlight(self, args):
|
||||
"""
|
||||
Outputs a C symbol that may require being converted to ReST using
|
||||
the self.highlights variable
|
||||
"""
|
||||
|
||||
input_text = args
|
||||
output = ""
|
||||
in_literal = False
|
||||
litprefix = ""
|
||||
block = ""
|
||||
|
||||
for line in input_text.strip("\n").split("\n"):
|
||||
|
||||
# If we're in a literal block, see if we should drop out of it.
|
||||
# Otherwise, pass the line straight through unmunged.
|
||||
if in_literal:
|
||||
if line.strip(): # If the line is not blank
|
||||
# If this is the first non-blank line in a literal block,
|
||||
# figure out the proper indent.
|
||||
if not litprefix:
|
||||
r = KernRe(r'^(\s*)')
|
||||
if r.match(line):
|
||||
litprefix = '^' + r.group(1)
|
||||
else:
|
||||
litprefix = ""
|
||||
|
||||
output += line + "\n"
|
||||
elif not KernRe(litprefix).match(line):
|
||||
in_literal = False
|
||||
else:
|
||||
output += line + "\n"
|
||||
else:
|
||||
output += line + "\n"
|
||||
|
||||
# Not in a literal block (or just dropped out)
|
||||
if not in_literal:
|
||||
block += line + "\n"
|
||||
if self.sphinx_literal.match(line) or self.sphinx_cblock.match(line):
|
||||
in_literal = True
|
||||
litprefix = ""
|
||||
output += self.highlight_block(block)
|
||||
block = ""
|
||||
|
||||
# Handle any remaining block
|
||||
if block:
|
||||
output += self.highlight_block(block)
|
||||
|
||||
# Print the output with the line prefix
|
||||
for line in output.strip("\n").split("\n"):
|
||||
self.data += self.lineprefix + line + "\n"
|
||||
|
||||
def out_section(self, args, out_docblock=False):
|
||||
"""
|
||||
Outputs a block section.
|
||||
|
||||
This could use some work; it's used to output the DOC: sections, and
|
||||
starts by putting out the name of the doc section itself, but that
|
||||
tends to duplicate a header already in the template file.
|
||||
"""
|
||||
for section, text in args.sections.items():
|
||||
# Skip sections that are in the nosymbol_table
|
||||
if section in self.nosymbol:
|
||||
continue
|
||||
|
||||
if out_docblock:
|
||||
if not self.out_mode == self.OUTPUT_INCLUDE:
|
||||
self.data += f".. _{section}:\n\n"
|
||||
self.data += f'{self.lineprefix}**{section}**\n\n'
|
||||
else:
|
||||
self.data += f'{self.lineprefix}**{section}**\n\n'
|
||||
|
||||
self.print_lineno(args.section_start_lines.get(section, 0))
|
||||
self.output_highlight(text)
|
||||
self.data += "\n"
|
||||
self.data += "\n"
|
||||
|
||||
def out_doc(self, fname, name, args):
|
||||
if not self.check_doc(name, args):
|
||||
return
|
||||
self.out_section(args, out_docblock=True)
|
||||
|
||||
def out_function(self, fname, name, args):
|
||||
|
||||
oldprefix = self.lineprefix
|
||||
signature = ""
|
||||
|
||||
func_macro = args.get('func_macro', False)
|
||||
if func_macro:
|
||||
signature = name
|
||||
else:
|
||||
if args.get('functiontype'):
|
||||
signature = args['functiontype'] + " "
|
||||
signature += name + " ("
|
||||
|
||||
ln = args.declaration_start_line
|
||||
count = 0
|
||||
for parameter in args.parameterlist:
|
||||
if count != 0:
|
||||
signature += ", "
|
||||
count += 1
|
||||
dtype = args.parametertypes.get(parameter, "")
|
||||
|
||||
if function_pointer.search(dtype):
|
||||
signature += function_pointer.group(1) + parameter + function_pointer.group(3)
|
||||
else:
|
||||
signature += dtype
|
||||
|
||||
if not func_macro:
|
||||
signature += ")"
|
||||
|
||||
self.print_lineno(ln)
|
||||
if args.get('typedef') or not args.get('functiontype'):
|
||||
self.data += f".. c:macro:: {name}\n\n"
|
||||
|
||||
if args.get('typedef'):
|
||||
self.data += " **Typedef**: "
|
||||
self.lineprefix = ""
|
||||
self.output_highlight(args.get('purpose', ""))
|
||||
self.data += "\n\n**Syntax**\n\n"
|
||||
self.data += f" ``{signature}``\n\n"
|
||||
else:
|
||||
self.data += f"``{signature}``\n\n"
|
||||
else:
|
||||
self.data += f".. c:function:: {signature}\n\n"
|
||||
|
||||
if not args.get('typedef'):
|
||||
self.print_lineno(ln)
|
||||
self.lineprefix = " "
|
||||
self.output_highlight(args.get('purpose', ""))
|
||||
self.data += "\n"
|
||||
|
||||
# Put descriptive text into a container (HTML <div>) to help set
|
||||
# function prototypes apart
|
||||
self.lineprefix = " "
|
||||
|
||||
if args.parameterlist:
|
||||
self.data += ".. container:: kernelindent\n\n"
|
||||
self.data += f"{self.lineprefix}**Parameters**\n\n"
|
||||
|
||||
for parameter in args.parameterlist:
|
||||
parameter_name = KernRe(r'\[.*').sub('', parameter)
|
||||
dtype = args.parametertypes.get(parameter, "")
|
||||
|
||||
if dtype:
|
||||
self.data += f"{self.lineprefix}``{dtype}``\n"
|
||||
else:
|
||||
self.data += f"{self.lineprefix}``{parameter}``\n"
|
||||
|
||||
self.print_lineno(args.parameterdesc_start_lines.get(parameter_name, 0))
|
||||
|
||||
self.lineprefix = " "
|
||||
if parameter_name in args.parameterdescs and \
|
||||
args.parameterdescs[parameter_name] != KernelDoc.undescribed:
|
||||
|
||||
self.output_highlight(args.parameterdescs[parameter_name])
|
||||
self.data += "\n"
|
||||
else:
|
||||
self.data += f"{self.lineprefix}*undescribed*\n\n"
|
||||
self.lineprefix = " "
|
||||
|
||||
self.out_section(args)
|
||||
self.lineprefix = oldprefix
|
||||
|
||||
def out_enum(self, fname, name, args):
|
||||
|
||||
oldprefix = self.lineprefix
|
||||
ln = args.declaration_start_line
|
||||
|
||||
self.data += f"\n\n.. c:enum:: {name}\n\n"
|
||||
|
||||
self.print_lineno(ln)
|
||||
self.lineprefix = " "
|
||||
self.output_highlight(args.get('purpose', ''))
|
||||
self.data += "\n"
|
||||
|
||||
self.data += ".. container:: kernelindent\n\n"
|
||||
outer = self.lineprefix + " "
|
||||
self.lineprefix = outer + " "
|
||||
self.data += f"{outer}**Constants**\n\n"
|
||||
|
||||
for parameter in args.parameterlist:
|
||||
self.data += f"{outer}``{parameter}``\n"
|
||||
|
||||
if args.parameterdescs.get(parameter, '') != KernelDoc.undescribed:
|
||||
self.output_highlight(args.parameterdescs[parameter])
|
||||
else:
|
||||
self.data += f"{self.lineprefix}*undescribed*\n\n"
|
||||
self.data += "\n"
|
||||
|
||||
self.lineprefix = oldprefix
|
||||
self.out_section(args)
|
||||
|
||||
def out_typedef(self, fname, name, args):
|
||||
|
||||
oldprefix = self.lineprefix
|
||||
ln = args.declaration_start_line
|
||||
|
||||
self.data += f"\n\n.. c:type:: {name}\n\n"
|
||||
|
||||
self.print_lineno(ln)
|
||||
self.lineprefix = " "
|
||||
|
||||
self.output_highlight(args.get('purpose', ''))
|
||||
|
||||
self.data += "\n"
|
||||
|
||||
self.lineprefix = oldprefix
|
||||
self.out_section(args)
|
||||
|
||||
def out_struct(self, fname, name, args):
|
||||
|
||||
purpose = args.get('purpose', "")
|
||||
declaration = args.get('definition', "")
|
||||
dtype = args.type
|
||||
ln = args.declaration_start_line
|
||||
|
||||
self.data += f"\n\n.. c:{dtype}:: {name}\n\n"
|
||||
|
||||
self.print_lineno(ln)
|
||||
|
||||
oldprefix = self.lineprefix
|
||||
self.lineprefix += " "
|
||||
|
||||
self.output_highlight(purpose)
|
||||
self.data += "\n"
|
||||
|
||||
self.data += ".. container:: kernelindent\n\n"
|
||||
self.data += f"{self.lineprefix}**Definition**::\n\n"
|
||||
|
||||
self.lineprefix = self.lineprefix + " "
|
||||
|
||||
declaration = declaration.replace("\t", self.lineprefix)
|
||||
|
||||
self.data += f"{self.lineprefix}{dtype} {name}" + ' {' + "\n"
|
||||
self.data += f"{declaration}{self.lineprefix}" + "};\n\n"
|
||||
|
||||
self.lineprefix = " "
|
||||
self.data += f"{self.lineprefix}**Members**\n\n"
|
||||
for parameter in args.parameterlist:
|
||||
if not parameter or parameter.startswith("#"):
|
||||
continue
|
||||
|
||||
parameter_name = parameter.split("[", maxsplit=1)[0]
|
||||
|
||||
if args.parameterdescs.get(parameter_name) == KernelDoc.undescribed:
|
||||
continue
|
||||
|
||||
self.print_lineno(args.parameterdesc_start_lines.get(parameter_name, 0))
|
||||
|
||||
self.data += f"{self.lineprefix}``{parameter}``\n"
|
||||
|
||||
self.lineprefix = " "
|
||||
self.output_highlight(args.parameterdescs[parameter_name])
|
||||
self.lineprefix = " "
|
||||
|
||||
self.data += "\n"
|
||||
|
||||
self.data += "\n"
|
||||
|
||||
self.lineprefix = oldprefix
|
||||
self.out_section(args)
|
||||
|
||||
|
||||
class ManFormat(OutputFormat):
|
||||
"""Consts and functions used by man pages output"""
|
||||
|
||||
highlights = (
|
||||
(type_constant, r"\1"),
|
||||
(type_constant2, r"\1"),
|
||||
(type_func, r"\\fB\1\\fP"),
|
||||
(type_enum, r"\\fI\1\\fP"),
|
||||
(type_struct, r"\\fI\1\\fP"),
|
||||
(type_typedef, r"\\fI\1\\fP"),
|
||||
(type_union, r"\\fI\1\\fP"),
|
||||
(type_param, r"\\fI\1\\fP"),
|
||||
(type_param_ref, r"\\fI\1\2\\fP"),
|
||||
(type_member, r"\\fI\1\2\3\\fP"),
|
||||
(type_fallback, r"\\fI\1\\fP")
|
||||
)
|
||||
blankline = ""
|
||||
|
||||
date_formats = [
|
||||
"%a %b %d %H:%M:%S %Z %Y",
|
||||
"%a %b %d %H:%M:%S %Y",
|
||||
"%Y-%m-%d",
|
||||
"%b %d %Y",
|
||||
"%B %d %Y",
|
||||
"%m %d %Y",
|
||||
]
|
||||
|
||||
def __init__(self, modulename):
|
||||
"""
|
||||
Creates class variables.
|
||||
|
||||
Not really mandatory, but it is a good coding style and makes
|
||||
pylint happy.
|
||||
"""
|
||||
|
||||
super().__init__()
|
||||
self.modulename = modulename
|
||||
self.symbols = []
|
||||
|
||||
dt = None
|
||||
tstamp = os.environ.get("KBUILD_BUILD_TIMESTAMP")
|
||||
if tstamp:
|
||||
for fmt in self.date_formats:
|
||||
try:
|
||||
dt = datetime.strptime(tstamp, fmt)
|
||||
break
|
||||
except ValueError:
|
||||
pass
|
||||
|
||||
if not dt:
|
||||
dt = datetime.now()
|
||||
|
||||
self.man_date = dt.strftime("%B %Y")
|
||||
|
||||
def arg_name(self, args, name):
|
||||
"""
|
||||
Return the name that will be used for the man page.
|
||||
|
||||
As we may have the same name on different namespaces,
|
||||
prepend the data type for all types except functions and typedefs.
|
||||
|
||||
The doc section is special: it uses the modulename.
|
||||
"""
|
||||
|
||||
dtype = args.type
|
||||
|
||||
if dtype == "doc":
|
||||
return self.modulename
|
||||
|
||||
if dtype in ["function", "typedef"]:
|
||||
return name
|
||||
|
||||
return f"{dtype} {name}"
|
||||
|
||||
def set_symbols(self, symbols):
|
||||
"""
|
||||
Get a list of all symbols from kernel_doc.
|
||||
|
||||
Man pages will uses it to add a SEE ALSO section with other
|
||||
symbols at the same file.
|
||||
"""
|
||||
self.symbols = symbols
|
||||
|
||||
def out_tail(self, fname, name, args):
|
||||
"""Adds a tail for all man pages"""
|
||||
|
||||
# SEE ALSO section
|
||||
self.data += f'.SH "SEE ALSO"' + "\n.PP\n"
|
||||
self.data += (f"Kernel file \\fB{args.fname}\\fR\n")
|
||||
if len(self.symbols) >= 2:
|
||||
cur_name = self.arg_name(args, name)
|
||||
|
||||
related = []
|
||||
for arg in self.symbols:
|
||||
out_name = self.arg_name(arg, arg.name)
|
||||
|
||||
if cur_name == out_name:
|
||||
continue
|
||||
|
||||
related.append(f"\\fB{out_name}\\fR(9)")
|
||||
|
||||
self.data += ",\n".join(related) + "\n"
|
||||
|
||||
# TODO: does it make sense to add other sections? Maybe
|
||||
# REPORTING ISSUES? LICENSE?
|
||||
|
||||
def msg(self, fname, name, args):
|
||||
"""
|
||||
Handles a single entry from kernel-doc parser.
|
||||
|
||||
Add a tail at the end of man pages output.
|
||||
"""
|
||||
super().msg(fname, name, args)
|
||||
self.out_tail(fname, name, args)
|
||||
|
||||
return self.data
|
||||
|
||||
def output_highlight(self, block):
|
||||
"""
|
||||
Outputs a C symbol that may require being highlighted with
|
||||
self.highlights variable using troff syntax
|
||||
"""
|
||||
|
||||
contents = self.highlight_block(block)
|
||||
|
||||
if isinstance(contents, list):
|
||||
contents = "\n".join(contents)
|
||||
|
||||
for line in contents.strip("\n").split("\n"):
|
||||
line = KernRe(r"^\s*").sub("", line)
|
||||
if not line:
|
||||
continue
|
||||
|
||||
if line[0] == ".":
|
||||
self.data += "\\&" + line + "\n"
|
||||
else:
|
||||
self.data += line + "\n"
|
||||
|
||||
def out_doc(self, fname, name, args):
|
||||
if not self.check_doc(name, args):
|
||||
return
|
||||
|
||||
out_name = self.arg_name(args, name)
|
||||
|
||||
self.data += f'.TH "{self.modulename}" 9 "{out_name}" "{self.man_date}" "API Manual" LINUX' + "\n"
|
||||
|
||||
for section, text in args.sections.items():
|
||||
self.data += f'.SH "{section}"' + "\n"
|
||||
self.output_highlight(text)
|
||||
|
||||
def out_function(self, fname, name, args):
|
||||
"""output function in man"""
|
||||
|
||||
out_name = self.arg_name(args, name)
|
||||
|
||||
self.data += f'.TH "{name}" 9 "{out_name}" "{self.man_date}" "Kernel Hacker\'s Manual" LINUX' + "\n"
|
||||
|
||||
self.data += ".SH NAME\n"
|
||||
self.data += f"{name} \\- {args['purpose']}\n"
|
||||
|
||||
self.data += ".SH SYNOPSIS\n"
|
||||
if args.get('functiontype', ''):
|
||||
self.data += f'.B "{args["functiontype"]}" {name}' + "\n"
|
||||
else:
|
||||
self.data += f'.B "{name}' + "\n"
|
||||
|
||||
count = 0
|
||||
parenth = "("
|
||||
post = ","
|
||||
|
||||
for parameter in args.parameterlist:
|
||||
if count == len(args.parameterlist) - 1:
|
||||
post = ");"
|
||||
|
||||
dtype = args.parametertypes.get(parameter, "")
|
||||
if function_pointer.match(dtype):
|
||||
# Pointer-to-function
|
||||
self.data += f'".BI "{parenth}{function_pointer.group(1)}" " ") ({function_pointer.group(2)}){post}"' + "\n"
|
||||
else:
|
||||
dtype = KernRe(r'([^\*])$').sub(r'\1 ', dtype)
|
||||
|
||||
self.data += f'.BI "{parenth}{dtype}" "{post}"' + "\n"
|
||||
count += 1
|
||||
parenth = ""
|
||||
|
||||
if args.parameterlist:
|
||||
self.data += ".SH ARGUMENTS\n"
|
||||
|
||||
for parameter in args.parameterlist:
|
||||
parameter_name = re.sub(r'\[.*', '', parameter)
|
||||
|
||||
self.data += f'.IP "{parameter}" 12' + "\n"
|
||||
self.output_highlight(args.parameterdescs.get(parameter_name, ""))
|
||||
|
||||
for section, text in args.sections.items():
|
||||
self.data += f'.SH "{section.upper()}"' + "\n"
|
||||
self.output_highlight(text)
|
||||
|
||||
def out_enum(self, fname, name, args):
|
||||
out_name = self.arg_name(args, name)
|
||||
|
||||
self.data += f'.TH "{self.modulename}" 9 "{out_name}" "{self.man_date}" "API Manual" LINUX' + "\n"
|
||||
|
||||
self.data += ".SH NAME\n"
|
||||
self.data += f"enum {name} \\- {args['purpose']}\n"
|
||||
|
||||
self.data += ".SH SYNOPSIS\n"
|
||||
self.data += f"enum {name}" + " {\n"
|
||||
|
||||
count = 0
|
||||
for parameter in args.parameterlist:
|
||||
self.data += f'.br\n.BI " {parameter}"' + "\n"
|
||||
if count == len(args.parameterlist) - 1:
|
||||
self.data += "\n};\n"
|
||||
else:
|
||||
self.data += ", \n.br\n"
|
||||
|
||||
count += 1
|
||||
|
||||
self.data += ".SH Constants\n"
|
||||
|
||||
for parameter in args.parameterlist:
|
||||
parameter_name = KernRe(r'\[.*').sub('', parameter)
|
||||
self.data += f'.IP "{parameter}" 12' + "\n"
|
||||
self.output_highlight(args.parameterdescs.get(parameter_name, ""))
|
||||
|
||||
for section, text in args.sections.items():
|
||||
self.data += f'.SH "{section}"' + "\n"
|
||||
self.output_highlight(text)
|
||||
|
||||
def out_typedef(self, fname, name, args):
|
||||
module = self.modulename
|
||||
purpose = args.get('purpose')
|
||||
out_name = self.arg_name(args, name)
|
||||
|
||||
self.data += f'.TH "{module}" 9 "{out_name}" "{self.man_date}" "API Manual" LINUX' + "\n"
|
||||
|
||||
self.data += ".SH NAME\n"
|
||||
self.data += f"typedef {name} \\- {purpose}\n"
|
||||
|
||||
for section, text in args.sections.items():
|
||||
self.data += f'.SH "{section}"' + "\n"
|
||||
self.output_highlight(text)
|
||||
|
||||
def out_struct(self, fname, name, args):
|
||||
module = self.modulename
|
||||
purpose = args.get('purpose')
|
||||
definition = args.get('definition')
|
||||
out_name = self.arg_name(args, name)
|
||||
|
||||
self.data += f'.TH "{module}" 9 "{out_name}" "{self.man_date}" "API Manual" LINUX' + "\n"
|
||||
|
||||
self.data += ".SH NAME\n"
|
||||
self.data += f"{args.type} {name} \\- {purpose}\n"
|
||||
|
||||
# Replace tabs with two spaces and handle newlines
|
||||
declaration = definition.replace("\t", " ")
|
||||
declaration = KernRe(r"\n").sub('"\n.br\n.BI "', declaration)
|
||||
|
||||
self.data += ".SH SYNOPSIS\n"
|
||||
self.data += f"{args.type} {name} " + "{" + "\n.br\n"
|
||||
self.data += f'.BI "{declaration}\n' + "};\n.br\n\n"
|
||||
|
||||
self.data += ".SH Members\n"
|
||||
for parameter in args.parameterlist:
|
||||
if parameter.startswith("#"):
|
||||
continue
|
||||
|
||||
parameter_name = re.sub(r"\[.*", "", parameter)
|
||||
|
||||
if args.parameterdescs.get(parameter_name) == KernelDoc.undescribed:
|
||||
continue
|
||||
|
||||
self.data += f'.IP "{parameter}" 12' + "\n"
|
||||
self.output_highlight(args.parameterdescs.get(parameter_name))
|
||||
|
||||
for section, text in args.sections.items():
|
||||
self.data += f'.SH "{section}"' + "\n"
|
||||
self.output_highlight(text)
|
||||
File diff suppressed because it is too large.
Load diff
@@ -0,0 +1,270 @@
|
||||
#!/usr/bin/env python3
|
||||
# SPDX-License-Identifier: GPL-2.0
|
||||
# Copyright(c) 2025: Mauro Carvalho Chehab <[email protected]>.
|
||||
|
||||
"""
|
||||
Regular expression ancillary classes.
|
||||
|
||||
Those help caching regular expressions and do matching for kernel-doc.
|
||||
"""
|
||||
|
||||
import re
|
||||
|
||||
# Local cache for regular expressions
|
||||
re_cache = {}
|
||||
|
||||
|
||||
class KernRe:
|
||||
"""
|
||||
Helper class to simplify regex declaration and usage.
|
||||
|
||||
It calls re.compile for a given pattern. It also allows adding
|
||||
regular expressions and define sub at class init time.
|
||||
|
||||
Regular expressions can be cached via an argument, helping to speedup
|
||||
searches.
|
||||
"""
|
||||
|
||||
def _add_regex(self, string, flags):
|
||||
"""
|
||||
Adds a new regex or reuses it from the cache.
|
||||
"""
|
||||
self.regex = re_cache.get(string, None)
|
||||
if not self.regex:
|
||||
self.regex = re.compile(string, flags=flags)
|
||||
if self.cache:
|
||||
re_cache[string] = self.regex
|
||||
|
||||
def __init__(self, string, cache=True, flags=0):
|
||||
"""
|
||||
Compile a regular expression and initialize internal vars.
|
||||
"""
|
||||
|
||||
self.cache = cache
|
||||
self.last_match = None
|
||||
|
||||
self._add_regex(string, flags)
|
||||
|
||||
def __str__(self):
|
||||
"""
|
||||
Return the regular expression pattern.
|
||||
"""
|
||||
return self.regex.pattern
|
||||
|
||||
def __add__(self, other):
|
||||
"""
|
||||
Allows adding two regular expressions into one.
|
||||
"""
|
||||
|
||||
return KernRe(str(self) + str(other), cache=self.cache or other.cache,
|
||||
flags=self.regex.flags | other.regex.flags)
|
||||
|
||||
def match(self, string):
|
||||
"""
|
||||
Handles a re.match storing its results
|
||||
"""
|
||||
|
||||
self.last_match = self.regex.match(string)
|
||||
return self.last_match
|
||||
|
||||
def search(self, string):
|
||||
"""
|
||||
Handles a re.search storing its results
|
||||
"""
|
||||
|
||||
self.last_match = self.regex.search(string)
|
||||
return self.last_match
|
||||
|
||||
def findall(self, string):
|
||||
"""
|
||||
Alias to re.findall
|
||||
"""
|
||||
|
||||
return self.regex.findall(string)
|
||||
|
||||
def split(self, string):
|
||||
"""
|
||||
Alias to re.split
|
||||
"""
|
||||
|
||||
return self.regex.split(string)
|
||||
|
||||
def sub(self, sub, string, count=0):
|
||||
"""
|
||||
Alias to re.sub
|
||||
"""
|
||||
|
||||
return self.regex.sub(sub, string, count=count)
|
||||
|
||||
def group(self, num):
|
||||
"""
|
||||
Returns the group results of the last match
|
||||
"""
|
||||
|
||||
return self.last_match.group(num)
|
||||
|
||||
|
||||
class NestedMatch:
|
||||
"""
|
||||
Finding nested delimiters is hard with regular expressions. It is
|
||||
even harder on Python with its normal re module, as there are several
|
||||
advanced regular expressions that are missing.
|
||||
|
||||
This is the case of this pattern:
|
||||
|
||||
'\\bSTRUCT_GROUP(\\(((?:(?>[^)(]+)|(?1))*)\\))[^;]*;'
|
||||
|
||||
which is used to properly match open/close parentheses of the
|
||||
string search STRUCT_GROUP(),
|
||||
|
||||
Add a class that counts pairs of delimiters, using it to match and
|
||||
replace nested expressions.
|
||||
|
||||
The original approach was suggested by:
|
||||
https://stackoverflow.com/questions/5454322/python-how-to-match-nested-parentheses-with-regex
|
||||
|
||||
Although I re-implemented it to make it more generic and match 3 types
|
||||
of delimiters. The logic checks if delimiters are paired. If not, it
|
||||
will ignore the search string.
|
||||
"""
|
||||
|
||||
# TODO: make NestedMatch handle multiple match groups
|
||||
#
|
||||
# Right now, regular expressions to match it are defined only up to
|
||||
# the start delimiter, e.g.:
|
||||
#
|
||||
# \bSTRUCT_GROUP\(
|
||||
#
|
||||
# is similar to: STRUCT_GROUP\((.*)\)
|
||||
# except that the content inside the match group is delimiter-aligned.
|
||||
#
|
||||
# The content inside parentheses is converted into a single replace
|
||||
# group (e.g. r`\1').
|
||||
#
|
||||
# It would be nice to change such definition to support multiple
|
||||
# match groups, allowing a regex equivalent to:
|
||||
#
|
||||
# FOO\((.*), (.*), (.*)\)
|
||||
#
|
||||
# it is probably easier to define it not as a regular expression, but
|
||||
# with some lexical definition like:
|
||||
#
|
||||
# FOO(arg1, arg2, arg3)
|
||||
|
||||
DELIMITER_PAIRS = {
|
||||
'{': '}',
|
||||
'(': ')',
|
||||
'[': ']',
|
||||
}
|
||||
|
||||
RE_DELIM = re.compile(r'[\{\}\[\]\(\)]')
|
||||
|
||||
def _search(self, regex, line):
|
||||
"""
|
||||
Finds paired blocks for a regex that ends with a delimiter.
|
||||
|
||||
The suggestion of using finditer to match pairs came from:
|
||||
https://stackoverflow.com/questions/5454322/python-how-to-match-nested-parentheses-with-regex
|
||||
but I ended using a different implementation to align all three types
|
||||
of delimiters and seek for an initial regular expression.
|
||||
|
||||
The algorithm seeks for open/close paired delimiters and places them
|
||||
into a stack, yielding a start/stop position of each match when the
|
||||
stack is zeroed.
|
||||
|
||||
The algorithm should work fine for properly paired lines, but will
|
||||
silently ignore end delimiters that precede a start delimiter.
|
||||
This should be OK for kernel-doc parser, as unaligned delimiters
|
||||
would cause compilation errors. So, we don't need to raise exceptions
|
||||
to cover such issues.
|
||||
"""
|
||||
|
||||
stack = []
|
||||
|
||||
for match_re in regex.finditer(line):
|
||||
start = match_re.start()
|
||||
offset = match_re.end()
|
||||
|
||||
d = line[offset - 1]
|
||||
if d not in self.DELIMITER_PAIRS:
|
||||
continue
|
||||
|
||||
end = self.DELIMITER_PAIRS[d]
|
||||
stack.append(end)
|
||||
|
||||
for match in self.RE_DELIM.finditer(line[offset:]):
|
||||
pos = match.start() + offset
|
||||
|
||||
d = line[pos]
|
||||
|
||||
if d in self.DELIMITER_PAIRS:
|
||||
end = self.DELIMITER_PAIRS[d]
|
||||
|
||||
stack.append(end)
|
||||
continue
|
||||
|
||||
# Does the end delimiter match what is expected?
|
||||
if stack and d == stack[-1]:
|
||||
stack.pop()
|
||||
|
||||
if not stack:
|
||||
yield start, offset, pos + 1
|
||||
break
|
||||
|
||||
def search(self, regex, line):
|
||||
"""
|
||||
This is similar to re.search:
|
||||
|
||||
It matches a regex that it is followed by a delimiter,
|
||||
returning occurrences only if all delimiters are paired.
|
||||
"""
|
||||
|
||||
for t in self._search(regex, line):
|
||||
|
||||
yield line[t[0]:t[2]]
|
||||
|
||||
def sub(self, regex, sub, line, count=0):
|
||||
"""
|
||||
This is similar to re.sub:
|
||||
|
||||
It matches a regex that it is followed by a delimiter,
|
||||
replacing occurrences only if all delimiters are paired.
|
||||
|
||||
if r'\1' is used, it works just like re: it places there the
|
||||
matched paired data with the delimiter stripped.
|
||||
|
||||
If count is different than zero, it will replace at most count
|
||||
items.
|
||||
"""
|
||||
out = ""
|
||||
|
||||
cur_pos = 0
|
||||
n = 0
|
||||
|
||||
for start, end, pos in self._search(regex, line):
|
||||
out += line[cur_pos:start]
|
||||
|
||||
# Value, ignoring start/end delimiters
|
||||
value = line[end:pos - 1]
|
||||
|
||||
# replaces \1 at the sub string, if \1 is used there
|
||||
new_sub = sub
|
||||
new_sub = new_sub.replace(r'\1', value)
|
||||
|
||||
out += new_sub
|
||||
|
||||
# Drop end ';' if any
|
||||
if line[pos] == ';':
|
||||
pos += 1
|
||||
|
||||
cur_pos = pos
|
||||
n += 1
|
||||
|
||||
if count and count >= n:
|
||||
break
|
||||
|
||||
# Append the remaining string
|
||||
l = len(line)
|
||||
out += line[cur_pos:l]
|
||||
|
||||
return out
|
||||
Reference in new issue
Block a user