Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix video embeds, columns, and resource link breakage #2410

Merged
merged 14 commits into from
Oct 16, 2024
Merged
Binary file modified arcade/resources/assets/images/cybercity_background/foreground.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
Modified & re-released under the same license below:
below
Artwork created by Luis Zuno (@ansimuz)
https://ansimuz.itch.io/cyberpunk-street-environment

Expand Down
85 changes: 73 additions & 12 deletions doc/conf.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
#!/usr/bin/env python
"""Sphinx configuration file"""
from __future__ import annotations
from functools import cache
import logging
from pathlib import Path
from textwrap import dedent
from typing import Any, NamedTuple
import docutils.nodes
Expand All @@ -20,12 +23,78 @@

# --- Pre-processing Tasks

log = logging.getLogger('conf.py')
logging.basicConfig(level=logging.INFO)

HERE = Path(__file__).resolve()
REPO_LOCAL_ROOT = HERE.parent.parent
ARCADE_MODULE = REPO_LOCAL_ROOT / "arcade"
UTIL_DIR = REPO_LOCAL_ROOT / "util"

log.info(f"Absolute path for our conf.py : {str(HERE)!r}")
log.info(f"Absolute path for the repo root : {str(REPO_LOCAL_ROOT)!r}")
log.info(f"Absolute path for the arcade module : {str(REPO_LOCAL_ROOT)!r}")
log.info(f"Absolute path for the util dir : {str(UTIL_DIR)!r}")

# _temp_version = (REPO_LOCAL_ROOT / "arcade" / "VERSION").read_text().replace("-",'')

sys.path.insert(0, str(REPO_LOCAL_ROOT))
sys.path.insert(0, str(ARCADE_MODULE))
log.info(f"Inserted elements in system path: First two are now:")
for i in range(2):
log.info(f" {i}: {sys.path[i]!r}")

# Don't change to
# from arcade.version import VERSION
# or read the docs build will fail.
from version import VERSION # pyright: ignore [reportMissingImports]
log.info(f"Got version {VERSION!r}")

REPO_URL_BASE="https://github.com/pythonarcade/arcade"
if 'dev' in VERSION:
GIT_REF = 'development'
log.info(f"Got .dev release: using {GIT_REF!r}")
else:
GIT_REF = VERSION
log.info(f"Got real release: using {GIT_REF!r}")


# We'll pass this to our generation scripts to initialize their globals
RESOURCE_GLOBALS = dict(
GIT_REF=GIT_REF,
BASE_URL_REPO=REPO_URL_BASE,
# This double-bracket escapes brackets in f-strings
FMT_URL_REF_PAGE=f"{REPO_URL_BASE}/blob/{GIT_REF}/{{}}",
FMT_URL_REF_EMBED=f"{REPO_URL_BASE}/blob/{GIT_REF}/{{}}?raw=true",
)

def run_util(filename, run_name="__main__", init_globals=None):

full_absolute_path = UTIL_DIR / filename
full_str = str(full_absolute_path)

log.info(f"Running {full_str!r} with:")
log.info(f" run_name={run_name!r}")
kwargs = dict(run_name=run_name)
if init_globals is not None:
kwargs['init_globals'] = init_globals
log.info(f" init_globals={{")
num_left = len(init_globals)
for k, v in init_globals.items():
end = "," if num_left else ""
log.info(f" {k!r} : {v!r}{end}")
num_left -= num_left
log.info(f" }}")

runpy.run_path(full_str, **kwargs)

# Make thumbnails for the example code screenshots
runpy.run_path('../util/generate_example_thumbnails.py', run_name='__main__')
# Create a listing of the resources
runpy.run_path('../util/create_resources_listing.py', run_name='__main__')
run_util("generate_example_thumbnails.py")
# Create a tabular representation of the resources with embeds
run_util("create_resources_listing.py", init_globals=RESOURCE_GLOBALS)
# Run the generate quick API index script
runpy.run_path('../util/update_quick_index.py', run_name='__main__')
run_util('../util/update_quick_index.py')


