diff --git a/.github/workflows/docs.yml b/.github/workflows/docs.yml index fb0658e..a04dd34 100644 --- a/.github/workflows/docs.yml +++ b/.github/workflows/docs.yml @@ -1,5 +1,13 @@ name: docs +# Every documentation version is a separate Hugo build, published under +# https://go-ndarray.github.io/docs// on the gh-pages branch, where +# is the minor version of ndarray the pages describe +# (params.ndarray.version in hugo.yaml: v0.9.1 -> 0.9). main is that +# version's source: each push to main replaces /docs//, and +# /docs/latest/ follows the newest. Pull requests build and check, and publish +# nothing. + on: push: branches: [main] @@ -12,48 +20,135 @@ on: permissions: contents: read +env: + HUGO_VERSION: "0.167.0" + # sha256 of hugo_${HUGO_VERSION}_linux-amd64.tar.gz, from the release's checksums.txt. + HUGO_SHA256: "4d84519b9f619e6d4c3fb45a50157abeabeb724f859c60605f44c23def6e1169" + DOCS_URL: "https://go-ndarray.github.io/docs" + jobs: - # Build on every pull request as well as on main, so a change to the docs or to - # the toolchain in requirements.txt has to prove it still builds before it - # lands. Without this the workflow ran only on main, which meant a dependency - # bump had no signal at all and nothing could say whether it was safe to merge. build: runs-on: ubuntu-latest + outputs: + version: ${{ steps.version.outputs.version }} steps: - name: Checkout uses: actions/checkout@v7 - - - name: Set up Python - uses: actions/setup-python@v7 with: - python-version: "3.14" - cache: pip - cache-dependency-path: requirements.txt + # themes/hextra (the tannevaled/hextra fork, pinned to a release tag) + # and branding (go-ndarray/brand) are submodules. + submodules: recursive + # The whole history: each page's creation and modification come from git. + fetch-depth: 0 - - name: Install MkDocs Material + mike + - name: Install Hugo ${{ env.HUGO_VERSION }} run: | - pip install --upgrade pip - pip install -r requirements.txt + curl -fsSL -o /tmp/hugo.tar.gz \ + "https://github.com/gohugoio/hugo/releases/download/v${HUGO_VERSION}/hugo_${HUGO_VERSION}_linux-amd64.tar.gz" + echo "${HUGO_SHA256} /tmp/hugo.tar.gz" | sha256sum -c - + sudo tar -xzf /tmp/hugo.tar.gz -C /usr/local/bin hugo + hugo version + + # The version label is ndarray's minor version, read from hugo.yaml, + # and it is the last segment of baseURL: the version selector reads it there. + - name: Version + id: version + run: | + ndarray=$(hugo config --format json | jq -r '.params.ndarray.version') + version=$(printf '%s\n' "${ndarray}" | sed -nE 's/^v(0|[1-9][0-9]*)\.(0|[1-9][0-9]*)\.(0|[1-9][0-9]*)$/\1.\2/p') + if [ -z "${version}" ]; then + echo "::error::params.ndarray.version in hugo.yaml is \"${ndarray}\", not vMAJOR.MINOR.PATCH" + exit 1 + fi + echo "version=${version}" >> "$GITHUB_OUTPUT" + echo "ndarray ${ndarray}: documentation version ${version}" + + # Each page's creation (first commit, followed across renames) and its + # author, for the page history at its foot (params.page.history). + - name: Page history + run: | + mkdir -p data + sh themes/hextra/scripts/page-history.sh > data/pagehistory.json + + # --panicOnWarning: a deprecated setting or a template warning fails the + # build rather than scrolling by. A broken {{< relref >}} is an error. + - name: Build + env: + HUGO_ENVIRONMENT: production + run: >- + hugo --gc --minify --panicOnWarning --printPathWarnings + --baseURL "${DOCS_URL}/${{ steps.version.outputs.version }}/" + + # Internal links and anchors, offline, with the site laid out under + # /docs// as it is served. + - name: Lay out the site for the link check + run: | + mkdir -p _check/docs + cp -R public "_check/docs/${{ steps.version.outputs.version }}" + + - name: Check links + uses: lycheeverse/lychee-action@v2 + with: + args: >- + --offline --no-progress --include-fragments + --index-files index.html + --root-dir ${{ github.workspace }}/_check + '_check/docs/**/*.html' + fail: true - # --strict turns a broken link or an unknown config key into a failure - # rather than a warning nobody reads. - - name: Build the site (strict) - run: mkdocs build --strict + - uses: actions/upload-artifact@v7 + with: + name: site + path: public + retention-days: 7 + + # The offline check above proves the site's own links and anchors. An + # https:// URL is text to it: this job fetches each one the pages name. + links: + name: every external link resolves + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v7 + - name: Check + run: | + set -uo pipefail + # The character class matters: an extractor stopping only at ) and " + # carries a closing > or < into the request. + urls=$(grep -rhoE 'https?://[^)">< [:space:]]+' content/ README.md | sed -E 's/[.,`]+$//' | sort -u) + test -n "$urls" || { echo "::error::no links found; are the pages still there?"; exit 1; } + echo "checking $(printf '%s\n' "$urls" | wc -l) distinct URLs" + fail=0 + while read -r u; do + case "$u" in + # Renders whatever it is asked for; proves nothing. + *img.shields.io*) continue ;; + # Documented placeholders, not hosts. + *example.com*|*example.org*|*example.net*|*localhost*|*127.0.0.1*|*.invalid*) continue ;; + esac + # Three attempts: one is a flake detector rather than a link + # checker, and a lane that fails at random gets ignored. + code=000 + for _ in 1 2 3; do + code=$(curl -sS -o /dev/null -w '%{http_code}' -L --max-time 25 "$u" 2>/dev/null) || true + code=${code:-000} + case "$code" in 2*|3*) break ;; esac + sleep 3 + done + case "$code" in + 2*|3*) printf ' ok %s %s\n' "$code" "$u" ;; + *) printf ' FAIL %s %s\n' "$code" "$u"; fail=1 ;; + esac + done <<< "$urls" + test "$fail" -eq 0 || { echo "::error::at least one link does not resolve"; exit 1; } deploy: # Only main publishes. A pull request stops after build above: it has no - # business pushing to gh-pages, and `permissions: contents: write` is not - # granted to a fork's PR anyway. - if: github.event_name == 'push' || github.event_name == 'workflow_dispatch' + # business pushing to gh-pages. + if: github.event_name != 'pull_request' && github.ref == 'refs/heads/main' needs: build - # Two deploys must never run at once: `mike deploy --push` pushes gh-pages, - # and a second job doing the same is rejected outright -- - # - # ! [rejected] gh-pages -> gh-pages (fetch first) - # - # cancel-in-progress stays FALSE deliberately: killing a publish half way - # through is worse than making it wait, and the queued run then fetches the - # gh-pages the previous one just wrote. + # Never two deploys at once: both would push gh-pages and the second would + # be rejected. cancel-in-progress stays false -- killing a publish half way + # through is worse than making it wait. concurrency: group: gh-pages-deploy cancel-in-progress: false @@ -61,32 +156,35 @@ jobs: permissions: contents: write steps: - - name: Checkout - uses: actions/checkout@v7 + # The publishing script, from the commit being published. + - uses: actions/checkout@v7 with: - fetch-depth: 0 + path: source - - name: Set up Python - uses: actions/setup-python@v7 + # The published site. Only the version being built is replaced; every + # other version (the MkDocs-built 0.1 and 0.2) stays as it is. + - uses: actions/checkout@v7 with: - python-version: "3.14" + ref: gh-pages + path: gh-pages - - name: Install MkDocs Material + mike - run: | - pip install --upgrade pip - pip install -r requirements.txt + - uses: actions/download-artifact@v7 + with: + name: site + path: site - - name: Configure git for mike - run: | - git config user.name "github-actions[bot]" - git config user.email "41898282+github-actions[bot]@users.noreply.github.com" + - name: Publish ${{ needs.build.outputs.version }} + run: source/scripts/publish-version.sh "${{ needs.build.outputs.version }}" site gh-pages - # mike publishes versioned docs to the gh-pages branch; GitHub Pages serves - # that branch at https://go-ndarray.github.io/docs/. The version label - # tracks main as "0.2" aliased "latest"; tag-driven versions can be added - # later. /docs/ redirects to the default ("latest"). - - name: Deploy versioned docs + - name: Push gh-pages + working-directory: gh-pages run: | - git fetch origin gh-pages --depth=1 || true - mike deploy --push --update-aliases 0.2 latest - mike set-default --push latest + git config user.name "github-actions[bot]" + git config user.email "41898282+github-actions[bot]@users.noreply.github.com" + git add -A + if git diff --cached --quiet; then + echo "nothing changed" + exit 0 + fi + git commit -q -m "Deployed ${GITHUB_SHA::7} to ${{ needs.build.outputs.version }} with Hugo ${HUGO_VERSION}" + git push origin HEAD:gh-pages diff --git a/.github/workflows/redeploy-version.yml b/.github/workflows/redeploy-version.yml deleted file mode 100644 index 466f2ae..0000000 --- a/.github/workflows/redeploy-version.yml +++ /dev/null @@ -1,95 +0,0 @@ -name: redeploy an older version - -# docs.yml publishes main as the current version, aliased "latest". An older -# version stays on gh-pages exactly as it was last built, so a change to the -# theme (such as the outdated-version banner in overrides/main.html) never -# reaches it. This workflow rebuilds ONE older version: -# -# - its text (docs/) comes from `source`, the commit that version was -# last deployed from ("Deployed to " in the gh-pages log), -# so the content readers see does not change; -# - the configuration and theme overrides (mkdocs.yml, overrides/) come -# from main, which is the point of the rebuild. -# -# It refuses to touch the version that carries the "latest" alias (docs.yml -# owns that one) or a version that does not already exist, and it never -# changes aliases or the default version. - -on: - workflow_dispatch: - inputs: - version: - description: "Existing non-latest version to rebuild (e.g. 0.1)" - required: true - type: string - source: - description: "Commit whose docs/ that version was deployed from" - required: true - type: string - -permissions: - contents: read - -jobs: - redeploy: - # Same group as docs.yml's deploy job: two mike pushes to gh-pages must - # never run at once. - concurrency: - group: gh-pages-deploy - cancel-in-progress: false - runs-on: ubuntu-latest - permissions: - contents: write - env: - VERSION: ${{ inputs.version }} - SOURCE: ${{ inputs.source }} - steps: - - name: Checkout - uses: actions/checkout@v7 - with: - fetch-depth: 0 - - - name: Set up Python - uses: actions/setup-python@v7 - with: - python-version: "3.14" - - - name: Install MkDocs Material + mike - run: | - pip install --upgrade pip - pip install -r requirements.txt - - - name: Refuse anything but an existing, non-latest version - run: | - git fetch origin gh-pages --depth=1 - git show origin/gh-pages:versions.json > "$RUNNER_TEMP/versions.json" - python3 - "$RUNNER_TEMP/versions.json" <<'PY' - import json, os, sys - version = os.environ["VERSION"] - entries = {e["version"]: e for e in json.load(open(sys.argv[1]))} - if version not in entries: - sys.exit(f"version {version!r} is not on gh-pages; nothing to rebuild") - if "latest" in entries[version]["aliases"]: - sys.exit(f"version {version!r} is latest; docs.yml publishes it") - print(f"rebuilding {version!r} (aliases: {entries[version]['aliases']})") - PY - - - name: Take docs/ from the source commit - run: | - git cat-file -e "$SOURCE^{commit}" - git rm -r -q docs - git checkout "$SOURCE" -- docs - git log -1 --format='source: %H %s' "$SOURCE" - - - name: Build the site (strict) - run: mkdocs build --strict - - - name: Configure git for mike - run: | - git config user.name "github-actions[bot]" - git config user.email "41898282+github-actions[bot]@users.noreply.github.com" - - - name: Redeploy the version - run: | - mike deploy --push "$VERSION" \ - --message "Redeployed $VERSION: docs/ from ${SOURCE}, config from $(git rev-parse --short HEAD)" diff --git a/.gitignore b/.gitignore index 053c435..7cd05fc 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,8 @@ -/site/ -.venv/ -__pycache__/ +/public/ +/resources/ +/_check/ +.hugo_build.lock +.DS_Store + +# Written by themes/hextra/scripts/page-history.sh before each build (CI and local). +/data/pagehistory.json diff --git a/.gitmodules b/.gitmodules new file mode 100644 index 0000000..8fca74b --- /dev/null +++ b/.gitmodules @@ -0,0 +1,6 @@ +[submodule "themes/hextra"] + path = themes/hextra + url = https://github.com/tannevaled/hextra.git +[submodule "branding"] + path = branding + url = https://github.com/go-ndarray/brand.git diff --git a/README.md b/README.md index fa416f6..f0a9571 100644 --- a/README.md +++ b/README.md @@ -1,17 +1,152 @@ -# go-ndarray docs +# docs -Documentation site for [go-ndarray](https://github.com/go-ndarray/ndarray) — -built with MkDocs Material, versioned with [mike](https://github.com/jimporter/mike), -and served at . +[![docs](https://github.com/go-ndarray/docs/actions/workflows/docs.yml/badge.svg)](https://github.com/go-ndarray/docs/actions/workflows/docs.yml) +[![License](https://img.shields.io/badge/license-BSD--3--Clause-047857?style=flat-square)](LICENSE) -## Local preview +**The go-ndarray documentation site**, published at +. + +A [Hugo](https://gohugo.io/) site with the [tannevaled/hextra](https://github.com/tannevaled/hextra) +fork of the [Hextra](https://github.com/imfing/hextra) theme, built the same way +as the [go-fileshare documentation](https://github.com/go-fileshare/docs), with +go-ndarray's branding. It documents +[`go-ndarray/ndarray`](https://github.com/go-ndarray/ndarray). + +## Versions + +A version of the documentation is **the minor version of ndarray it +describes**: + +| Source | Published under | +| --- | --- | +| branch `main`, with `params.ndarray.version: v0.9.1` in `hugo.yaml` | `https://go-ndarray.github.io/docs//`, here `/docs/0.9/` | +| the newest version | also `https://go-ndarray.github.io/docs/latest/` | + +`https://go-ndarray.github.io/docs/` redirects to `latest/`. Each push to +`main` replaces the directory of the version it describes, and nothing else. + +To document a new ndarray release, change `params.ndarray.version` with the +pages. A patch release (`v0.9.2`) updates `0.9`; a minor release (`v0.10.0`) +publishes `0.10` beside it, `latest` moves to it, and `0.9` stays as it was last +published, with a banner pointing its readers to the latest version. + +The version selector in the navbar lists every version from +`docs/versions.json` and opens the same page in the version chosen, else that +version's home. + +Versions 0.1 and 0.2 were built with MkDocs and mike before this site moved to +Hugo (0.2 was the label MkDocs kept publishing up to ndarray v0.9.1). Their +directories on `gh-pages` are kept as they were published, and they read the +same `versions.json` (mike's format). + +## Mirrored pages + +Two pages are copies of files in `go-ndarray/ndarray`, kept verbatim so that +re-syncing them is a copy, not an edit: + +| Page | Mirrors | What is ours | +| --- | --- | --- | +| `content/en/performance.md` | [`docs/perf.md`](https://github.com/go-ndarray/ndarray/blob/main/docs/perf.md), from the heading `## How a pure-Go library can beat NumPy` to the end | the front matter, and the introduction and *Where it stands* summary above that heading | +| `content/en/security.md` | [`SECURITY.md`](https://github.com/go-ndarray/ndarray/blob/main/SECURITY.md), from `## Reporting` to the end | the front matter and the `> Mirrored from … at vX.Y.Z` line; repository-relative references are rewritten as GitHub links (`this repository` → `[the repository](https://github.com/go-ndarray/ndarray/security)`, `` `fuzz_test.go` `` → ``[`fuzz_test.go`](https://github.com/go-ndarray/ndarray/blob/main/fuzz_test.go)``, …) | + +To re-sync from a checkout of ndarray at tag `vX.Y.Z` (`$ND`): + +```sh +# performance: keep our part above the heading, replace the rest +{ sed '/^## How a pure-Go library can beat NumPy/,$d' content/en/performance.md + sed -n '/^## How a pure-Go library can beat NumPy/,$p' "$ND/docs/perf.md"; } > perf.tmp +mv perf.tmp content/en/performance.md + +# security: keep our part above "## Reporting", replace the rest, then +# rewrite the repository-relative references as GitHub links again +{ sed '/^## Reporting/,$d' content/en/security.md + sed -n '/^## Reporting/,$p' "$ND/SECURITY.md"; } > sec.tmp +mv sec.tmp content/en/security.md +git diff content/en/security.md # check the links, bump "at vX.Y.Z" +``` + +then update the version in the `> Mirrored from` line, the *Where it stands* +summary of the performance page if the numbers moved, and +`params.ndarray.version` in `hugo.yaml`. + +The mirrored text keeps MkDocs' heading anchors in its in-page links +(`#parallel-gemm-start-once-wait-on-work-2026-10-04`). Where Hugo's anchor for a +heading differs from the one MkDocs made, `layouts/_markup/render-heading.html` +adds MkDocs' as a second anchor, so those links, and links published while the +site was built with MkDocs, keep working without editing the mirrored text. + +## Languages + +English only, at the root of each version (`/docs//performance/`). +The content lives in `content/en/` so that a language can be added like on the +go-fileshare site: `content//` with the same file names, an +`i18n/.yaml`, and a `languages.` entry in `hugo.yaml` (label, +`contentDir`, weight, flag, translated description, date format). Declare a +language only with its content: a declared language without pages is an empty +entry in the switch. + +## Layout + +| Path | What | +| --- | --- | +| `content/en/` | the pages; `_index.md` is the home, `weight` orders the sidebar | +| `i18n/en.yaml` | the site's strings, beside the theme's | +| `hugo.yaml` | `baseURL`, the ndarray version described, the navbar (version › theme › search › GitHub), the brand mounts | +| `layouts/_partials/custom/version-select.html` | the version selector (reads `versions.json`), from go-fileshare/docs | +| `layouts/_partials/custom/content-begin.html` | the outdated-version banner (reads `versions.json`), as the MkDocs site had | +| `layouts/_markup/render-heading.html` | Hextra's heading, plus MkDocs' anchor where it differs | +| `layouts/_partials/favicons.html` | the brand's favicons | +| `assets/css/custom.css` | the brand green as Hextra's primary colour, the table headers | +| `themes/hextra` | submodule: [tannevaled/hextra](https://github.com/tannevaled/hextra), pinned to a release tag | +| `branding` | submodule: [go-ndarray/brand](https://github.com/go-ndarray/brand) (the mark, its PNGs and ICO) | +| `scripts/publish-version.sh` | puts one build into a `gh-pages` checkout and rewrites `versions.json`, `latest` and the root redirect | + +## Writing a page + +Each page starts with a title, a one-sentence description, and tags: + +```yaml +--- +title: "Performance — go-ndarray vs NumPy" +linkTitle: "Performance" +weight: 20 +description: "…" +tags: [performance] +--- +``` + +Link to another page by its file (`[Performance](performance.md)`): the theme +resolves it to the page's URL. The build's offline link check fails on a +link, or an anchor, that does not exist. + +## Working locally ```sh -pip install -r requirements.txt -mkdocs serve +git clone --recurse-submodules https://github.com/go-ndarray/docs.git +cd docs +mkdir -p data && sh themes/hextra/scripts/page-history.sh > data/pagehistory.json +hugo server --baseURL http://localhost:1313/docs/0.9/ ``` -Pushing to `main` deploys the `latest` version to the `gh-pages` branch via -GitHub Actions; GitHub Pages serves it under `/docs/`. +then open . Without a `versions.json` the +selector shows only the current version. Hugo 0.146 or later (CI uses the +version pinned in the workflow); no Python, no Node. + +To upgrade the theme: `git -C themes/hextra checkout ` (a tag of the +fork), commit the submodule, and run the production build of the workflow. + +## Publication + +`.github/workflows/docs.yml`: + +| Job | When | What | +| --- | --- | --- | +| `build` | pull requests, `main` | builds with `--baseURL …/docs//`, checks internal links and anchors (lychee, offline), uploads the site | +| `links` | pull requests, `main` | fetches every external URL the pages name | +| `deploy` | `main` | `scripts/publish-version.sh` into `gh-pages`, then a commit and a push; one deploy at a time | + +GitHub Pages serves the `gh-pages` branch. + +## Licence -BSD-3-Clause © the go-ndarray/docs authors. +BSD-3-Clause. diff --git a/assets/css/custom.css b/assets/css/custom.css new file mode 100644 index 0000000..9a5df0a --- /dev/null +++ b/assets/css/custom.css @@ -0,0 +1,35 @@ +/* go-ndarray branding on Hextra (github.com/go-ndarray/brand). + * + * The mark's emerald gradient, #34D399 -> #047857 (branding/svg/color/ + * go-ndarray.svg); the MkDocs site before this one used Material's green. + * Hextra derives its whole primary scale (links, active sidebar entry, + * buttons) from these three variables. + * + * Light theme: #047857 = hsl(163 94% 24%), the gradient's dark end. + * Dark theme: #34D399 = hsl(158 64% 52%), its light end. */ +:root { + --primary-hue: 163deg; + --primary-saturation: 94%; + --primary-lightness: 24%; +} +.dark { + --primary-hue: 158deg; + --primary-saturation: 64%; + --primary-lightness: 52%; +} + +/* The performance page is mostly tables: their headers stand out in the brand + * colour and their text is a size smaller than the prose. In the dark theme + * the header keeps the deep green: white on #34D399 cannot be read. */ +.content table { font-size: 0.875em; } +.content table th { background: #047857; color: #fff; } + +/* The version selector's list hangs from its button's right edge, so that it + * stays on screen at the end of the navbar. */ +.fs-version-select .hextra-nav-menu-items { left: auto !important; right: 0; transform: none !important; } + +/* The outdated-version banner (layouts/_partials/custom/content-begin.html). */ +.nd-outdated { margin-top: 1rem; padding: 0.75rem 1rem; border-radius: 0.5rem; + border: 1px solid #f59e0b; background: #fffbeb; color: #78350f; } +.dark .nd-outdated { background: #451a03; color: #fde68a; border-color: #b45309; } +.nd-outdated a { text-decoration: underline; font-weight: 600; } diff --git a/branding b/branding new file mode 160000 index 0000000..dd14ca0 --- /dev/null +++ b/branding @@ -0,0 +1 @@ +Subproject commit dd14ca0e507d3da3b8abb8c811a68dee36978dee diff --git a/docs/index.md b/content/en/_index.md similarity index 96% rename from docs/index.md rename to content/en/_index.md index a80745c..ae15e2e 100644 --- a/docs/index.md +++ b/content/en/_index.md @@ -1,4 +1,11 @@ -# go-ndarray documentation +--- +title: "go-ndarray documentation" +linkTitle: "Home" +type: docs +cascade: + type: docs +description: "A pure-Go (no cgo) NumPy-style n-dimensional array for float64: views, broadcasting, ufuncs, reductions, linear algebra; multicore + SIMD." +--- **A pure-Go (no cgo) NumPy-style n-dimensional array** for `float64` — the `numpy` equivalent for Go. Creation routines, strided **views that share data**, diff --git a/docs/performance.md b/content/en/performance.md similarity index 99% rename from docs/performance.md rename to content/en/performance.md index c8bd1c7..f31ee20 100644 --- a/docs/performance.md +++ b/content/en/performance.md @@ -1,4 +1,12 @@ -# Performance — go-ndarray vs NumPy +--- +# From "## How a pure-Go library can beat NumPy" down, this page is +# go-ndarray/ndarray docs/perf.md, copied verbatim (README: "Mirrored pages"). +title: "Performance — go-ndarray vs NumPy" +linkTitle: "Performance" +weight: 20 +description: "Measured head-to-head of go-ndarray against NumPy and OpenBLAS, and where NumPy still leads." +tags: [performance, simd, numpy] +--- Honest, reproducible head-to-head of `go-ndarray/ndarray` against **NumPy 2.2.4** on identical hardware. "On n'a pas le droit de se tromper": every number diff --git a/docs/roadmap.md b/content/en/roadmap.md similarity index 98% rename from docs/roadmap.md rename to content/en/roadmap.md index 13c2075..8d4a3b2 100644 --- a/docs/roadmap.md +++ b/content/en/roadmap.md @@ -1,4 +1,10 @@ -# Implementation plan — N-dimensional arrays in pure Go (`go-ndarray/ndarray`) +--- +title: "Implementation plan — N-dimensional arrays in pure Go (`go-ndarray/ndarray`)" +linkTitle: "Roadmap" +weight: 10 +description: "The plan for go-ndarray and what ships today." +tags: [roadmap] +--- > Goal: a **pure-Go (CGO=0)** NumPy-style N-dimensional array library, as a > **standalone, reusable** module with correct scalar kernels now and a clean diff --git a/docs/security.md b/content/en/security.md similarity index 97% rename from docs/security.md rename to content/en/security.md index 38a0940..95672b4 100644 --- a/docs/security.md +++ b/content/en/security.md @@ -1,4 +1,12 @@ -# Security +--- +# Below the "Mirrored from" line, this page is go-ndarray/ndarray SECURITY.md, +# with its repository-relative links rewritten to GitHub (README: "Mirrored pages"). +title: "Security" +linkTitle: "Security" +weight: 30 +description: "How to report a vulnerability, what the library guarantees, and the audit log." +tags: [security] +--- > Mirrored from [`SECURITY.md`](https://github.com/go-ndarray/ndarray/blob/main/SECURITY.md) at v0.9.1. diff --git a/docs/assets/logo.svg b/docs/assets/logo.svg deleted file mode 100644 index 8c9adf2..0000000 --- a/docs/assets/logo.svg +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - - - - diff --git a/hugo.yaml b/hugo.yaml new file mode 100644 index 0000000..697c906 --- /dev/null +++ b/hugo.yaml @@ -0,0 +1,166 @@ +# go-ndarray documentation (Hugo + Hextra). +# +# Each documentation version is a separate build under +# https://go-ndarray.github.io/docs//, where is the minor +# version of ndarray the pages describe (0.9, 0.10, ...): the label readers +# have used since the site was built with MkDocs and mike. The CI sets it +# through --baseURL, from params.ndarray.version below. This default is the +# same build. +baseURL: "https://go-ndarray.github.io/docs/0.9/" +title: "go-ndarray" +# themes/hextra is the tannevaled/hextra fork, pinned at a release tag: +# upstream Hextra plus opt-in features (partial menu items, combos, +# related-sites menu, page subtitle and history, darkreaderLock). +theme: hextra + +# English is the default language and stays at the root of each version +# (/docs//performance/), so every URL published by MkDocs keeps +# working; other languages would live under their code +# (/docs//fr/performance/). The versions built with MkDocs (0.1 and +# 0.2) are English only: the version selector falls back to their English page +# (layouts/_partials/custom/version-select.html). +defaultContentLanguage: en +defaultContentLanguageInSubdir: false + +# Each language has its own content directory, with the same file names, so +# that a page and its translation are paired. A language is declared here only +# once its content exists: a declared language without content would be an +# empty entry in the language switch. +languages: + en: + label: "English" + contentDir: content/en + weight: 1 + +enableRobotsTXT: true +# Git history dates every page: "Last updated on" at the bottom of each page. +enableGitInfo: true + +markup: + goldmark: + renderer: + unsafe: true + parser: + attribute: + title: true + highlight: + noClasses: false + +module: + hugoVersion: + extended: false + min: 0.146.0 + # The marks and favicons come from the go-ndarray brand repository + # (submodule branding/), published under /images/brand/. Only the org mark's + # colour formats are mounted. + mounts: + - source: static + target: static + - source: assets + target: assets + - source: branding/svg/color + target: static/images/brand/svg + files: ["go-ndarray.svg"] + - source: branding/png/color + target: static/images/brand/png + files: ["*/go-ndarray.png"] + - source: branding/ico + target: static/images/brand/ico + files: ["go-ndarray.ico"] + +menu: + main: + # Navbar, left to right: version, theme, search, sources. + - name: Version + weight: 3 + params: + type: partial + partial: custom/version-select.html + - name: Theme + weight: 4 + params: + type: theme-toggle + combo: true + - name: Search + weight: 5 + params: + type: search + - name: GitHub + url: https://github.com/go-ndarray/ndarray + weight: 6 + params: + icon: github + +params: + description: "A pure-Go (CGO=0) NumPy-style n-dimensional array library for float64: views, broadcasting, ufuncs, reductions, linalg; multicore + SIMD." + + # The ndarray release these pages describe. Its minor version is the + # documentation's version label (v0.9.1 -> 0.9): the CI reads it here. + ndarray: + version: "v0.9.1" + + navbar: + displayTitle: true + displayLogo: true + logo: + # The green mark reads on both themes; the name is the site title. + path: images/brand/svg/go-ndarray.svg + dark: images/brand/svg/go-ndarray.svg + width: 32 + height: 32 + # Related sites, opened from the chevron beside the title (the logo itself + # leads to the documentation's home). + menu: + - name: go-ndarray + url: https://go-ndarray.github.io/ + logo: images/brand/svg/go-ndarray.svg + - name: Playground + url: https://go-ndarray.github.io/playground/ + logo: images/brand/svg/go-ndarray.svg + + theme: + default: system + # The theme toggle is in the navbar (menu above), not at the foot of the sidebar. + displayToggle: false + # Dark Reader would darken the light theme itself; with the lock, the dark + # theme is our own. + darkreaderLock: true + + footer: + enable: true + displayCopyright: true + displayPoweredBy: false + + displayUpdatedDate: true + dateFormat: "January 2, 2006" + + editURL: + enable: true + base: "https://github.com/go-ndarray/docs/edit/main/content" + + toc: + displayTags: true + + page: + width: normal + # The front matter description as a subtitle under the title. + displayDescription: true + # "Created on ... by ... · Modified on ... by ..." at the foot of each page: + # creation from data/pagehistory.json, which the build writes with + # themes/hextra/scripts/page-history.sh (it needs the full git history); + # modification from git (enableGitInfo). + history: true + contextMenu: + enable: false + + highlight: + copy: + enable: true + display: hover + + search: + enable: true + type: flexsearch + flexsearch: + index: content + tokenize: forward diff --git a/i18n/en.yaml b/i18n/en.yaml new file mode 100644 index 0000000..ceaaf97 --- /dev/null +++ b/i18n/en.yaml @@ -0,0 +1,5 @@ +# Site strings beside Hextra's own (themes/hextra/i18n/en.yaml). +copyright: "© 2026 the go-ndarray authors · BSD-3-Clause" +version: "Version" +outdated: "You are reading the documentation of an outdated version, whose text may no longer be accurate." +outdatedLink: "Go to the latest version." diff --git a/layouts/_markup/render-heading.html b/layouts/_markup/render-heading.html new file mode 100644 index 0000000..0735823 --- /dev/null +++ b/layouts/_markup/render-heading.html @@ -0,0 +1,24 @@ +{{- /* Hextra's heading (themes/hextra/layouts/_markup/render-heading.html), + plus the anchor MkDocs gave the same heading when it differs from Hugo's. + + MkDocs (Python-Markdown's toc) drops non-ASCII and punctuation, then folds + every run of spaces and hyphens into one hyphen: "Performance — go-ndarray + vs NumPy" is #performance-go-ndarray-vs-numpy there, and + #performance--go-ndarray-vs-numpy for Hugo. Links published while the site + was built with MkDocs, and the mirrored pages copied verbatim from + go-ndarray/ndarray (whose in-page links use the MkDocs form), keep working + through the second, empty anchor, without editing the mirrored text. */ -}} +{{- $legacy := .PlainText | transform.HTMLUnescape | lower -}} +{{- $legacy = replaceRE `[^a-z0-9_\s-]` "" $legacy | strings.TrimSpace -}} +{{- $legacy = replaceRE `[-\s]+` "-" $legacy -}} + + {{- .Text | safeHTML -}} + {{- if gt .Level 1 -}} + + {{- if and $legacy (ne $legacy .Anchor) -}} + + {{- end -}} + + {{- end -}} + +{{- /* Drop trailing newlines */ -}} diff --git a/layouts/_partials/custom/content-begin.html b/layouts/_partials/custom/content-begin.html new file mode 100644 index 0000000..9dd6dee --- /dev/null +++ b/layouts/_partials/custom/content-begin.html @@ -0,0 +1,30 @@ +{{- /* Outdated-version banner, as the MkDocs site had (overrides/main.html). + + A build cannot know whether a newer version was published after it, so the + banner is hidden and the browser reveals it when /versions.json + gives the alias "latest" to another version than this one. The link goes to + the docs root, which redirects to latest/; it is set with the banner, so + the offline link check (where only this version exists) never sees it. */ -}} +{{- $basePath := strings.TrimSuffix "/" (urls.Parse site.BaseURL).Path -}} +{{- $current := path.Base $basePath -}} +{{- $docsRoot := printf "%s/" (strings.TrimSuffix "/" (path.Dir $basePath)) -}} + + diff --git a/layouts/_partials/custom/version-select.html b/layouts/_partials/custom/version-select.html new file mode 100644 index 0000000..f355162 --- /dev/null +++ b/layouts/_partials/custom/version-select.html @@ -0,0 +1,119 @@ +{{- /* Navbar version selector, as in go-fileshare/docs and claimward/docs. + + Each documentation version is a separate build published under + //, where is the minor version of ndarray the + pages describe (0.9, ...), and its baseURL ends with that version. A build + cannot know the versions published after it, so the server renders only the + current one; in the browser the list is filled from /versions.json, + which the deploy job rewrites in the format mike writes (version, title, + aliases, properties), so the MkDocs-built versions (0.1, 0.2) and the Hugo + builds read the same file. + + The site is ready for several languages: English at the root of a version, + the others under their code (/fr/...). Each option opens, in that + version, the first of these that exists: the current page in the current + language; the same page in English (the MkDocs-built versions are English + only); the home in the current language; the version's home. A page that a + version does not have therefore never ends on a 404. The language prefix is + read from the home's permalink, not guessed from the URL. + + Built on Hextra's own navbar dropdown (assets/js/core/nav-menu.js), so its + keyboard handling applies. */ -}} +{{- $page := . -}} +{{- $basePath := strings.TrimSuffix "/" (urls.Parse site.BaseURL).Path -}} +{{- $current := path.Base $basePath -}} +{{- $docsRoot := printf "%s/" (strings.TrimSuffix "/" (path.Dir $basePath)) -}} +{{- $versionRoot := printf "%s%s/" $docsRoot $current -}} +{{- $rest := strings.TrimPrefix $versionRoot $page.RelPermalink -}} +{{- /* "fr/" in French, "" in English (the default language, at the root). */ -}} +{{- $langPrefix := strings.TrimPrefix $versionRoot site.Home.RelPermalink -}} +{{- /* The same page in the default language: same file names in every content//. */ -}} +{{- $restDefault := cond (ne $langPrefix "") (strings.TrimPrefix $langPrefix $rest) $rest -}} +{{- $dev := site.Params.versions.development | default "dev" -}} +{{- $itemClass := "hx:text-gray-600 hx:hover:text-gray-800 hx:dark:text-gray-400 hx:dark:hover:text-gray-200 hx:relative hx:cursor-pointer hx:whitespace-nowrap hx:rounded-sm hx:py-1.5 hx:transition-colors hx:ltr:pl-3 hx:ltr:pr-9 hx:rtl:pr-3 hx:rtl:pl-9 hx:flex hx:items-center hx:gap-1 hx:hover:bg-gray-100 hx:dark:hover:bg-neutral-800" -}} +{{- $iconAttrs := `height="1em" class="hx:inline-block"` -}} +
+ + + + + +
+ diff --git a/layouts/_partials/favicons.html b/layouts/_partials/favicons.html new file mode 100644 index 0000000..dbf2f79 --- /dev/null +++ b/layouts/_partials/favicons.html @@ -0,0 +1,8 @@ +{{- /* The favicons are the go-ndarray brand's org mark (submodule branding/, + mounted under /images/brand/ by hugo.yaml) rather than files copied into + static/. Replaces Hextra's favicons.html, which expects them at the root. */ -}} + + + + + diff --git a/mkdocs.yml b/mkdocs.yml deleted file mode 100644 index 78080ac..0000000 --- a/mkdocs.yml +++ /dev/null @@ -1,61 +0,0 @@ -site_name: go-ndarray docs -site_description: "A pure-Go (CGO=0) NumPy-style n-dimensional array library for float64 — views, broadcasting, ufuncs, reductions, linalg; multicore + SIMD." -site_url: https://go-ndarray.github.io/docs/ -repo_url: https://github.com/go-ndarray/ndarray -repo_name: go-ndarray/ndarray -edit_uri: "" - -theme: - name: material - # overrides/main.html defines the "outdated" block: the banner that tells - # a reader of any version other than "latest" to go to the latest one. - custom_dir: overrides - logo: assets/logo.svg - favicon: assets/logo.svg - features: - - navigation.sections - - navigation.top - - navigation.instant - - content.code.copy - - search.suggest - palette: - - media: "(prefers-color-scheme)" - toggle: - icon: material/brightness-auto - name: Switch to light mode - - media: "(prefers-color-scheme: light)" - scheme: default - primary: green - toggle: - icon: material/brightness-7 - name: Switch to dark mode - - media: "(prefers-color-scheme: dark)" - scheme: slate - primary: green - toggle: - icon: material/brightness-4 - name: Switch to system preference - -extra: - version: - provider: mike - default: latest - -nav: - - Home: index.md - - Roadmap: roadmap.md - - Performance: performance.md - - Security: security.md - -markdown_extensions: - - admonition - - attr_list - - def_list - - footnotes - - tables - - toc: - permalink: true - - pymdownx.highlight: - anchor_linenums: true - - pymdownx.superfences - - pymdownx.inlinehilite diff --git a/overrides/main.html b/overrides/main.html deleted file mode 100644 index 126d246..0000000 --- a/overrides/main.html +++ /dev/null @@ -1,19 +0,0 @@ -{% extends "base.html" %} - -{# - Version warning. mkdocs-material fills this block on every page, then its - mike integration reveals it only when the version being read is not the - default one (extra.version.default: latest in mkdocs.yml). Readers of an - older version therefore see that it is outdated, with a link to the latest. - The link goes to the parent of the version directory, the site root, which - mike redirects to "latest". base_url ~ '/../' resolves there from every page: - base_url is relative ("." or "..") on ordinary pages and absolute - ("/docs/0.1") on 404.html, where '../' ~ base_url would not. -#} -{% block outdated %} - You are reading the documentation of an outdated version, whose text may - no longer be accurate. - - Go to the latest version. - -{% endblock %} diff --git a/requirements.txt b/requirements.txt deleted file mode 100644 index 34eb296..0000000 --- a/requirements.txt +++ /dev/null @@ -1,2 +0,0 @@ -mkdocs-material==9.7.7 -mike==2.2.0 diff --git a/scripts/publish-version.sh b/scripts/publish-version.sh new file mode 100755 index 0000000..22a6489 --- /dev/null +++ b/scripts/publish-version.sh @@ -0,0 +1,82 @@ +#!/bin/sh +# Publish one documentation build into a checkout of the gh-pages branch. +# +# scripts/publish-version.sh +# +# is the minor version of ndarray the build describes, MAJOR.MINOR +# (0.9): the label mike used for this site, so every URL it published keeps +# its meaning. The build replaces // and nothing else: every +# other version already published -- the MkDocs-built 0.1 and 0.2 among them -- +# is kept as it is. Then, from the directories present: +# +# versions.json rewritten in mike's format ({version, title, aliases}), +# newest first; the version selector of the Hugo builds and +# that of the MkDocs builds both read it; +# latest a symbolic link to the newest version (as mike made it); +# index.html the root, redirecting to latest/ (as mike made it). +# +# Needs jq and rsync. Commits nothing: the workflow does. +set -eu + +die() { echo "publish-version.sh: $*" >&2; exit 1; } + +[ $# -eq 3 ] || die "usage: $0 " +version=$1 +site=$2 +pages=$3 +label='^(0|[1-9][0-9]*)\.(0|[1-9][0-9]*)$' + +printf '%s\n' "$version" | grep -Eq "$label" || die "version must be MAJOR.MINOR (0.9), not \"$version\"" +[ -f "$site/index.html" ] || die "$site/index.html is missing: build the site first" +[ -d "$pages" ] || die "$pages is not a directory" +command -v jq >/dev/null || die "jq is required" +command -v rsync >/dev/null || die "rsync is required" + +# The version's own directory, replaced whole. A real directory: if it was a +# link (an alias), the link goes, not what it pointed to. +[ -L "$pages/$version" ] && rm "$pages/$version" +mkdir -p "$pages/$version" +rsync -a --delete "$site/" "$pages/$version/" + +# The versions present, newest first (0.10 before 0.9: compared as numbers). +versions=$(cd "$pages" && for d in *; do + [ -d "$d" ] && [ ! -L "$d" ] && printf '%s\n' "$d" | grep -Eq "$label" && printf '%s\n' "$d" +done | sort -t. -k1,1nr -k2,2nr) +latest=$(printf '%s\n' "$versions" | sed -n 1p) + +# versions.json: the newest carries the alias "latest". +for v in $versions; do + if [ "$v" = "$latest" ]; then + jq -n --arg v "$v" '{version: $v, title: $v, aliases: ["latest"]}' + else + jq -n --arg v "$v" '{version: $v, title: $v, aliases: []}' + fi +done | jq -s . >"$pages/versions.json.tmp" +mv "$pages/versions.json.tmp" "$pages/versions.json" + +# latest -> the newest version, and the root redirect. +rm -rf "$pages/latest" +ln -s "$latest" "$pages/latest" +cat >"$pages/index.html" <<'EOF' + + + + + Redirecting + + + + + Redirecting to latest/... + + +EOF +touch "$pages/.nojekyll" + +echo "published $version; latest is $latest; versions: $(jq -r '[.[].version] | join(", ")' "$pages/versions.json")" diff --git a/themes/hextra b/themes/hextra new file mode 160000 index 0000000..1d8daa4 --- /dev/null +++ b/themes/hextra @@ -0,0 +1 @@ +Subproject commit 1d8daa42d0406250fa00b7e803db2ab104a7fcab