From 929f7ec4952170fcc91e84ee50207025006a7ba8 Mon Sep 17 00:00:00 2001 From: Rito Rhymes Date: Sun, 3 May 2026 20:23:19 -0400 Subject: [PATCH 1/4] Prepare Theme for Local Build Rendering Render external requires entries as direct links without forcing Zola to resolve them as local pages. This supports targeted --only rendering, where cross-repo proposal dependencies can be rewritten to external URLs instead of local pages. Move the theme demo homepage badges into front matter, a homepage_badges macro, and theme-owned styles so the demo homepage stays markdown-friendly. Declare Zola 0.22.1 as the minimum supported version, enable bottom footnotes, and update markdown highlighting to the class-based config with github-light/dark themes and ABNF/CSL-JSON grammars. --- config.toml | 18 +++++++++++------- content/_index.md | 16 +++++++++------- sass/assets/css/style.scss | 28 ++++++++++++++++++++++++++++ templates/eip.html | 6 ++++++ templates/index.html | 12 ++++++++++++ templates/macros.html | 24 ++++++++++++++++++++++-- theme.toml | 4 ++-- 7 files changed, 90 insertions(+), 18 deletions(-) diff --git a/config.toml b/config.toml index 81e0f5e..a160d5f 100644 --- a/config.toml +++ b/config.toml @@ -22,15 +22,19 @@ generate_feeds = true internal_level = "warn" [markdown] -# Whether to do syntax highlighting -# Theme can be customised by setting the `highlight_theme` variable to a theme supported by Zola -highlight_code = true +bottom_footnotes = true -highlight_theme = "css" +[markdown.highlighting] +error_on_missing_language = true -highlight_themes_css = [ - { theme = "base16-ocean-dark", filename = "assets/css/syntax-theme-dark.css" }, - { theme = "base16-ocean-light", filename = "assets/css/syntax-theme-light.css" }, +style = "class" + +light_theme = "github-light" +dark_theme = "github-dark" + +extra_grammars = [ + "syntaxes/abnf/ABNF.json", + "syntaxes/csl-json/CSL-JSON.json", ] [extra.github] diff --git a/content/_index.md b/content/_index.md index bbf4869..cdf17d9 100644 --- a/content/_index.md +++ b/content/_index.md @@ -1,13 +1,15 @@ +++ +[extra] +homepage_badges = [ + { href = "https://discord.gg/Nz6rtfJ8Cu", image = "https://dcbadge.limes.pink/api/server/Nz6rtfJ8Cu?style=flat", alt = "Badge for EIP Editor Discord channel" }, + { href = "https://discord.gg/EVTQ9crVgQ", image = "https://dcbadge.limes.pink/api/server/EVTQ9crVgQ?style=flat", alt = "Badge for Ethereum R&D Discord channel" }, + { href = "https://discord.gg/mRzPXmmYEA", image = "https://dcbadge.limes.pink/api/server/mRzPXmmYEA?style=flat", alt = "Badge for Ethereum Wallets Discord channel" }, + { href = "/atom.xml", image = "https://img.shields.io/badge/rss-Everything-red.svg", alt = "RSS feed for everything" }, + { href = "/status/last-call/atom.xml", image = "https://img.shields.io/badge/rss-Last%20Calls-red.svg", alt = "RSS feed for last calls" }, +] +++ -

EIPs - Discord channel for ECH eip-editer - Discord channel for Eth R&D eip-editing - Discord server for discussions about proposals that impact Ethereum wallets - RSS - RSS -

+# EIPs

Ethereum Improvement Proposals (EIPs) describe standards for the Ethereum platform, including core protocol specifications, client APIs, and contract standards. Network upgrades are discussed separately in the Ethereum Project Management repository.

