aboutsummaryrefslogtreecommitdiffstats
path: root/tools/example_gallery/main.py
diff options
context:
space:
mode:
Diffstat (limited to 'tools/example_gallery/main.py')
-rw-r--r--tools/example_gallery/main.py681
1 files changed, 518 insertions, 163 deletions
diff --git a/tools/example_gallery/main.py b/tools/example_gallery/main.py
index a34210722..b5aa632c0 100644
--- a/tools/example_gallery/main.py
+++ b/tools/example_gallery/main.py
@@ -14,12 +14,43 @@ since there is no special requirements.
import json
import math
+import os
import shutil
+import zipfile
+import sys
from argparse import ArgumentParser, RawTextHelpFormatter
+from dataclasses import dataclass
+from enum import IntEnum, Enum
from pathlib import Path
from textwrap import dedent
+
+class Format(Enum):
+ RST = 0
+ MD = 1
+
+
+class ModuleType(IntEnum):
+ ESSENTIALS = 0
+ ADDONS = 1
+ M2M = 2
+
+
+SUFFIXES = {Format.RST: "rst", Format.MD: "md"}
+
+
opt_quiet = False
+
+
+LITERAL_INCLUDE = ".. literalinclude::"
+
+
+IMAGE_SUFFIXES = (".png", ".jpg", ".jpeg", ".gif", ".svg", ".svgz", ".webp")
+
+
+IGNORED_SUFFIXES = IMAGE_SUFFIXES + (".pdf", ".pyc", ".obj", ".mesh")
+
+
suffixes = {
".h": "cpp",
".cpp": "cpp",
@@ -30,9 +61,25 @@ suffixes = {
".qrc": "xml",
".ui": "xml",
".xbel": "xml",
+ ".xml": "xml",
}
+BASE_CONTENT = """\
+Examples
+========
+
+ A collection of examples are provided with |project| to help new users
+ to understand different use cases of the module.
+
+ You can find all these examples inside the
+ `pyside-setup <https://code.qt.io/cgit/pyside/pyside-setup.git/>`_ repository
+ on the `examples <https://code.qt.io/cgit/pyside/pyside-setup.git/tree/examples>`_
+ directory.
+
+"""
+
+
def ind(x):
return " " * 4 * x
@@ -40,10 +87,8 @@ def ind(x):
def get_lexer(path):
if path.name == "CMakeLists.txt":
return "cmake"
- suffix = path.suffix
- if suffix in suffixes:
- return suffixes[suffix]
- return "text"
+ lexer = suffixes.get(path.suffix)
+ return lexer if lexer else "text"
def add_indent(s, level):
@@ -56,6 +101,110 @@ def add_indent(s, level):
return new_s
+def check_img_ext(i):
+ """Check whether path is an image."""
+ return i.suffix in IMAGE_SUFFIXES
+
+
+@dataclass
+class ModuleDescription:
+ """Specifies a sort key and type for a Qt module."""
+ sort_key: int = 0
+ module_type: ModuleType = ModuleType.ESSENTIALS
+ description: str = ''
+
+
+MODULE_DESCRIPTIONS = {
+ "async": ModuleDescription(16, ModuleType.ESSENTIALS, ''),
+ "corelib": ModuleDescription(15, ModuleType.ESSENTIALS, ''),
+ "dbus": ModuleDescription(22, ModuleType.ESSENTIALS, ''),
+ "designer": ModuleDescription(11, ModuleType.ESSENTIALS, ''),
+ "gui": ModuleDescription(25, ModuleType.ESSENTIALS, ''),
+ "network": ModuleDescription(20, ModuleType.ESSENTIALS, ''),
+ "opengl": ModuleDescription(26, ModuleType.ESSENTIALS, ''),
+ "qml": ModuleDescription(0, ModuleType.ESSENTIALS, ''),
+ "quick": ModuleDescription(1, ModuleType.ESSENTIALS, ''),
+ "quickcontrols": ModuleDescription(2, ModuleType.ESSENTIALS, ''),
+ "samplebinding": ModuleDescription(30, ModuleType.ESSENTIALS, ''),
+ "scriptableapplication": ModuleDescription(30, ModuleType.ESSENTIALS, ''),
+ "sql": ModuleDescription(21, ModuleType.ESSENTIALS, ''),
+ "uitools": ModuleDescription(12, ModuleType.ESSENTIALS, ''),
+ "widgetbinding": ModuleDescription(30, ModuleType.ESSENTIALS, ''),
+ "widgets": ModuleDescription(10, ModuleType.ESSENTIALS, ''),
+ "xml": ModuleDescription(24, ModuleType.ESSENTIALS, ''),
+ "Qt Demos": ModuleDescription(0, ModuleType.ADDONS, ''), # from Qt repos
+ "3d": ModuleDescription(30, ModuleType.ADDONS, ''),
+ "axcontainer": ModuleDescription(20, ModuleType.ADDONS, ''),
+ "bluetooth": ModuleDescription(20, ModuleType.ADDONS, ''),
+ "charts": ModuleDescription(12, ModuleType.ADDONS, ''),
+ "datavisualization": ModuleDescription(11, ModuleType.ADDONS, ''),
+ "demos": ModuleDescription(0, ModuleType.ADDONS, ''),
+ "external": ModuleDescription(20, ModuleType.ADDONS, ''),
+ "graphs": ModuleDescription(10, ModuleType.ADDONS, ''),
+ "httpserver": ModuleDescription(0, ModuleType.ADDONS, ''),
+ "location": ModuleDescription(20, ModuleType.ADDONS, ''),
+ "multimedia": ModuleDescription(12, ModuleType.ADDONS, ''),
+ "networkauth": ModuleDescription(20, ModuleType.ADDONS, ''),
+ "pdf": ModuleDescription(20, ModuleType.ADDONS, ''),
+ "pdfwidgets": ModuleDescription(20, ModuleType.ADDONS, ''),
+ "quick3d": ModuleDescription(20, ModuleType.ADDONS, ''),
+ "remoteobjects": ModuleDescription(20, ModuleType.ADDONS, ''),
+ "serialbus": ModuleDescription(30, ModuleType.ADDONS, ''),
+ "serialport": ModuleDescription(30, ModuleType.ADDONS, ''),
+ "spatialaudio": ModuleDescription(20, ModuleType.ADDONS, ''),
+ "speech": ModuleDescription(20, ModuleType.ADDONS, ''),
+ "statemachine": ModuleDescription(30, ModuleType.ADDONS, ''),
+ "webchannel": ModuleDescription(30, ModuleType.ADDONS, ''),
+ "webenginequick": ModuleDescription(15, ModuleType.ADDONS, ''),
+ "webenginewidgets": ModuleDescription(16, ModuleType.ADDONS, ''),
+ "coap": ModuleDescription(0, ModuleType.M2M, ''),
+ "mqtt": ModuleDescription(0, ModuleType.M2M, ''),
+ "opcua": ModuleDescription(0, ModuleType.M2M, '')
+}
+
+
+def module_sort_key(name):
+ """Return key for sorting modules."""
+ description = MODULE_DESCRIPTIONS.get(name)
+ module_type = int(description.module_type) if description else 5
+ sort_key = description.sort_key if description else 100
+ return f"{module_type}:{sort_key:04}:{name}"
+
+
+def module_title(name):
+ """Return title for a module."""
+ result = name.title()
+ description = MODULE_DESCRIPTIONS.get(name)
+ if description:
+ if description.description:
+ result += " - " + description.description
+ if description.module_type == ModuleType.M2M:
+ result += " (M2M)"
+ elif description.module_type == ModuleType.ADDONS:
+ result += " (Add-ons)"
+ else:
+ result += " (Essentials)"
+ return result
+
+
+@dataclass
+class ExampleData:
+ """Example data for formatting the gallery."""
+
+ def __init__(self):
+ self.headline = ""
+
+ example: str
+ module: str
+ extra: str
+ doc_file: str
+ file_format: Format
+ abs_path: str
+ has_doc: bool
+ img_doc: Path
+ headline: str
+
+
def get_module_gallery(examples):
"""
This function takes a list of dictionaries, that contain examples
@@ -63,45 +212,40 @@ def get_module_gallery(examples):
"""
gallery = (
- ".. panels::\n"
- f"{ind(1)}:container: container-lg pb-3\n"
- f"{ind(1)}:column: col-lg-3 col-md-6 col-sm-6 col-xs-12 p-2\n\n"
+ ".. grid:: 1 4 4 4\n"
+ f"{ind(1)}:gutter: 2\n\n"
)
# Iteration per rows
for i in range(math.ceil(len(examples))):
e = examples[i]
- url = e["rst"].replace(".rst", ".html")
- name = e["example"]
- underline = f'{e["module"]}'
+ suffix = SUFFIXES[e.file_format]
+ url = e.doc_file.replace(f".{suffix}", ".html")
+ name = e.example
+ underline = e.module
- if e["extra"]:
- underline += f'/{e["extra"]}'
+ if e.extra:
+ underline += f"/{e.extra}"
if i > 0:
- gallery += f"{ind(1)}---\n"
- elif e["img_doc"]:
- gallery += f"{ind(1)}---\n"
-
- if e["img_doc"]:
- img_name = e['img_doc'].name
- else:
- img_name = "../example_no_image.png"
-
- gallery += f"{ind(1)}:img-top: {img_name}\n"
- gallery += f"{ind(1)}:img-top-cls: + d-flex align-self-center\n\n"
+ gallery += "\n"
+ img_name = e.img_doc.name if e.img_doc else "../example_no_image.png"
# Fix long names
if name.startswith("chapter"):
name = name.replace("chapter", "c")
+ elif name.startswith("advanced"):
+ name = name.replace("advanced", "a")
+
+ desc = e.headline
+ if not desc:
+ desc = f"found in the ``{underline}`` directory."
- gallery += f"{ind(1)}`{name} <{url}>`_\n"
- gallery += f"{ind(1)}+++\n"
- gallery += f"{ind(1)}{underline}\n"
- gallery += f"\n{ind(1)}.. link-button:: {url}\n"
- gallery += f"{ind(2)}:type: url\n"
- gallery += f"{ind(2)}:text: Go to Example\n"
- gallery += f"{ind(2)}:classes: btn-qt btn-block stretched-link\n"
+ gallery += f"{ind(1)}.. grid-item-card:: {name}\n"
+ gallery += f"{ind(2)}:class-item: cover-img\n"
+ gallery += f"{ind(2)}:link: {url}\n"
+ gallery += f"{ind(2)}:img-top: {img_name}\n\n"
+ gallery += f"{ind(2)}{desc}\n"
return f"{gallery}\n"
@@ -115,39 +259,99 @@ def remove_licenses(s):
return "\n".join(new_s)
-def get_code_tabs(files, project_dir):
+def make_zip_archive(zip_name, src, skip_dirs=None):
+ src_path = Path(src).expanduser().resolve(strict=True)
+ if skip_dirs is None:
+ skip_dirs = []
+ if not isinstance(skip_dirs, list):
+ print("Error: A list needs to be passed for 'skip_dirs'")
+ return
+ with zipfile.ZipFile(src_path.parents[0] / Path(zip_name), 'w', zipfile.ZIP_DEFLATED) as zf:
+ for file in src_path.rglob('*'):
+ skip = False
+ _parts = file.relative_to(src_path).parts
+ for sd in skip_dirs:
+ if sd in _parts:
+ skip = True
+ break
+ if not skip:
+ zf.write(file, file.relative_to(src_path.parent))
+
+
+def doc_file(project_dir, project_file_entry):
+ """Return the (optional) .rstinc file describing a source file."""
+ rst_file = project_dir
+ if rst_file.name != "doc": # Special case: Dummy .pyproject file in doc dir
+ rst_file /= "doc"
+ rst_file /= Path(project_file_entry).name + ".rstinc"
+ return rst_file if rst_file.is_file() else None
+
+
+def get_code_tabs(files, project_dir, file_format):
content = "\n"
+ # Prepare ZIP file, and copy to final destination
+ zip_name = f"{project_dir.name}.zip"
+ make_zip_archive(zip_name, project_dir, skip_dirs=["doc"])
+ zip_src = f"{project_dir}.zip"
+ zip_dst = EXAMPLES_DOC / zip_name
+ shutil.move(zip_src, zip_dst)
+
+ if file_format == Format.RST:
+ content += f":download:`Download this example <{zip_name}>`\n\n"
+ else:
+ content += f"{{download}}`Download this example <{zip_name}>`\n\n"
+ content += "```{eval-rst}\n"
+
for i, project_file in enumerate(files):
+ if i == 0:
+ content += ".. tab-set::\n\n"
+
pfile = Path(project_file)
- if pfile.suffix in (".jpg", ".png", ".pyc"):
+ if pfile.suffix in IGNORED_SUFFIXES:
continue
- content += f".. tabbed:: {project_file}\n\n"
+ content += f"{ind(1)}.. tab-item:: {project_file}\n\n"
+
+ doc_rstinc_file = doc_file(project_dir, project_file)
+ if doc_rstinc_file:
+ indent = ind(2)
+ for line in doc_rstinc_file.read_text("utf-8").split("\n"):
+ content += indent + line + "\n"
+ content += "\n"
lexer = get_lexer(pfile)
- content += add_indent(f".. code-block:: {lexer}", 1)
+ content += add_indent(f"{ind(1)}.. code-block:: {lexer}", 1)
content += "\n"
_path = project_dir / project_file
_file_content = ""
try:
- with open(_path, "r") as _f:
+ with open(_path, "r", encoding="utf-8") as _f:
_file_content = remove_licenses(_f.read())
except UnicodeDecodeError as e:
- print(f"example_gallery: error decoding {_path}:{e}")
+ print(f"example_gallery: error decoding {project_dir}/{_path}:{e}",
+ file=sys.stderr)
+ raise
+ except FileNotFoundError as e:
+ print(f"example_gallery: error opening {project_dir}/{_path}:{e}",
+ file=sys.stderr)
raise
- content += add_indent(_file_content, 2)
+ content += add_indent(_file_content, 3)
content += "\n\n"
+
+ if file_format == Format.MD:
+ content += "```"
+
return content
def get_header_title(example_dir):
- _title = str(example_dir)
_index = example_dir.parts.index("examples")
- url_name = "/".join(example_dir.parts[_index + 1:])
- url = f"{BASE_URL}/{url_name}"
+ rel_path = "/".join(example_dir.parts[_index:])
+ _title = rel_path
+ url = f"{BASE_URL}/{rel_path}"
return (
"..\n This file was auto-generated by the 'examples_gallery' "
"script.\n Any change will be lost!\n\n"
@@ -157,23 +361,271 @@ def get_header_title(example_dir):
)
+def rel_path(from_path, to_path):
+ """Determine relative paths for paths that are not subpaths (where
+ relative_to() fails) via a common root."""
+ common = Path(*os.path.commonprefix([from_path.parts, to_path.parts]))
+ up_dirs = len(from_path.parts) - len(common.parts)
+ prefix = up_dirs * "../"
+ rel_to_common = os.fspath(to_path.relative_to(common))
+ return f"{prefix}{rel_to_common}"
+
+
+def read_rst_file(project_dir, project_files, doc_rst):
+ """Read the example .rst file and expand literal includes to project files
+ by relative paths to the example directory. Note: sphinx does not
+ handle absolute paths as expected, they need to be relative."""
+ content = ""
+ with open(doc_rst, encoding="utf-8") as doc_f:
+ content = doc_f.read()
+ if LITERAL_INCLUDE not in content:
+ return content
+
+ result = []
+ path_to_example = rel_path(EXAMPLES_DOC, project_dir)
+ for line in content.split("\n"):
+ if line.startswith(LITERAL_INCLUDE):
+ file = line[len(LITERAL_INCLUDE) + 1:].strip()
+ if file in project_files:
+ line = f"{LITERAL_INCLUDE} {path_to_example}/{file}"
+ result.append(line)
+ return "\n".join(result)
+
+
+def get_headline(text, file_format):
+ """Find the headline in the .rst file."""
+ if file_format == Format.RST:
+ underline = text.find("\n====")
+ if underline != -1:
+ start = text.rfind("\n", 0, underline - 1)
+ return text[start + 1:underline]
+ elif file_format == Format.MD:
+ headline = text.find("# ")
+ if headline != -1:
+ new_line = text.find("\n", headline + 1)
+ if new_line != -1:
+ return text[headline + 2:new_line].strip()
+ return ""
+
+
+def get_doc_source_file(original_doc_dir, example_name):
+ """Find the doc source file, return (Path, Format)."""
+ if original_doc_dir.is_dir():
+ for file_format in (Format.RST, Format.MD):
+ suffix = SUFFIXES[file_format]
+ result = original_doc_dir / f"{example_name}.{suffix}"
+ if result.is_file():
+ return result, file_format
+ return None, Format.RST
+
+
+def get_screenshot(image_dir, example_name):
+ """Find screen shot: We look for an image with the same
+ example_name first, if not, we select the first."""
+ if not image_dir.is_dir():
+ return None
+ images = [i for i in image_dir.glob("*") if i.is_file() and check_img_ext(i)]
+ example_images = [i for i in images if i.name.startswith(example_name)]
+ if example_images:
+ return example_images[0]
+ if images:
+ return images[0]
+ return None
+
+
+def write_resources(src_list, dst):
+ """Write a list of example resource paths to the dst path."""
+ for src in src_list:
+ resource_written = shutil.copy(src, dst / src.name)
+ if not opt_quiet:
+ print("Written resource:", resource_written)
+
+
+@dataclass
+class ExampleParameters:
+ """Parameters obtained from scanning the examples directory."""
+
+ def __init__(self):
+ self.file_format = Format.RST
+ self.src_doc_dir = self.src_doc_file_path = self.src_screenshot = None
+ self.extra_names = ""
+
+ example_dir: Path
+ module_name: str
+ example_name: str
+ extra_names: str
+ file_format: Format
+ target_doc_file: str
+ src_doc_dir: Path
+ src_doc_file_path: Path
+ src_screenshot: Path
+
+
+def detect_pyside_example(example_root, pyproject_file):
+ """Detemine parameters of a PySide example."""
+ p = ExampleParameters()
+
+ p.example_dir = pyproject_file.parent
+ if p.example_dir.name == "doc": # Dummy pyproject in doc dir (scriptableapplication)
+ p.example_dir = p.example_dir.parent
+
+ parts = p.example_dir.parts[len(example_root.parts):]
+ p.module_name = parts[0]
+ p.example_name = parts[-1]
+ # handling subdirectories besides the module level and the example
+ p.extra_names = "" if len(parts) == 2 else "_".join(parts[1:-1])
+
+ # Check for a 'doc' directory inside the example
+ src_doc_dir = p.example_dir / "doc"
+
+ if src_doc_dir.is_dir():
+ src_doc_file_path, fmt = get_doc_source_file(src_doc_dir, p.example_name)
+ if src_doc_file_path:
+ p.src_doc_file_path = src_doc_file_path
+ p.file_format = fmt
+ p.src_doc_dir = src_doc_dir
+ p.src_screenshot = get_screenshot(src_doc_dir, p.example_name)
+
+ target_suffix = SUFFIXES[p.file_format]
+ doc_file = f"example_{p.module_name}_{p.extra_names}_{p.example_name}.{target_suffix}"
+ p.target_doc_file = doc_file.replace("__", "_")
+ return p
+
+
+def detect_qt_example(example_root, pyproject_file):
+ """Detemine parameters of an example from a Qt repository."""
+ p = ExampleParameters()
+
+ p.example_dir = pyproject_file.parent
+ p.module_name = "Qt Demos"
+ p.example_name = p.example_dir.name
+ # Check for a 'doc' directory inside the example (qdoc)
+ doc_root = p.example_dir / "doc"
+ if doc_root.is_dir():
+ src_doc_file_path, fmt = get_doc_source_file(doc_root / "src", p.example_name)
+ if src_doc_file_path:
+ p.src_doc_file_path = src_doc_file_path
+ p.file_format = fmt
+ p.src_doc_dir = doc_root
+ p.src_screenshot = get_screenshot(doc_root / "images", p.example_name)
+
+ target_suffix = SUFFIXES[p.file_format]
+ p.target_doc_file = f"example_qtdemos_{p.example_name}.{target_suffix}"
+ return p
+
+
+def write_example(example_root, pyproject_file, pyside_example=True):
+ """Read the project file and documentation, create the .rst file and
+ copy the data. Return a tuple of module name and a dict of example data."""
+ p = (detect_pyside_example(example_root, pyproject_file) if pyside_example
+ else detect_qt_example(example_root, pyproject_file))
+
+ result = ExampleData()
+ result.example = p.example_name
+ result.module = p.module_name
+ result.extra = p.extra_names
+ result.doc_file = p.target_doc_file
+ result.file_format = p.file_format
+ result.abs_path = str(p.example_dir)
+ result.has_doc = bool(p.src_doc_file_path)
+ result.img_doc = p.src_screenshot
+
+ files = []
+ try:
+ with pyproject_file.open("r", encoding="utf-8") as pyf:
+ pyproject = json.load(pyf)
+ # iterate through the list of files in .pyproject and
+ # check if they exist, before appending to the list.
+ for f in pyproject["files"]:
+ if not Path(f).exists:
+ print(f"example_gallery: {f} listed in {pyproject_file} does not exist")
+ raise FileNotFoundError
+ else:
+ files.append(f)
+ except (json.JSONDecodeError, KeyError, FileNotFoundError) as e:
+ print(f"example_gallery: error reading {pyproject_file}: {e}")
+ raise
+
+ headline = ""
+ if files:
+ doc_file = EXAMPLES_DOC / p.target_doc_file
+ with open(doc_file, "w", encoding="utf-8") as out_f:
+ if p.src_doc_file_path:
+ content_f = read_rst_file(p.example_dir, files, p.src_doc_file_path)
+ headline = get_headline(content_f, p.file_format)
+ if not headline:
+ print(f"example_gallery: No headline found in {doc_file}",
+ file=sys.stderr)
+
+ # Copy other files in the 'doc' directory, but
+ # excluding the main '.rst' file and all the
+ # directories.
+ resources = []
+ if pyside_example:
+ for _f in p.src_doc_dir.glob("*"):
+ if _f != p.src_doc_file_path and not _f.is_dir():
+ resources.append(_f)
+ else: # Qt example: only use image.
+ if p.src_screenshot:
+ resources.append(p.src_screenshot)
+ write_resources(resources, EXAMPLES_DOC)
+ else:
+ content_f = get_header_title(p.example_dir)
+ content_f += get_code_tabs(files, pyproject_file.parent, p.file_format)
+ out_f.write(content_f)
+
+ if not opt_quiet:
+ print(f"Written: {doc_file}")
+ else:
+ if not opt_quiet:
+ print("Empty '.pyproject' file, skipping")
+
+ result.headline = headline
+
+ return (p.module_name, result)
+
+
+def example_sort_key(example: ExampleData):
+ name = example.example
+ return "AAA" + name if "gallery" in name else name
+
+
+def sort_examples(example):
+ result = {}
+ for module in example.keys():
+ result[module] = sorted(example.get(module), key=example_sort_key)
+ return result
+
+
+def scan_examples_dir(examples_dir, pyside_example=True):
+ """Scan a directory of examples."""
+ for pyproject_file in examples_dir.glob("**/*.pyproject"):
+ if pyproject_file.name != "examples.pyproject":
+ module_name, data = write_example(examples_dir, pyproject_file,
+ pyside_example)
+ if module_name not in examples:
+ examples[module_name] = []
+ examples[module_name].append(data)
+
+
if __name__ == "__main__":
# Only examples with a '.pyproject' file will be listed.
DIR = Path(__file__).parent
EXAMPLES_DOC = Path(f"{DIR}/../../sources/pyside6/doc/examples").resolve()
EXAMPLES_DIR = Path(f"{DIR}/../../examples/").resolve()
- BASE_URL = "https://code.qt.io/cgit/pyside/pyside-setup.git/tree/examples"
+ BASE_URL = "https://code.qt.io/cgit/pyside/pyside-setup.git/tree"
columns = 5
gallery = ""
parser = ArgumentParser(description=__doc__, formatter_class=RawTextHelpFormatter)
- TARGET_HELP = f"Directory into which to generate RST files (default: {str(EXAMPLES_DOC)})"
+ TARGET_HELP = f"Directory into which to generate Doc files (default: {str(EXAMPLES_DOC)})"
parser.add_argument("--target", "-t", action="store", dest="target_dir", help=TARGET_HELP)
+ parser.add_argument("--qt-src-dir", "-s", action="store", help="Qt source directory")
parser.add_argument("--quiet", "-q", action="store_true", help="Quiet")
options = parser.parse_args()
opt_quiet = options.quiet
if options.target_dir:
- EXAMPLES_DOC = Path(options.target_dir)
+ EXAMPLES_DOC = Path(options.target_dir).resolve()
# This main loop will be in charge of:
# * Getting all the .pyproject files,
@@ -183,121 +635,22 @@ if __name__ == "__main__":
examples = {}
# Create the 'examples' directory if it doesn't exist
- if not EXAMPLES_DOC.is_dir():
- EXAMPLES_DOC.mkdir()
-
- for pyproject_file in EXAMPLES_DIR.glob("**/*.pyproject"):
- if pyproject_file.name == "examples.pyproject":
- continue
- example_dir = pyproject_file.parent
- if example_dir.name == "doc": # Dummy pyproject in doc dir (scriptableapplication)
- example_dir = example_dir.parent
-
- parts = example_dir.parts[len(EXAMPLES_DIR.parts):]
-
- module_name = parts[0]
- example_name = parts[-1]
- # handling subdirectories besides the module level and the example
- extra_names = "" if len(parts) == 2 else "_".join(parts[1:-1])
-
- rst_file = f"example_{module_name}_{extra_names}_{example_name}.rst"
-
- def check_img_ext(i):
- EXT = (".png", ".jpg", ".jpeg", ".gif")
- if i.suffix in EXT:
- return True
- return False
-
- # Check for a 'doc' directory inside the example
- has_doc = False
- img_doc = None
- original_doc_dir = Path(example_dir / "doc")
- if original_doc_dir.is_dir():
- has_doc = True
- images = [i for i in original_doc_dir.glob("*") if i.is_file() and check_img_ext(i)]
- if len(images) > 0:
- # We look for an image with the same example_name first, if not, we select the first
- image_path = [i for i in images if example_name in str(i)]
- if not image_path:
- image_path = images[0]
- else:
- img_doc = image_path[0]
-
- if module_name not in examples:
- examples[module_name] = []
-
- examples[module_name].append(
- {
- "example": example_name,
- "module": module_name,
- "extra": extra_names,
- "rst": rst_file,
- "abs_path": str(example_dir),
- "has_doc": has_doc,
- "img_doc": img_doc,
- }
- )
-
- files = []
- try:
- with pyproject_file.open("r") as pyf:
- pyproject = json.load(pyf)
- files = pyproject["files"]
- except (json.JSONDecodeError, KeyError) as e:
- print(f"example_gallery: error reading {pyproject_file}: {e}")
- raise
-
- if files:
- rst_file_full = EXAMPLES_DOC / rst_file
-
- with open(rst_file_full, "w") as out_f:
- if has_doc:
- doc_rst = original_doc_dir / f"{example_name}.rst"
-
- with open(doc_rst) as doc_f:
- content_f = doc_f.read()
-
- # Copy other files in the 'doc' directory, but
- # excluding the main '.rst' file and all the
- # directories.
- for _f in original_doc_dir.glob("*"):
- if _f == doc_rst or _f.is_dir():
- continue
- src = _f
- dst = EXAMPLES_DOC / _f.name
-
- resource_written = shutil.copy(src, dst)
- if not opt_quiet:
- print("Written resource:", resource_written)
- else:
- content_f = get_header_title(example_dir)
- content_f += get_code_tabs(files, pyproject_file.parent)
- out_f.write(content_f)
-
- if not opt_quiet:
- print(f"Written: {EXAMPLES_DOC}/{rst_file}")
- else:
- if not opt_quiet:
- print("Empty '.pyproject' file, skipping")
-
- base_content = dedent(
- """\
- ..
- This file was auto-generated from the 'pyside-setup/tools/example_gallery'
- All editions in this file will be lost.
-
- |project| Examples
- ===================
-
- A collection of examples are provided with |project| to help new users
- to understand different use cases of the module.
-
- You can find all these examples inside the ``pyside-setup`` on the ``examples``
- directory, or you can access them after installing |pymodname| from ``pip``
- inside the ``site-packages/PySide6/examples`` directory.
-
- """
- )
+ # If it does exist, remove it and create a new one to start fresh
+ if EXAMPLES_DOC.is_dir():
+ shutil.rmtree(EXAMPLES_DOC, ignore_errors=True)
+ if not opt_quiet:
+ print("WARNING: Deleted old html directory")
+ EXAMPLES_DOC.mkdir(exist_ok=True)
+
+ scan_examples_dir(EXAMPLES_DIR)
+ if options.qt_src_dir:
+ qt_src = Path(options.qt_src_dir)
+ if not qt_src.is_dir():
+ print("Invalid Qt source directory: {}", file=sys.stderr)
+ sys.exit(-1)
+ scan_examples_dir(qt_src.parent / "qtdoc", pyside_example=False)
+
+ examples = sort_examples(examples)
# We generate a 'toctree' at the end of the file, to include the new
# 'example' rst files, so we get no warnings, and also that users looking
@@ -316,12 +669,14 @@ if __name__ == "__main__":
# Writing the main example rst file.
index_files = []
with open(f"{EXAMPLES_DOC}/index.rst", "w") as f:
- f.write(base_content)
- for module_name, e in sorted(examples.items()):
+ f.write(BASE_CONTENT)
+ for module_name in sorted(examples.keys(), key=module_sort_key):
+ e = examples.get(module_name)
for i in e:
- index_files.append(i["rst"])
- f.write(f"{module_name.title()}\n")
- f.write(f"{'*' * len(module_name.title())}\n")
+ index_files.append(i.doc_file)
+ title = module_title(module_name)
+ f.write(f"{title}\n")
+ f.write(f"{'*' * len(title)}\n")
f.write(get_module_gallery(e))
f.write("\n\n")
f.write(footer_index)