a73x

internal/site/dl.go

Ref:   Size: 2.6 KiB   History

package site

import (
	"fmt"
	"os"
	"path/filepath"
	"sort"
	"strings"
)

// downloadsMarkdown renders the /dl/ page body from a dist/<version> dir:
// every artifact linked at its stable /dl/<version>/ URL with size and
// sha256, plus the verification snippet. An empty distDir renders a
// docs-only preview note instead.
func downloadsMarkdown(distDir string) (string, error) {
	if distDir == "" {
		return "# downloads\n\nNo release is staged in this build. Release artifacts live under\n`/dl/<version>/`, with `/dl/latest/` pointing at the newest.\n\n" + apiSpecLine, nil
	}
	version := filepath.Base(distDir)
	sums, err := parseSums(filepath.Join(distDir, "SHA256SUMS"))
	if err != nil {
		return "", fmt.Errorf("dist %s: %w", distDir, err)
	}
	entries, err := os.ReadDir(distDir)
	if err != nil {
		return "", err
	}

	var b strings.Builder
	fmt.Fprintf(&b, "# downloads — %s\n\n", version)
	b.WriteString("| file | size | sha256 |\n|---|---|---|\n")
	names := make([]string, 0, len(entries))
	for _, e := range entries {
		if !e.IsDir() {
			names = append(names, e.Name())
		}
	}
	sort.Strings(names)
	for _, name := range names {
		info, err := os.Stat(filepath.Join(distDir, name))
		if err != nil {
			return "", err
		}
		// SHA256SUMS cannot list itself, so its own row has no checksum; an
		// empty code span would render as literal backticks.
		sha := "—"
		if s := sums[name]; s != "" {
			sha = "`" + s + "`"
		}
		fmt.Fprintf(&b, "| [%s](/dl/%s/%s) | %s | %s |\n",
			name, version, name, humanSize(info.Size()), sha)
	}
	fmt.Fprintf(&b, "\nVerify after downloading (checksums: [SHA256SUMS](/dl/%s/SHA256SUMS)):\n\n", version)
	b.WriteString("    sha256sum -c SHA256SUMS --ignore-missing\n")
	b.WriteString("\n" + apiSpecLine)
	return b.String(), nil
}

// apiSpecLine links the served API contract from the downloads page; the spec
// publishes at the site root in every build, dist or not.
const apiSpecLine = "[openapi.json](/openapi.json) — the server HTTP API, OpenAPI 3.1\n"

// parseSums reads sha256sum output: "<hex>  <name>" per line.
func parseSums(path string) (map[string]string, error) {
	raw, err := os.ReadFile(path)
	if err != nil {
		return nil, err
	}
	sums := map[string]string{}
	for _, line := range strings.Split(strings.TrimSpace(string(raw)), "\n") {
		fields := strings.Fields(line)
		if len(fields) == 2 {
			sums[strings.TrimPrefix(fields[1], "*")] = fields[0]
		}
	}
	return sums, nil
}

func humanSize(n int64) string {
	switch {
	case n >= 1<<20:
		return fmt.Sprintf("%.1f MiB", float64(n)/(1<<20))
	case n >= 1<<10:
		return fmt.Sprintf("%.1f KiB", float64(n)/(1<<10))
	default:
		return fmt.Sprintf("%d B", n)
	}
}