Files

348 lines
15 KiB
Python
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""Generate the expanded, rebranded static site from the Chadnet mirror + curated additions.
Every page is written as *.html and every internal page-link points to the .html file,
so any static host renders it instead of offering a download."""
import json, os, re, shutil, html as H
import content_extra as CE
SITE_NAME = "Library of GigaChad"
SITE_SHORT = "GigaChad"
BLOB = "Become a GigaChad."
MIRROR = "/home/hermes/chad-library/mirror"
SITED = "/home/hermes/chad-library/site"
TOP = [
("arts-and-literature", "Arts & Literature", "ships.jpg"),
("computers-and-internet", "Computers & Internet", "matrix.png"),
("health", "Health", "pietro.png"),
("history", "History", "rome2.jpg"),
("politics-philosophy-and-society","Politics, Philosophy & Society","putin.jpg"),
("practical-skills", "Practical Skills", "forest.jpg"),
("religion-and-spirituality", "Religion & Spirituality", "religion.jpg"),
("science", "Science", "cocaine.jpg"),
]
NEW_TOP = [
("3d-printing-and-fabrication", "3D Printing & Fabrication", "matrix.png"),
("physical-culture", "Physical Culture", "putin.jpg"),
("homesteading-and-off-grid", "Homesteading & Off-Grid", "forest.jpg"),
("engineering-and-radio", "Engineering & Radio", "matrix.png"),
("other", "Other (WIP)", "xp.jpg"),
]
ALL_TOP = TOP + NEW_TOP
NEW_SET = {"3d-printing-and-fabrication","physical-culture","homesteading-and-off-grid","engineering-and-radio"}
TITLE_BY_SLUG = {s: t for s, t, _ in ALL_TOP}
BG_BY_SLUG = {s: "/icons/home/" + b for s, _, b in ALL_TOP}
NEW_CATEGORY_OBJS = [CE.NEW_CATEGORY_3DPRINT, CE.NEW_CATEGORY_FITNESS,
CE.NEW_CATEGORY_HOMESTEAD, CE.NEW_CATEGORY_ELECTRONICS]
ICON = {
"folder": "/icons/links/folder.png",
"globe": "/icons/links/globe.svg",
"document": "/icons/links/document.png",
"painting": "/icons/links/painting.png",
"youtube": "/icons/links/youtube.svg",
"article": "/icons/links/article.png",
"chadsquare": "/icons/links/chad-square.png",
"link": "/icons/links/book.png",
}
def esc(t): return H.escape(str(t))
def page(slug): return slug + ".html"
# ---------------------------------------------------------------- link fixing
def fix_href(val):
"""Rewrite a single (relative) href/src to the flat *.html scheme.
External, fragments, anchors and file-assets stay; internal page links get .html."""
val = val.strip()
if not val:
return val
if val.startswith(("http", "//", "mailto", "tel", "data:", "javascript:", "ftp:")):
return val
frag = q = ""
if "#" in val:
val, frag = val.split("#", 1); frag = "#" + frag
if "?" in val:
val, q = val.split("?", 1); q = "?" + q
if not val:
return q + frag
absolute = val.startswith("/")
# strip leading dir prefixes; keep the real remaining subpath for files
segments = [x for x in val.split("/") if x not in ("", ".", "..")]
if not segments:
return ("/" if absolute else "") + q + frag
base = segments[-1]
if "." in base:
# file asset — keep its real subpath (files live under /files or root)
rel = "/".join(segments)
return "/" + rel + q + frag
# internal page — flatten to a root *.html
return "/" + base + ".html" + q + frag
def rewrite_html_links(content):
return re.sub(r'(href|src)=(["\'])([^"\']*)\2',
lambda m: m.group(1) + "=" + m.group(2) + fix_href(m.group(3)) + m.group(2),
content)
def rebrand(html):
html = html.replace("https://wiki.chadnet.org", "")
html = html.replace("https://lib.chadnet.org", "")
html = html.replace("Library of Chadnet", SITE_NAME)
html = html.replace("— Chadnet", "— " + SITE_SHORT)
html = html.replace("– Chadnet", "– " + SITE_SHORT)
html = html.replace('>wiki.chadnet.org<', '>' + SITE_SHORT + '<')
html = html.replace("Chadnet", SITE_SHORT)
return html
# ---------------------------------------------------------------- renderers
def head(title):
return f"""<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>{esc(title)}</title>
<link rel="icon" href="/favicon.ico">
<link rel="stylesheet" href="/style.css?v=1">
<meta property="og:type" content="website">
<meta property="og:title" content="{esc(title)}">
<meta property="og:description" content="The greatest website on the interwebz!">
</head>
<body>
"""
def banner():
return '<div style="text-align: center;"><a href="/"><img class="epic" src="/icons/chads/chad.png"></a></div>'
def footer():
return f""" <hr>
<p class="C">{esc(SITE_NAME)} | <a href="/">{esc(SITE_SHORT)}</a></p>
</body>
</html>"""
def nav(parent_slug):
if not parent_slug:
back = '<a href="/">Home</a>'
else:
back = f'<a href="/{esc(page(parent_slug))}">{esc(TITLE_BY_SLUG.get(parent_slug, parent_slug))}</a>'
return f'<p class="nav"><span class="nav-left">Back: {back}</span><span class="nav-right"><a href="/theme">Theme</a></span></p>'
def render_item(it, extra=False):
href = it["href"]
text = it["text"] or ""
note = it.get("note", "")
itype = it["type"]
icon = it.get("icon") or ICON.get(itype, ICON["link"])
if not href:
a = esc(text)
elif href.startswith("http") or href.startswith("//"):
a = f'<a href="{esc(href)}" target="_blank">{esc(text)}</a>'
elif href.startswith("mailto"):
a = f'<a href="{esc(href)}">{esc(text)}</a>'
else:
a = f'<a href="{esc(fix_href(href))}">{esc(text)}</a>'
if note:
a += " " + note
return f' <li><img src="{esc(icon)}" draggable="false">{a}</li>'
def render_section(h2key, items, extra=False):
out = []
if h2key:
out.append(f'<h2>{esc(h2key)}</h2>')
out.append('<ul class="category">')
for it in items:
out.append(render_item(it, extra))
out.append('</ul>')
return "\n".join(out)
def _clean_title(t):
# strip any trailing "… <token> Chadnet" (dash may be mojibake), then any "— GigaChad"
t = re.sub(r"\s+\S+\s*Chadnet\s*$", "", t) # "… — Chadnet" footer from source
t = re.sub(r"\s*[–—-]\s*GigaChad\s*$", "", t)
return t.strip()
def category_page(slug):
d = json.load(open(os.path.join(MIRROR, "..", "site_data.json")))
pg = d["pages"].get(slug)
title = TITLE_BY_SLUG.get(slug, _clean_title(pg["title"]) if pg else slug)
parent = pg["parent"] if pg else None
sections = list(pg["sections"]) if pg else []
if slug in CE.EXPANSIONS:
sections = sections + CE.EXPANSIONS[slug]
out = [head(f"{title} — {SITE_SHORT}"), f' <h1>{esc(title)}</h1>',
banner(), ' <hr>', nav(parent)]
for h2key, items in sections:
out.append(render_section(h2key, items))
out.append(footer())
return "\n".join(out)
def new_category_page(cat):
title = cat["title"]
out = [head(f"{title} — {SITE_SHORT}"), f' <h1>{esc(title)}</h1>',
banner(), ' <hr>', nav(None)]
if cat.get("blurb"):
out.append(f'<p>{esc(cat["blurb"])}</p>')
for h2key, items in cat["sections"]:
out.append(render_section(h2key, items, extra=True))
out.append(footer())
return "\n".join(out)
def homepage():
tiles = []
for slug, title, _ in ALL_TOP:
newtag = ' <span style="font-size:14px;">(NEW)</span>' if slug in NEW_SET else ""
tiles.append(f'<a href="/{esc(page(slug))}"><div class="maincat">\n'
f' <div style="background-image: url(\'{BG_BY_SLUG[slug]}\');background-position:50%% 50%%;"></div>\n'
f' <h1>{esc(title)}{newtag}</h1>\n </div></a>\n <br>')
tiles_html = "\n".join(tiles)
return f"""<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>{esc(SITE_NAME)}</title>
<link rel="icon" href="/favicon.ico">
<link rel="stylesheet" href="/style.css?v=1">
<meta property="og:type" content="website">
<meta property="og:title" content="{esc(SITE_NAME)}">
<meta property="og:description" content="The greatest website on the interwebz!">
</head>
<body>
<h1>
{esc(SITE_NAME)}
<br>
<small id="message"><noscript><em><q>{esc(BLOB)}</q></em></noscript></small>
</h1>
<script>setMsg();</script>
<div style="text-align: center;">
<div class="epic" style="display: inline-block;">
<a href="/"><img class="glow" src="/icons/chads/chad.png"></a>
</div>
</div>
<hr>
<p class="C"><a href="/about#how-do-i-contribute">Submit article</a> | <a href="/theme">Theme</a><br><a href="/donate"><b>Donate</b></a></p>
<p>Welcome to {esc(SITE_NAME)}—a library of information. <a href="/about">Moar info</a>.</p>
<p><strong>News:</strong>
Everything Chadnet ever had, plus all-new categories — 3D Printing &amp; Fabrication, Physical Culture, Homesteading &amp; Off-Grid, Engineering &amp; Radio — and hundreds more resources.
</p>
{tiles_html}
<hr>
<p class="C">{esc(SITE_NAME)} | <a href="/">{esc(SITE_SHORT)}</a></p>
</body>
</html>"""
MSG = ["Because chad.net was taken.","A GigaChad owns his printer.","More power to the people.",
"Print it. Assemble it. Carry it.","Respect the machine, or the machine respects you.","Veni, vidi, vici.",
"Stronger every year.","Under the ice.","Become ungovernable.","Real men make things.","VICTORY TO THE GODS!",
"Many articools!","Fueled by curiosity.","TALLY-HO!","Wake up.","Remember who you are.","Grug loves filament.",
"A site for sore eyes!","Shop class will save America.","Self-reliance is the point.","Sun and steel.",
"A man prepares.","Build your own.","The library never closes.","Handmade.","Operated from the garage.",
"Yes hello.","Yarr!","Standing by.","Bring back the shop class.","Do it yourself, or find who does."]
def msg_js():
return "let messages = " + json.dumps(MSG) + ";\n" + (
"function setMsg() {\n const el = document.getElementById('message');\n"
" if (el) el.textContent = '« ' + messages[Math.floor(Math.random() * messages.length)] + ' »';\n}\n")
def simple_page(title, body):
o = head(title) + f' <h1>{esc(title)}</h1>\n' + banner() + '\n <hr>\n' + nav(None) + '\n'
for line in body.split("\n"):
line = line.strip()
if not line: continue
if line.startswith("###"): o += f"<h3>{esc(line[4:])}</h3>\n"
elif line.startswith("##"): o += f"<h2>{esc(line[3:])}</h2>\n"
elif line.startswith("```") and line == "```": o += "</pre>\n"
elif line.startswith("```"): o += "<pre>"
elif line.startswith("- "): o += f"<li>{esc(line[2:])}</li>\n"
else: o += f"<p>{esc(line)}</p>\n"
o += footer()
return o
# ----------------------------------------------------------------- main
def main():
shutil.rmtree(SITED, ignore_errors=True)
shutil.copytree(MIRROR, SITED)
for f in ["_progress.log"]:
p = os.path.join(SITED, f)
if os.path.exists(p): os.remove(p)
d = json.load(open(os.path.join(SITED, "..", "site_data.json")))
data_slugs = set(d["pages"].keys()) | set(TITLE_BY_SLUG)
# 1) rename extensionless pages -> .html ; rewrite links + rebrand every real page
renamed = {}
for root, _, files in os.walk(SITED):
for fn in files:
if "." in fn:
continue
full = os.path.join(root, fn)
try:
data = open(full, encoding="utf-8", errors="ignore").read()
except Exception:
continue
if "<html" not in data.lower() and "<body" not in data.lower():
continue
newfull = os.path.join(root, fn + ".html")
os.rename(full, newfull)
renamed.setdefault(os.path.relpath(root, SITED), []).append(fn)
# 2) every html page: rewrite internal links and rebrand
for root, _, files in os.walk(SITED):
for fn in files:
if not fn.endswith(".html"):
continue
full = os.path.join(root, fn)
try:
c = open(full, encoding="utf-8", errors="ignore").read()
except Exception:
continue
c = rewrite_html_links(c)
c = rebrand(c)
open(full, "w").write(c)
# 3) custom message.js
with open(os.path.join(SITED, "message.js"), "w") as f:
f.write(msg_js())
# 4) homepage
with open(os.path.join(SITED, "index.html"), "w") as f:
f.write(homepage())
# 5) regenerate category pages and new categories
for slug in list(data_slugs):
with open(os.path.join(SITED, page(slug)), "w") as f:
f.write(category_page(slug))
for cat in NEW_CATEGORY_OBJS:
with open(os.path.join(SITED, page(cat["slug"])), "w") as f:
f.write(new_category_page(cat))
# 6) about / donate / theme
about = ("## What is " + SITE_NAME + "?\n\n"
"It's a sort of mix between a web directory, archive, and compendium of knowledge — "
"everything the Library of Chadnet had, and more: new categories like 3D Printing & Fabrication, "
"Physical Culture, Homesteading & Off-Grid and Engineering & Radio, hundreds more resources, "
"same do-it-yourself spirit.\n\n"
"Not all information here may be true, and we are not responsible for anything you do with it.\n\n"
"## How do I contribute?\n\n"
"E-mail an image, article link, PDF, video link, etc. to the maintainer, or open a PR in the repository.\n\n"
"## How often is it updated?\n\n"
"On and off. Sometimes every few days, sometimes after a week or two.\n\n"
"## I own something here and want to take it down (copyrightz n stuff)\n\n"
"Prove you are the creator and ask to have it taken down. Everything here is free and we link back to the original authors.\n\n"
"## I wish to give you money\n\n"
"Yes, you can send money or crypto. See the donations page.")
donate = ("# Donate\n\nSupport the cause: keep the library free and growing.\n\n"
"- Bitcoin, Ethereum, Monero, or a coffee — details TBD by the maintainer.\n\n"
"Every contribution helps buy filament and server uptime.")
theme = ("# Theme\n\nThe theme uses CSS variables set on `:root`. Override from the browser console:\n\n"
"```js\nlocalStorage.setItem('--bg', 'darkblue')\nlocalStorage.setItem('--link', 'orange')\n```\n\n"
"Refresh to apply. `localStorage.clear()` resets.")
with open(os.path.join(SITED, "about.html"), "w") as f: f.write(simple_page("About", about))
with open(os.path.join(SITED, "donate.html"), "w") as f: f.write(simple_page("Donate", donate))
with open(os.path.join(SITED, "theme.html"), "w") as f: f.write(simple_page("Theme", theme))
print("Site generated at", SITED)
if __name__ == "__main__":
main()