Skip to content

Commit 1758bcb

Browse files
committed
starting to build replacement for Jekyll
1 parent 6375ea9 commit 1758bcb

File tree

4 files changed

+241
-0
lines changed

4 files changed

+241
-0
lines changed

docs/Makefile

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
# Manage plotly.js documentation.
22

33
JEKYLL=bundle exec jekyll
4+
RUN = uv run
45
SCHEMA_SRC=../test/plot-schema.json
56
SCHEMA_DST=_data/plotschema.json
67

@@ -14,6 +15,11 @@ build:
1415
cp ${SCHEMA_SRC} ${SCHEMA_DST}
1516
${JEKYLL} build
1617

18+
## reference: build reference documentation in ./tmp
19+
reference:
20+
@mkdir -p tmp
21+
${RUN} bin/make_reference_pages.py --schema ${SCHEMA_SRC} --outdir tmp _posts/reference_pages/javascript/2020-07-20-bar.html
22+
1723
## serve: display documentation
1824
serve:
1925
@mkdir -p _data

docs/bin/make_reference_pages.py

Lines changed: 221 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,221 @@
1+
"""Rebuild a reference page from the Jekyll HTML and plot schema JSON file."""
2+
3+
import argparse
4+
from html import escape
5+
import json
6+
from pathlib import Path
7+
import re
8+
import sys
9+
10+
11+
INCLUDE_RE = re.compile(
12+
r'\{%\s*include\s+posts/reference-trace.html\s+trace_name="(.+?)"\s+trace_data=site\.data\.plotschema\.traces\.(.+?)\s*%}'
13+
)
14+
TITLE_RE = re.compile(r"<h2>.+?<code>(.+?)</code>.+?</h2>")
15+
16+
17+
PLOT_SCHEMA_CONTENT = """\
18+
<div class="description">
19+
A <code>{trace_name}</code> trace is an object with the key <code>"type"</code> equal to <code>"{trace_data_attributes_type}"</code>
20+
(i.e. <code>{{"type": "{trace_data_attributes_type}"}}</code>) and any of the keys listed below.
21+
<br><br>{trace_data_meta_description}<br><br>
22+
</div>
23+
"""
24+
25+
PLOT_SCHEMA_REPLACEMENTS = (
26+
('*', '"'),
27+
("{array}", "array"),
28+
("{arrays}", "arrays"),
29+
("{object}", "object"),
30+
("{2D array}", "2D array"),
31+
)
32+
33+
OBJ_EXCLUDES = {
34+
"_arrayAttrRegexps",
35+
"_deprecated",
36+
"_isLinkedToArray",
37+
"_isSubplotObj",
38+
"description",
39+
"editType",
40+
"extras",
41+
"flags",
42+
"impliedEdits",
43+
"items",
44+
"magic_underscores",
45+
"role",
46+
"stream",
47+
"transforms"
48+
"uid",
49+
}
50+
51+
SKIP_MUSTMATCH = {
52+
"annotations",
53+
"coloraxis",
54+
"geo",
55+
"images",
56+
"mapbox",
57+
"polar",
58+
"scene",
59+
"shapes",
60+
"sliders",
61+
"smith",
62+
"ternary",
63+
"updatemenus",
64+
"xaxis",
65+
"yaxis",
66+
}
67+
68+
69+
def main():
70+
"""Main driver."""
71+
args = _parse_args()
72+
args.outdir.mkdir(parents=True, exist_ok=True)
73+
74+
schema = json.loads(args.schema.read_text())
75+
assert "traces" in schema, f"'traces' missing from {args.schema}"
76+
77+
for src_path in args.inputs:
78+
_log(args.verbose > 0, f"...{src_path}")
79+
src_content = src_path.read_text()
80+
81+
m = TITLE_RE.search(src_content)
82+
if _log(not m, f"failed to match title in {src_path}"):
83+
continue
84+
title = m.group(1)
85+
86+
m = INCLUDE_RE.search(src_content)
87+
if _log(not m, f"failed to match include in {src_path}"):
88+
continue
89+
if _log(m.group(1) != title, f"title {title} != include title {m.group(1)} in {src_path}"):
90+
continue
91+
trace_name = m.group(2)
92+
trace_data = schema["traces"].get(trace_name, None)
93+
if _log(trace_data is None, f"trace '{trace_name}' not found in {args.schema}"):
94+
continue
95+
96+
html = _reference_trace(args, schema, trace_name, trace_data)
97+
print(html)
98+
99+
100+
def _get_field(value, key, default=None):
101+
"""Simulate Jekyll's obj.field (which is 'nil' if 'obj' is a string)."""
102+
if isinstance(value, str):
103+
return default
104+
assert isinstance(value, dict), f"{value} not recognized"
105+
return value.get(key, default)
106+
107+
108+
def _log(condition, msg):
109+
"""Conditionally report progress."""
110+
if condition:
111+
print(msg, file=sys.stderr)
112+
return condition
113+
114+
115+
def _parse_args():
116+
"""Parse command-line arguments."""
117+
parser = argparse.ArgumentParser(description="Generate HTML reference documentation")
118+
parser.add_argument("inputs", nargs="+", type=Path, help="Input Jekyll files")
119+
parser.add_argument("--schema", type=Path, help="Path to plot schema JSON file")
120+
parser.add_argument("--outdir", type=Path, help="Output directory")
121+
parser.add_argument("--verbose", type=int, default=0, help="Integer verbosity level")
122+
return parser.parse_args()
123+
124+
125+
def _reference_block(args, accum, attributes, parent_link, parent_path, block, mustmatch=None):
126+
"""Generate HTML documentation for a trace's attributes."""
127+
accum.append("<ul>")
128+
for key, value in attributes.items():
129+
last_three = key[-3:]
130+
if (last_three == "src") or (key in OBJ_EXCLUDES):
131+
continue
132+
if _skip_mustmatch(key, mustmatch):
133+
continue
134+
accum.append("<li>")
135+
136+
id = f"{parent_link}-{key}"
137+
accum.append(f'<a class="attribute-name" id="{id}" href="#{id}">')
138+
accum.append(f" {key}")
139+
accum.append("</a>")
140+
141+
accum.append(f"<br><em>Parent:</em> <code>{parent_path.replace('-', '.')}</code>")
142+
143+
if (key == "type") and (block == "data"):
144+
accum.append("<br />")
145+
accum.append(f"<em>Type:</em> *{value}*")
146+
147+
if _get_field(value, "valType"):
148+
_reference_block_valtype(accum, value)
149+
150+
if _get_field(value, "dflt"):
151+
_reference_block_dflt(accum, value)
152+
153+
if _get_field(value, "items") and (_get_field(value, "valType") != "info_array"):
154+
_reference_block_array(accum, value)
155+
elif _get_field(value, "role") == "object":
156+
accum.append('<br><em>Type:</em> {{object}} containing one or more of the keys listed below.')
157+
158+
if _get_field(value, "description", "") != "":
159+
accum.append(f"<p>{escape(value.get('description'))}</p>")
160+
161+
if _get_field(value, "role") == "object":
162+
_reference_block_object(accum, value)
163+
164+
accum.append("</li>")
165+
accum.append("</ul>")
166+
167+
168+
def _reference_block_valtype(accum, value):
169+
"""Handle a value type."""
170+
171+
172+
def _reference_block_dflt(accum, value):
173+
"""Handle a default."""
174+
175+
176+
def _reference_block_array(accum, value):
177+
"""Handle an array."""
178+
179+
180+
def _reference_block_object(accum, value):
181+
"""Handle an object."""
182+
183+
184+
def _reference_trace(args, schema, trace_name, trace_data):
185+
"""Generate HTML documentation for a trace."""
186+
plot_schema_content = PLOT_SCHEMA_CONTENT.format(
187+
trace_name=trace_name,
188+
trace_data_attributes_type=trace_data["attributes"]["type"],
189+
trace_data_meta_description=trace_data["meta"]["description"],
190+
)
191+
plot_schema_content = _replace_special(plot_schema_content)
192+
accum = [plot_schema_content]
193+
194+
parent_link = trace_name
195+
parent_path = f"data[type={trace_name}]"
196+
attributes = trace_data["attributes"]
197+
_reference_block(args, accum, attributes, parent_link, parent_path, "data")
198+
199+
return "\n".join(accum)
200+
201+
202+
def _replace_special(text):
203+
"""Handle our funky special-case strings."""
204+
for original, replacement in PLOT_SCHEMA_REPLACEMENTS:
205+
text = text.replace(original, replacement)
206+
return text
207+
208+
209+
def _skip_mustmatch(key, mustmatch):
210+
if mustmatch is None:
211+
return False
212+
if mustmatch == "global":
213+
return key in SKIP_MUSTMATCH
214+
elif key != mustmatch:
215+
return True
216+
else:
217+
return False
218+
219+
220+
if __name__ == "__main__":
221+
main()

pyproject.toml

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
[project]
2+
name = "plotly.js"
3+
description = "Plotly JavaScript charting library"
4+
version = "3.1.0"
5+
requires-python = ">=3.12"
6+
dependencies = []

uv.lock

Lines changed: 8 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)