Skip to content

Commit 8331e50

Browse files
authored
Fix video embeds, columns, and resource link breakage (#2410)
* Centralize resource embed URLs from conf.py * Run create_resources_listing.py via main() instead of as __main__ * Move git ref and version read earlier in conf.py * Template in globals via init_globals instead of complicated passing * Encapsulate runpath.run_py call with default args * Further cleanup of resource gen * Remove unused variables * Stop doing repeated . access on path.suffix * Use dict for audio and video mimetype info in tags * Remove cruft from GLSL embed code path (it's not an audio file) * For n < 3 files, reduce the number of columns * Add video embeds * Link GitHub pages for Tiled map .json files * Move config closer to top * Add quotes to copyable literals and string-like paths * Fix brittle relative paths in conf.py * Set top-level pathlib.Path-based constants * Add more logging for debug convenience * Fix spacing + rename default max cols * Add override for sound listings to avoid overflowing the content pane horizontally * Asset display tweak * Quote resource folder captions + fix ":resources:./"
1 parent b8e803b commit 8331e50

File tree

4 files changed

+183
-46
lines changed

4 files changed

+183
-46
lines changed
Loading

arcade/resources/assets/images/cybercity_background/public-license.txt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
Modified & re-released under the same license below:
2+
below
13
Artwork created by Luis Zuno (@ansimuz)
24
https://ansimuz.itch.io/cyberpunk-street-environment
35

doc/conf.py

Lines changed: 73 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,9 @@
11
#!/usr/bin/env python
22
"""Sphinx configuration file"""
3+
from __future__ import annotations
34
from functools import cache
5+
import logging
6+
from pathlib import Path
47
from textwrap import dedent
58
from typing import Any, NamedTuple
69
import docutils.nodes
@@ -20,12 +23,78 @@
2023

2124
# --- Pre-processing Tasks
2225

26+
log = logging.getLogger('conf.py')
27+
logging.basicConfig(level=logging.INFO)
28+
29+
HERE = Path(__file__).resolve()
30+
REPO_LOCAL_ROOT = HERE.parent.parent
31+
ARCADE_MODULE = REPO_LOCAL_ROOT / "arcade"
32+
UTIL_DIR = REPO_LOCAL_ROOT / "util"
33+
34+
log.info(f"Absolute path for our conf.py : {str(HERE)!r}")
35+
log.info(f"Absolute path for the repo root : {str(REPO_LOCAL_ROOT)!r}")
36+
log.info(f"Absolute path for the arcade module : {str(REPO_LOCAL_ROOT)!r}")
37+
log.info(f"Absolute path for the util dir : {str(UTIL_DIR)!r}")
38+
39+
# _temp_version = (REPO_LOCAL_ROOT / "arcade" / "VERSION").read_text().replace("-",'')
40+
41+
sys.path.insert(0, str(REPO_LOCAL_ROOT))
42+
sys.path.insert(0, str(ARCADE_MODULE))
43+
log.info(f"Inserted elements in system path: First two are now:")
44+
for i in range(2):
45+
log.info(f" {i}: {sys.path[i]!r}")
46+
47+
# Don't change to
48+
# from arcade.version import VERSION
49+
# or read the docs build will fail.
50+
from version import VERSION # pyright: ignore [reportMissingImports]
51+
log.info(f"Got version {VERSION!r}")
52+
53+
REPO_URL_BASE="https://github.com/pythonarcade/arcade"
54+
if 'dev' in VERSION:
55+
GIT_REF = 'development'
56+
log.info(f"Got .dev release: using {GIT_REF!r}")
57+
else:
58+
GIT_REF = VERSION
59+
log.info(f"Got real release: using {GIT_REF!r}")
60+
61+
62+
# We'll pass this to our generation scripts to initialize their globals
63+
RESOURCE_GLOBALS = dict(
64+
GIT_REF=GIT_REF,
65+
BASE_URL_REPO=REPO_URL_BASE,
66+
# This double-bracket escapes brackets in f-strings
67+
FMT_URL_REF_PAGE=f"{REPO_URL_BASE}/blob/{GIT_REF}/{{}}",
68+
FMT_URL_REF_EMBED=f"{REPO_URL_BASE}/blob/{GIT_REF}/{{}}?raw=true",
69+
)
70+
71+
def run_util(filename, run_name="__main__", init_globals=None):
72+
73+
full_absolute_path = UTIL_DIR / filename
74+
full_str = str(full_absolute_path)
75+
76+
log.info(f"Running {full_str!r} with:")
77+
log.info(f" run_name={run_name!r}")
78+
kwargs = dict(run_name=run_name)
79+
if init_globals is not None:
80+
kwargs['init_globals'] = init_globals
81+
log.info(f" init_globals={{")
82+
num_left = len(init_globals)
83+
for k, v in init_globals.items():
84+
end = "," if num_left else ""
85+
log.info(f" {k!r} : {v!r}{end}")
86+
num_left -= num_left
87+
log.info(f" }}")
88+
89+
runpy.run_path(full_str, **kwargs)
90+
2391
# Make thumbnails for the example code screenshots
24-
runpy.run_path('../util/generate_example_thumbnails.py', run_name='__main__')
25-
# Create a listing of the resources
26-
runpy.run_path('../util/create_resources_listing.py', run_name='__main__')
92+
run_util("generate_example_thumbnails.py")
93+
# Create a tabular representation of the resources with embeds
94+
run_util("create_resources_listing.py", init_globals=RESOURCE_GLOBALS)
2795
# Run the generate quick API index script
28-
runpy.run_path('../util/update_quick_index.py', run_name='__main__')
96+
run_util('../util/update_quick_index.py')
97+
2998

3099
autodoc_inherit_docstrings = False
31100
autodoc_default_options = {
@@ -39,16 +108,8 @@
39108
# Special methods in api docs gets a special prefix emoji
40109
prettyspecialmethods_signature_prefix = '🧙'
41110

42-
sys.path.insert(0, os.path.abspath('..'))
43-
sys.path.insert(0, os.path.abspath('../arcade'))
44-
45-
# Don't change to
46-
# from arcade.version import VERSION
47-
# or read the docs build will fail.
48-
from version import VERSION # pyright: ignore [reportMissingImports]
49111

50112
RELEASE = VERSION
51-
52113
# -- General configuration ------------------------------------------------
53114

54115
# Add any Sphinx extension module names here, as strings. They can be

util/create_resources_listing.py

Lines changed: 108 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -3,9 +3,15 @@
33
44
Generate quick API indexes in Restructured Text Format for Sphinx documentation.
55
"""
6+
import math
67
import sys
8+
from collections import defaultdict
9+
from functools import lru_cache
710
from pathlib import Path
811
from typing import List
12+
import logging
13+
14+
log = logging.getLogger(__name__)
915

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

23+
24+
def announce_templating(var_name):
25+
_v = globals()[var_name]
26+
log.warning(f"Templated {var_name} as {_v!r}")
27+
28+
# The following are provided via runpy.run_path's init_globals keyword
29+
# in conf.py. Uncomment for easy debugger run without IDE config.
30+
try:
31+
_ = GIT_REF # noqa
32+
except Exception as _:
33+
GIT_REF = "development"
34+
announce_templating("GIT_REF")
35+
try:
36+
_URL_BASE = "https://github.com/pythonarcade/arcade"
37+
_ = FMT_URL_REF_PAGE # noqa
38+
except Exception as _:
39+
FMT_URL_REF_PAGE = f"{_URL_BASE}/blob/{GIT_REF}/{{}}"
40+
announce_templating("FMT_URL_REF_PAGE")
41+
try:
42+
_ = FMT_URL_REF_EMBED # noqa
43+
except Exception as _:
44+
FMT_URL_REF_EMBED = f"{_URL_BASE}/blob/{GIT_REF}/{{}}?raw=true"
45+
announce_templating("FMT_URL_REF_EMBED")
46+
47+
1748
MODULE_DIR = Path(__file__).parent.resolve()
1849
ARCADE_ROOT = MODULE_DIR.parent
1950
RESOURCE_DIR = ARCADE_ROOT / "arcade" / "resources"
2051
OUT_FILE = ARCADE_ROOT / "doc" / "api_docs" / "resources.rst"
21-
RESOURCE_URL = "https://github.com/pythonarcade/arcade/blob/development/{}?raw=true"
2252

23-
COLUMNS = 3
53+
2454
# Metadata for the resource list: utils\create_resource_list.py
2555
skip_extensions = [
2656
".glsl",
@@ -39,6 +69,22 @@ def skipped_file(file_path: Path):
3969
return file_path.suffix in skip_extensions
4070

4171

72+
MAX_COLS: dict[str, int] = defaultdict(lambda: 3)
73+
MAX_COLS[":resources:sounds/"] = 2
74+
75+
76+
@lru_cache(maxsize=None)
77+
def get_header_num_cols(resource_stub: str, n_files = math.inf) -> int:
78+
return int(min(MAX_COLS[resource_stub], n_files))
79+
80+
81+
@lru_cache(maxsize=None)
82+
def get_column_widths_for_n(n: int) -> str:
83+
width = str(100 // n)
84+
return ' '.join((width for _ in range(n)))
85+
86+
87+
@lru_cache(maxsize=None) # Cache b/c re-using elsewhere
4288
def create_resource_path(
4389
path: Path,
4490
prefix: str = "",
@@ -71,18 +117,25 @@ def process_resource_directory(out, dir: Path):
71117
# out.write("-" * len(cur_node.name) + "\n\n")
72118

73119
file_list = [item for item in path.iterdir() if not (item.is_dir() or skipped_file(item))]
74-
if len(file_list) > 0:
120+
num_files = len(file_list)
121+
if num_files > 0:
122+
75123
# header_title = f":resources:{path.relative_to(RESOURCE_DIR).as_posix()}/"
76-
header_title = create_resource_path(path, suffix="/")
77-
if header_title == ":resources:images/":
124+
raw_header = create_resource_path(path, suffix="/")
125+
header_title = raw_header[:-2] if raw_header.endswith("./") else raw_header
126+
127+
if raw_header == ":resources:images/":
78128
for f in file_list:
79129
print(f.name)
80130
# out.write(f"\n{header_title}\n")
81131
# out.write("-" * (len(header_title)) + "\n\n")
82132

133+
n_cols = get_header_num_cols(raw_header, num_files)
134+
widths = get_column_widths_for_n(n_cols)
135+
83136
out.write(f"\n")
84-
out.write(f".. list-table:: {header_title}\n")
85-
out.write(f" :widths: 33 33 33\n")
137+
out.write(f".. list-table:: \"{header_title}\"\n")
138+
out.write(f" :widths: {widths}\n")
86139
out.write(f" :header-rows: 0\n")
87140
out.write(f" :class: resource-table\n\n")
88141

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

94147

148+
SUFFIX_TO_AUDIO_TYPE = {
149+
'.wav': 'x-wav',
150+
'.ogg': 'ogg',
151+
'.mp3': 'mpeg',
152+
}
153+
SUFFIX_TO_VIDEO_TYPE = {
154+
'.mp4': 'mp4',
155+
'.webm': 'webm',
156+
'.avi': 'avi'
157+
}
158+
95159
def process_resource_files(out, file_list: List[Path]):
96-
start_row = True
97160
cell_count = 0
98161

162+
prefix = create_resource_path(file_list[0].parent, suffix="/")
163+
164+
COLUMNS = get_header_num_cols(prefix, len(file_list))
165+
166+
log.info(f"Processing {prefix=!r} with {COLUMNS=!r}")
99167
for path in file_list:
100168
resource_path = path.relative_to(ARCADE_ROOT).as_posix()
169+
suffix = path.suffix
101170

102171
if cell_count % COLUMNS == 0:
103172
start_row = "*"
104-
if path.suffix in [".png", ".jpg", ".gif", ".svg"]:
173+
else:
174+
start_row = " "
175+
name = path.name
176+
resource_copyable = f"{create_resource_path(path)}"
177+
if suffix in [".png", ".jpg", ".gif", ".svg"]:
105178
out.write(f" {start_row} - .. image:: ../../{resource_path}\n\n")
106-
out.write(f" {path.name}\n")
107-
cell_count += 1
108-
elif path.suffix == ".wav":
109-
file_path = RESOURCE_URL.format(resource_path)
179+
out.write(f" {name}\n")
180+
elif suffix in SUFFIX_TO_AUDIO_TYPE:
181+
file_path = FMT_URL_REF_EMBED.format(resource_path)
182+
src_type=SUFFIX_TO_AUDIO_TYPE[suffix]
110183
out.write(f" {start_row} - .. raw:: html\n\n")
111-
out.write(f" <audio controls><source src='{file_path}' type='audio/x-wav'></audio><br />{path.name}\n")
112-
cell_count += 1
113-
elif path.suffix == ".mp3":
114-
file_path = RESOURCE_URL.format(resource_path)
184+
out.write(f" <audio controls><source src='{file_path}' type='audio/{src_type}'></audio>\n")
185+
out.write(f" <br /><code class='literal'>&quot;{resource_copyable}&quot;</code>\n")
186+
# out.write(f" <br /><a href={FMT_URL_REF_PAGE.format(resource_path)}>{path.name} on GitHub</a>\n")
187+
elif suffix in SUFFIX_TO_VIDEO_TYPE:
188+
file_path = FMT_URL_REF_EMBED.format(resource_path)
189+
src_type = SUFFIX_TO_VIDEO_TYPE[suffix]
115190
out.write(f" {start_row} - .. raw:: html\n\n")
116-
out.write(f" <audio controls><source src='{file_path}' type='audio/mpeg'></audio><br />{path.name}\n")
117-
cell_count += 1
118-
elif path.suffix == ".ogg":
119-
file_path = RESOURCE_URL.format(resource_path)
120-
out.write(f" {start_row} - .. raw:: html\n\n")
121-
out.write(f" <audio controls><source src='{file_path}' type='audio/ogg'></audio><br />{path.name}\n")
122-
cell_count += 1
123-
elif path.suffix == ".glsl":
124-
file_path = RESOURCE_URL.format(resource_path)
125-
out.write(f" {start_row} - `{path.name} <{file_path}>`_\n")
126-
# out.write(f" {start_row} - .. raw:: html\n\n")
127-
# out.write(f" <audio controls><source src='{file_path}' type='audio/ogg'></audio><br />{path.name}\n")
128-
cell_count += 1
191+
out.write(f" <video style=\"max-width: 100%\" controls><source src='{file_path}' type='video/{src_type}'></video>\n")
192+
out.write(f" <br /><code class='literal'>&quot;{resource_copyable}&quot;</code>\n")
193+
elif suffix == ".glsl":
194+
file_path = FMT_URL_REF_PAGE.format(resource_path)
195+
out.write(f" {start_row} - `{path} <{file_path}>`_\n")
196+
# Link Tiled maps
197+
elif suffix == ".json":
198+
file_path = FMT_URL_REF_PAGE.format(resource_path)
199+
out.write(f" {start_row} - `{name} <{file_path}>`_\n")
129200
else:
130-
out.write(f" {start_row} - {path.name}\n")
131-
cell_count += 1
132-
133-
start_row = " "
201+
out.write(f" {start_row} - {name}\n")
202+
# The below doesn't work because of how raw HTML / Sphinx images interact:
203+
# out.write(f" <br /><code class='literal'>{resource_copyable}</code>\n")
204+
cell_count += 1
134205

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

236+
164237
vfs = Vfs()
165238

239+
166240
def main():
167241
resources()
168242
vfs.write()

0 commit comments

Comments
 (0)