add some code
This commit is contained in:
770
managed_components/lvgl__lvgl/docs/api_doc_builder.py
Normal file
770
managed_components/lvgl__lvgl/docs/api_doc_builder.py
Normal file
@@ -0,0 +1,770 @@
|
||||
"""api_doc_builder.py
|
||||
|
||||
Create and provide links to API pages in LVGL doc build.
|
||||
|
||||
Uses doxygen_xml.py module to:
|
||||
|
||||
- Prep and run Doxygen
|
||||
- make Doxygen XML output available, and
|
||||
- make Doxygen-documented symbols from the C code available.
|
||||
"""
|
||||
import os
|
||||
import re
|
||||
import doxygen_xml
|
||||
from announce import *
|
||||
|
||||
old_html_files = {}
|
||||
EMIT_WARNINGS = True
|
||||
rst_section_line_char = '='
|
||||
|
||||
# Multi-line match ``API + newline + \*\*\* + whitespace``.
|
||||
# NB: the ``\s*`` at the end forces the regex to match the whitespace
|
||||
# at the end including all \r\n's. This will match UP TO:
|
||||
# - the next non-blank character (could be many blank lines), or
|
||||
# - to the end of the file, whichever comes first.
|
||||
_re_api_section_sep = re.compile(r'(?mi)^API *\r?\n^\*\*\*\s*')
|
||||
|
||||
# Regex to identify '.. API equals: lv_obj_t, lv_array_t' directives.
|
||||
_re_api_equals = re.compile(r'(?mi)^\s*\.\.\s+API\s+equals:\s*([\w,\s]+)\r\n\s*')
|
||||
|
||||
# Regex to identify '.. API startswith: lv_obj, lv_array' directives.
|
||||
_re_api_startswith = re.compile(r'(?mi)^\s*\.\.\s+API\s+startswith:\s*([\w,\s]+)\r\n\s*')
|
||||
|
||||
# Regex to match comma and whitespace list-item separators on multiple lines.
|
||||
_re_multi_line_comma_sep = re.compile(r'(?m)[,\s]+')
|
||||
|
||||
# Regex to identify editor-added hyperlinks: :ref:`lv_obj_h`
|
||||
_re_editor_added_hyperlink = re.compile(r'^\s*:ref:`(\w+)`')
|
||||
|
||||
# Separator to mark place where this script added hyperlinks.
|
||||
_auto_gen_sep = '.. Autogenerated'
|
||||
|
||||
# List of symbol dictionaries
|
||||
_defines = {}
|
||||
_enums = {}
|
||||
_variables = {}
|
||||
_namespaces = {}
|
||||
_structs = {}
|
||||
_unions = {}
|
||||
_typedefs = {}
|
||||
_functions = {}
|
||||
|
||||
_symbol_dict_list = [
|
||||
_defines,
|
||||
_enums,
|
||||
_variables,
|
||||
_namespaces,
|
||||
_structs,
|
||||
_unions,
|
||||
_typedefs,
|
||||
_functions
|
||||
]
|
||||
|
||||
|
||||
def old_clean_name(nme):
|
||||
"""Strip beginning "_lv" and ending "_t"."""
|
||||
# Handle error:
|
||||
# AttributeError: 'NoneType' object has no attribute 'startswith'
|
||||
if nme is None:
|
||||
return nme
|
||||
|
||||
if nme.startswith('_lv_'):
|
||||
nme = nme[4:]
|
||||
elif nme.startswith('lv_'):
|
||||
nme = nme[3:]
|
||||
|
||||
if nme.endswith('_t'):
|
||||
nme = nme[:-2]
|
||||
|
||||
return nme
|
||||
|
||||
|
||||
# Definitions:
|
||||
# - "section" => The name "abc_def" has 2 sections.
|
||||
# - N = number of sections in `item_name`.
|
||||
# After removing leading '_lv_', 'lv_' and trailing '_t' from `obj_name`,
|
||||
# do the remaining first N "sections" of `obj_name` match `item_name`
|
||||
# (case sensitive)?
|
||||
def old_is_name_match(item_name, obj_name):
|
||||
# Handle error:
|
||||
# AttributeError: 'NoneType' object has no attribute 'split'
|
||||
if obj_name is None:
|
||||
return False
|
||||
|
||||
sect_count = item_name.count('_') + 1
|
||||
|
||||
obj_name = obj_name.split('_')
|
||||
|
||||
# Reject (False) if `obj_name` doesn't have as many sections as `item_name`.
|
||||
if len(obj_name) < sect_count:
|
||||
return False
|
||||
|
||||
obj_name = '_'.join(obj_name[:sect_count])
|
||||
|
||||
return item_name == obj_name
|
||||
|
||||
|
||||
def old_get_includes(name1, name2, obj, includes):
|
||||
name2 = old_clean_name(name2)
|
||||
|
||||
if not old_is_name_match(name1, name2):
|
||||
return
|
||||
|
||||
if obj.parent is not None and hasattr(obj.parent, 'header_file'):
|
||||
header_file = obj.parent.header_file
|
||||
elif hasattr(obj, 'header_file'):
|
||||
header_file = obj.header_file
|
||||
else:
|
||||
return
|
||||
|
||||
if not header_file:
|
||||
return
|
||||
|
||||
if header_file not in old_html_files:
|
||||
return
|
||||
|
||||
includes.add((header_file, old_html_files[header_file]))
|
||||
|
||||
|
||||
def _conditionally_add_hyperlink(obj, genned_link_set: set, exclude_set: set):
|
||||
"""
|
||||
Add hyperlink names to `link_set` if:
|
||||
- not in `exclude_set`, and
|
||||
- not already in `link_set`.
|
||||
:param obj: "thing" from dictionary with matching symbol.
|
||||
These are objects instantiated from classes
|
||||
in `doxygen_xml` module such as
|
||||
STRUCT, FUNCTION, DEFINE, ENUMVALUE, etc.
|
||||
:param genned_link_set: Set in which to accumulate link names
|
||||
:param exclude_set: Set with link names not to add to `link_set`
|
||||
:return:
|
||||
"""
|
||||
if obj.file_name is not None:
|
||||
link_name = os.path.basename(obj.file_name).replace('.', '_')
|
||||
if link_name not in genned_link_set:
|
||||
if link_name not in exclude_set:
|
||||
genned_link_set.add(link_name)
|
||||
|
||||
|
||||
def _add_startswith_matches(strings: [str], genned_link_set, editor_link_set):
|
||||
"""
|
||||
Add set of hyperlinks to `genned_link_set` that are not already in
|
||||
`editor_link_set`, for C symbols that start with strings in `strings`.
|
||||
|
||||
:param strings: List of strings to match against
|
||||
:param genned_link_set: Generated link set
|
||||
:param editor_link_set: Hyperlinks added by editor
|
||||
:return: n/a
|
||||
"""
|
||||
for partial_symbol in strings:
|
||||
for symbol_dict in _symbol_dict_list:
|
||||
for key in symbol_dict:
|
||||
if key is None:
|
||||
# Dictionary `enums` has a key `None` which contains
|
||||
# all enum values from all unnamed enums, and each
|
||||
# enum value has a `file_name` field.
|
||||
enum_values_list = symbol_dict[None].members
|
||||
for enum_val in enum_values_list:
|
||||
if enum_val.name.startswith(partial_symbol):
|
||||
_conditionally_add_hyperlink(enum_val, genned_link_set, editor_link_set)
|
||||
else:
|
||||
if key.startswith(partial_symbol):
|
||||
obj = symbol_dict[key]
|
||||
_conditionally_add_hyperlink(obj, genned_link_set, editor_link_set)
|
||||
|
||||
|
||||
def _add_exact_matches(symbols: [str], genned_link_set, editor_link_set):
|
||||
"""
|
||||
Add set of hyperlinks to `genned_link_set` that are not already in
|
||||
`editor_link_set`, for exact C symbol matches.
|
||||
|
||||
:param symbols: List of C symbols to match against
|
||||
:param genned_link_set: Generated link set
|
||||
:param editor_link_set: Hyperlinks added by editor
|
||||
:return: n/a
|
||||
"""
|
||||
for symbol in symbols:
|
||||
for symbol_dict in _symbol_dict_list:
|
||||
if symbol in symbol_dict:
|
||||
obj = symbol_dict[symbol]
|
||||
_conditionally_add_hyperlink(obj, genned_link_set, editor_link_set)
|
||||
|
||||
|
||||
def _hyperlink_sort_value(init_value: str):
|
||||
if init_value.endswith('_h'):
|
||||
result = init_value[:-2]
|
||||
else:
|
||||
result = init_value
|
||||
|
||||
return result
|
||||
|
||||
|
||||
def _process_end_of_eligible_doc(b: str, rst_file: str) -> (str, str, int):
|
||||
"""
|
||||
Edit end section after API section heading.
|
||||
|
||||
3. Initialize:
|
||||
- links_added_count = 0
|
||||
- editor_link_set = set()
|
||||
- genned_link_set = set()
|
||||
- C = '' # string for generated hyperlinks
|
||||
4. Remove `_auto_gen_sep` and everything after it:
|
||||
- new_B = B.split(_auto_gen_sep, 1)[0]
|
||||
5. With `new_B, add any editor-added hyperlinks to set:
|
||||
`editor_link_set`.
|
||||
6. If `_re_api_equals` match present:
|
||||
- build list of symbols
|
||||
- compute list of hyperlinks from symbols
|
||||
- add hyperlinks to `genned_link_set` if not in `editor_link_set`.
|
||||
7. If `_re_api_startswith` match present:
|
||||
- build list of symbols
|
||||
- compute list of hyperlinks from symbols
|
||||
- add hyperlinks to `genned_link_set` if not in `editor_link_set`.
|
||||
8. Lacking either of the above custom directives, use the lower-case
|
||||
stem of the filename and prefix it with "lv_" and try an
|
||||
"API startswith" search with it.
|
||||
9. If len(genned_link_set) > 0:
|
||||
- `C` = _auto_gen_sep + '\n\n' + `genned_link_set`
|
||||
(with a blank line between each).
|
||||
10. Return tuple: (new_B, C, links_added_count).
|
||||
|
||||
:param b: End of document after API section heading + whitespace.
|
||||
:return: Tuple: (new_B, C, links_added_count)
|
||||
"""
|
||||
# 3. Initialize:
|
||||
editor_link_set = set()
|
||||
genned_link_set = set()
|
||||
c = ''
|
||||
api_directives_found_count = 0
|
||||
|
||||
# 4. Remove `_auto_gen_sep` and everything after it:
|
||||
new_b = b.split(_auto_gen_sep, 1)[0]
|
||||
|
||||
# 5. With `new_B, add any editor-added hyperlinks to set:
|
||||
# `editor_link_set`.
|
||||
for line in new_b.splitlines():
|
||||
match = _re_editor_added_hyperlink.match(line)
|
||||
if match is not None:
|
||||
editor_link_set.add(match[1])
|
||||
|
||||
# 6. If `_re_api_equals` present:
|
||||
# - build list of symbols
|
||||
# - compute list of hyperlinks from symbols
|
||||
# - add hyperlinks to `genned_link_set` if not in `editor_link_set`.
|
||||
match = _re_api_equals.search(new_b)
|
||||
if match is not None:
|
||||
api_directives_found_count += 1
|
||||
comma_sep_list = match[1].strip()
|
||||
symbols = _re_multi_line_comma_sep.split(comma_sep_list)
|
||||
_add_exact_matches(symbols, genned_link_set, editor_link_set)
|
||||
|
||||
# 7. If `_re_api_startswith` present:
|
||||
# - build list of symbols
|
||||
# - compute list of hyperlinks from symbols
|
||||
# - add hyperlinks to `genned_link_set` if not in `editor_link_set`.
|
||||
match = _re_api_startswith.search(new_b)
|
||||
if match is not None:
|
||||
api_directives_found_count += 1
|
||||
comma_sep_list = match[1].strip()
|
||||
symbols = _re_multi_line_comma_sep.split(comma_sep_list)
|
||||
_add_startswith_matches(symbols, genned_link_set, editor_link_set)
|
||||
|
||||
# 8. Lacking either of the above custom directives, use the lower-case
|
||||
# stem of the filename and prefix it with "lv_" and try an
|
||||
# "API startswith" with it.
|
||||
if api_directives_found_count == 0:
|
||||
base = os.path.basename(rst_file)
|
||||
stem = os.path.splitext(base)[0]
|
||||
_add_startswith_matches(['lv_' + stem], genned_link_set, editor_link_set)
|
||||
|
||||
# 9. If len(genned_link_set) > 0:
|
||||
# - `C` = _auto_gen_sep + '\n\n' + `genned_link_set`
|
||||
# (with a blank line between each).
|
||||
links_added_count = len(genned_link_set)
|
||||
|
||||
if links_added_count > 0:
|
||||
c = _auto_gen_sep + '\n\n'
|
||||
for link_name in sorted(genned_link_set, key=_hyperlink_sort_value):
|
||||
c += ':ref:`' + link_name + '`\n\n'
|
||||
|
||||
# 10. Return tuple: (new_B, C, links_added_count).
|
||||
return new_b, c, links_added_count
|
||||
|
||||
|
||||
def _process_one_file(rst_file: str):
|
||||
"""
|
||||
Add applicable API hyperlinks to one file.
|
||||
|
||||
Eligible
|
||||
An `.rst` file is eligible if it contains an API section at
|
||||
its end. This can happen also in `index.rst` files when
|
||||
they head a single subject for which an API section is
|
||||
appropriate there and not in the sub-docs. So `index.rst`
|
||||
files are included, whereas they were not included previously.
|
||||
|
||||
Algorithm:
|
||||
----------
|
||||
A. Doc editors may have already added a set of hyperlinks of their
|
||||
own. This routine takes note of and does not duplicate what is
|
||||
already there.
|
||||
|
||||
B. Doc editors may also have added specifications for this routine
|
||||
that look like this:
|
||||
|
||||
.. API equals: lv_obj_t, lv_arc_t, lv_barcode_t,
|
||||
lv_win_t, lv_list_t,
|
||||
lv_button_t
|
||||
|
||||
.. API startswith: lv_obj, lv_arc, lv_barcode,
|
||||
lv_win, lv_list,
|
||||
lv_button
|
||||
|
||||
as directives for this routine to build a set of applicable
|
||||
hyperlinks.
|
||||
|
||||
C. Lacking any of the above custom directives, use the lower-case
|
||||
stem of the filename and prefix it with "lv_" and try an
|
||||
"API startswith" search with it.
|
||||
|
||||
Any hyperlinks added by this routine are prefixed with the
|
||||
reStructuredText comment defined by the `_auto_gen_sep`
|
||||
variable, normally:
|
||||
|
||||
.. Autogenerated
|
||||
|
||||
If `rst_file` is eligible, edit after API section heading such that:
|
||||
- any editor-added hyperlinks are retained at the top of the list;
|
||||
- `_auto_gen_sep` (looked for in case a source file ends up having it;
|
||||
anything after it is replaced);
|
||||
- applicable hyperlinks added such that they do not repeat those
|
||||
added by editors of `.rst` file.
|
||||
|
||||
Steps to Implement:
|
||||
-------------------
|
||||
0. links_added_count = 0
|
||||
1. Determine if eligible.
|
||||
- If not, skip to step 12.
|
||||
- If so, continue.
|
||||
2. Split doc into 2 parts:
|
||||
A. beginning through API section heading and subsequent
|
||||
whitespace including subsequent blank lines;
|
||||
B. anything after that which may include editor-added hyperlinks.
|
||||
3-10. new_B, C, links_added_count = _process_end_of_eligible_doc()
|
||||
11. Write `A` + `new_B` + `C` back to `rst_file`.
|
||||
12. Return links_added_count.
|
||||
|
||||
:param rst_file: Full path to `.rst` file in question.
|
||||
It may or may not be eligible.
|
||||
:return: Number of links added.
|
||||
"""
|
||||
links_added_count = 0
|
||||
|
||||
with open(rst_file, 'rb') as f:
|
||||
try:
|
||||
rst_contents = f.read().decode('utf-8')
|
||||
except UnicodeDecodeError:
|
||||
announce(__file__, f'Error: UnicodeDecodeError in [{rst_file}].')
|
||||
raise
|
||||
|
||||
eligible_match = _re_api_section_sep.search(rst_contents)
|
||||
|
||||
if eligible_match is not None:
|
||||
# Eligible (API section found).
|
||||
i = eligible_match.end()
|
||||
# Split just after the API section heading + whitespace.
|
||||
a = rst_contents[:i]
|
||||
b = rst_contents[i:]
|
||||
new_b, c, links_added_count = _process_end_of_eligible_doc(b, rst_file)
|
||||
|
||||
if links_added_count > 0:
|
||||
rst_contents = a + new_b + c
|
||||
|
||||
with open(rst_file, 'wb') as f:
|
||||
f.write(rst_contents.encode('utf-8'))
|
||||
|
||||
return links_added_count
|
||||
|
||||
|
||||
def _build_one_local_dictionary(local_dict, remote_dict):
|
||||
"""
|
||||
Remove '_' prefix in symbols beginning with '_lv' to make
|
||||
symbols like `lv_obj_t` actually connect with the struct
|
||||
in `lv_obj_private.h`, and not the typedef in `lv_types.h`.
|
||||
|
||||
:param local_dict: Local (adjusted) symbol dictionary
|
||||
:param remote_dict: Dictionary from `doxygen_xml` module
|
||||
:return: n/a
|
||||
"""
|
||||
for symbol in remote_dict:
|
||||
# Note: symbol `None` is actually a valid symbol in the
|
||||
# `enums` dictionary, containing all enum-value symbols
|
||||
# for enums without names.
|
||||
if symbol is None or not symbol.startswith('_lv'):
|
||||
loc_symbol = symbol
|
||||
else:
|
||||
# Remove '_' prefix.
|
||||
loc_symbol = symbol[1:]
|
||||
|
||||
local_dict[loc_symbol] = remote_dict[symbol]
|
||||
|
||||
|
||||
def _build_local_symbol_dictionaries():
|
||||
"""
|
||||
Build "work-around" dictionaries so that a symbol like `lv_obj_t`
|
||||
actually connects with the struct in `lv_obj_private.h`, and not
|
||||
the typedef in `lv_types.h`.
|
||||
|
||||
:return: n/a
|
||||
"""
|
||||
_build_one_local_dictionary(_defines, doxygen_xml.defines)
|
||||
_build_one_local_dictionary(_enums, doxygen_xml.enums)
|
||||
_build_one_local_dictionary(_variables, doxygen_xml.variables)
|
||||
_build_one_local_dictionary(_namespaces, doxygen_xml.namespaces)
|
||||
_build_one_local_dictionary(_structs, doxygen_xml.structures)
|
||||
_build_one_local_dictionary(_unions, doxygen_xml.unions)
|
||||
_build_one_local_dictionary(_typedefs, doxygen_xml.typedefs)
|
||||
_build_one_local_dictionary(_functions, doxygen_xml.functions)
|
||||
|
||||
|
||||
def _add_hyperlinks_to_eligible_files(intermediate_dir: str,
|
||||
new_algorithm: bool,
|
||||
*doc_rel_paths: [str]):
|
||||
"""
|
||||
Add applicable hyperlinks to eligible docs found joining
|
||||
`intermediate_dir` with each relative path in `doc_rel_paths`.
|
||||
|
||||
See API-link algorithm documented under `_process_one_file()`.
|
||||
|
||||
:param intermediate_dir: Top directory where hyperlinks are to be added.
|
||||
:param doc_rel_paths: Tuple of relative paths from `intermediate_dir` to
|
||||
walk to find docs eligible for API hyperlinks.
|
||||
:return:
|
||||
"""
|
||||
if new_algorithm:
|
||||
# Populate local symbol dictionary set with
|
||||
# symbols WITHOUT any '_' prefixes.
|
||||
_build_local_symbol_dictionaries()
|
||||
|
||||
# Build `.rst` file list.
|
||||
file_list = []
|
||||
|
||||
for rel_path in doc_rel_paths:
|
||||
top_dir = os.path.join(intermediate_dir, rel_path)
|
||||
for dir_bep, sub_dirs, files in os.walk(top_dir, topdown=False):
|
||||
for file in files:
|
||||
if file.lower().endswith('.rst'):
|
||||
file_list.append(os.path.join(dir_bep, file))
|
||||
|
||||
total_eligible_doc_count = 0
|
||||
total_links_added_count = 0
|
||||
|
||||
# For each `.rst` file, add appropriate API hyperlinks.
|
||||
for rst_file in file_list:
|
||||
links_added_count = _process_one_file(rst_file)
|
||||
|
||||
if links_added_count > 0:
|
||||
total_links_added_count += links_added_count
|
||||
total_eligible_doc_count += 1
|
||||
# announce(__file__, f'Eligible doc: [{rst_file}].')
|
||||
|
||||
announce(__file__, f'Docs eligible for API hyperlinks: {total_eligible_doc_count:>4}')
|
||||
announce(__file__, f'API hyperlinks added : {total_links_added_count:>4}')
|
||||
else:
|
||||
for folder in doc_rel_paths:
|
||||
# Fetch a list of '.rst' files excluding 'index.rst'.
|
||||
rst_files = list(
|
||||
(os.path.splitext(item)[0], os.path.join(folder, item))
|
||||
for item in os.listdir(folder)
|
||||
if item.endswith('.rst') and 'index.rst' not in item
|
||||
)
|
||||
|
||||
# For each .RST file in that directory...
|
||||
for stem, path in rst_files:
|
||||
# Start with an empty set.
|
||||
html_includes = set()
|
||||
|
||||
# Build `html_includes` set as a list of tuples containing
|
||||
# (name, html_file). Example: "draw.rst" has `stem` == 'draw',
|
||||
# and generates a list of tuples from .H files where matching
|
||||
# C-code-element names were found. Example:
|
||||
# {('lv_draw_line', 'draw\\lv_draw_line.html'),
|
||||
# ('lv_draw_sdl', 'draw\\sdl\\lv_draw_sdl.html'),
|
||||
# ('lv_draw_sw_blend_to_i1', 'draw\\sw\\blend\\lv_draw_sw_blend_to_i1.html'),
|
||||
# etc.}
|
||||
for symbol_dict in (
|
||||
doxygen_xml.defines,
|
||||
doxygen_xml.enums,
|
||||
doxygen_xml.variables,
|
||||
doxygen_xml.namespaces,
|
||||
doxygen_xml.structures,
|
||||
doxygen_xml.unions,
|
||||
doxygen_xml.typedefs,
|
||||
doxygen_xml.functions
|
||||
):
|
||||
for key, obj in symbol_dict.items():
|
||||
old_get_includes(stem, key, obj, html_includes)
|
||||
|
||||
if html_includes:
|
||||
# Convert `html_includes` set to a list of strings containing the
|
||||
# Sphinx hyperlink syntax "link references". Example from above:
|
||||
# [':ref:`lv_draw_line_h`\n',
|
||||
# ':ref:`lv_draw_sdl_h`\n',
|
||||
# ':ref:`lv_draw_sw_blend_to_i1_h`\n',
|
||||
# etc.]
|
||||
html_includes = list(
|
||||
':ref:`{0}_h`\n'.format(inc)
|
||||
for inc, _ in html_includes
|
||||
)
|
||||
|
||||
# Convert that list to a single string of Sphinx hyperlink
|
||||
# references with blank lines between them.
|
||||
# :ref:`lv_draw_line_h`
|
||||
#
|
||||
# :ref:`lv_draw_sdl_h`
|
||||
#
|
||||
# :ref:`lv_draw_sw_blend_to_i1_h`
|
||||
#
|
||||
# etc.
|
||||
output = ('\n'.join(html_includes)) + '\n'
|
||||
|
||||
# Append that string to the source .RST file being processed.
|
||||
with open(path, 'rb') as f:
|
||||
try:
|
||||
data = f.read().decode('utf-8')
|
||||
except UnicodeDecodeError:
|
||||
print(path)
|
||||
raise
|
||||
|
||||
data = data.split(_auto_gen_sep, 1)[0]
|
||||
|
||||
data += f'{_auto_gen_sep}\n\n'
|
||||
data += output
|
||||
|
||||
with open(path, 'wb') as f:
|
||||
f.write(data.encode('utf-8'))
|
||||
|
||||
|
||||
def _create_rst_files_for_dir(src_root_dir_len: int,
|
||||
src_dir_bep: str,
|
||||
elig_h_files: [str],
|
||||
elig_sub_dirs: [str],
|
||||
out_root_dir: str):
|
||||
"""
|
||||
- Create `index.rst` file and add its top section.
|
||||
- For each file in `elig_h_files`:
|
||||
- Create one `.rst` file.
|
||||
- Add reference to it in `index.rst`.
|
||||
- For each subdir in `elig_sub_dirs`:
|
||||
- add reference "sub_dir_name/index" in `index.rst`.
|
||||
|
||||
:param src_root_dir_len: Length of source-root path string, used with `out_root_dir` to build paths
|
||||
:param src_dir_bep: Directory currently *being processed*
|
||||
:param elig_h_files: Eligible `.h` files directly contained in `src_dir_bep`
|
||||
:param elig_sub_dirs: List of sub-dirs that contained eligible `.h` files
|
||||
:param out_root_dir: Root of output directory, used with to build paths.
|
||||
:return: n/a
|
||||
"""
|
||||
indent = ' '
|
||||
sub_path = src_dir_bep[src_root_dir_len:]
|
||||
out_dir = str(os.path.join(out_root_dir, sub_path))
|
||||
|
||||
# Ensure dir exists. Multiple dirs MAY have to be created
|
||||
# since `.rst` files are created in bottom-up sequence.
|
||||
if not os.path.isdir(out_dir):
|
||||
os.makedirs(out_dir)
|
||||
|
||||
# For top-level directory only... (the last index.rst created,
|
||||
# since they are created in bottom-up sequence)
|
||||
if len(sub_path) == 0 and out_dir.endswith(os.sep):
|
||||
# Trim trailing slash from `out_dir`.
|
||||
out_dir = out_dir[:-1]
|
||||
|
||||
# index.rst
|
||||
with open(os.path.join(out_dir, 'index.rst'), 'w') as f:
|
||||
subdir_stem = os.path.split(out_dir)[-1]
|
||||
section_line = (rst_section_line_char * len(subdir_stem)) + '\n'
|
||||
f.write(section_line)
|
||||
f.write(subdir_stem + '\n')
|
||||
f.write(section_line)
|
||||
f.write('\n')
|
||||
f.write('.. toctree::\n :maxdepth: 1\n\n')
|
||||
|
||||
# One entry per `.rst` file
|
||||
for h_file in elig_h_files:
|
||||
filename = os.path.basename(h_file)
|
||||
stem = os.path.splitext(filename)[0]
|
||||
f.write(indent + stem + '\n')
|
||||
|
||||
# One entry per eligible subdirectory.
|
||||
for sub_dir in elig_sub_dirs:
|
||||
stem = os.path.split(sub_dir)[-1]
|
||||
f.write(indent + stem + '/index\n')
|
||||
|
||||
# One .rst file per h_file
|
||||
for h_file in elig_h_files:
|
||||
filename = os.path.basename(h_file)
|
||||
stem = os.path.splitext(filename)[0]
|
||||
rst_file = os.path.join(out_dir, stem + '.rst')
|
||||
html_file = os.path.join(sub_path, stem + '.html')
|
||||
old_html_files[stem] = html_file
|
||||
|
||||
with open(rst_file, 'w') as f:
|
||||
# Sphinx link target.
|
||||
f.write(f'.. _{stem}_h:\n\n')
|
||||
# Doc title.
|
||||
section_line = (rst_section_line_char * len(filename)) + '\n'
|
||||
f.write(section_line)
|
||||
f.write(filename + '\n')
|
||||
f.write(section_line)
|
||||
f.write('\n')
|
||||
# Content for `breathe`.
|
||||
f.write(f'.. doxygenfile:: {filename}\n')
|
||||
f.write(' :project: lvgl\n\n')
|
||||
|
||||
|
||||
def _recursively_create_api_rst_files(depth: int,
|
||||
src_root_len: int,
|
||||
src_dir_bep: str,
|
||||
out_root_dir: str) -> int:
|
||||
"""
|
||||
Create `.rst` files for the eligible `.h` found in `src_dir_bep` and
|
||||
recursively for subdirectories below it. ("bep" = being processed.)
|
||||
|
||||
Eligible
|
||||
An `.h` file is eligible if Doxygen generated documentation for it.
|
||||
The `EXCLUDE_PATTERNS` Doxygen configuration value can cause
|
||||
Doxygen to skip certain files and directories, in which case,
|
||||
the `.h` files skipped ARE NOT eligible.
|
||||
|
||||
Whether a subdirectory is eligible to be included in an `index.rst`
|
||||
file depends upon whether any eligible `.h` files were recursively
|
||||
found within it. And that isn't known until this function finishes
|
||||
(recursively) processing a directory and returns the number of
|
||||
eligible `.h` files found. Thus, the steps taken within are:
|
||||
|
||||
- Discover all eligible `.h` files directly contained in `src_dir_bep`.
|
||||
- Recursively do the same for each subdirectory, adding the returned
|
||||
count of eligible `.h` files to the sum (`elig_h_file_count`).
|
||||
- If `elig_h_file_count > 0`:
|
||||
- call _create_rst_files_for_dir() to generate appropriate
|
||||
`.rst` files for this directory.
|
||||
- Return `elig_h_file_count`.
|
||||
|
||||
Once we have accumulated this information, then we can generate
|
||||
all the `.rst` files for the current directory without any further
|
||||
directory-tree walking.
|
||||
|
||||
:param depth: Only used for testing/debugging
|
||||
:param src_root_len: Length of source-root path
|
||||
:param src_dir_bep: Source directory *being processed*
|
||||
:param out_root_dir: Output root directory (used to build output paths)
|
||||
:return: Number of `.h` files encountered (so caller knows
|
||||
whether that directory recursively held any
|
||||
eligible `.h` files, to know whether to include
|
||||
"subdir/index" in caller's local `index.rst` file).
|
||||
"""
|
||||
elig_h_files = []
|
||||
sub_dirs = []
|
||||
elig_sub_dirs = []
|
||||
elig_h_file_count = 0
|
||||
|
||||
# For each "thing" found in `src_dir_bep`, build lists:
|
||||
# `elig_sub_dirs` and `elig_h_files`.
|
||||
# By design change, we are including files with 'private'
|
||||
# in their names. Reason: advanced users who need to use
|
||||
# the structs defined within will need the documentation
|
||||
# in those API pages!
|
||||
for dir_item in os.listdir(src_dir_bep):
|
||||
path_bep = os.path.join(src_dir_bep, dir_item)
|
||||
if os.path.isdir(path_bep):
|
||||
sub_dirs.append(path_bep) # Add to sub-dir list.
|
||||
else:
|
||||
if dir_item.lower().endswith('.h'):
|
||||
eligible = (dir_item in doxygen_xml.files)
|
||||
if eligible:
|
||||
elig_h_files.append(path_bep) # Add to .H file list.
|
||||
elig_h_file_count += 1
|
||||
|
||||
# For each subdir...
|
||||
for sub_dir in sub_dirs:
|
||||
subdir_eligible_h_file_count = \
|
||||
_recursively_create_api_rst_files(depth + 1,
|
||||
src_root_len,
|
||||
sub_dir,
|
||||
out_root_dir)
|
||||
|
||||
if subdir_eligible_h_file_count > 0:
|
||||
elig_sub_dirs.append(sub_dir)
|
||||
elig_h_file_count += subdir_eligible_h_file_count
|
||||
|
||||
if elig_h_file_count > 0:
|
||||
# Create index.rst plus .RST files for any direct .H files in dir.
|
||||
_create_rst_files_for_dir(src_root_len,
|
||||
src_dir_bep,
|
||||
elig_h_files,
|
||||
elig_sub_dirs,
|
||||
out_root_dir)
|
||||
|
||||
return elig_h_file_count
|
||||
|
||||
|
||||
def create_api_rst_files(src_root_dir: str, out_root_dir: str):
|
||||
"""
|
||||
Create `.rst` files for API pages based on the `.h` files found
|
||||
in a tree-walk of `a_src_root` and the current contents of the
|
||||
`doxygen_xml.files` dictionary (used to filter out `.h` files that
|
||||
Doxygen generated no documentation for). Output the `.rst` files
|
||||
into `out_root_dir` mirroring the `a_src_root` directory structure.
|
||||
|
||||
:param src_root_dir: root source directory to walk
|
||||
:param out_root_dir: output directory
|
||||
:return: n/a
|
||||
"""
|
||||
src_root_len = len(src_root_dir) + 1
|
||||
_recursively_create_api_rst_files(0, src_root_len, src_root_dir, out_root_dir)
|
||||
|
||||
|
||||
def build_api_docs(lvgl_src_dir, intermediate_dir, doxyfile_src_file, *doc_rel_paths):
|
||||
"""
|
||||
- Prep and run Doxygen, outputting XML.
|
||||
- Load that XML in a form that can quickly tie C symbols to the
|
||||
source files they came from.
|
||||
- Generate API page `.rst` files for source files Doxygen generated
|
||||
documentation for.
|
||||
- Add hyperlinks to these API pages for `.rst` files in `*doc_rel_paths`
|
||||
that are eligible.
|
||||
|
||||
:param lvgl_src_dir: Path to LVGL src directory
|
||||
:param intermediate_dir: Path to intermediate dir being built
|
||||
:param doxyfile_src_file: Full path to src doxygen configuration file
|
||||
:param doc_rel_paths: List of relative paths from `intermediate_dir` to
|
||||
walk to find docs eligible for API hyperlinks.
|
||||
"""
|
||||
# ---------------------------------------------------------------------
|
||||
# - Generate Doxyfile replacing tokens,
|
||||
# - run Doxygen generating XML, and
|
||||
# - load the generated XML from Doxygen output.
|
||||
# ---------------------------------------------------------------------
|
||||
doxygen_xml.EMIT_WARNINGS = EMIT_WARNINGS
|
||||
|
||||
xml_parser = doxygen_xml.DoxygenXml(lvgl_src_dir,
|
||||
intermediate_dir,
|
||||
doxyfile_src_file,
|
||||
silent_mode=False
|
||||
)
|
||||
|
||||
# ---------------------------------------------------------------------
|
||||
# Generate .RST files for API pages.
|
||||
# ---------------------------------------------------------------------
|
||||
announce(__file__, "Generating API documentation .RST files...")
|
||||
api_out_root_dir = os.path.join(intermediate_dir, 'API')
|
||||
create_api_rst_files(lvgl_src_dir, api_out_root_dir)
|
||||
|
||||
# ---------------------------------------------------------------------
|
||||
# For each directory entry in `doc_rel_paths` array...
|
||||
# - add API hyperlinks to .RST files in the directories in passed array.
|
||||
# ---------------------------------------------------------------------
|
||||
announce(__file__, "Adding API-page hyperlinks to source docs...")
|
||||
_add_hyperlinks_to_eligible_files(intermediate_dir,
|
||||
True,
|
||||
*doc_rel_paths)
|
||||
Reference in New Issue
Block a user