mirror of
https://github.com/lvgl/lvgl.git
synced 2026-02-06 14:12:20 +08:00
Some checks are pending
Arduino Lint / lint (push) Waiting to run
MicroPython CI / Build esp32 port (push) Waiting to run
MicroPython CI / Build rp2 port (push) Waiting to run
MicroPython CI / Build stm32 port (push) Waiting to run
MicroPython CI / Build unix port (push) Waiting to run
C/C++ CI / Build OPTIONS_16BIT - Ubuntu (push) Waiting to run
C/C++ CI / Build OPTIONS_24BIT - Ubuntu (push) Waiting to run
C/C++ CI / Build OPTIONS_FULL_32BIT - Ubuntu (push) Waiting to run
C/C++ CI / Build OPTIONS_NORMAL_8BIT - Ubuntu (push) Waiting to run
C/C++ CI / Build OPTIONS_SDL - Ubuntu (push) Waiting to run
C/C++ CI / Build OPTIONS_VG_LITE - Ubuntu (push) Waiting to run
C/C++ CI / Build OPTIONS_16BIT - cl - Windows (push) Waiting to run
C/C++ CI / Build OPTIONS_16BIT - gcc - Windows (push) Waiting to run
C/C++ CI / Build OPTIONS_24BIT - cl - Windows (push) Waiting to run
C/C++ CI / Build OPTIONS_24BIT - gcc - Windows (push) Waiting to run
C/C++ CI / Build OPTIONS_FULL_32BIT - cl - Windows (push) Waiting to run
C/C++ CI / Build OPTIONS_FULL_32BIT - gcc - Windows (push) Waiting to run
C/C++ CI / Build OPTIONS_VG_LITE - cl - Windows (push) Waiting to run
C/C++ CI / Build OPTIONS_VG_LITE - gcc - Windows (push) Waiting to run
C/C++ CI / Build ESP IDF ESP32S3 (push) Waiting to run
C/C++ CI / Run tests with 32bit build (push) Waiting to run
C/C++ CI / Run tests with 64bit build (push) Waiting to run
BOM Check / bom-check (push) Waiting to run
Verify that lv_conf_internal.h matches repository state / verify-conf-internal (push) Waiting to run
Verify the widget property name / verify-property-name (push) Waiting to run
Verify code formatting / verify-formatting (push) Waiting to run
Build docs / build-and-deploy (push) Waiting to run
Test API JSON generator / Test API JSON (push) Waiting to run
Check Makefile / Build using Makefile (push) Waiting to run
Check Makefile for UEFI / Build using Makefile for UEFI (push) Waiting to run
Port repo release update / run-release-branch-updater (push) Waiting to run
Verify Kconfig / verify-kconfig (push) Waiting to run
771 lines
29 KiB
Python
771 lines
29 KiB
Python
"""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 enumvalues from all unnamed enums, and each
|
|
# enumvalue 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 enumvalue 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: 2\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)
|