autodoc_inherit_docstrings = False
autodoc_default_options = {
Expand All @@ -39,16 +108,8 @@
# Special methods in api docs gets a special prefix emoji
prettyspecialmethods_signature_prefix = '🧙'

sys.path.insert(0, os.path.abspath('..'))
sys.path.insert(0, os.path.abspath('../arcade'))

# Don't change to
# from arcade.version import VERSION
# or read the docs build will fail.
from version import VERSION # pyright: ignore [reportMissingImports]

RELEASE = VERSION

# -- General configuration ------------------------------------------------

# Add any Sphinx extension module names here, as strings. They can be
Expand Down
142 changes: 108 additions & 34 deletions util/create_resources_listing.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,15 @@

Generate quick API indexes in Restructured Text Format for Sphinx documentation.
"""
import math
import sys
from collections import defaultdict
from functools import lru_cache
from pathlib import Path
from typing import List
import logging

log = logging.getLogger(__name__)

# Ensure we get utility and Arcade imports first
sys.path.insert(0, str(Path(__file__).parent.resolve()))
Expand All @@ -14,13 +20,37 @@
import arcade
from doc_helpers.vfs import Vfs


def announce_templating(var_name):
_v = globals()[var_name]
log.warning(f"Templated {var_name} as {_v!r}")

# The following are provided via runpy.run_path's init_globals keyword
# in conf.py. Uncomment for easy debugger run without IDE config.
try:
_ = GIT_REF # noqa
except Exception as _:
GIT_REF = "development"
announce_templating("GIT_REF")
try:
_URL_BASE = "https://github.com/pythonarcade/arcade"
_ = FMT_URL_REF_PAGE # noqa
except Exception as _:
FMT_URL_REF_PAGE = f"{_URL_BASE}/blob/{GIT_REF}/{{}}"
announce_templating("FMT_URL_REF_PAGE")
try:
_ = FMT_URL_REF_EMBED # noqa
except Exception as _:
FMT_URL_REF_EMBED = f"{_URL_BASE}/blob/{GIT_REF}/{{}}?raw=true"
announce_templating("FMT_URL_REF_EMBED")


MODULE_DIR = Path(__file__).parent.resolve()
ARCADE_ROOT = MODULE_DIR.parent
RESOURCE_DIR = ARCADE_ROOT / "arcade" / "resources"
OUT_FILE = ARCADE_ROOT / "doc" / "api_docs" / "resources.rst"
RESOURCE_URL = "https://github.com/pythonarcade/arcade/blob/development/{}?raw=true"

COLUMNS = 3

# Metadata for the resource list: utils\create_resource_list.py
skip_extensions = [
".glsl",
Expand All @@ -39,6 +69,22 @@ def skipped_file(file_path: Path):
return file_path.suffix in skip_extensions


MAX_COLS: dict[str, int] = defaultdict(lambda: 3)
MAX_COLS[":resources:sounds/"] = 2


@lru_cache(maxsize=None)
def get_header_num_cols(resource_stub: str, n_files = math.inf) -> int:
return int(min(MAX_COLS[resource_stub], n_files))


@lru_cache(maxsize=None)
def get_column_widths_for_n(n: int) -> str:
width = str(100 // n)
return ' '.join((width for _ in range(n)))


@lru_cache(maxsize=None) # Cache b/c re-using elsewhere
def create_resource_path(
path: Path,
prefix: str = "",
Expand Down Expand Up @@ -71,18 +117,25 @@ def process_resource_directory(out, dir: Path):
# out.write("-" * len(cur_node.name) + "\n\n")

file_list = [item for item in path.iterdir() if not (item.is_dir() or skipped_file(item))]
if len(file_list) > 0:
num_files = len(file_list)
if num_files > 0:

# header_title = f":resources:{path.relative_to(RESOURCE_DIR).as_posix()}/"
header_title = create_resource_path(path, suffix="/")
if header_title == ":resources:images/":
raw_header = create_resource_path(path, suffix="/")
header_title = raw_header[:-2] if raw_header.endswith("./") else raw_header

if raw_header == ":resources:images/":
for f in file_list:
print(f.name)
# out.write(f"\n{header_title}\n")
# out.write("-" * (len(header_title)) + "\n\n")

n_cols = get_header_num_cols(raw_header, num_files)
widths = get_column_widths_for_n(n_cols)

out.write(f"\n")
out.write(f".. list-table:: {header_title}\n")
out.write(f" :widths: 33 33 33\n")
out.write(f".. list-table:: \"{header_title}\"\n")
out.write(f" :widths: {widths}\n")
out.write(f" :header-rows: 0\n")
out.write(f" :class: resource-table\n\n")

Expand All @@ -92,46 +145,65 @@ def process_resource_directory(out, dir: Path):
process_resource_directory(out, path)


SUFFIX_TO_AUDIO_TYPE = {
'.wav': 'x-wav',
'.ogg': 'ogg',
'.mp3': 'mpeg',
}
SUFFIX_TO_VIDEO_TYPE = {
'.mp4': 'mp4',
'.webm': 'webm',
'.avi': 'avi'
}

def process_resource_files(out, file_list: List[Path]):
start_row = True
cell_count = 0

prefix = create_resource_path(file_list[0].parent, suffix="/")

COLUMNS = get_header_num_cols(prefix, len(file_list))

log.info(f"Processing {prefix=!r} with {COLUMNS=!r}")
for path in file_list:
resource_path = path.relative_to(ARCADE_ROOT).as_posix()
suffix = path.suffix

if cell_count % COLUMNS == 0:
start_row = "*"
if path.suffix in [".png", ".jpg", ".gif", ".svg"]:
else:
start_row = " "
name = path.name
resource_copyable = f"{create_resource_path(path)}"
if suffix in [".png", ".jpg", ".gif", ".svg"]:
out.write(f" {start_row} - .. image:: ../../{resource_path}\n\n")
out.write(f" {path.name}\n")
cell_count += 1
elif path.suffix == ".wav":
file_path = RESOURCE_URL.format(resource_path)
out.write(f" {name}\n")
elif suffix in SUFFIX_TO_AUDIO_TYPE:
file_path = FMT_URL_REF_EMBED.format(resource_path)
src_type=SUFFIX_TO_AUDIO_TYPE[suffix]
out.write(f" {start_row} - .. raw:: html\n\n")
out.write(f" <audio controls><source src='{file_path}' type='audio/x-wav'></audio><br />{path.name}\n")
cell_count += 1
elif path.suffix == ".mp3":
file_path = RESOURCE_URL.format(resource_path)
out.write(f" <audio controls><source src='{file_path}' type='audio/{src_type}'></audio>\n")
out.write(f" <br /><code class='literal'>&quot;{resource_copyable}&quot;</code>\n")
# out.write(f" <br /><a href={FMT_URL_REF_PAGE.format(resource_path)}>{path.name} on GitHub</a>\n")
elif suffix in SUFFIX_TO_VIDEO_TYPE:
file_path = FMT_URL_REF_EMBED.format(resource_path)
src_type = SUFFIX_TO_VIDEO_TYPE[suffix]
out.write(f" {start_row} - .. raw:: html\n\n")
out.write(f" <audio controls><source src='{file_path}' type='audio/mpeg'></audio><br />{path.name}\n")
cell_count += 1
elif path.suffix == ".ogg":
file_path = RESOURCE_URL.format(resource_path)
out.write(f" {start_row} - .. raw:: html\n\n")
out.write(f" <audio controls><source src='{file_path}' type='audio/ogg'></audio><br />{path.name}\n")
cell_count += 1
elif path.suffix == ".glsl":
file_path = RESOURCE_URL.format(resource_path)
out.write(f" {start_row} - `{path.name} <{file_path}>`_\n")
# out.write(f" {start_row} - .. raw:: html\n\n")
# out.write(f" <audio controls><source src='{file_path}' type='audio/ogg'></audio><br />{path.name}\n")
cell_count += 1
out.write(f" <video style=\"max-width: 100%\" controls><source src='{file_path}' type='video/{src_type}'></video>\n")
out.write(f" <br /><code class='literal'>&quot;{resource_copyable}&quot;</code>\n")
elif suffix == ".glsl":
file_path = FMT_URL_REF_PAGE.format(resource_path)
out.write(f" {start_row} - `{path} <{file_path}>`_\n")
# Link Tiled maps
elif suffix == ".json":
file_path = FMT_URL_REF_PAGE.format(resource_path)
out.write(f" {start_row} - `{name} <{file_path}>`_\n")
else:
out.write(f" {start_row} - {path.name}\n")
cell_count += 1

start_row = " "
out.write(f" {start_row} - {name}\n")
# The below doesn't work because of how raw HTML / Sphinx images interact:
# out.write(f" <br /><code class='literal'>{resource_copyable}</code>\n")
cell_count += 1

# Finish any remaining columns with empty cells
while cell_count % COLUMNS > 0:
out.write(f" -\n")
cell_count += 1
Expand Down Expand Up @@ -161,8 +233,10 @@ def resources():
out.close()
print("Done creating resources.rst")


vfs = Vfs()


def main():
resources()
vfs.write()
Expand Down
Loading