blueprints/reindex_postprocess.py
Alex Kirk caa7ea840c
Blueprint and Apps for your Personal Playground (#166)
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2026-01-27 13:49:42 +01:00

293 lines
10 KiB
Python

import json
import os
import re
import subprocess
import sys
from functools import lru_cache
highlighted_blueprints = {
'Feed Reader with the Friends Plugin',
'Gaming News',
'Skincare Blog',
'Non-profit Organization',
'Personal Resume',
'Coffee Shop',
'Personal Blog',
'University Website',
'Photography Portfolio',
'Art Gallery',
}
@lru_cache(maxsize=512)
def get_last_commit_for_file(path):
"""
Get the commit hash where this specific file was last changed.
This ensures URLs are stable and only change when the file itself changes.
"""
try:
result = subprocess.check_output(
['git', 'log', '-1', '--format=%H', '--', path],
text=True
).strip()
return result if result else 'trunk'
except Exception:
return 'trunk'
def build_raw_repo_url(path):
rel = path.lstrip('./').replace('\\', '/')
commit_hash = get_last_commit_for_file(path)
return 'https://raw.githubusercontent.com/wordpress/blueprints/{rev}/{path}'.format(
rev=commit_hash,
path=rel
)
def build_raw_blueprint_url(path):
return build_raw_repo_url(path)
def build_preview_url(path):
return 'https://playground.wordpress.net/?blueprint-url={0}'.format(build_raw_blueprint_url(path))
def build_edit_url(path):
return 'https://playground.wordpress.net/builder/builder.html?blueprint-url={0}'.format(build_raw_blueprint_url(path))
def resolve_screenshot_path(meta, blueprint_path):
screenshot = meta.get('screenshot')
if screenshot:
normalized = screenshot.replace('\\', '/').lstrip('./')
if normalized.startswith('http://') or normalized.startswith('https://'):
return normalized
if screenshot.startswith('/'):
return normalized
blueprint_dir = os.path.dirname(blueprint_path).replace('\\', '/')
return f"{blueprint_dir}/{normalized}".replace('//', '/')
blueprint_dir = os.path.dirname(blueprint_path)
return os.path.join(blueprint_dir, 'screenshot.jpg').replace('\\', '/')
def screenshot_source_exists(path):
if not path:
return False
if re.match(r'^[a-z]+://', path):
return True
absolute = os.path.abspath(path)
return os.path.exists(absolute)
def build_screenshot_html(preview, screenshot_path, title):
label = title or 'Blueprint'
if screenshot_source_exists(screenshot_path):
return '<p align="left"><img src="{src}" alt="{alt} screenshot" width="400"></p>'.format(
preview=preview,
src=screenshot_path,
alt=label
)
return '<p align="left"><em>No screenshot yet for {name}. Open it in Playground.</em></p>'.format(
name=label,
preview=preview
)
def build_apps_index():
index = {}
for root, dirs, files in os.walk('apps'):
for file in files:
if file.endswith('.json'):
path = os.path.join(root, file)
with open(path, 'r') as f:
data = json.load(f)
meta = data.get('meta', {})
index[path] = dict(meta)
index = dict(sorted(index.items(), key=lambda item: item[1].get('title', '')))
with open('apps.json', 'w') as f:
json.dump(index, f, indent=2)
return index
def build_json_index():
index = {}
for root, dirs, files in os.walk('blueprints'):
for file in files:
if file == 'blueprint.json':
path = os.path.join(root, file)
with open(path, 'r') as f:
data = json.load(f)
meta = data.get('meta', {})
meta_with_media = dict(meta)
screenshot_path = resolve_screenshot_path(meta, path)
if screenshot_source_exists(screenshot_path):
if screenshot_path.startswith('http://') or screenshot_path.startswith('https://'):
screenshot_url = screenshot_path
else:
screenshot_url = build_raw_repo_url(screenshot_path)
meta_with_media['screenshot_url'] = screenshot_url
# Add featured flag based on whether the title is in highlighted_blueprints
meta_with_media['featured'] = meta.get('title', '') in highlighted_blueprints
index[path] = meta_with_media
# Sort index alphabetically by title
index = dict(sorted(index.items(), key=lambda item: (
item[1].get('title', '') not in highlighted_blueprints,
item[1].get('title', '')
)))
with open('index.json', 'w') as f:
json.dump(index, f, indent=2)
return index
def get_dot_template_files():
dot_template_files = []
for root, dirs, files in os.walk('.'):
for file in files:
if file.endswith('.template'):
path = os.path.join(root, file)
dot_template_files.append(path)
return dot_template_files
def build_gallery_html(index_data=None):
"""
Generate gallery.html from gallery.html.template and embed the blueprint index JSON
so the front-end can render without additional network requests.
"""
template_file = 'gallery.html.template'
output_file = 'gallery.html'
if os.path.exists(template_file):
with open(template_file, 'r') as f:
content = f.read()
placeholder = '{BLUEPRINT_INDEX_JSON}'
if placeholder in content:
if index_data is None:
if os.path.exists('index.json'):
with open('index.json', 'r') as index_file:
index_data = json.load(index_file)
else:
index_data = {}
serialized_index = json.dumps(index_data or {}, ensure_ascii=False, separators=(',', ':'))
serialized_index = serialized_index.replace('</script', '<\\/script')
content = content.replace(placeholder, serialized_index)
with open(output_file, 'w') as f:
f.write(content)
def build_markdown_table():
with open('index.json', 'r') as f:
index = json.load(f)
entries = []
for path, meta in index.items():
title = meta.get('title', '')
display_title = title or 'Untitled Blueprint'
if display_title in highlighted_blueprints:
display_title = f"<strong>{display_title}</strong>"
preview = build_preview_url(path)
screenshot_path = resolve_screenshot_path(meta, path)
screenshot_html = build_screenshot_html(preview, screenshot_path, title)
description = meta.get('description', '')
description_html = f'<p>{description}</p>' if description else ''
preview_button = f'<p><a href="{preview}"><img src="playground-preview-button.svg" alt="Try it in Playground" width="220"></a></p>'
edit_url = build_edit_url(path)
author = meta.get('author', '').strip()
if author:
author_link = f'<a href="https://github.com/{author}">@{author}</a>'
meta_line = (
'<p><small>'
f'By {author_link} • <a href="https://github.com/wordpress/blueprints/blob/trunk/{path}">View source</a> '
f'• <a href="{edit_url}">Edit</a>'
'</small></p>'
)
else:
meta_line = (
'<p><small>'
f'<a href="https://github.com/wordpress/blueprints/blob/trunk/{path}">View source</a> '
f'• <a href="{edit_url}">Edit</a>'
'</small></p>'
)
entry = (
f'<h2>{display_title}</h2>\n'
f'{description_html}\n'
f'{meta_line}\n'
f'{preview_button}\n'
f'{screenshot_html}\n'
'<br clear="all">\n'
)
entries.append(entry)
formatted_table = '\n\n'.join(entries)
# Replace "{BLUEPRINTS_TABLE}" in all the *.template files
DOT_TEMPLATE_FILES = get_dot_template_files()
for file in DOT_TEMPLATE_FILES:
with open(file, 'r') as f:
template = f.read()
with open(file.replace('.template', ''), 'w') as f:
f.write(re.sub(r'{BLUEPRINTS_TABLE}', ''.join(formatted_table), template))
def rewrite_branch_urls_to_trunk():
with open('index.json', 'r') as f:
index = json.load(f)
for path, meta in index.items():
with open(path, 'r') as f:
original_blueprint = f.read()
json_blueprint = json.loads(original_blueprint)
map_url_resources(json_blueprint, branch_url_mapper)
new_blueprint = json.dumps(json_blueprint, indent="\t")
# Only write if content changed to avoid unnecessary modifications
if original_blueprint != new_blueprint:
with open(path, 'w') as f:
f.write(new_blueprint)
def map_url_resources(blueprint_fragment, mapper):
"""
Recursively map URL resources in a blueprint using a mapper function.
A URL resource is a dictionary with a "resource": "url" entry, and a "url" key.
"""
if isinstance(blueprint_fragment, dict):
if 'resource' in blueprint_fragment and blueprint_fragment['resource'] == 'url' and 'url' in blueprint_fragment:
blueprint_fragment['url'] = mapper(blueprint_fragment['url'])
else:
for key, value in blueprint_fragment.items():
map_url_resources(value, mapper)
elif isinstance(blueprint_fragment, list):
for item in blueprint_fragment:
map_url_resources(item, mapper)
def branch_url_mapper(url):
"""
Rewrite a raw.githubusercontent.com URL to point to the trunk branch.
>>> branch_url_mapper('https://raw.githubusercontent.com/wordpress/blueprints/my-branch/blueprint.json')
'https://raw.githubusercontent.com/wordpress/blueprints/trunk/blueprint.json'
>>> branch_url_mapper('https://raw.githubusercontent.com/wordpress/blueprints/trunk/blueprint.json')
'https://raw.githubusercontent.com/wordpress/blueprints/trunk/blueprint.json'
"""
if not url.startswith("https://raw.githubusercontent.com"):
return url
return re.sub(r'https://raw.githubusercontent.com/wordpress/blueprints/([^/]+)', r'https://raw.githubusercontent.com/wordpress/blueprints/trunk', url)
if '--test' in sys.argv:
print("Running doctests")
import doctest
doctest.testmod()
else:
print("Reindexing")
index_data = build_json_index()
build_apps_index()
build_markdown_table()
build_gallery_html(index_data)
rewrite_branch_urls_to_trunk()