diff --git a/sass/assets/css/style.scss b/sass/assets/css/style.scss index 4bf4052..a56bd86 100644 --- a/sass/assets/css/style.scss +++ b/sass/assets/css/style.scss @@ -33,6 +33,34 @@ $content-width: 1152px; vertical-align: baseline; } +.homepage-badges { + display: flex; + flex-wrap: wrap; + align-items: center; + gap: 0.5rem; + margin: -0.25rem 0 1.25rem; +} + +.homepage-badge-link { + display: inline-flex; + align-items: center; + line-height: 1; +} + +.homepage-badge-link:hover { + text-decoration: none; +} + +.homepage-badge-link img { + display: block; + max-width: 100%; + height: 20px; +} + +.homepage-badge-link .badge { + font-size: 0.875rem; +} + .footer-col-wrapper { color: #111; } diff --git a/templates/eip.html b/templates/eip.html index 0c959ec..5881b6c 100644 --- a/templates/eip.html +++ b/templates/eip.html @@ -230,11 +230,17 @@

Requires {% for required in page.extra.requires -%} + {% if required is starting_with("http") %} + {% set required_segment = required | split(pat="/") | last %} + {% set required_label = required_segment | replace(from="eip-", to="EIP-") | replace(from="erc-", to="ERC-") %} + {{ required_label }} + {% else %} {% set path = required | trim_start_matches(pat="@/") %} {% set page = get_page(path=path) %} {{ macros::eip_number(page=page) }} + {% endif %} {%- if not loop.last %}, {% endif -%} {%- endfor %} diff --git a/templates/index.html b/templates/index.html index 575141f..5cd5ace 100644 --- a/templates/index.html +++ b/templates/index.html @@ -1,5 +1,17 @@ {% extends "base.html" %} +{% import "macros.html" as macros %} {% block content %} +{% if section.extra.homepage_badges %} +{% set badge_markup = macros::homepage_badges(badges=section.extra.homepage_badges) %} +{% set content_with_badges = section.content | replace(from="

", to="" ~ badge_markup) %} +{% if content_with_badges == section.content %} +{{ badge_markup | safe }} {{ section.content | safe }} +{% else %} +{{ content_with_badges | safe }} +{% endif %} +{% else %} +{{ section.content | safe }} +{% endif %} {% endblock content %} diff --git a/templates/macros.html b/templates/macros.html index 1ec4536..4ce27b7 100644 --- a/templates/macros.html +++ b/templates/macros.html @@ -1,7 +1,27 @@ {%- macro eip_number(page) -%} {%- if page.extra.category and page.extra.category == "ERC" -%} - ERC- + ERC- {%- else -%} - EIP- + EIP- {%- endif -%}{{- page.extra.number -}} {%- endmacro eip_number -%} + +{%- macro homepage_badges(badges) -%} +{%- if badges -%} +
+ {%- for badge in badges -%} + + {%- if badge.image -%} + {{ badge.alt | default(value=badge.label | default(value='Homepage badge')) }} + {%- else -%} + {{ badge.label }} + {%- endif -%} + + {%- endfor -%} +
+{%- endif -%} +{%- endmacro homepage_badges -%} diff --git a/theme.toml b/theme.toml index d0df4e4..f58d1d0 100644 --- a/theme.toml +++ b/theme.toml @@ -1,9 +1,9 @@ name = "eips-theme" description = "A theme for Ethereum Improvement Proposals" license = "MIT" -homepage = "https://github.com/eips-wg/eips-theme" +homepage = "https://github.com/eips-wg/theme" # The minimum version of Zola required -min_version = "0.17.2" +min_version = "0.22.1" # An optional live demo URL demo = "https://eips-wg.github.io/eips-theme" From bd01be9695fff07efa89f2ce8ad7823ec254f6fc Mon Sep 17 00:00:00 2001 From: Rito Rhymes Date: Sun, 3 May 2026 20:23:57 -0400 Subject: [PATCH 2/4] Add Theme Development Setup Add POSIX and PowerShell setup scripts for theme contributors that reuse or install build-eips, ensure a supported Zola is available, and bootstrap the surrounding workspace through an active proposal repo. Document the theme local development workflow, including workspace layout, setup flags, serve versus preview, server/base URL settings, targeted rendering, and source/output overrides. Keep this focused on setup and documentation; theme rendering behavior continues to be owned by the earlier theme PR. --- README.md | 146 ++++++++++ scripts/dev-setup | 646 ++++++++++++++++++++++++++++++++++++++++++ scripts/dev-setup.ps1 | 609 +++++++++++++++++++++++++++++++++++++++ 3 files changed, 1401 insertions(+) create mode 100644 README.md create mode 100755 scripts/dev-setup create mode 100644 scripts/dev-setup.ps1 diff --git a/README.md b/README.md new file mode 100644 index 0000000..9f8ee87 --- /dev/null +++ b/README.md @@ -0,0 +1,146 @@ +# EIPs Theme + +Zola theme used by the Ethereum Improvement Proposal (EIP) site, including EIPs and ERCs. This repo contains the shared templates, Sass, static assets, syntax definitions, and Zola configuration used when rendering proposal content. + +## Local Development + +### Minimum Requirements + +Local workspace commands require these tools on `PATH`: + +* Git +* `build-eips` +* Zola 0.22.1 + +Git must be installed separately. The setup script locates or installs `build-eips` and Zola, adds locally installed tool directories to `PATH` for the current shell session, and prints guidance for making those `PATH` changes permanent. + +### Bootstrap The Workspace + +Run the setup script once from this repo. + +Linux and macOS: + +```sh +./scripts/dev-setup +``` + +Windows PowerShell: + +```powershell +.\scripts\dev-setup.ps1 +``` + +The setup script initializes the workspace one directory above this repo, runs `build-eips doctor`, and prints the next local commands. + +After setup, the generated workspace guide is available at `../WORKSPACE.md`. Use that file for the full command reference and workspace details. + +The script expects the theme repo to live inside the surrounding multi-repo workspace. After setup, the workspace has this layout: + +```text +EIPs-project/ +├── .build-eips.toml +├── WORKSPACE.md +├── .local-build/ +├── EIPs/ +├── ERCs/ +└── theme/ +``` + +The script anchors setup through `../EIPs` by default and clones `https://github.com/ethereum/EIPs.git` there when that checkout is missing. Set `ACTIVE_REPO_ROOT` to use another proposal repo checkout: + +```sh +ACTIVE_REPO_ROOT=../ERCs ./scripts/dev-setup +``` + +Validate the workspace at any point with: + +```sh +build-eips -C ../EIPs doctor +``` + +### Advanced Setup Options + +Pass optional flags through the script when you want to work on the system tooling or an additional proposal repo: + +```sh +./scripts/dev-setup --platform-dev +./scripts/dev-setup --template +./scripts/dev-setup --template --platform-dev +``` + +`--template` adds the optional `template/` proposal template repo. `--platform-dev` adds local `preprocessor/` and `eipw/` checkouts for build system development. + +After setup, run local site commands against the active proposal repo: + +```sh +build-eips -C ../EIPs check +build-eips -C ../EIPs serve +build-eips -C ../EIPs doctor +``` + +### Serve And Preview + +Local serving keeps two distinct modes: + +* `build-eips serve` for the runtime dev loop +* `build-eips preview` for serving already-built static output (`build-eips build` must be run first) + +`build-eips serve` runs Zola's fast serve mode under the hood. It watches tracked edits in the active proposal repo and incrementally updates the local site for content changes. + +It also watches tracked edits under `theme/` in the workspace. Theme changes can take longer to apply because they affect the whole rendered site. During `serve`, staging a new theme file with `git add` triggers a theme rescan; no extra file edit or restart should be needed. + +`serve --clean` ignores active-repo dirty edits but still watches the local theme. + +`build-eips preview` serves the resolved output directory for the active repo without invoking Zola, preprocessing markdown, or rebuilding anything. If the output directory does not exist yet, it fails and tells you to run `build-eips build` first. + +### Local Server And Base URL + +The `[server]` table in `.build-eips.toml` controls the local bind address for both `serve` and `preview`; the default is `127.0.0.1:1111`. Per-command `--host` and `--port` flags override `.build-eips.toml` for one run. These settings do not change build base URLs. + +The `[site].base_url` value in `.build-eips.toml` is the default local rendered site URL. Starter `.build-eips.toml` files set it to `http://127.0.0.1:1111`. If you change `[server].port`, update `[site].base_url` too when generated links should match the local server. + +Per-command `--base-url` on `build` or `serve` is a one-run override and wins over `.build-eips.toml`, including with staging or parity commands. + +`preview` serves existing output, so build with `--base-url` first when previewed HTML should contain a different local link target. + +### Target Specific Proposals + +Full local `build` and `serve` runs can take time because they process every proposal file. Use targeted rendering when you only need to test a few proposals or theme changes against a small proposal set: + +```bash +build-eips serve --only 555 +build-eips build --only 555 +build-eips build --only 555 678 +``` + +You can also set a default target list in the workspace `.build-eips.toml`: + +```toml +[render] +only = [555, 678] +``` + +CLI `--only` replaces `[render].only` for that run. For edge cases and exact filtering behavior, see `../WORKSPACE.md`. + +### Source And Output Overrides + +Workspace-local sources come from the standard workspace layout. The local theme is `workspace/theme`, and local sibling repos are `workspace/` from the active repo manifest. + +Use `--remote-siblings` when you need to force remote sibling proposal sources for a single command. + +Use global `--build-root ` when you want a separate prepared repo and output directory, for example to compare two builds side by side. The path replaces the default `.local-build/` location for each command where you pass it, so use the same `--build-root` value when serving or previewing builds. + + +Example: + +```bash +build-eips -C /work/EIPs-project/EIPs --build-root /tmp/eips-local build --base-url http://127.0.0.1:1111 +build-eips -C /work/EIPs-project/EIPs --build-root /tmp/eips-staging --staging build --base-url http://127.0.0.1:1112 + +build-eips -C /work/EIPs-project/EIPs --build-root /tmp/eips-local preview --port 1111 +build-eips -C /work/EIPs-project/EIPs --build-root /tmp/eips-staging preview --port 1112 + +# Or using serve +build-eips -C /work/EIPs-project/EIPs --build-root /tmp/eips-local serve --port 1111 +build-eips -C /work/EIPs-project/EIPs --build-root /tmp/eips-staging --staging serve --port 1112 +``` diff --git a/scripts/dev-setup b/scripts/dev-setup new file mode 100755 index 0000000..50bd5ff --- /dev/null +++ b/scripts/dev-setup @@ -0,0 +1,646 @@ +#!/bin/sh + +set -eu + +DEFAULT_ACTIVE_REPO_URL=https://github.com/ethereum/EIPs.git + +say() { + printf '%s\n' "$*" +} + +die() { + printf 'error: %s\n' "$*" >&2 + exit 1 +} + +shell_quote() { + printf "'%s'" "$(printf '%s' "$1" | sed "s/'/'\\\\''/g")" +} + +absolute_path() { + case "$1" in + /*) + printf '%s\n' "$1" + ;; + *) + printf '%s/%s\n' "$INVOCATION_DIR" "$1" + ;; + esac +} + +ensure_active_repo_root() { + if [ "$ACTIVE_REPO_EXPLICIT" = true ]; then + if [ ! -d "$ACTIVE_REPO_ROOT" ]; then + die "configured ACTIVE_REPO_ROOT does not exist: $ACTIVE_REPO_ROOT. Fix ACTIVE_REPO_ROOT or unset it to let this script clone EIPs." + fi + if [ ! -e "$ACTIVE_REPO_ROOT/.git" ]; then + die "configured ACTIVE_REPO_ROOT is not a git checkout: $ACTIVE_REPO_ROOT. Fix ACTIVE_REPO_ROOT or unset it to let this script clone EIPs." + fi + else + if [ ! -e "$ACTIVE_REPO_ROOT" ]; then + command -v git >/dev/null 2>&1 || die "need git to clone default proposal repo from $DEFAULT_ACTIVE_REPO_URL" + + say "No active proposal repo found at $ACTIVE_REPO_ROOT." + say "Cloning default proposal repo from $DEFAULT_ACTIVE_REPO_URL" + say "This may take a few minutes..." + if ! git clone "$DEFAULT_ACTIVE_REPO_URL" "$ACTIVE_REPO_ROOT"; then + die "failed to clone default proposal repo from $DEFAULT_ACTIVE_REPO_URL to $ACTIVE_REPO_ROOT" + fi + say "Cloned default proposal repo to $ACTIVE_REPO_ROOT" + fi + + if [ ! -d "$ACTIVE_REPO_ROOT" ]; then + die "default active proposal repo path exists but is not a directory: $ACTIVE_REPO_ROOT. Remove it or set ACTIVE_REPO_ROOT." + fi + if [ ! -e "$ACTIVE_REPO_ROOT/.git" ]; then + die "default active proposal repo path exists but is not a git checkout: $ACTIVE_REPO_ROOT. Remove it or set ACTIVE_REPO_ROOT." + fi + fi + + ACTIVE_REPO_ROOT=$(CDPATH= cd -- "$ACTIVE_REPO_ROOT" && pwd) +} + +normalize_dir_for_path_compare() { + dir=$1 + [ -n "$dir" ] || return 1 + + while [ "$dir" != "/" ] && [ "${dir%/}" != "$dir" ]; do + dir=${dir%/} + done + + case "$dir" in + /*) + absolute_dir=$dir + ;; + *) + absolute_dir=$(pwd -P)/$dir + ;; + esac + + if [ -d "$absolute_dir" ]; then + (CDPATH= cd -P -- "$absolute_dir" 2>/dev/null && pwd -P) + else + printf '%s\n' "$absolute_dir" + fi +} + +path_contains_dir() { + target=$(normalize_dir_for_path_compare "$1") || return 1 + old_ifs=$IFS + IFS=: + for path_entry in ${PATH:-}; do + [ -n "$path_entry" ] || continue + candidate=$(normalize_dir_for_path_compare "$path_entry") || continue + if [ "$candidate" = "$target" ]; then + IFS=$old_ifs + return 0 + fi + done + IFS=$old_ifs + return 1 +} + +move_dir_to_front_of_script_path() { + dir=$1 + target=$(normalize_dir_for_path_compare "$dir") || return 1 + new_path= + old_ifs=$IFS + IFS=: + for path_entry in ${PATH:-}; do + [ -n "$path_entry" ] || continue + candidate=$(normalize_dir_for_path_compare "$path_entry") || continue + if [ "$candidate" = "$target" ]; then + continue + fi + + if [ -n "$new_path" ]; then + new_path=$new_path:$path_entry + else + new_path=$path_entry + fi + done + IFS=$old_ifs + + if [ -n "$new_path" ]; then + updated_path=$dir:$new_path + else + updated_path=$dir + fi + + if [ "${PATH:-}" != "$updated_path" ]; then + PATH=$updated_path + add_path_note "$dir" + fi +} + +add_path_note() { + dir=$1 + target=$(normalize_dir_for_path_compare "$dir") || return 1 + old_ifs=$IFS + IFS=' +' + for path_note in $PATH_NOTES; do + candidate=$(normalize_dir_for_path_compare "$path_note") || continue + if [ "$candidate" = "$target" ]; then + IFS=$old_ifs + return 0 + fi + done + IFS=$old_ifs + + case " +$PATH_NOTES +" in + *" +$dir +"*) + return 0 + ;; + esac + + if [ -n "$PATH_NOTES" ]; then + PATH_NOTES=$PATH_NOTES' +'$dir + else + PATH_NOTES=$dir + fi +} + +pick_home_install_dir() { + [ -n "${HOME:-}" ] || return 1 + dir=$HOME/.local/bin + mkdir -p "$dir" 2>/dev/null || return 1 + [ -d "$dir" ] || return 1 + [ -w "$dir" ] || return 1 + printf '%s\n' "$dir" +} + +pick_writable_path_dir() { + old_ifs=$IFS + IFS=: + for dir in ${PATH:-}; do + [ -n "$dir" ] || continue + [ -d "$dir" ] || continue + [ -w "$dir" ] || continue + printf '%s\n' "$dir" + IFS=$old_ifs + return 0 + done + IFS=$old_ifs + return 1 +} + +pick_install_dir() { + if dir=$(pick_home_install_dir); then + printf '%s\n' "$dir" + return 0 + fi + + pick_writable_path_dir +} + +download() { + url=$1 + destination=$2 + + if command -v curl >/dev/null 2>&1; then + curl -fsSL "$url" -o "$destination" + return 0 + fi + + if command -v wget >/dev/null 2>&1; then + wget -qO "$destination" "$url" + return 0 + fi + + die "need curl or wget to download release binaries" +} + +file_sha256() { + path=$1 + + if command -v sha256sum >/dev/null 2>&1; then + sha256sum "$path" | awk '{ print $1 }' | tr 'A-F' 'a-f' + return 0 + fi + + if command -v shasum >/dev/null 2>&1; then + shasum -a 256 "$path" | awk '{ print $1 }' | tr 'A-F' 'a-f' + return 0 + fi + + if command -v openssl >/dev/null 2>&1; then + openssl dgst -sha256 -r "$path" | awk '{ print $1 }' | tr 'A-F' 'a-f' + return 0 + fi + + die "need sha256sum, shasum, or openssl to verify release checksums" +} + +normalized_sidecar_text() { + sidecar_path=$1 + bom=$(printf '\357\273\277') + cr=$(printf '\r') + first_three=$(dd if="$sidecar_path" bs=3 count=1 2>/dev/null || true) + + if [ "$first_three" = "$bom" ]; then + dd if="$sidecar_path" bs=3 skip=1 2>/dev/null | tr -d "$cr" + else + tr -d "$cr" < "$sidecar_path" + fi +} + +verify_archive_checksum() { + archive_path=$1 + sidecar_path=$2 + archive_name=$3 + + [ -f "$sidecar_path" ] || die "missing checksum sidecar: $sidecar_path" + + checksum_lines=$(normalized_sidecar_text "$sidecar_path" | sed '/^[[:space:]]*$/d') + checksum_line_count=$(printf '%s\n' "$checksum_lines" | sed '/^[[:space:]]*$/d' | wc -l | tr -d ' ') + [ "$checksum_line_count" = "1" ] || die "checksum sidecar must contain exactly one checksum line" + + set -f + set -- $checksum_lines + set +f + [ "$#" -eq 2 ] || die "checksum sidecar must contain only a hash and archive filename" + + expected_hash=$(printf '%s' "$1" | tr 'A-F' 'a-f') + expected_name=$2 + + [ ${#expected_hash} -eq 64 ] || die "checksum sidecar hash must be 64 hex characters" + case "$expected_hash" in + *[!0123456789abcdef]*) + die "checksum sidecar hash must be hex" + ;; + esac + case "$expected_name" in + */*|*\\*) + die "checksum sidecar filename must be a basename" + ;; + esac + [ "$expected_name" = "$archive_name" ] || die "checksum sidecar filename '$expected_name' does not match '$archive_name'" + + actual_hash=$(file_sha256 "$archive_path") + [ "$actual_hash" = "$expected_hash" ] || die "checksum mismatch for $archive_name" +} + +verify_file_hash() { + archive_path=$1 + expected_hash=$2 + archive_name=$3 + + actual_hash=$(file_sha256 "$archive_path") + [ "$actual_hash" = "$expected_hash" ] || die "checksum mismatch for $archive_name" +} + +cleanup_tmpdir() { + if [ -n "${INSTALL_TMP_TO_CLEAN:-}" ]; then + rm -f "$INSTALL_TMP_TO_CLEAN" + INSTALL_TMP_TO_CLEAN= + fi + if [ -n "${TMPDIR_TO_CLEAN:-}" ]; then + rm -rf "$TMPDIR_TO_CLEAN" + TMPDIR_TO_CLEAN= + fi +} + +cleanup_for_signal() { + cleanup_tmpdir + trap - EXIT INT TERM HUP QUIT + exit 1 +} + +set_cleanup_traps() { + trap cleanup_tmpdir EXIT + trap cleanup_for_signal INT TERM HUP QUIT +} + +clear_cleanup_traps() { + trap - EXIT INT TERM HUP QUIT +} + +ensure_install_dir() { + [ -n "${INSTALL_DIR:-}" ] && return 0 + + INSTALL_DIR=$(pick_install_dir) || die "could not find a writable install directory" + move_dir_to_front_of_script_path "$INSTALL_DIR" +} + +install_build_eips() { + case "$(uname -s)" in + Linux) + archive_name=build-eips-ubuntu.tar.xz + ;; + Darwin) + archive_name=build-eips-macos.tar.xz + ;; + *) + die "unsupported operating system for automatic build-eips installation" + ;; + esac + + command -v tar >/dev/null 2>&1 || die "need tar to unpack the build-eips release archive" + ensure_install_dir + + tmpdir=$(mktemp -d) + TMPDIR_TO_CLEAN=$tmpdir + set_cleanup_traps + + archive_path=$tmpdir/$archive_name + sidecar_path=$tmpdir/$archive_name.sha256 + release_base_url=https://github.com/eips-wg/preprocessor/releases/latest/download + + say "Installing build-eips from $release_base_url/$archive_name" + download "$release_base_url/$archive_name" "$archive_path" + download "$release_base_url/$archive_name.sha256" "$sidecar_path" + verify_archive_checksum "$archive_path" "$sidecar_path" "$archive_name" + + tar -xf "$archive_path" -C "$tmpdir" + [ -f "$tmpdir/build-eips" ] || die "release archive did not contain build-eips" + + install_tmp=$(mktemp "$INSTALL_DIR/.build-eips.XXXXXX") || die "could not create temporary install file in $INSTALL_DIR" + INSTALL_TMP_TO_CLEAN=$install_tmp + cp "$tmpdir/build-eips" "$install_tmp" + chmod +x "$install_tmp" + mv "$install_tmp" "$INSTALL_DIR/build-eips" + INSTALL_TMP_TO_CLEAN= + + BUILD_EIPS=$INSTALL_DIR/build-eips + cleanup_tmpdir + clear_cleanup_traps +} + +unsupported_zola_platform() { + os=$1 + arch=$2 + die "Unsupported platform $os/$arch for automatic Zola install. Install Zola 0.22.1 manually from https://github.com/getzola/zola/releases and ensure it is on PATH." +} + +is_linux_musl() { + if command -v ldd >/dev/null 2>&1 && ldd --version 2>&1 | grep -qi musl; then + return 0 + fi + + set -- /lib/ld-musl-*.so.1 + [ -e "$1" ] +} + +select_zola_release() { + os=$(uname -s) + arch=$(uname -m) + + case "$os" in + Linux) + if is_linux_musl; then + case "$arch" in + x86_64) + ZOLA_ARCHIVE_NAME=zola-v0.22.1-x86_64-unknown-linux-musl.tar.gz + ZOLA_ARCHIVE_HASH=227df99b664421240a8ba77747067c51259b08159125d5603763b3b173b9a881 + ;; + *) + unsupported_zola_platform "$os" "$arch" + ;; + esac + else + case "$arch" in + x86_64) + ZOLA_ARCHIVE_NAME=zola-v0.22.1-x86_64-unknown-linux-gnu.tar.gz + ZOLA_ARCHIVE_HASH=0ca09aa40376aaa9ddfb512ff9ad963262ef95edb0d0f2d5ec6961b6f5cf22ef + ;; + aarch64|arm64) + ZOLA_ARCHIVE_NAME=zola-v0.22.1-aarch64-unknown-linux-gnu.tar.gz + ZOLA_ARCHIVE_HASH=8af437ec6352f33ccd24d7a1cfcb54a3db95d3ce376dc69525b4ef3fb6b8c1d1 + ;; + *) + unsupported_zola_platform "$os" "$arch" + ;; + esac + fi + ;; + Darwin) + case "$arch" in + x86_64) + ZOLA_ARCHIVE_NAME=zola-v0.22.1-x86_64-apple-darwin.tar.gz + ZOLA_ARCHIVE_HASH=3898709e154ae0593933264a540c869348bdb10d7f1b03a42dfb78d63703b3b5 + ;; + arm64|aarch64) + ZOLA_ARCHIVE_NAME=zola-v0.22.1-aarch64-apple-darwin.tar.gz + ZOLA_ARCHIVE_HASH=46ac45a9e7628dba8593b124ee8794f4f9aa1c6b569918ecd4bbc5d0be190515 + ;; + *) + unsupported_zola_platform "$os" "$arch" + ;; + esac + ;; + *) + unsupported_zola_platform "$os" "$arch" + ;; + esac +} + +parse_zola_version() { + output=$1 + + set -f + set -- $output + set +f + [ "$#" -ge 2 ] || return 1 + + ZOLA_FOUND_VERSION=$2 + version_core=$(printf '%s\n' "$ZOLA_FOUND_VERSION" | sed -n 's/^\([0-9][0-9]*\)\.\([0-9][0-9]*\)\.\([0-9][0-9]*\).*/\1 \2 \3/p') + [ -n "$version_core" ] || return 1 + + set -- $version_core + ZOLA_VERSION_MAJOR=$1 + ZOLA_VERSION_MINOR=$2 + ZOLA_VERSION_PATCH=$3 + ZOLA_VERSION_SUFFIX=$(printf '%s\n' "$ZOLA_FOUND_VERSION" | sed -n 's/^[0-9][0-9]*\.[0-9][0-9]*\.[0-9][0-9]*//p') + + return 0 +} + +zola_version_relation() { + if [ "$ZOLA_VERSION_MAJOR" -lt 0 ]; then + printf '%s\n' below + elif [ "$ZOLA_VERSION_MAJOR" -gt 0 ]; then + printf '%s\n' newer + elif [ "$ZOLA_VERSION_MINOR" -lt 22 ]; then + printf '%s\n' below + elif [ "$ZOLA_VERSION_MINOR" -gt 22 ]; then + printf '%s\n' newer + elif [ "$ZOLA_VERSION_PATCH" -lt 1 ]; then + printf '%s\n' below + elif [ "$ZOLA_VERSION_PATCH" -gt 1 ]; then + printf '%s\n' newer + elif [ -n "$ZOLA_VERSION_SUFFIX" ]; then + printf '%s\n' below + else + printf '%s\n' equal + fi +} + +install_zola() { + command -v tar >/dev/null 2>&1 || die "need tar to unpack the zola release archive" + select_zola_release + ensure_install_dir + + tmpdir=$(mktemp -d) + TMPDIR_TO_CLEAN=$tmpdir + set_cleanup_traps + + archive_path=$tmpdir/$ZOLA_ARCHIVE_NAME + release_base_url=https://github.com/getzola/zola/releases/download/v0.22.1 + + say "Installing zola 0.22.1 from $release_base_url/$ZOLA_ARCHIVE_NAME" + download "$release_base_url/$ZOLA_ARCHIVE_NAME" "$archive_path" + verify_file_hash "$archive_path" "$ZOLA_ARCHIVE_HASH" "$ZOLA_ARCHIVE_NAME" + + tar -xzf "$archive_path" -C "$tmpdir" + [ -f "$tmpdir/zola" ] || die "zola release archive did not contain zola" + + install_tmp=$(mktemp "$INSTALL_DIR/.zola.XXXXXX") || die "could not create temporary zola install file in $INSTALL_DIR" + INSTALL_TMP_TO_CLEAN=$install_tmp + cp "$tmpdir/zola" "$install_tmp" + chmod +x "$install_tmp" + mv "$install_tmp" "$INSTALL_DIR/zola" + INSTALL_TMP_TO_CLEAN= + + ZOLA=$INSTALL_DIR/zola + cleanup_tmpdir + clear_cleanup_traps +} + +ensure_zola() { + if command -v zola >/dev/null 2>&1; then + ZOLA=$(command -v zola) + elif [ -n "${HOME:-}" ] && [ -x "$HOME/.local/bin/zola" ]; then + INSTALL_DIR=$HOME/.local/bin + ZOLA=$INSTALL_DIR/zola + move_dir_to_front_of_script_path "$INSTALL_DIR" + else + install_zola + return 0 + fi + + if [ -n "${ZOLA:-}" ]; then + zola_output=$("$ZOLA" --version 2>/dev/null || true) + if parse_zola_version "$zola_output"; then + relation=$(zola_version_relation) + case "$relation" in + below) + say "Found zola $ZOLA_FOUND_VERSION below supported 0.22.1. Installing zola 0.22.1." + install_zola + ;; + equal) + say "Using existing zola $ZOLA_FOUND_VERSION at $ZOLA" + ;; + newer) + say "Found zola $ZOLA_FOUND_VERSION. build-eips is tested with zola 0.22.1 or newer. Continuing with the installed version." + ;; + esac + else + say "Found zola with unparseable version output. Installing zola 0.22.1." + install_zola + fi + fi +} + +WORKSPACE_FLAGS= + +while [ "$#" -gt 0 ]; do + case "$1" in + --template|--platform-dev) + WORKSPACE_FLAGS="$WORKSPACE_FLAGS $1" + ;; + -h|--help) + say "usage: $0 [--template] [--platform-dev]" + exit 0 + ;; + *) + die "unknown argument: $1" + ;; + esac + shift +done + +INVOCATION_DIR=$(pwd) +SCRIPT_DIR=$(CDPATH= cd -- "$(dirname -- "$0")" && pwd) +THEME_ROOT=$(CDPATH= cd -- "$SCRIPT_DIR/.." && pwd) +WORKSPACE_ROOT=$(CDPATH= cd -- "$THEME_ROOT/.." && pwd) +INSTALL_DIR= +PATH_NOTES= +TMPDIR_TO_CLEAN= +INSTALL_TMP_TO_CLEAN= + +# Theme is not an active proposal repo, so build-eips init is anchored through a +# sibling proposal repo. Default to EIPs and allow ERCs/custom layouts via env. +if [ -n "${ACTIVE_REPO_ROOT:-}" ]; then + ACTIVE_REPO_EXPLICIT=true + ACTIVE_REPO_ROOT=$(absolute_path "$ACTIVE_REPO_ROOT") +else + ACTIVE_REPO_EXPLICIT=false + ACTIVE_REPO_ROOT=$WORKSPACE_ROOT/EIPs +fi + +ensure_active_repo_root + +if command -v build-eips >/dev/null 2>&1; then + BUILD_EIPS=$(command -v build-eips) + say "Using existing build-eips at $BUILD_EIPS" +elif [ -n "${HOME:-}" ] && [ -x "$HOME/.local/bin/build-eips" ]; then + BUILD_EIPS=$HOME/.local/bin/build-eips + INSTALL_DIR=$HOME/.local/bin + move_dir_to_front_of_script_path "$INSTALL_DIR" + say "Using existing build-eips at $BUILD_EIPS" +else + install_build_eips +fi + +ensure_zola + +say "Theme repo: $THEME_ROOT" +say "Workspace root: $WORKSPACE_ROOT" +say "Active proposal repo: $ACTIVE_REPO_ROOT" + +say "Bootstrapping workspace at $WORKSPACE_ROOT" +if ! "$BUILD_EIPS" -C "$ACTIVE_REPO_ROOT" init "$WORKSPACE_ROOT" $WORKSPACE_FLAGS; then + die "build-eips init failed" +fi + +say "Running build-eips doctor" +if ! "$BUILD_EIPS" -C "$ACTIVE_REPO_ROOT" doctor; then + say "Warning: build-eips doctor reported issues above. Fix them before relying on direct build-eips commands." +fi + +WORKSPACE_DOC=$WORKSPACE_ROOT/WORKSPACE.md +say "" +if [ -f "$WORKSPACE_DOC" ]; then + say "Workspace docs: $WORKSPACE_DOC (../WORKSPACE.md from this repo)" +else + say "Warning: workspace docs were not found at $WORKSPACE_DOC after build-eips init" +fi + +if [ -n "$PATH_NOTES" ]; then + say "" + say "Updated PATH for this script process only:" + old_ifs=$IFS + IFS=' +' + for path_note in $PATH_NOTES; do + say " $path_note" + done + say "To make this permanent in your shell, add:" + for path_note in $PATH_NOTES; do + say " export PATH=$(shell_quote "$path_note"):\$PATH" + done + IFS=$old_ifs +fi + +say "" +say "Next commands:" +say " cd $(shell_quote "$THEME_ROOT")" +say " build-eips -C $(shell_quote "$ACTIVE_REPO_ROOT") serve" +say " build-eips -C $(shell_quote "$ACTIVE_REPO_ROOT") check" +say " build-eips -C $(shell_quote "$ACTIVE_REPO_ROOT") doctor" diff --git a/scripts/dev-setup.ps1 b/scripts/dev-setup.ps1 new file mode 100644 index 0000000..1399417 --- /dev/null +++ b/scripts/dev-setup.ps1 @@ -0,0 +1,609 @@ +param( + [switch]$Template, + [switch]$PlatformDev +) + +Set-StrictMode -Version Latest +$ErrorActionPreference = "Stop" +$DefaultActiveRepoUrl = "https://github.com/ethereum/EIPs.git" + +function Say { + param([string]$Message) + + Write-Host $Message +} + +function Die { + param([string]$Message) + + throw "error: $Message" +} + +function ConvertTo-NormalizedDirectoryPath { + param([string]$Directory) + + $trimmed = $Directory.Trim('"') + $trimChars = [char[]]@([System.IO.Path]::DirectorySeparatorChar, [System.IO.Path]::AltDirectorySeparatorChar) + + try { + return ([System.IO.Path]::GetFullPath($trimmed)).TrimEnd($trimChars) + } catch { + return $trimmed.TrimEnd($trimChars) + } +} + +function Test-DirectoryOnPath { + param([string]$Directory) + + if ([string]::IsNullOrWhiteSpace($env:Path)) { + return $false + } + + $target = ConvertTo-NormalizedDirectoryPath -Directory $Directory + foreach ($entry in ($env:Path -split ";")) { + if ([string]::IsNullOrWhiteSpace($entry)) { + continue + } + + $candidate = ConvertTo-NormalizedDirectoryPath -Directory $entry + if ([string]::Equals($candidate, $target, [System.StringComparison]::OrdinalIgnoreCase)) { + return $true + } + } + + return $false +} + +function Add-PathNote { + param([string]$InstallDir) + + $target = ConvertTo-NormalizedDirectoryPath -Directory $InstallDir + foreach ($pathNote in $script:PathNotes) { + $candidate = ConvertTo-NormalizedDirectoryPath -Directory $pathNote + if ([string]::Equals($candidate, $target, [System.StringComparison]::OrdinalIgnoreCase)) { + return + } + } + + $script:PathNotes += $InstallDir +} + +function Move-DirectoryToFrontOfSessionPath { + param([string]$InstallDir) + + $target = ConvertTo-NormalizedDirectoryPath -Directory $InstallDir + $remainingEntries = @() + + if (-not [string]::IsNullOrWhiteSpace($env:Path)) { + foreach ($entry in ($env:Path -split ";")) { + if ([string]::IsNullOrWhiteSpace($entry)) { + continue + } + + $candidate = ConvertTo-NormalizedDirectoryPath -Directory $entry + if ([string]::Equals($candidate, $target, [System.StringComparison]::OrdinalIgnoreCase)) { + continue + } + + $remainingEntries += $entry + } + } + + $newEntries = @($InstallDir) + if ($remainingEntries.Count -gt 0) { + $newEntries += $remainingEntries + } + + $updatedPath = [string]::Join(";", $newEntries) + if (-not [string]::Equals($env:Path, $updatedPath, [System.StringComparison]::Ordinal)) { + $env:Path = $updatedPath + + Add-PathNote -InstallDir $InstallDir + } +} + +function Find-BuildEipsOnPath { + foreach ($commandName in @("build-eips", "build-eips.exe")) { + $commands = @(Get-Command -Name $commandName -CommandType Application -ErrorAction SilentlyContinue) + if ($commands.Count -gt 0) { + return $commands[0].Source + } + } + + return $null +} + +function Find-ZolaOnPath { + foreach ($commandName in @("zola", "zola.exe")) { + $commands = @(Get-Command -Name $commandName -CommandType Application -ErrorAction SilentlyContinue) + if ($commands.Count -gt 0) { + return $commands[0].Source + } + } + + return $null +} + +function Assert-InstallDirWritable { + param([string]$InstallDir) + + $probePath = $null + + try { + New-Item -ItemType Directory -Path $InstallDir -Force | Out-Null + $probeName = ".build-eips-write-test-{0}.tmp" -f ([System.Guid]::NewGuid().ToString("N")) + $probePath = Join-Path -Path $InstallDir -ChildPath $probeName + [System.IO.File]::WriteAllText($probePath, "") + Remove-Item -LiteralPath $probePath -Force + $probePath = $null + } catch { + Die ("install directory cannot be created or written ({0}): {1}" -f $InstallDir, $_.Exception.Message) + } finally { + if (($null -ne $probePath) -and (Test-Path -LiteralPath $probePath)) { + Remove-Item -LiteralPath $probePath -Force -ErrorAction SilentlyContinue + } + } +} + +function Invoke-ReleaseDownload { + param( + [string]$Url, + [string]$Destination + ) + + $previousProgressPreference = $ProgressPreference + $ProgressPreference = "SilentlyContinue" + try { + Invoke-WebRequest -Uri $Url -OutFile $Destination -UseBasicParsing + } catch { + Die ("failed to download {0}: {1}" -f $Url, $_.Exception.Message) + } finally { + $ProgressPreference = $previousProgressPreference + } +} + +function Test-AsciiHexHash { + param([string]$Hash) + + return $Hash -match "^[0-9a-fA-F]{64}$" +} + +function Assert-ArchiveChecksum { + param( + [string]$ArchivePath, + [string]$SidecarPath, + [string]$ArchiveName + ) + + if (-not (Test-Path -LiteralPath $SidecarPath -PathType Leaf)) { + Die "missing checksum sidecar: $SidecarPath" + } + + $sidecarText = [System.IO.File]::ReadAllText($SidecarPath) + $checksumLines = @($sidecarText -split "\r?\n" | Where-Object { $_.Trim().Length -gt 0 }) + if ($checksumLines.Count -ne 1) { + Die "checksum sidecar must contain exactly one checksum line" + } + + $fields = @($checksumLines[0].Trim() -split "\s+") + if ($fields.Count -ne 2) { + Die "checksum sidecar must contain only a hash and archive filename" + } + + $expectedHash = $fields[0].ToLowerInvariant() + $expectedName = $fields[1] + + if (-not (Test-AsciiHexHash -Hash $expectedHash)) { + Die "checksum sidecar hash must be 64 hex characters" + } + if ($expectedName -match '[/\\]') { + Die "checksum sidecar filename must be a basename" + } + if ($expectedName -ne $ArchiveName) { + Die ("checksum sidecar filename '{0}' does not match '{1}'" -f $expectedName, $ArchiveName) + } + + $actualHash = (Get-FileHash -Algorithm SHA256 -Path $ArchivePath).Hash.ToLowerInvariant() + if ($actualHash -ne $expectedHash) { + Die "checksum mismatch for $ArchiveName" + } +} + +function Assert-FileSha256 { + param( + [string]$ArchivePath, + [string]$ExpectedHash, + [string]$ArchiveName + ) + + $actualHash = (Get-FileHash -Algorithm SHA256 -Path $ArchivePath).Hash.ToLowerInvariant() + if ($actualHash -ne $ExpectedHash.ToLowerInvariant()) { + Die "checksum mismatch for $ArchiveName" + } +} + +function Install-BuildEips { + param( + [string]$InstallDir, + [string]$BuildEipsPath + ) + + $archiveName = "build-eips-windows.zip" + $releaseBaseUrl = "https://github.com/eips-wg/preprocessor/releases/latest/download" + $tmpRoot = Join-Path -Path ([System.IO.Path]::GetTempPath()) -ChildPath ("build-eips-" + [System.Guid]::NewGuid().ToString("N")) + $archivePath = Join-Path -Path $tmpRoot -ChildPath $archiveName + $sidecarPath = Join-Path -Path $tmpRoot -ChildPath "$archiveName.sha256" + $extractDir = Join-Path -Path $tmpRoot -ChildPath "extract" + + try { + Assert-InstallDirWritable -InstallDir $InstallDir + + New-Item -ItemType Directory -Path $extractDir -Force | Out-Null + + [Net.ServicePointManager]::SecurityProtocol = [Net.ServicePointManager]::SecurityProtocol -bor [Net.SecurityProtocolType]::Tls12 + + Say "Installing build-eips from $releaseBaseUrl/$archiveName" + Invoke-ReleaseDownload -Url "$releaseBaseUrl/$archiveName" -Destination $archivePath + Invoke-ReleaseDownload -Url "$releaseBaseUrl/$archiveName.sha256" -Destination $sidecarPath + Assert-ArchiveChecksum -ArchivePath $archivePath -SidecarPath $sidecarPath -ArchiveName $archiveName + + Expand-Archive -LiteralPath $archivePath -DestinationPath $extractDir -Force + + $extractedBuildEips = Join-Path -Path $extractDir -ChildPath "build-eips.exe" + if (-not (Test-Path -LiteralPath $extractedBuildEips -PathType Leaf)) { + Die "release archive did not contain expected build-eips.exe" + } + + try { + Move-Item -LiteralPath $extractedBuildEips -Destination $BuildEipsPath -Force + } catch { + Die ("build-eips.exe is in use. Close any running build-eips process and re-run this script. Details: {0}" -f $_.Exception.Message) + } + + return $BuildEipsPath + } catch { + Die ("failed to install build-eips: {0}" -f $_.Exception.Message) + } finally { + if (Test-Path -LiteralPath $tmpRoot) { + Remove-Item -LiteralPath $tmpRoot -Recurse -Force -ErrorAction SilentlyContinue + } + } +} + +function Get-ZolaReleaseAsset { + $architecture = $env:PROCESSOR_ARCHITECTURE + if ([string]::IsNullOrWhiteSpace($architecture)) { + $architecture = "unknown" + } + if (($architecture -eq "x86") -and (-not [string]::IsNullOrWhiteSpace($env:PROCESSOR_ARCHITEW6432))) { + $architecture = $env:PROCESSOR_ARCHITEW6432 + } + + switch ($architecture.ToUpperInvariant()) { + "AMD64" { + return @{ + ArchiveName = "zola-v0.22.1-x86_64-pc-windows-msvc.zip" + Hash = "2c8b368f5abdf2b2478748f9549a761fd6599238e18948eccb76a7cae51f5dc1" + } + } + default { + Die "Unsupported platform Windows/$architecture for automatic Zola install. Install Zola 0.22.1 manually from https://github.com/getzola/zola/releases and ensure it is on PATH." + } + } +} + +function Install-Zola { + param( + [string]$InstallDir, + [string]$ZolaPath + ) + + $asset = Get-ZolaReleaseAsset + $archiveName = $asset.ArchiveName + $archiveHash = $asset.Hash + $releaseBaseUrl = "https://github.com/getzola/zola/releases/download/v0.22.1" + $tmpRoot = Join-Path -Path ([System.IO.Path]::GetTempPath()) -ChildPath ("zola-" + [System.Guid]::NewGuid().ToString("N")) + $archivePath = Join-Path -Path $tmpRoot -ChildPath $archiveName + $extractDir = Join-Path -Path $tmpRoot -ChildPath "extract" + + try { + Assert-InstallDirWritable -InstallDir $InstallDir + + New-Item -ItemType Directory -Path $extractDir -Force | Out-Null + + [Net.ServicePointManager]::SecurityProtocol = [Net.ServicePointManager]::SecurityProtocol -bor [Net.SecurityProtocolType]::Tls12 + + Say "Installing zola 0.22.1 from $releaseBaseUrl/$archiveName" + Invoke-ReleaseDownload -Url "$releaseBaseUrl/$archiveName" -Destination $archivePath + Assert-FileSha256 -ArchivePath $archivePath -ExpectedHash $archiveHash -ArchiveName $archiveName + + Expand-Archive -LiteralPath $archivePath -DestinationPath $extractDir -Force + + $extractedZola = Join-Path -Path $extractDir -ChildPath "zola.exe" + if (-not (Test-Path -LiteralPath $extractedZola -PathType Leaf)) { + Die "zola release archive did not contain expected zola.exe" + } + + try { + Move-Item -LiteralPath $extractedZola -Destination $ZolaPath -Force + } catch { + Die ("zola.exe is in use. Close any running zola process and re-run this script. Details: {0}" -f $_.Exception.Message) + } + + return $ZolaPath + } catch { + Die ("failed to install zola: {0}" -f $_.Exception.Message) + } finally { + if (Test-Path -LiteralPath $tmpRoot) { + Remove-Item -LiteralPath $tmpRoot -Recurse -Force -ErrorAction SilentlyContinue + } + } +} + +function Get-ZolaVersionInfo { + param([string]$ZolaPath) + + try { + $output = & $ZolaPath --version 2>$null + if ($LASTEXITCODE -ne 0) { + return $null + } + } catch { + return $null + } + + $fields = @(($output -join " ") -split "\s+" | Where-Object { $_.Length -gt 0 }) + if ($fields.Count -lt 2) { + return $null + } + + $versionToken = $fields[1] + if ($versionToken -notmatch "^([0-9]+)\.([0-9]+)\.([0-9]+)(.*)$") { + return $null + } + + return @{ + VersionToken = $versionToken + Version = [version]("{0}.{1}.{2}" -f $Matches[1], $Matches[2], $Matches[3]) + Suffix = $Matches[4] + } +} + +function Get-ZolaVersionRelation { + param([hashtable]$VersionInfo) + + $minimumVersion = [version]"0.22.1" + if ($VersionInfo.Version -lt $minimumVersion) { + return "below" + } + if ($VersionInfo.Version -gt $minimumVersion) { + return "newer" + } + if (-not [string]::IsNullOrEmpty($VersionInfo.Suffix)) { + return "below" + } + + return "equal" +} + +function Install-PinnedZola { + $defaultPaths = Get-DefaultInstallPaths + $installedZola = Install-Zola -InstallDir $defaultPaths.InstallDir -ZolaPath $defaultPaths.ZolaPath + Move-DirectoryToFrontOfSessionPath -InstallDir $defaultPaths.InstallDir + + return $installedZola +} + +function Initialize-Zola { + $zolaPath = Find-ZolaOnPath + if ($null -eq $zolaPath) { + $defaultPaths = Get-DefaultInstallPaths + if (Test-Path -LiteralPath $defaultPaths.ZolaPath -PathType Leaf) { + $zolaPath = $defaultPaths.ZolaPath + Move-DirectoryToFrontOfSessionPath -InstallDir $defaultPaths.InstallDir + } + } + + if ($null -eq $zolaPath) { + return (Install-PinnedZola) + } + + $versionInfo = Get-ZolaVersionInfo -ZolaPath $zolaPath + if ($null -eq $versionInfo) { + Say "Found zola with unparseable version output. Installing zola 0.22.1." + return (Install-PinnedZola) + } + + $relation = Get-ZolaVersionRelation -VersionInfo $versionInfo + switch ($relation) { + "below" { + Say ("Found zola {0} below supported 0.22.1. Installing zola 0.22.1." -f $versionInfo.VersionToken) + return (Install-PinnedZola) + } + "equal" { + Say ("Using existing zola {0} at {1}" -f $versionInfo.VersionToken, $zolaPath) + return $zolaPath + } + "newer" { + Say ("Found zola {0}. build-eips is tested with zola 0.22.1 or newer. Continuing with the installed version." -f $versionInfo.VersionToken) + return $zolaPath + } + } +} + +function Resolve-ActiveRepoRoot { + param( + [string]$InvocationDir, + [string]$WorkspaceRoot + ) + + $activeRepoExplicit = $false + if (-not [string]::IsNullOrWhiteSpace($env:ACTIVE_REPO_ROOT)) { + $activeRepoExplicit = $true + if ([System.IO.Path]::IsPathRooted($env:ACTIVE_REPO_ROOT)) { + $activeRepoCandidate = $env:ACTIVE_REPO_ROOT + } else { + $activeRepoCandidate = Join-Path -Path $InvocationDir -ChildPath $env:ACTIVE_REPO_ROOT + } + } else { + $activeRepoCandidate = Join-Path -Path $WorkspaceRoot -ChildPath "EIPs" + } + + if ($activeRepoExplicit) { + try { + $resolved = (Resolve-Path -LiteralPath $activeRepoCandidate).ProviderPath + } catch { + Die "configured ACTIVE_REPO_ROOT does not exist: $activeRepoCandidate. Fix ACTIVE_REPO_ROOT or unset it to let this script clone EIPs." + } + + if (-not (Test-Path -LiteralPath $resolved -PathType Container)) { + Die "configured ACTIVE_REPO_ROOT is not a directory: $resolved. Fix ACTIVE_REPO_ROOT or unset it to let this script clone EIPs." + } + if (-not (Test-Path -LiteralPath (Join-Path -Path $resolved -ChildPath ".git"))) { + Die "configured ACTIVE_REPO_ROOT is not a git checkout: $resolved. Fix ACTIVE_REPO_ROOT or unset it to let this script clone EIPs." + } + + return @{ + Path = $resolved + Explicit = $true + } + } + + if (-not (Test-Path -LiteralPath $activeRepoCandidate)) { + $gitCommand = Get-Command git -CommandType Application -ErrorAction SilentlyContinue + if ($null -eq $gitCommand) { + Die "need git to clone default proposal repo from $DefaultActiveRepoUrl" + } + + Say "No active proposal repo found at $activeRepoCandidate." + Say "Cloning default proposal repo from $DefaultActiveRepoUrl" + Say "This may take a few minutes..." + & git clone $DefaultActiveRepoUrl $activeRepoCandidate + $GitCloneExitCode = $LASTEXITCODE + if ($GitCloneExitCode -ne 0) { + Die "failed to clone default proposal repo from $DefaultActiveRepoUrl to $activeRepoCandidate" + } + Say "Cloned default proposal repo to $activeRepoCandidate" + } + + try { + $resolved = (Resolve-Path -LiteralPath $activeRepoCandidate).ProviderPath + } catch { + Die "default active proposal repo path does not exist after clone: $activeRepoCandidate" + } + + if (-not (Test-Path -LiteralPath $resolved -PathType Container)) { + Die "default active proposal repo path exists but is not a directory: $resolved. Remove it or set ACTIVE_REPO_ROOT." + } + if (-not (Test-Path -LiteralPath (Join-Path -Path $resolved -ChildPath ".git"))) { + Die "default active proposal repo path exists but is not a git checkout: $resolved. Remove it or set ACTIVE_REPO_ROOT." + } + + return @{ + Path = $resolved + Explicit = $false + } +} + +function ConvertTo-PowerShellQuotedPath { + param([string]$Path) + + return "'{0}'" -f ($Path -replace "'", "''") +} + +function Get-DefaultInstallPaths { + if ([string]::IsNullOrWhiteSpace($env:LOCALAPPDATA)) { + Die "LOCALAPPDATA is not set; cannot determine the user-local install directory" + } + + $installDir = Join-Path -Path (Join-Path -Path $env:LOCALAPPDATA -ChildPath "build-eips") -ChildPath "bin" + $buildEipsPath = Join-Path -Path $installDir -ChildPath "build-eips.exe" + $zolaPath = Join-Path -Path $installDir -ChildPath "zola.exe" + + return @{ + InstallDir = $installDir + BuildEipsPath = $buildEipsPath + ZolaPath = $zolaPath + } +} + +$PathNotes = @() +$WorkspaceFlags = @() +if ($Template) { + $WorkspaceFlags += "--template" +} +if ($PlatformDev) { + $WorkspaceFlags += "--platform-dev" +} + +$InvocationDir = (Get-Location).ProviderPath +$ScriptDir = (Resolve-Path -LiteralPath $PSScriptRoot).ProviderPath +$ThemeRoot = (Resolve-Path -LiteralPath (Split-Path -Path $ScriptDir -Parent)).ProviderPath +$WorkspaceRoot = (Resolve-Path -LiteralPath (Split-Path -Path $ThemeRoot -Parent)).ProviderPath +$ActiveRepoInfo = Resolve-ActiveRepoRoot -InvocationDir $InvocationDir -WorkspaceRoot $WorkspaceRoot +$ActiveRepoRoot = $ActiveRepoInfo["Path"] +$ActiveRepoExplicit = $ActiveRepoInfo["Explicit"] + +$BuildEipsPath = Find-BuildEipsOnPath +if ($null -ne $BuildEipsPath) { + Say "Using existing build-eips at $BuildEipsPath" +} else { + $defaultPaths = Get-DefaultInstallPaths + $DefaultInstallDir = $defaultPaths.InstallDir + $DefaultBuildEipsPath = $defaultPaths.BuildEipsPath + + if (Test-Path -LiteralPath $DefaultBuildEipsPath -PathType Leaf) { + $BuildEipsPath = $DefaultBuildEipsPath + Move-DirectoryToFrontOfSessionPath -InstallDir $DefaultInstallDir + Say "Using existing build-eips at $BuildEipsPath" + } else { + $BuildEipsPath = Install-BuildEips -InstallDir $DefaultInstallDir -BuildEipsPath $DefaultBuildEipsPath + Move-DirectoryToFrontOfSessionPath -InstallDir $DefaultInstallDir + } +} + +$ZolaPath = Initialize-Zola + +Say "Theme repo: $ThemeRoot" +Say "Workspace root: $WorkspaceRoot" +Say "Active proposal repo: $ActiveRepoRoot" +Say "If PowerShell blocks this script, run:" +Say " powershell -ExecutionPolicy Bypass -File .\scripts\dev-setup.ps1" + +Say "Bootstrapping workspace at $WorkspaceRoot" +& $BuildEipsPath -C $ActiveRepoRoot init $WorkspaceRoot @WorkspaceFlags +$WorkspaceInitExitCode = $LASTEXITCODE +if ($WorkspaceInitExitCode -ne 0) { + Die "build-eips init failed with exit code $WorkspaceInitExitCode" +} + +Say "Running build-eips doctor" +& $BuildEipsPath -C $ActiveRepoRoot doctor +$WorkspaceDoctorExitCode = $LASTEXITCODE +if ($WorkspaceDoctorExitCode -ne 0) { + Say "Warning: build-eips doctor reported issues above. Fix them before relying on direct build-eips commands." +} + +$WorkspaceDocPath = Join-Path -Path $WorkspaceRoot -ChildPath "WORKSPACE.md" +Say "" +if (Test-Path -LiteralPath $WorkspaceDocPath -PathType Leaf) { + Say "Workspace docs: $WorkspaceDocPath (../WORKSPACE.md from this repo)" +} else { + Say "Warning: workspace docs were not found at $WorkspaceDocPath after build-eips init" +} + +if ($PathNotes.Count -gt 0) { + Say "" + Say 'Updated PATH for this PowerShell session only:' + foreach ($pathNote in $PathNotes) { + Say " $pathNote" + } + Say "To make this permanent, add the listed directory or directories to your user Path in Windows Environment Variables." +} + +Say "" +Say "Next commands:" +Say (" cd {0}" -f (ConvertTo-PowerShellQuotedPath -Path $ThemeRoot)) +Say (" build-eips -C {0} serve" -f (ConvertTo-PowerShellQuotedPath -Path $ActiveRepoRoot)) +Say (" build-eips -C {0} check" -f (ConvertTo-PowerShellQuotedPath -Path $ActiveRepoRoot)) +Say (" build-eips -C {0} doctor" -f (ConvertTo-PowerShellQuotedPath -Path $ActiveRepoRoot)) From bef6425764102be3a5a4cb01dd588e6b78969830 Mon Sep 17 00:00:00 2001 From: Rito Rhymes Date: Mon, 18 May 2026 21:44:13 -0400 Subject: [PATCH 3/4] Add taxonomy page descriptions Render configured taxonomy descriptions on taxonomy list and term pages. Store the text beside the existing taxonomy metadata in config/zola.toml so future taxonomy terms can add page copy without changing the templates. Keep badge tooltip text unchanged. The new descriptions are visible page intro text and can provide more detail than hover tooltips. --- config/zola.toml | 6 ++++++ templates/taxonomy_list.html | 12 ++++++++++++ templates/taxonomy_single.html | 10 ++++++++++ 3 files changed, 28 insertions(+) diff --git a/config/zola.toml b/config/zola.toml index ab7b6dc..65b0d00 100644 --- a/config/zola.toml +++ b/config/zola.toml @@ -88,21 +88,27 @@ tooltip = "This EIP is completed and no longer expected to change." [extra.taxonomies.category.erc] tooltip = "Application-level standards and conventions, including contract standards such as token standards, name registries, URI schemes, library/package formats, and wallet formats." +description = "ERCs define application-level standards and conventions for the Ethereum ecosystem. They include token standards, registries, URI schemes, wallet formats, and other interfaces used by applications and contracts." [extra.taxonomies.category.interface] tooltip = "Improvements around client API/RPC specifications and standards, and also certain language-level standards like method names and contract ABIs." +description = "Interface EIPs cover client APIs, RPC standards, contract ABIs, and other language-level interfaces. They help applications, tools, wallets, and clients interact consistently." [extra.taxonomies.category.networking] tooltip = "Improvements around devp2p and Light Ethereum Subprotocol, as well as proposed improvements to network protocol specifications of whisper and swarm." +description = "Networking EIPs cover Ethereum peer-to-peer networking, light client protocols, and related network protocol specifications. They describe how Ethereum nodes and clients communicate." [extra.taxonomies.category.core] tooltip = "Improvements requiring a consensus fork, as well as changes that are not necessarily consensus critical but may be relevant to “core dev” discussions." +description = "Core EIPs cover changes to Ethereum consensus, execution, and other low-level protocol behavior usually debated by core developers. They include hard-fork changes as well as non-consensus protocol work relevant to core dev discussions." [extra.taxonomies.type.informational] tooltip = "Describes an Ethereum design issue, or provides general guidelines or information to the Ethereum community, but does not propose a new feature. Informational EIPs do not necessarily represent Ethereum community consensus or a recommendation, so users and implementers are free to ignore Informational EIPs or follow their advice." +description = "Informational EIPs provide design notes, background, or general guidance for the Ethereum community. They do not propose a new feature, but they can document useful context, suggestions, or analysis." [extra.taxonomies.type.meta] tooltip = "Describes a process surrounding Ethereum or proposes a change to (or an event in) a process." +description = "Meta EIPs describe Ethereum processes or propose changes to those processes. They include coordination, tooling, and changes to the EIP workflow itself." [extra.taxonomies.type.standards-track] tooltip = "Describes any change that affects most or all Ethereum implementations or any application." diff --git a/templates/taxonomy_list.html b/templates/taxonomy_list.html index 43d0206..96464f3 100644 --- a/templates/taxonomy_list.html +++ b/templates/taxonomy_list.html @@ -6,9 +6,21 @@ {% block content %}

Proposals by {{ taxonomy.name | capitalize }}

+{% set configured_taxonomies = config.extra | get(key="taxonomies", default=false) %} +{% if configured_taxonomies %} +{% set taxonomy_metadata = configured_taxonomies | get(key=taxonomy.name, default=false) %} +{% else %} +{% set taxonomy_metadata = false %} +{% endif %} {% for term in terms %}

{{ term.name }}

+ {% if taxonomy_metadata %} + {% set term_metadata = taxonomy_metadata | get(key=term.slug, default=false) %} + {% if term_metadata and term_metadata.description %} +

{{ term_metadata.description }}

+ {% endif %} + {% endif %} {% include "includes/eiptable.html" %}
diff --git a/templates/taxonomy_single.html b/templates/taxonomy_single.html index 56bdd9f..6b25b08 100644 --- a/templates/taxonomy_single.html +++ b/templates/taxonomy_single.html @@ -22,6 +22,16 @@

{{ taxonomy.name | title }}: {{ term.name | title }} {% include "includes/termfeed.html" %}

+ {% set configured_taxonomies = config.extra | get(key="taxonomies", default=false) %} + {% if configured_taxonomies %} + {% set taxonomy_metadata = configured_taxonomies | get(key=taxonomy.name, default=false) %} + {% if taxonomy_metadata %} + {% set term_metadata = taxonomy_metadata | get(key=term.slug, default=false) %} + {% if term_metadata and term_metadata.description %} +

{{ term_metadata.description }}

+ {% endif %} + {% endif %} + {% endif %} {% include "includes/eiptable.html" %} {% endblock content %} From 6547f1aa0a0d9147a3be5fe83014c95d741acb0e Mon Sep 17 00:00:00 2001 From: Rito Rhymes Date: Sun, 10 May 2026 22:00:26 -0400 Subject: [PATCH 4/4] Render the hardfork page and proposal memberships Render the generated hardfork index at /hardforks/ and add a Network Upgrade row to proposal pages when the preprocessor provides membership metadata. Keep the hardfork page summary in the active Zola config under section_descriptions so the template does not hardcode page copy. Use the generated URLs from front matter directly so targeted builds can link omitted proposals to their public pages without template lookups. Add focused styles, navigation, and demo fixture content for populated and empty hardfork entries. --- config/zola.toml | 4 + content/00165.md | 17 +++ content/00214.md | 9 ++ content/hardforks/_index.md | 76 +++++++++++ sass/assets/css/style.scss | 121 ++++++++++++++++++ templates/eip.html | 21 +++ templates/hardforks.html | 100 +++++++++++++++ .../includes/network_upgrade_membership.html | 23 ++++ 8 files changed, 371 insertions(+) create mode 100644 content/hardforks/_index.md create mode 100644 templates/hardforks.html create mode 100644 templates/includes/network_upgrade_membership.html diff --git a/config/zola.toml b/config/zola.toml index 65b0d00..328b727 100644 --- a/config/zola.toml +++ b/config/zola.toml @@ -62,8 +62,12 @@ banner_links = [ { label = "ERC", href = "/category/erc/" }, { label = "Meta", href = "/type/meta/" }, { label = "Informational", href = "/type/informational/" }, + { label = "Hardforks", href = "/hardforks/" }, ] +[extra.section_descriptions.hardforks] +text = "Hardforks group EIPs by network upgrade and inclusion stage, based on their hardfork Meta EIPs." + [extra.github] eip_repository = "https://github.com/ethereum/EIPs" erc_repository = "https://github.com/ethereum/ERCs" diff --git a/content/00165.md b/content/00165.md index 9acf881..cce4a00 100644 --- a/content/00165.md +++ b/content/00165.md @@ -20,6 +20,23 @@ number = 165 requires = ["@/00214.md"] category = "ERC" +[[extra.network_upgrades]] +display_name = "Pectra" +slug = "pectra" +meta_eip = 7201 +meta_url = "/7201/" +stage_key = "included" +stage_label = "Included" +subgroup = "Other EIPs" + +[[extra.network_upgrades]] +display_name = "Glamsterdam" +slug = "glamsterdam" +meta_eip = 7773 +meta_url = "https://eips.ethereum.org/EIPS/eip-7773" +stage_key = "scheduled" +stage_label = "Scheduled for Inclusion" + [[extra.author_details]] email = "chris@ethereum.org" name = "Christian Reitwießner" diff --git a/content/00214.md b/content/00214.md index 8ef9afc..73c9b08 100644 --- a/content/00214.md +++ b/content/00214.md @@ -19,6 +19,15 @@ type = "Standards Track" number = 214 status = "Final" +[[extra.network_upgrades]] +display_name = "Pectra" +slug = "pectra" +meta_eip = 7201 +meta_url = "/7201/" +stage_key = "included" +stage_label = "Included" +subgroup = "Core EIPs" + [[extra.author_details]] email = "vitalik@ethereum.org" name = "Vitalik Buterin" diff --git a/content/hardforks/_index.md b/content/hardforks/_index.md new file mode 100644 index 0000000..8bcfc26 --- /dev/null +++ b/content/hardforks/_index.md @@ -0,0 +1,76 @@ ++++ +title = "Hardforks" +template = "hardforks.html" + +[[extra.network_upgrades]] +slug = "pectra" +display_name = "Pectra" +meta_eip = 7201 +meta_url = "/7201/" +meta_status = "Final" + +[[extra.network_upgrades.stages]] +key = "included" +label = "Included" + +[[extra.network_upgrades.stages.rows]] +number = 214 +title = "New opcode STATICCALL" +status = "Final" +type = "Standards Track" +url = "/214/" +category = "Core" +subgroup = "Core EIPs" + +[[extra.network_upgrades.stages.rows]] +number = 165 +title = "Standard Interface Detection" +status = "Final" +type = "Standards Track" +url = "/165/" +category = "ERC" +subgroup = "Other EIPs" + +[[extra.network_upgrades]] +slug = "glamsterdam" +display_name = "Glamsterdam" +meta_eip = 7773 +meta_url = "https://eips.ethereum.org/EIPS/eip-7773" +meta_status = "Draft" + +[[extra.network_upgrades.stages]] +key = "scheduled" +label = "Scheduled for Inclusion" + +[[extra.network_upgrades.stages.rows]] +number = 165 +title = "Standard Interface Detection" +status = "Final" +type = "Standards Track" +url = "https://eips.ethereum.org/EIPS/eip-165" +category = "ERC" + +[[extra.network_upgrades.stages.rows]] +number = 7773 +title = "Glamsterdam Meta EIP" +status = "Draft" +type = "Meta" +url = "https://eips.ethereum.org/EIPS/eip-7773" + +[[extra.network_upgrades.stages]] +key = "considered" +label = "Considered for Inclusion" +rows = [] + +[[extra.network_upgrades]] +slug = "empty-demo" +display_name = "Empty Demo" +meta_eip = 7201 +meta_url = "/7201/" +meta_status = "Draft" + +[[extra.network_upgrades.stages]] +key = "included" +label = "Included" +rows = [] ++++ diff --git a/sass/assets/css/style.scss b/sass/assets/css/style.scss index a56bd86..ac3e5f8 100644 --- a/sass/assets/css/style.scss +++ b/sass/assets/css/style.scss @@ -78,6 +78,74 @@ table.preamble > tbody > tr > :first-child { white-space: nowrap; } +.network-upgrade-membership, +.network-upgrade-membership-list li { + display: flex; + flex-wrap: wrap; + align-items: baseline; + gap: 0.35rem; +} + +.network-upgrade-membership-list { + margin: 0; + padding-left: 1.25rem; +} + +.network-upgrade-membership-list li + li { + margin-top: 0.25rem; +} + +.network-upgrade-subgroup, +.hardfork-status, +.hardfork-row-tag { + display: inline-flex; + align-items: center; + border-radius: 0.25rem; + padding: 0.1rem 0.4rem; + background-color: rgba(var(--bs-secondary-rgb), 0.12); + color: var(--bs-secondary-color); + font-size: 0.875em; + line-height: 1.4; +} + +.network-upgrade-stage { + cursor: help; +} + +.network-upgrade-stage--included { + --bs-bg-opacity: 1; + background-color: rgba(var(--bs-success-rgb),var(--bs-bg-opacity)) !important; +} + +.network-upgrade-stage--scheduled, +.network-upgrade-stage--scheduled-for-inclusion { + --bs-bg-opacity: 1; + background-color: rgba(var(--bs-primary-rgb),var(--bs-bg-opacity)) !important; +} + +.badge.text-light.network-upgrade-stage--considered, +.badge.text-light.network-upgrade-stage--considered-for-inclusion { + --bs-bg-opacity: 1; + background-color: rgba(var(--bs-warning-rgb),var(--bs-bg-opacity)) !important; + color: #000 !important; +} + +.network-upgrade-stage--proposed, +.network-upgrade-stage--proposed-for-inclusion { + background-color: #6f42c1 !important; +} + +.network-upgrade-stage--declined, +.network-upgrade-stage--declined-for-inclusion { + --bs-bg-opacity: 1; + background-color: rgba(var(--bs-danger-rgb),var(--bs-bg-opacity)) !important; +} + +.network-upgrade-subgroup, +.hardfork-row-tag { + background-color: rgba(var(--bs-primary-rgb), 0.1); +} + address.author { display: inline; } @@ -146,6 +214,59 @@ pre,code { min-width: 33%; } +.network-upgrades-header { + margin-bottom: 1.5rem; +} + +.network-upgrades-summary, +.network-upgrades-intro { + max-width: 48rem; +} + +.hardfork-section { + border-top: 1px solid var(--bs-border-color); + padding-top: 4rem; +} + +.hardfork-section + .hardfork-section { + margin-top: 1.5rem; +} + +.hardfork-header h2, +.hardfork-stage h3 { + display: flex; + align-items: center; +} + +.hardfork-meta { + display: flex; + flex-wrap: wrap; + align-items: center; + gap: 0.5rem; + margin-bottom: 1rem; +} + +.hardfork-stage { + margin-top: 1rem; +} + +.hardfork-stage h3 { + font-size: 1.15rem; +} + +.hardfork-row-number { + white-space: nowrap; +} + +.hardfork-row-tag { + margin-left: 0.35rem; +} + +.hardfork-empty { + color: var(--bs-secondary-color); + font-style: italic; +} + .site-header .row { text-align: center; } diff --git a/templates/eip.html b/templates/eip.html index 5881b6c..657ffb5 100644 --- a/templates/eip.html +++ b/templates/eip.html @@ -245,6 +245,27 @@

{%- endfor %} + {% endif %} {% if page.extra.network_upgrades %} + + Network Upgrade + + {% if page.extra.network_upgrades | length == 1 %} + {% for upgrade in page.extra.network_upgrades %} + + {% include "includes/network_upgrade_membership.html" %} + + {% endfor %} + {% else %} +
    + {% for upgrade in page.extra.network_upgrades %} +
  • + {% include "includes/network_upgrade_membership.html" %} +
  • + {% endfor %} +
+ {% endif %} + + {% endif %} {% if page.extra.withdrawal_reason %} diff --git a/templates/hardforks.html b/templates/hardforks.html new file mode 100644 index 0000000..c975634 --- /dev/null +++ b/templates/hardforks.html @@ -0,0 +1,100 @@ +{% extends "base.html" %} + +{% block title %}{{ section.title }} | {{ config.title }}{% endblock title %} + +{% block extra_head %} + + + +{% endblock extra_head %} + +{% block content %} +
+
+

{{ section.title }}

+ {% set section_descriptions = config.extra | get(key="section_descriptions", default=false) %} + {% if section_descriptions %} + {% set hardfork_description = section_descriptions | get(key="hardforks", default=false) %} + {% if hardfork_description and hardfork_description.text %} +

+ {{ hardfork_description.text }} +

+ {% endif %} + {% endif %} + {% if section.content %} +
+ {{ section.content | safe }} +
+ {% endif %} +
+ + {% if section.extra.network_upgrades %} + {% for upgrade in section.extra.network_upgrades %} + {% set_global hardfork_has_rows = false %} +
+
+

+ + {{ upgrade.display_name }} +

+

+ EIP-{{ upgrade.meta_eip }} + {{ upgrade.meta_status }} +

+
+ + {% for stage in upgrade.stages %} + {% if stage.rows %} + {% set_global hardfork_has_rows = true %} +
+

+ + {{ stage.label }} +

+ + + + + + + + + + + + {% for row in stage.rows %} + + + + + + + + {% endfor %} + +
ProposalTitleStatusTypeCategory
+ {% if row.category and row.category == "ERC" %}ERC{% else %}EIP{% endif %}-{{ row.number }} + + {{ row.title }} + {% if row.subgroup %} + {{ row.subgroup }} + {% endif %} + {{ row.status }}{{ row.type }} + {% if row.category %} + {{ row.category }} + {% endif %} +
+
+ {% endif %} + {% endfor %} + + {% if not hardfork_has_rows %} +

No EIPs are listed for this hardfork.

+ {% endif %} +
+ {% endfor %} + {% else %} +

No hardforks are listed yet.

+ {% endif %} +
+{% endblock content %} diff --git a/templates/includes/network_upgrade_membership.html b/templates/includes/network_upgrade_membership.html new file mode 100644 index 0000000..e87db03 --- /dev/null +++ b/templates/includes/network_upgrade_membership.html @@ -0,0 +1,23 @@ +{% set stage_description = upgrade.stage_label %} +{% if upgrade.stage_key == "included" %} +{% set stage_description = "Included in this hardfork" %} +{% elif upgrade.stage_key == "scheduled" or upgrade.stage_key == "scheduled-for-inclusion" %} +{% set stage_description = "Scheduled for inclusion in this hardfork" %} +{% elif upgrade.stage_key == "considered" or upgrade.stage_key == "considered-for-inclusion" %} +{% set stage_description = "Under consideration for this hardfork" %} +{% elif upgrade.stage_key == "proposed" or upgrade.stage_key == "proposed-for-inclusion" %} +{% set stage_description = "Proposed for this hardfork" %} +{% elif upgrade.stage_key == "declined" or upgrade.stage_key == "declined-for-inclusion" %} +{% set stage_description = "Declined for this hardfork" %} +{% endif %} +{{ upgrade.display_name }} + + {{- upgrade.stage_label -}} + +{% if upgrade.subgroup %} +{{ upgrade.subgroup }} +{% endif %}