Skip to content
fossil-skin Artifact [3f64bda7ca]
Login

Artifact [3f64bda7ca]

Artifact 3f64bda7ca93bb6d094b57d4d977f034b4b61a40b71d517968749e5b045df90e:


#!/usr/bin/env python3
"""Push skin files to all repos and regenerate the landing page repo grid."""
import sqlite3, time, os, glob, re, html, hashlib

REPOS_DIR  = "/var/fossil/repos"
SKIN_DIR   = os.path.dirname(os.path.abspath(__file__))
WWW_DIR    = os.path.join(os.path.dirname(SKIN_DIR), "www")
INDEX_HTML = os.path.join(WWW_DIR, "index.html")

# Default icon assigned when no meta file exists for a repo
DEFAULT_ICON = "📦"
# Default public caps: wiki readable only
DEFAULT_PUBLIC_CAPS = "jh"

def sidecar_meta(fossil_path):
    """Read optional sidecar metadata for one repo.

    Format:
      line 1: emoji icon
      line 2: landing-page description
      line 3: optional Fossil caps for the "nobody" user (not logged in)
      line 4: optional Fossil caps for the "anonymous" user (defaults to line 3)
    """
    meta_path = fossil_path.replace(".fossil", ".meta")
    icon = DEFAULT_ICON
    desc = ""
    nobody_caps = DEFAULT_PUBLIC_CAPS
    anon_caps = None
    if os.path.exists(meta_path):
        lines = open(meta_path).read().splitlines()
        if len(lines) >= 1 and lines[0].strip():
            icon = lines[0].strip()
        if len(lines) >= 2 and lines[1].strip():
            desc = lines[1].strip()
        if len(lines) >= 3 and lines[2].strip():
            nobody_caps = lines[2].strip()
        if len(lines) >= 4 and lines[3].strip():
            anon_caps = lines[3].strip()
    return icon, desc, nobody_caps, anon_caps or nobody_caps

# ── Skin parts pushed to every repo ───────────────────────────────────────
parts = {
    "css":         open(f"{SKIN_DIR}/css.txt").read(),
    "header":      open(f"{SKIN_DIR}/header.txt").read(),
    "footer":      open(f"{SKIN_DIR}/footer.txt").read(),
    "skin-detail": "white-foreground",
}

# ── Apply skin to repos ────────────────────────────────────────────────────
repos = sorted(glob.glob(f"{REPOS_DIR}/*.fossil"))
mtime = int(time.time())

for repo in repos:
    _, _, nobody_caps, anon_caps = sidecar_meta(repo)
    con = sqlite3.connect(repo)
    for name, value in parts.items():
        con.execute("INSERT OR REPLACE INTO config(name,value,mtime) VALUES(?,?,?)",
                    (name, value, mtime))
    # Public access defaults to wiki-only unless a repo's .meta file overrides it.
    con.execute("UPDATE user SET cap=?, mtime=? WHERE login='nobody'", (nobody_caps, mtime))
    con.execute("UPDATE user SET cap=?, mtime=? WHERE login='anonymous'", (anon_caps, mtime))
    con.commit()
    con.close()
    print(f"  skinned {os.path.basename(repo)}")

# ── Regenerate landing page repo grid ─────────────────────────────────────
def repo_meta(fossil_path):
    """Read project-name and description from a .fossil file.
    Falls back to filename if not set. Also checks for an optional
    .meta file alongside the .fossil for icon/desc overrides."""
    name = os.path.basename(fossil_path).replace(".fossil", "")
    desc = ""
    icon = DEFAULT_ICON

    # Read project-name from SQLite config
    try:
        con = sqlite3.connect(fossil_path)
        row = con.execute("SELECT value FROM config WHERE name='project-name'").fetchone()
        if row:
            name = row[0]
        row = con.execute("SELECT value FROM config WHERE name='project-description'").fetchone()
        if row and row[0]:
            desc = row[0]
        con.close()
    except Exception:
        pass

    # Optional sidecar file: /var/fossil/repos/<name>.meta
    # Format:
    #   line 1: emoji icon
    #   line 2: landing-page description
    #   line 3/4: optional caps for nobody / anonymous
    meta_path = fossil_path.replace(".fossil", ".meta")
    if os.path.exists(meta_path):
        icon_override, desc_override, _, _ = sidecar_meta(fossil_path)
        icon = icon_override
        if desc_override:
            desc = desc_override

    return name, icon, desc

def make_card(name, icon, desc, slug):
    esc_name = html.escape(name)
    esc_desc = html.escape(desc)
    esc_icon = html.escape(icon)
    return (
        f'        <a class="repo-card" href="/{slug}/">\n'
        f'          <div class="repo-icon">{esc_icon}</div>\n'
        f'          <div class="repo-info">\n'
        f'            <div class="repo-name">{esc_name}</div>\n'
        f'            <div class="repo-desc">{esc_desc}</div>\n'
        f'          </div>\n'
        f'          <span class="repo-arrow">→</span>\n'
        f'        </a>'
    )

cards = []
for repo in repos:
    slug = os.path.basename(repo).replace(".fossil", "")
    name, icon, desc = repo_meta(repo)
    cards.append(make_card(name, icon, desc, slug))

grid_html = "\n\n".join(cards)

index = open(INDEX_HTML).read()
index = re.sub(
    r'<!-- REPO-GRID-START -->.*?<!-- REPO-GRID-END -->',
    f'<!-- REPO-GRID-START -->\n{grid_html}\n        <!-- REPO-GRID-END -->',
    index,
    flags=re.DOTALL,
)
open(INDEX_HTML, "w").write(index)
print("  regenerated index.html")
print("done.")