-
Notifications
You must be signed in to change notification settings - Fork 27
Story 2430: Library Subpage Integration #2524
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
Open
julhoang
wants to merge
30
commits into
develop
Choose a base branch
from
feat/library-subpage-content
base: develop
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
+2,171
−153
Open
Changes from all commits
Commits
Show all changes
30 commits
Select commit
Hold shift + click to select a range
fc2b513
feat: add per-library Slack channel URL with cppalliance fallback
julhoang 93053c7
feat: wire up documentation card
julhoang e0d39b7
feat: version-aware all contributors with identity-based dedup
julhoang e3c4e7a
feat: ingest and parse optional meta/website.adoc into LibraryVersion
julhoang 58210c6
feat: render website.adoc About/Install/Quick Start/Freeform with emp…
julhoang ea05c29
feat: render Designed For as a markdown card with V3 demo
julhoang 3377d80
feat: wire up benchmark card
julhoang 7192424
feat: wire up compiler explorer URL
julhoang f39e785
feat: store raw website.adoc source with admin section-status editing
julhoang 98cc580
chore: remove caption and source from the benchmark section
julhoang 47eaa0c
fix: fix cropped benchmark card
julhoang 3e1f442
feat: skip website.adoc refresh during beta cycle
julhoang bd50f7c
refactor: build category tag links via Category.get_filter_url
julhoang 52541b1
refactor: extract LibraryDetail v3 context helpers
julhoang 679e0d8
fix: harden website.adoc fetch against timeouts and connection errors
julhoang fa09761
fix: guard godbolt version map against malformed API payload
julhoang 5ac2177
fix: add default to library slack_url field
julhoang bc581c2
fix: count all releases in All Contributors for master/develop views
julhoang 0289ac9
fix: preserve // lines inside code fences in freeform parser
julhoang e8c117d
chore: fix template wording and exception chaining nitpicks
julhoang 2187471
chore: improve website demo file content
julhoang 3d86f88
feat: implement autoflow masonry layout
julhoang 26ececb
chore: renamed added_text template param to first_boost_version
julhoang 4781434
style: improve card ordering on mobile view
julhoang e576330
style: add missing green theme to the benchmark card
julhoang 1cdb5c6
feat: add placeholder for library subpage empty state
julhoang 10d70d1
style: nit adjustment to card order
julhoang 28804ed
chore: update fetch source ref from master to develop branch
julhoang c6eaf61
doc: add regex pattern examples
julhoang 3b9143a
fix: ensure versions are sorted by numerical value instead of name
julhoang File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,122 @@ | ||
| """Build Compiler Explorer (godbolt.org) "Edit in Compiler Explorer" links. | ||
|
|
||
| A library's website.adoc [#playground] code is encoded into a godbolt.org | ||
| ``/clientstate/<base64>`` URL with Boost (matching the viewed release, or the | ||
| newest Compiler Explorer offers) pre-selected. Building the URL is a pure, | ||
| offline operation; only the Boost version→id lookup needs the CE libraries API, | ||
| which is cached. | ||
|
|
||
| Verified against Compiler Explorer: | ||
| - ClientState: {sessions:[{id,language,source,compilers:[{id,options,libs}]}]} | ||
| - libs entry: {"id": "boost", "version": "<version id>"} (e.g. "190") | ||
| - version ids come from /api/libraries/c++ -> boost.versions[].id | ||
| - the base64 is URL-safe (decoded server-side via Buffer.from(x, "base64")) | ||
| """ | ||
|
|
||
| import base64 | ||
| import json | ||
|
|
||
| import requests | ||
| import structlog | ||
| from django.core.cache import caches | ||
|
|
||
| logger = structlog.get_logger() | ||
|
|
||
| GODBOLT_BASE_URL = "https://godbolt.org" | ||
| GODBOLT_LIBRARIES_URL = f"{GODBOLT_BASE_URL}/api/libraries/c++" | ||
| GODBOLT_CLIENTSTATE_URL = f"{GODBOLT_BASE_URL}/clientstate/" | ||
|
|
||
| # A current, widely-available gcc on Compiler Explorer. If it is ever retired | ||
| # the playground still opens with the code pre-filled — only the preselected | ||
| # compiler is lost — so this is safe to bump when convenient. | ||
| DEFAULT_COMPILER_ID = "g142" # gcc 14.2 | ||
|
|
||
| _CACHE_ALIAS = "static_content" | ||
| _BOOST_VERSIONS_CACHE_KEY = "godbolt_boost_version_map" | ||
| _BOOST_VERSIONS_TTL = 60 * 60 * 24 # one day | ||
|
|
||
|
|
||
| def _fetch_boost_version_map(): | ||
| """Fetch {version_string: version_id} for Boost from the CE libraries API. | ||
|
|
||
| e.g. {"1.90.0": "190", ...}. Returns {} on any failure. | ||
| """ | ||
| try: | ||
| response = requests.get( | ||
| GODBOLT_LIBRARIES_URL, | ||
| headers={"Accept": "application/json"}, | ||
| timeout=5, | ||
| ) | ||
| response.raise_for_status() | ||
| libraries = response.json() | ||
| for library in libraries: | ||
| if library.get("id") == "boost": | ||
| return { | ||
| version["version"]: version["id"] | ||
| for version in library.get("versions", []) | ||
| if version.get("version") and version.get("id") | ||
| } | ||
| except (requests.RequestException, ValueError, AttributeError, TypeError, KeyError): | ||
| logger.exception("godbolt_libraries_fetch_failed") | ||
| return {} | ||
| return {} | ||
|
|
||
|
|
||
| def get_boost_version_map(): | ||
| """Return the cached Boost version→id map, fetching + caching on a miss.""" | ||
| cache = caches[_CACHE_ALIAS] | ||
| version_map = cache.get(_BOOST_VERSIONS_CACHE_KEY) | ||
| if version_map is None: | ||
| version_map = _fetch_boost_version_map() | ||
| cache.set(_BOOST_VERSIONS_CACHE_KEY, version_map, _BOOST_VERSIONS_TTL) | ||
| return version_map | ||
|
|
||
|
|
||
| def _version_sort_key(version_string): | ||
| return [int(part) if part.isdigit() else 0 for part in version_string.split(".")] | ||
|
|
||
|
|
||
| def resolve_boost_version_id(boost_version, version_map=None): | ||
| """Map a Boost release (e.g. "1.90.0") to a CE version id (e.g. "190"). | ||
|
|
||
| Falls back to the newest version Compiler Explorer offers when there is no | ||
| exact match (CE lags new Boost releases). Returns None when CE exposes no | ||
| Boost versions. | ||
| """ | ||
| if version_map is None: | ||
| version_map = get_boost_version_map() | ||
| if not version_map: | ||
| return None | ||
| if boost_version in version_map: | ||
| return version_map[boost_version] | ||
| newest = max(version_map, key=_version_sort_key) | ||
| return version_map[newest] | ||
|
|
||
|
|
||
| def build_compiler_explorer_url( | ||
| code, boost_version, version_map=None, compiler_id=DEFAULT_COMPILER_ID | ||
| ): | ||
| """Build a godbolt.org /clientstate/ URL with `code` pre-filled and Boost | ||
| selected (matching `boost_version`, else the newest CE offers). | ||
|
|
||
| Returns None when there is no code to load. Boost is omitted from the | ||
| session (rather than blocking the link) if CE's version list is unavailable. | ||
| """ | ||
| if not code: | ||
| return None | ||
|
|
||
| version_id = resolve_boost_version_id(boost_version, version_map=version_map) | ||
| libs = [{"id": "boost", "version": version_id}] if version_id else [] | ||
| client_state = { | ||
| "sessions": [ | ||
| { | ||
| "id": 1, | ||
| "language": "c++", | ||
| "source": code, | ||
| "compilers": [{"id": compiler_id, "options": "", "libs": libs}], | ||
| } | ||
| ] | ||
| } | ||
| payload = json.dumps(client_state, separators=(",", ":")).encode("utf-8") | ||
| encoded = base64.urlsafe_b64encode(payload).decode("ascii").rstrip("=") | ||
| return f"{GODBOLT_CLIENTSTATE_URL}{encoded}" | ||
65 changes: 65 additions & 0 deletions
65
libraries/management/commands/import_library_version_website_adoc.py
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,65 @@ | ||
| import djclick as click | ||
|
|
||
| from django.conf import settings | ||
|
|
||
| from libraries.tasks import store_library_version_website_adoc | ||
| from versions.models import Version | ||
|
|
||
|
|
||
| @click.command() | ||
| @click.option( | ||
| "--release", | ||
| help="Boost version number (example: 1.81.0). A partial value processes all " | ||
| "matching versions (example: '--release=1.7' processes 1.70.0, 1.71.0, ...).", | ||
| ) | ||
| @click.option( | ||
| "--new", | ||
| default=True, | ||
| type=click.BOOL, | ||
| help="True (default): process the most recent version only. False: process all " | ||
| "versions >= --min-version. Overridden by --release.", | ||
| ) | ||
| @click.option( | ||
| "--min-version", | ||
| default=settings.MINIMUM_BOOST_VERSION, | ||
| help="Minimum Boost version to process (default: settings.MINIMUM_BOOST_VERSION).", | ||
| ) | ||
| def command(release: str, new: bool, min_version: str): | ||
| """Fetch, parse, and store each library's meta/website.adoc for the targeted | ||
| Boost versions. | ||
|
|
||
| The most recent version is fetched from `develop` (freshest maintainer content, | ||
| matching the daily task); older versions are fetched from their release tag (the | ||
| frozen snapshot, matching release import). A repo without the file is left | ||
| untouched. | ||
| """ | ||
| # Order/compare on the numeric version_array so boost-1.100.0 > boost-1.71.0 | ||
| # (plain name ordering is lexicographic and breaks once minor/patch hits 100). | ||
| min_version_parts = [int(part) for part in min_version.split(".")] | ||
| version_qs = ( | ||
| Version.objects.with_partials() | ||
| .active() | ||
| .with_version_split() | ||
| .filter(version_array__gte=min_version_parts) | ||
| ) | ||
| most_recent = ( | ||
| version_qs.filter(beta=False, full_release=True) | ||
| .order_by("-version_array") | ||
| .first() | ||
| ) | ||
|
|
||
| if release: | ||
| versions = list( | ||
| version_qs.filter(name__icontains=release).order_by("-version_array") | ||
| ) | ||
| elif new: | ||
| versions = [most_recent] if most_recent else [] | ||
| else: | ||
| versions = list(version_qs.order_by("-version_array")) | ||
|
|
||
| for version in versions: | ||
| ref = "develop" if version == most_recent else version.name | ||
| click.secho(f"Processing {version.name} (ref={ref})...", fg="green") | ||
| store_library_version_website_adoc(version, ref=ref) | ||
|
|
||
| click.secho("Finished importing website.adoc content.", fg="green") |
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.