aboutsummaryrefslogtreecommitdiff
path: root/utils/buildsite.sh
diff options
context:
space:
mode:
Diffstat (limited to 'utils/buildsite.sh')
-rwxr-xr-xutils/buildsite.sh396
1 files changed, 396 insertions, 0 deletions
diff --git a/utils/buildsite.sh b/utils/buildsite.sh
new file mode 100755
index 00000000..e00d3088
--- /dev/null
+++ b/utils/buildsite.sh
@@ -0,0 +1,396 @@
+#!/bin/sh -e
+#
+# Build the Alice Linux static website into public/.
+# Run from the project root.
+#
+# Dependencies: cmark, curl
+
+cd "$(dirname "$0")/.."
+
+for dep in cmark curl; do
+ command -v "$dep" >/dev/null 2>&1 || {
+ printf 'Error: "%s" is required but not found in PATH.\n' "$dep" >&2
+ exit 1
+ }
+done
+
+rm -rf public
+mkdir -p public
+
+html_escape() {
+ # takes a single string argument, prints escaped to stdout
+ printf '%s' "$1" | sed 's/\&/\&amp;/g;s/</\&lt;/g;s/>/\&gt;/g;s/"/\&quot;/g'
+}
+
+port_version() {
+ grep '^version=' "$1/abuild" | cut -d = -f2- || true
+}
+
+port_release() {
+ grep '^release=' "$1/abuild" | cut -d = -f2- || true
+}
+
+port_depends() {
+ [ -f "$1/depends" ] || return 0
+ grep -Ev '^(#|$)' "$1/depends" | tr '\n' ' ' | sed 's/[[:space:]]*$//' || true
+}
+
+generate_ports_page() {
+ {
+ sed "s/@TITLE@/ports/g" files/header
+ echo "<p>Package ports generated from <code>repos/core</code>, <code>repos/extra</code>, and <code>repos/community</code>.</p>"
+ echo "<div class=\"ports-toolbar\">"
+ echo "<button type=\"button\" class=\"active\" data-repo=\"all\">all</button>"
+ echo "<button type=\"button\" data-repo=\"core\">core</button>"
+ echo "<button type=\"button\" data-repo=\"extra\">extra</button>"
+ echo "<button type=\"button\" data-repo=\"community\">community</button>"
+ echo "<input id=\"ports-search\" type=\"search\" placeholder=\"filter ports\" autocomplete=\"off\">"
+ echo "</div>"
+ echo "<div class=\"ports-count\"><span id=\"ports-visible\">0</span> / <span id=\"ports-total\">0</span> ports</div>"
+ echo "<table id=\"ports-table\">"
+ echo "<thead><tr><th>repo</th><th>name</th><th>version</th><th>dependencies</th></tr></thead>"
+ echo "<tbody>"
+
+ for repo in core extra community; do
+ for port in repos/$repo/*; do
+ [ -f "$port/abuild" ] || continue
+ name=${port##*/}
+ version=$(port_version "$port")
+ release=$(port_release "$port")
+ depends=$(port_depends "$port")
+ [ "$release" ] && version=$version-$release
+
+ e_repo=$(html_escape "$repo")
+ e_name=$(html_escape "$name")
+ e_version=$(html_escape "$version")
+ e_depends=$(html_escape "$depends")
+
+ printf '<tr data-repo="%s"><td>%s</td><td><a href="https://codeberg.org/emmett1/alicelinux/src/branch/main/repos/%s/%s">%s</a></td><td>%s</td><td>%s</td></tr>\n' \
+ "$e_repo" \
+ "$e_repo" \
+ "$e_repo" \
+ "$e_name" \
+ "$e_name" \
+ "$e_version" \
+ "$e_depends"
+ done
+ done
+
+ echo "</tbody>"
+ echo "</table>"
+ cat << 'EOF'
+<script>
+(function () {
+ var table = document.getElementById('ports-table');
+ var rows = Array.prototype.slice.call(table.querySelectorAll('tbody tr'));
+ var search = document.getElementById('ports-search');
+ var visible = document.getElementById('ports-visible');
+ var total = document.getElementById('ports-total');
+ var buttons = Array.prototype.slice.call(document.querySelectorAll('.ports-toolbar button'));
+ var repo = 'all';
+
+ total.textContent = rows.length;
+
+ function filterPorts() {
+ var query = search.value.trim().toLowerCase();
+ var count = 0;
+
+ rows.forEach(function (row) {
+ var repoMatch = repo === 'all' || row.dataset.repo === repo;
+ var textMatch = !query || row.textContent.toLowerCase().indexOf(query) !== -1;
+ var show = repoMatch && textMatch;
+ row.style.display = show ? '' : 'none';
+ if (show) count++;
+ });
+
+ visible.textContent = count;
+ }
+
+ buttons.forEach(function (button) {
+ button.addEventListener('click', function () {
+ repo = button.dataset.repo;
+ buttons.forEach(function (b) { b.classList.remove('active'); });
+ button.classList.add('active');
+ filterPorts();
+ });
+ });
+
+ search.addEventListener('input', filterPorts);
+ filterPorts();
+}());
+</script>
+EOF
+ cat files/footer
+ } > public/ports.html
+}
+
+# copy static .html files (from repo root, readme, etc.) into public/
+find . -type f -name "*.html" ! -path './public/*' | sed 's|^\./||' | while IFS= read -r i; do
+ dir=${i%/*}
+ file=${i##*/}
+ title=${dir##*/}
+ [ "$dir" = "$file" ] && {
+ title=home; dir=
+ }
+ mkdir -p "public/$dir"
+ printf 'copy html for %s...\n' "$i"
+ {
+ sed "s/@TITLE@/$title/g" files/header
+ cat "$i"
+ cat files/footer
+ } > "public/$dir/$file"
+done
+
+generate_ports_page
+
+generate_commits_page() {
+ {
+ sed "s/@TITLE@/commits/g" files/header
+ cat << 'EOF'
+<p>Recent commits from <a href="https://codeberg.org/emmett1/alicelinux">codeberg.org/emmett1/alicelinux</a>.</p>
+<ul id="commits"></ul>
+<div id="commits-loading">loading…</div>
+<div id="commits-error"></div>
+<script>
+(function () {
+ var list = document.getElementById('commits');
+ var loading = document.getElementById('commits-loading');
+ var errEl = document.getElementById('commits-error');
+ var page = 1;
+ var loadingMore = false;
+ var hasMore = true;
+ var PER_PAGE = 30;
+
+ function fmtHash(hash) {
+ return hash.substring(0, 7);
+ }
+
+ function fmtDate(iso) {
+ var d = new Date(iso);
+ var y = d.getFullYear();
+ var m = String(d.getMonth() + 1).padStart(2, '0');
+ var day = String(d.getDate()).padStart(2, '0');
+ var h = String(d.getHours()).padStart(2, '0');
+ var min = String(d.getMinutes()).padStart(2, '0');
+ return y + '-' + m + '-' + day + ' ' + h + ':' + min;
+ }
+
+ function esc(s) {
+ var d = document.createElement('div');
+ d.textContent = s;
+ return d.innerHTML;
+ }
+
+ function loadCommits() {
+ if (!hasMore || loadingMore) return;
+ loadingMore = true;
+ loading.style.display = 'block';
+
+ var url = 'https://codeberg.org/api/v1/repos/emmett1/alicelinux/commits?limit=' + PER_PAGE + '&page=' + page;
+
+ fetch(url)
+ .then(function (r) {
+ if (!r.ok) throw new Error('HTTP ' + r.status);
+ var link = r.headers.get('X-HasMore');
+ if (link === 'false' || link === null) hasMore = false;
+ return r.json();
+ })
+ .then(function (data) {
+ if (!data.length || data.length < PER_PAGE) hasMore = false;
+
+ data.forEach(function (c) {
+ var li = document.createElement('li');
+ var hash = c.sha || '';
+ var msg = c.commit.message.split('\n')[0] || '';
+ var author = c.commit.author.name || 'unknown';
+ var date = c.commit.author.date || '';
+ var url = c.html_url || '#';
+
+ li.innerHTML =
+ '<span class="commit-hash"><a href="' + esc(url) + '">' + esc(fmtHash(hash)) + '</a></span>' +
+ '<span class="commit-msg"><a href="' + esc(url) + '">' + esc(msg) + '</a></span>' +
+ '<span class="commit-meta">' + esc(author) + '<br>' + esc(fmtDate(date)) + '</span>';
+ list.appendChild(li);
+ });
+
+ page++;
+ loadingMore = false;
+ loading.style.display = 'none';
+ })
+ .catch(function (err) {
+ loadingMore = false;
+ loading.style.display = 'none';
+ hasMore = false;
+ errEl.textContent = 'Failed to load commits: ' + err.message;
+ errEl.style.display = 'block';
+ });
+ }
+
+ window.addEventListener('scroll', function () {
+ if (loadingMore || !hasMore) return;
+ var scrollBottom = window.scrollY + window.innerHeight;
+ var threshold = document.body.scrollHeight - 600;
+ if (scrollBottom >= threshold) loadCommits();
+ });
+
+ loadCommits();
+}());
+</script>
+EOF
+ cat files/footer
+ } > public/commits.html
+}
+
+generate_commits_page
+
+generate_download_page() {
+ {
+ sed "s/@TITLE@/download/g" files/header
+
+ listing=$(curl -sL --max-time 10 https://dl.alicelinux.org/ 2>/dev/null || true)
+
+ cat << 'EOF'
+<div id="dl-status" class="dl-empty">loading…</div>
+<table class="dl-table" id="dl-table"><tbody>
+EOF
+
+ if [ -n "$listing" ]; then
+ echo "$listing" | sed -n '/<tbody>/,/<\/tbody>/p' | while IFS= read -r row; do
+ case $row in
+ *'<tr>'*)
+ href=$(printf '%s' "$row" | sed 's/.*<a href="\([^"]*\)">.*/\1/')
+ name=$(printf '%s' "$row" | sed 's/.*<a href="[^"]*">\([^<]*\)<.*/\1/')
+ size=$(printf '%s' "$row" | sed 's/.*<td class="s"[^>]*>\([^<]*\)<.*/\1/')
+ date=$(printf '%s' "$row" | sed 's/.*<td class="m">\([^<]*\)<.*/\1/')
+ type=$(printf '%s' "$row" | sed 's/.*<td class="t">\([^<]*\)<.*/\1/')
+ [ "$name" = "../" ] && continue
+ url="https://dl.alicelinux.org/$href"
+ e_url=$(html_escape "$url")
+ e_name=$(html_escape "$name")
+ e_size=$(html_escape "$size")
+ e_date=$(html_escape "$date")
+ if [ "$type" = "Directory" ]; then
+ printf '<tr><td><a href="%s">%s/</a></td><td>%s</td><td>%s</td></tr>\n' \
+ "$e_url" "$e_name" "$e_size" "$e_date"
+ else
+ printf '<tr><td><a href="%s">%s</a></td><td>%s</td><td>%s</td></tr>\n' \
+ "$e_url" "$e_name" "$e_size" "$e_date"
+ fi
+ ;;
+ esac
+ done
+ fi
+
+ cat << 'EOF'
+</tbody></table>
+<script>
+(function () {
+ var table = document.getElementById('dl-table');
+ var tbody = table.querySelector('tbody');
+ var status = document.getElementById('dl-status');
+
+ function esc(s) {
+ var d = document.createElement('div');
+ d.textContent = s;
+ return d.innerHTML;
+ }
+
+ function rowHTML(href, name, size, date, isDir) {
+ var url = 'https://dl.alicelinux.org/' + href;
+ var label = esc(name) + (isDir ? '/' : '');
+ return '<tr><td><a href="' + esc(url) + '">' + label + '</a></td><td>' + esc(size) + '</td><td>' + esc(date) + '</td></tr>';
+ }
+
+ fetch('https://dl.alicelinux.org/')
+ .then(function (r) {
+ if (!r.ok) throw new Error('HTTP ' + r.status);
+ return r.text();
+ })
+ .then(function (html) {
+ var parser = new DOMParser();
+ var doc = parser.parseFromString(html, 'text/html');
+ var rows = doc.querySelectorAll('table tbody tr');
+ var frag = document.createDocumentFragment();
+
+ rows.forEach(function (row) {
+ var cells = row.querySelectorAll('td');
+ if (!cells.length) return;
+ var link = cells[0].querySelector('a');
+ if (!link) return;
+ var href = link.getAttribute('href');
+ var name = link.textContent;
+ if (name === '../') return;
+ var size = cells[2] ? cells[2].textContent.trim() : '';
+ var date = cells[1] ? cells[1].textContent.trim() : '';
+ var isDir = cells[3] && cells[3].textContent.trim() === 'Directory';
+ var tr = document.createElement('tr');
+ tr.innerHTML = rowHTML(href, name, size, date, isDir);
+ frag.appendChild(tr);
+ });
+
+ if (frag.childNodes.length) {
+ tbody.innerHTML = '';
+ tbody.appendChild(frag);
+ }
+ status.textContent = '';
+ status.style.display = 'none';
+ })
+ .catch(function () {
+ var rows = tbody.querySelectorAll('tr');
+ if (rows.length) {
+ status.textContent = '';
+ status.style.display = 'none';
+ } else {
+ status.innerHTML = 'Unable to load. Visit <a href="https://dl.alicelinux.org">dl.alicelinux.org</a> directly.';
+ }
+ });
+}());
+</script>
+EOF
+ cat files/footer
+ } > public/download.html
+}
+
+generate_download_page
+
+# build docs index
+cat docs/readme.md > docs/index.md
+for f in docs/*.md; do
+ case $f in */readme.md|*/index.md) continue;; esac
+ title=$(head -n1 "$f")
+ file=${f##*/}
+ echo "- [$title](./${file%.md}.html)" >> docs/index.md
+done
+
+# convert markdown to html
+find . -type f -name "*.md" ! -path './public/*' | sed 's|^\./||' | while IFS= read -r i; do
+ dir=${i%/*}
+ file=${i##*/}
+ title=${dir##*/}
+ [ "$dir" = "$file" ] && {
+ title=home; dir=
+ }
+ mkdir -p "public/$dir"
+ printf 'generating html for %s...\n' "$i"
+ {
+ sed "s/@TITLE@/$title/g" files/header
+ cmark "$i"
+ cat files/footer
+ } > "public/${i%.md}.html"
+done
+
+# move readme.html to index.html
+find public -type f -name "readme.html" | while IFS= read -r i; do
+ mv -n "$i" "${i%/*}/index.html"
+done
+
+rm -f docs/index.md
+
+if [ -d files ]; then
+ cp -ra files public/
+fi
+
+echo alicelinux.org > public/.domains
+echo alicelinux.emmett1.my >> public/.domains
+
+exit 0