First commit with working display
This commit is contained in:
561
managed_components/lvgl__lvgl/docs/example_list.py
Normal file
561
managed_components/lvgl__lvgl/docs/example_list.py
Normal file
@@ -0,0 +1,561 @@
|
||||
#!/usr/bin/env python3
|
||||
""" example_list.py -- Build `examples.rst`
|
||||
|
||||
`examples.rst` is built by gathering all examples recursively under
|
||||
`lvgl/examples/`. The recognized directives in that file are then
|
||||
processed by Sphinx using `lvgl/docs/src/_ext/lv_example.py`.
|
||||
|
||||
Subsection names within `examples.rst` are made up of the capitalized
|
||||
directory names ('_' and '-' characters are converted to spaces) or
|
||||
are spelled out in `index.rst` files if the subsection names should be
|
||||
spelled differently. See "index.rst Format" section below for details.
|
||||
|
||||
An "example" is defined as:
|
||||
|
||||
- being anywhere under the `lvgl/examples/` directory,
|
||||
- the presence of an `index.rst` in a directory with examples, and
|
||||
- example C code in file names typically starting with "lv_example_".
|
||||
|
||||
That C code and a live, interactive running example, is pulled into each
|
||||
example in the EXAMPLES HTML page, as well as every doc where such
|
||||
examples are included, by a directive like this:
|
||||
|
||||
.. code::
|
||||
|
||||
.. include:: /examples/layouts/flex/index.rst
|
||||
|
||||
Note that in the intermediate dir (from which LVGL user docs are generated),
|
||||
``./examples/`` is a top-level subdirectory, so no relative "../../.." etc.
|
||||
is required in the path. Using a path starting at "root" tells Sphinx to
|
||||
start at the top-level directory where the docs are being generated,
|
||||
typically ``lvgl/docs/intermediate/``.
|
||||
|
||||
See `build.py` for more information about the intermediate directory.
|
||||
|
||||
If a subdirectory is included in the `avoid_dirs` list herein, it will
|
||||
not generate any output in the resulting `examples.rst` file.
|
||||
Example: `lvgl/examples/assets/`.
|
||||
|
||||
|
||||
Usage
|
||||
*****
|
||||
|
||||
.. code::
|
||||
|
||||
import example_list
|
||||
example_list.make_warnings_into_errors() # Optional
|
||||
example_list.DEBUG_MODE = True # Optional
|
||||
example_list.exec(intermediate_dir)
|
||||
|
||||
or run it directly:
|
||||
|
||||
.. code:: bash
|
||||
|
||||
python3 example_list.py "/home/lvgl/docs/intermediate"
|
||||
|
||||
In both cases, the `intermediate_dir` passed is expected to have
|
||||
an `./examples/` directory in it (copy of `lvgl/examples/`) as a
|
||||
top-level subdirectory.
|
||||
|
||||
|
||||
Examples Directory Requirements
|
||||
*******************************
|
||||
|
||||
.. code:: text
|
||||
|
||||
lvgl/examples/
|
||||
index.rst (.. dir_order: pseudo-directive since sub-dirs
|
||||
are not presented in alphabetical order)
|
||||
anim/
|
||||
index.rst (see below for expected contents)
|
||||
lv_example_anim_1.c
|
||||
lv_example_anim_2.c
|
||||
lv_example_anim_3.c
|
||||
lv_example_anim_4.c
|
||||
lv_example_anim_timeline_1.c
|
||||
...
|
||||
layouts/
|
||||
flex/
|
||||
index.rst (see below for expected contents)
|
||||
lv_example_flex_1.c
|
||||
lv_example_flex_2.c
|
||||
lv_example_flex_3.c
|
||||
etc.
|
||||
grid/
|
||||
etc.
|
||||
libs/
|
||||
index.rst (section-heading name: "3rd-Party Libraries"
|
||||
[since it is different than parent directory name])
|
||||
barcode/
|
||||
index.rst (see below for expected contents)
|
||||
lv_example_barcode_1.c
|
||||
lv_example_barcode_1.h
|
||||
bmp/
|
||||
etc.
|
||||
etc.
|
||||
etc.
|
||||
|
||||
.. note:: The above shows only 2 levels of directories but deeper
|
||||
levels are supported (up to 5 below document title).
|
||||
|
||||
|
||||
index.rst Format
|
||||
****************
|
||||
|
||||
Examples
|
||||
--------
|
||||
Examples are included in `index.rst` files when they are sibling files with
|
||||
the example C code.
|
||||
|
||||
.. code::
|
||||
|
||||
Example 1 Title <-- required for each example
|
||||
--------------- <-- required for each example
|
||||
<-- blank lines are ignored
|
||||
.. lv_example:: anim/lv_example_anim_1 <-- path relative to the `lvgl/examples/` dir
|
||||
:language: c
|
||||
|
||||
Repeat the above pattern for each example in the current directory. That number
|
||||
may be zero (0) for directories like `libs/` in which all examples are in directories
|
||||
below that level. See directory structure above.
|
||||
|
||||
For paths outside the current directory, simply provide the path to the code example
|
||||
relative to the `lvgl/examples/` directory. Example from
|
||||
`lvgl/examples/widgets/scale/index.rst`:
|
||||
|
||||
.. code::
|
||||
|
||||
...
|
||||
|
||||
A round scale style simulating a compass
|
||||
----------------------------------------
|
||||
|
||||
.. lv_example:: widgets/scale/lv_example_scale_12
|
||||
:language: c
|
||||
|
||||
Axis ticks and labels with scrolling on a chart
|
||||
-----------------------------------------------
|
||||
|
||||
.. lv_example:: widgets/chart/lv_example_chart_2 <-- path is outside scale/ dir
|
||||
:language: c
|
||||
|
||||
.. note::
|
||||
|
||||
Starting the example code filename with `lv_example_` is not a requirement of the
|
||||
this script, but does make it clear that it is an example, so this pattern should
|
||||
be preserved for new and changed examples.
|
||||
|
||||
|
||||
Custom Section Headings
|
||||
-----------------------
|
||||
If a section heading needs to be spelled differently than the capitalized name of the
|
||||
parent directory, then an `index.rst` file in that directory may contain the desired
|
||||
section-heading name in an ``.. example_heading`` pseudo-directive. Example from
|
||||
`lvgl/examples/libs/index.rst`:
|
||||
|
||||
.. code::
|
||||
|
||||
.. example_heading: 3rd-Party Libraries
|
||||
|
||||
|
||||
Directory Reordering
|
||||
--------------------
|
||||
There are cases where it is not appropriate to present the contents of a
|
||||
set of subdirectories in alphabetical order. When this is the case, a
|
||||
pseudo-directive in the `index.rst` file in the parent directory can be specified
|
||||
to govern the sequence its subdirectories are processed. The example below
|
||||
is from `lvgl/examples/widgets/index.rst`. It is provided in order to
|
||||
cause the "Base Widget" (obj) directory to be processed first (and thus
|
||||
included in the output first).
|
||||
|
||||
.. code::
|
||||
|
||||
.. dir_order:
|
||||
|
||||
obj
|
||||
animimg
|
||||
arc
|
||||
arclabel
|
||||
bar
|
||||
button
|
||||
buttonmatrix
|
||||
etc.
|
||||
|
||||
.. note::
|
||||
|
||||
A warning is issued if either:
|
||||
|
||||
- a subdirectory is named that does not exist, or
|
||||
- a subdirectory exists that is not in the list and not in the `avoid_dirs` list.
|
||||
|
||||
|
||||
Making Warnings into Errors
|
||||
***************************
|
||||
|
||||
If `make_warnings_into_errors()` was called, if there were any warnings
|
||||
issued, after the output is completely generated, this script will exit
|
||||
with a non-zero exit code. This can be done by client modules like this:
|
||||
|
||||
.. code::
|
||||
|
||||
import example_list
|
||||
example_list.make_warnings_into_errors()
|
||||
example_list.exec(intermediate_dir)
|
||||
|
||||
To turn it off:
|
||||
|
||||
.. code::
|
||||
|
||||
example_list.make_warnings_into_errors(False)
|
||||
|
||||
|
||||
Debug Output
|
||||
************
|
||||
|
||||
Enable debug output like this:
|
||||
|
||||
.. code::
|
||||
|
||||
example_list.DEBUG_MODE = True
|
||||
|
||||
Suppressing DEBUG_MODE is the default.
|
||||
|
||||
|
||||
"""
|
||||
import os
|
||||
import sys
|
||||
from io import TextIOWrapper
|
||||
from announce import *
|
||||
|
||||
# -------------------------------------------------------------------------
|
||||
# This is the order that LVGL documentation uses for the section heading
|
||||
# levels. header_defs[0] is the highest and header_defs[5] is the lowest.
|
||||
# If this order is not kept in the reST files Sphinx will complain, and
|
||||
# have difficulty formatting the TOC correctly.
|
||||
# -------------------------------------------------------------------------
|
||||
TITLE = '='
|
||||
CHAPTER = '*'
|
||||
SECTION = '-'
|
||||
SUBSECTION = '~'
|
||||
SUBSUBSECTION = '^'
|
||||
SUBSUBSUBSECTION = "'"
|
||||
|
||||
header_defs = [
|
||||
TITLE,
|
||||
CHAPTER,
|
||||
SECTION,
|
||||
SUBSECTION,
|
||||
SUBSUBSECTION,
|
||||
SUBSUBSUBSECTION,
|
||||
]
|
||||
|
||||
LV_EXAMPLE_DIRECTIVE = '.. lv_example::'
|
||||
EXAMPLE_HEADING_DIRECTIVE = '.. example_heading:'
|
||||
DIR_ORDER_DIRECTIVE = '.. dir_order:'
|
||||
DIR_SEP = os.sep
|
||||
INDEX_FILENAME = 'index.rst'
|
||||
MAKE_WARNINGS_INTO_ERRORS = False
|
||||
DEBUG_MODE = False
|
||||
THIS_FILE = os.path.basename(__file__)
|
||||
_warning_issued = False
|
||||
_top_level_heading_count = 0
|
||||
|
||||
avoid_dirs = [
|
||||
os.path.join('examples', 'arduino'),
|
||||
os.path.join('examples', 'assets'),
|
||||
]
|
||||
|
||||
|
||||
def make_warnings_into_errors(val: bool = True):
|
||||
global MAKE_WARNINGS_INTO_ERRORS
|
||||
MAKE_WARNINGS_INTO_ERRORS = val
|
||||
|
||||
|
||||
def _default_section_heading(level: int, path: str, is_file: bool) -> str:
|
||||
if is_file:
|
||||
dir_path = os.path.dirname(path)
|
||||
else:
|
||||
dir_path = path
|
||||
|
||||
parent_dir = os.path.basename(dir_path)
|
||||
|
||||
# Compose default section heading based on capitalized words in `parent_dir`.
|
||||
word_list = parent_dir.replace('_', ' ').replace('-', ' ').split(' ')
|
||||
result = ''
|
||||
|
||||
for word in word_list:
|
||||
result += ' ' + word.capitalize()
|
||||
|
||||
# Remove leading space.
|
||||
if result:
|
||||
result = result[1:]
|
||||
|
||||
return result
|
||||
|
||||
|
||||
def _warn(msg: str):
|
||||
global _warning_issued
|
||||
if MAKE_WARNINGS_INTO_ERRORS:
|
||||
warning_type = 'Error'
|
||||
else:
|
||||
warning_type = 'Warning'
|
||||
|
||||
if is_silent_mode():
|
||||
print(f'{THIS_FILE}: ' + '\x1b[31m' + f'>>> {warning_type}: ' + msg + '\x1b[0m')
|
||||
else:
|
||||
announce_colored(THIS_FILE, 'red', f'>>> {warning_type}: ' + msg)
|
||||
|
||||
_warning_issued = True
|
||||
|
||||
|
||||
def _in_avoid_dirs_list(dir_bep: str) -> bool:
|
||||
result = False
|
||||
|
||||
for avoid_dir in avoid_dirs:
|
||||
if avoid_dir in dir_bep:
|
||||
result = True
|
||||
break
|
||||
|
||||
return result
|
||||
|
||||
|
||||
def _validate_sub_dirs(sub_dirs: list[str], index_rst_path: str) -> bool:
|
||||
""" Validate sub-dirs that come from an `index.rst` `.. dir_order:` directive.
|
||||
|
||||
1. Check that that each one is an existing directory.
|
||||
2. Check that there are none missing except those in `avoid_dirs`.
|
||||
|
||||
Issue warning if either fails and set `_warning_issued`.
|
||||
If MAKE_WARNINGS_INTO_ERRORS, this will make script exit with an error
|
||||
code at the end.
|
||||
|
||||
:param sub_dirs: List of sub-dirs to validate
|
||||
:param index_rst_path: Path to index.rst file
|
||||
:return: Whether `sub_dirs` is valid per 1 and 2 above
|
||||
"""
|
||||
result = True
|
||||
|
||||
# Check that each sub-dir is an existing directory.
|
||||
for sub_dir in sub_dirs:
|
||||
if not os.path.isdir(sub_dir):
|
||||
result = False
|
||||
_warn(f'Dir-order directive in {index_rst_path} contains dir [{sub_dir}] that does not exist.')
|
||||
# We won't break here so that all such dirs can be listed.
|
||||
|
||||
# Check that there are none missing except those in `avoid_dirs`.
|
||||
dir_path = os.path.dirname(index_rst_path)
|
||||
actual_dirs = []
|
||||
|
||||
for dir_item in os.listdir(dir_path):
|
||||
path_bep = os.path.join(dir_path, dir_item)
|
||||
if os.path.isdir(path_bep):
|
||||
actual_dirs.append(path_bep)
|
||||
|
||||
# Are there any missing that are not in `avoid_dirs` list?
|
||||
# If so, issue warning about each directory missing.
|
||||
for sub_dir in actual_dirs:
|
||||
if sub_dir not in sub_dirs:
|
||||
if not _in_avoid_dirs_list(sub_dir):
|
||||
_warn(f'Dir-order directive in {index_rst_path} is missing dir [{sub_dir}].')
|
||||
result = False
|
||||
|
||||
return result
|
||||
|
||||
|
||||
def _emit_heading(level: int, hdg: str, f: TextIOWrapper):
|
||||
"""
|
||||
Emit reST heading using `header_defs`.
|
||||
|
||||
:param level: Directory depth below the top directory in tree. [0-5]
|
||||
:param hdg: Heading text
|
||||
:param f: Output file (examples.rst)
|
||||
:return:
|
||||
"""
|
||||
assert 0 <= level < len(header_defs), "level out of range"
|
||||
global _top_level_heading_count
|
||||
underline = header_defs[level] * len(hdg)
|
||||
announce(__file__, f'Section heading [{hdg}] at level [{level}].')
|
||||
|
||||
if level == 0:
|
||||
_top_level_heading_count += 1
|
||||
if _top_level_heading_count > 1:
|
||||
f.write('\n\n\n') # 3 extra lines above (doc) root titles, except 1st
|
||||
f.write(underline)
|
||||
f.write('\n')
|
||||
elif level == 1:
|
||||
f.write('\n\n') # 2 extra lines above chapters
|
||||
elif level == 2:
|
||||
f.write('\n') # 1 extra line above sections
|
||||
|
||||
f.write(hdg)
|
||||
f.write('\n')
|
||||
f.write(underline)
|
||||
f.write('\n\n')
|
||||
|
||||
|
||||
def _generate_output_from_dir(level: int,
|
||||
root_len: int,
|
||||
file_or_dir: str,
|
||||
is_file: bool,
|
||||
orig_dir_list: list[str],
|
||||
f: TextIOWrapper) -> list[str]:
|
||||
""" Output to `f` based on contents of `file_or_dir`.
|
||||
|
||||
:param level: Directory depth below the top directory in tree. [0-4]
|
||||
:param root_len: Length of root path -- is deleted from paths so that
|
||||
paths are relative to the `lvgl/examples/` dir.
|
||||
:param file_or_dir: Path to "thing" being processed.
|
||||
:param is_file: True if `file_or_dir` is an `index.rst` file.
|
||||
:param orig_dir_list: List of sub-dirs below dir being processed.
|
||||
Will be returned from this function if a
|
||||
dir-order directive is NOT found in index file.
|
||||
:param f: Output file (examples.rst)
|
||||
:return: `orig_dir_list` or `dir_order_override` if a
|
||||
dir-order directive is found in index file.
|
||||
"""
|
||||
dir_order_override = []
|
||||
result_dir_list = orig_dir_list
|
||||
|
||||
# It is an error to proceed with `level` out of range. Clamping it to be in
|
||||
# range is also an error because it would cause the output to be corrupted with
|
||||
# an invalid section-heading underscore, which would generate an error later.
|
||||
if 0 <= level and level + 1 < len(header_defs):
|
||||
section_heading = _default_section_heading(level, file_or_dir, is_file)
|
||||
example_tuples = []
|
||||
relative_dir = ''
|
||||
|
||||
if is_file:
|
||||
announce(__file__, f'Processing file [{file_or_dir}]...')
|
||||
# We are processing an index.rst file.
|
||||
with open(file_or_dir, 'r', encoding='utf-8') as fidx:
|
||||
# It is important that this is NOT fidx.readlines() because
|
||||
# it leaves blank lines containing '\n' instead of ''.
|
||||
lines = fidx.read().split('\n')
|
||||
|
||||
example_title = ''
|
||||
prev_line = ''
|
||||
dir_path = os.path.dirname(file_or_dir)
|
||||
in_dir_order_directive = False
|
||||
|
||||
# Accumulate data from `index.rst`. This needs to be done
|
||||
# first in case it overrides the default section heading.
|
||||
for line in lines:
|
||||
if in_dir_order_directive:
|
||||
leading_non_blank = (len(line) > 0) and not (line[0] == ' ' or line[0] == '\t')
|
||||
|
||||
if leading_non_blank:
|
||||
# Leading non-blank character ends dir-order directive.
|
||||
in_dir_order_directive = False
|
||||
else:
|
||||
# Still in dir-order directive.
|
||||
stripped_line = line.strip()
|
||||
if stripped_line:
|
||||
dir_order_override.append(os.path.join(dir_path, stripped_line))
|
||||
continue
|
||||
|
||||
stripped_line = line.strip()
|
||||
|
||||
if not stripped_line:
|
||||
continue # Skip blank line.
|
||||
elif stripped_line.startswith(EXAMPLE_HEADING_DIRECTIVE):
|
||||
new_heading = stripped_line.replace(EXAMPLE_HEADING_DIRECTIVE, '').strip()
|
||||
announce(__file__, f'Default section heading [{section_heading}] replaced with [{new_heading}]...')
|
||||
section_heading = new_heading
|
||||
elif stripped_line.startswith('---'):
|
||||
example_title = prev_line
|
||||
announce(__file__, f'Found example [{example_title}]...')
|
||||
elif stripped_line.startswith(LV_EXAMPLE_DIRECTIVE):
|
||||
rel_path_from_examples_dir = stripped_line.replace(LV_EXAMPLE_DIRECTIVE, '').strip()
|
||||
announce(__file__, f' [{rel_path_from_examples_dir}]')
|
||||
example_tuples.append( (example_title, rel_path_from_examples_dir) )
|
||||
elif not in_dir_order_directive and stripped_line.startswith(DIR_ORDER_DIRECTIVE):
|
||||
in_dir_order_directive = True
|
||||
announce(__file__, f'Found DIR-ORDER directive.')
|
||||
|
||||
prev_line = stripped_line
|
||||
|
||||
# Output section heading. This occurs even when we have descended into an
|
||||
# empty directory with example sub-dirs below it.
|
||||
_emit_heading(level, section_heading, f)
|
||||
|
||||
# Output examples, if any. Will be empty when we are in an empty directory
|
||||
# with example sub-dirs below it.
|
||||
for example_tuple in example_tuples:
|
||||
example_title = example_tuple[0]
|
||||
rel_path_from_examples_dir = example_tuple[1]
|
||||
example_hdg_underline = header_defs[level + 1] * len(example_title)
|
||||
announce(__file__, f'Writing example [{rel_path_from_examples_dir}]...')
|
||||
_emit_heading(level + 1, example_title, f)
|
||||
f.write(LV_EXAMPLE_DIRECTIVE + ' ' + rel_path_from_examples_dir)
|
||||
f.write('\n\n')
|
||||
|
||||
if dir_order_override:
|
||||
_validate_sub_dirs(dir_order_override, file_or_dir)
|
||||
result_dir_list = dir_order_override
|
||||
|
||||
return result_dir_list
|
||||
|
||||
|
||||
def process_dir_recursively(level: int, root_len: int, dir_bep: str, f: TextIOWrapper):
|
||||
""" Process dir `dir_bep` recursively, avoiding sub-dirs in `avoid_dirs`.
|
||||
|
||||
:param level: Directory depth below the top directory in tree.
|
||||
:param root_len: Length of root path (gets removed from full path)
|
||||
:param dir_bep: Directory *being processed*
|
||||
:param f: Output file (examples.rst)
|
||||
"""
|
||||
announce(__file__, f'Processing dir [{dir_bep}]...')
|
||||
sub_dirs = []
|
||||
idx_files = []
|
||||
|
||||
if not os.path.isdir(dir_bep):
|
||||
_warn(f'process_dir_recursively: `dir_bep` [{dir_bep}] does not exist.')
|
||||
return
|
||||
|
||||
# For each "thing" found in `dir_bep`, build lists: sub_dirs and idx_files.
|
||||
for dir_item in os.listdir(dir_bep):
|
||||
path_bep = os.path.join(dir_bep, dir_item)
|
||||
if os.path.isdir(path_bep):
|
||||
sub_dirs.append(path_bep)
|
||||
elif path_bep.endswith(INDEX_FILENAME):
|
||||
idx_files.append(path_bep)
|
||||
|
||||
if idx_files:
|
||||
sub_dirs = _generate_output_from_dir(level, root_len, idx_files[0], True, sub_dirs, f)
|
||||
# `sub_dirs` can be replaced if `index.rst` contains a dir-order directive.
|
||||
else:
|
||||
_generate_output_from_dir(level, root_len, dir_bep, False, sub_dirs, f)
|
||||
|
||||
# Now recursively process sub_dirs.
|
||||
for subdir in sub_dirs:
|
||||
if not _in_avoid_dirs_list(subdir):
|
||||
process_dir_recursively(level + 1, root_len, subdir, f)
|
||||
|
||||
|
||||
def exec(intermediate_dir):
|
||||
announce_set_silent_mode(not DEBUG_MODE)
|
||||
output_path = os.path.join(intermediate_dir, 'examples.rst')
|
||||
input_paths = [
|
||||
os.path.join('..', 'examples'),
|
||||
# os.path.join('..', 'demos')
|
||||
]
|
||||
|
||||
with open(output_path, 'w', encoding='utf-8') as f:
|
||||
f.write('.. _examples:\n')
|
||||
f.write('\n')
|
||||
|
||||
# Recursively walk the directories in `input_paths` array for
|
||||
# ``index.rst`` files.
|
||||
for root_path in input_paths:
|
||||
root_len = len(root_path) + 1
|
||||
process_dir_recursively(0, root_len, root_path, f)
|
||||
|
||||
if MAKE_WARNINGS_INTO_ERRORS and _warning_issued:
|
||||
exit(1)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
"""Make module run-able as well as importable."""
|
||||
base_dir = os.path.abspath(os.path.dirname(__file__))
|
||||
os.chdir(base_dir)
|
||||
exec(sys.argv[1])
|
||||
Reference in New Issue
Block a user