Commit Diff


commit - /dev/null
commit + 7d6d378a8d77a583c9b79f8ade1900108b773788
blob - /dev/null
blob + 0fe0a0f5dfb51bcd82a1f0fde0cea7aaecf82cc7 (mode 644)
--- /dev/null
+++ Makefile
@@ -0,0 +1,31 @@
+ENV =		MBLAZE=.mblaze OUTDIR='${OUTDIR}'
+MBLAZE_PAGER =	cat
+MDIR =		${HOME}/Maildir/op/GoT
+OUTDIR =	www
+
+.PHONY: all dirs assets clean
+
+all: .mblaze dirs assets
+	mlist '${MDIR}' | mthread -r | \
+		${ENV} mscan -f '%R %I %i %16D <%16f> %128S' | \
+		${ENV} ./mexp | ${ENV} ./mkindex > ${OUTDIR}/index.html
+
+dirs:
+	mkdir -p ${OUTDIR}/thread/
+
+assets: dirs got.png style.css
+	cp got.png ${OUTDIR}/got@2x.png
+	convert got.png -resize 200x200 ${OUTDIR}/got.png
+	convert got.png -resize 128x128 ${OUTDIR}/got-tiny@2x.png
+	convert got.png -resize 64x64 ${OUTDIR}/got-tiny.png
+	cp style.css ${OUTDIR}
+
+${OUTDIR}:
+	mkdir -p '${OUTDIR}'
+
+.mblaze:
+	mkdir -p .mblaze
+	touch .mblaze/seq
+
+clean:
+	rm -rf ${OUTDIR}
blob - /dev/null
blob + bb76eb365659ca981bba169a4fcb5354902be754 (mode 644)
--- /dev/null
+++ foot.html
@@ -0,0 +1,18 @@
+    <footer>
+      <p>Game Of Trees Mail Archive</p>
+      <nav>
+        <ul>
+          <li>
+            <a href='index.html'>Index</a>
+          </li>
+          <li>
+            <a href='https://lists.openbsd.org/cgi-bin/mj_wwwusr?user=&passw=&func=lists-long-full&extra=gameoftrees'>Subscribe to the Mailing List</a>
+          </li>
+          <li>
+            <a href='https://gameoftrees.org/'>Game of Trees</a>
+          </li>
+        </ul>
+      </nav>
+    </footer>
+  </body>
+</html>
blob - /dev/null
blob + 8fbd066279db65e381ce8a21be95c58ac4dfc788 (mode 644)
Binary files /dev/null and got.png differ
blob - /dev/null
blob + 645e87d154d795011826359bae74da233dc7a8d7 (mode 644)
--- /dev/null
+++ head.html
@@ -0,0 +1,9 @@
+<!doctype html>
+<html>
+  <head>
+    <meta charset="utf-8" />
+    <meta name="viewport" content="width=device-width" />
+    <link rel="stylesheet" href="/style.css" />
+    <title>TITLE</title>
+  </head>
+  <body>
blob - /dev/null
blob + ccd0aa94dcae27fdcdb7804e350487f62f70580b (mode 755)
--- /dev/null
+++ mexp
@@ -0,0 +1,111 @@
+#!/usr/bin/env perl
+
+use open ":std", ":encoding(UTF-8)";
+use utf8;
+use strict;
+use warnings;
+use v5.32;
+
+my $outdir = $ENV{'OUTDIR'};
+die 'Set $OUTDIR' unless defined $outdir;
+mkdir $outdir;
+
+my $hdr = do {
+	local $/ = undef;
+	open my $fh, "<", "head.html"
+	    or die "can't open head.html: $!";
+	<$fh>;
+};
+
+my $foot = do {
+	local $/ = undef;
+	open my $fh, "<", "foot.html"
+	    or die "can't open foot.html: $!";
+	<$fh>;
+};
+
+my $logo = <<EOF;
+<img srcset='/got-tiny.png, /got-tiny@2x.png 2x'
+     src='/got-tiny.png'
+     alt='"GOT", but the "O" is a cute, smiling sun' />
+EOF
+
+sub san {
+	my $str = shift;
+	$str =~ s/&/\&amp;/g;
+	$str =~ s/</\&lt;/g;
+	$str =~ s/>/\&gt;/g;
+	return $str;
+}
+
+my $tid;
+while (<>) {
+	chomp;
+	say; # continue the pipeline
+
+	m/^([^ ]+) <([^>]+)> (.+)(\d{4}-\d{2}-\d{2} \d{2}:\d{2}) <([^>]+)> (.*)/;
+	die "can't parse: $_" unless defined $1;
+	my ($fname, $mid, $indent, $date, $from, $subj) = ($1, $2, $3, $4, $5, $6);
+	$subj = san($subj);
+	$subj =~ s/\s+/ /g;
+	$subj =~ s/\s+$//;
+
+	$mid =~ s,_,__,g;
+	$mid =~ s,/,_,g;
+
+	chomp($mid);
+
+	next if -f "$outdir/$mid.html";
+
+	my $level = length($indent) - 1;
+	$level = 10 if $indent =~ m/\.\.\d{2}\.\./;
+
+	$tid = $mid if $level == 0;
+
+	my $dest = "$outdir/$mid.html";
+	open(my $fh, '>', "$dest") or die "can't open $dest: $!";
+
+	say $fh $hdr =~ s/TITLE/$subj/r;
+
+	open(my $mshow, "-|", "mshow", "-nNA", "text/plain", $fname)
+	    or die "can't exec mshow: $!";
+
+	print $fh "<header class='mail-header'>";
+	print $fh "<p>";
+	print $fh $logo;
+	print $fh "<a href='/'>‹ Back to the index</a>";
+	print $fh " or ";
+	print $fh "<a href='/thread/$tid.html#$mid'>→ go to the thread</a>.";
+	print $fh "</p>";
+	print $fh "<dl>";
+	while (<$mshow>) {
+		chomp;
+		last if /^$/;
+		my ($h, $v) = m/^([-A-Za-z]+): (.*)/;
+		die "bogus line? $fname : $_" unless (defined $h and defined $v);
+
+		# drop the (1 day ago) string
+		$v =~ s/\(.*\)//g if ($h eq "Date");
+
+		print $fh "<dt>", san($h), ":</dt>";
+		print $fh "<dd>", san($v), "</dd>";
+	}
+	print $fh "</dl></header>";
+
+	my $body = do {
+		local $/ = undef;
+		<$mshow>;
+	};
+
+	print $fh "<pre>";
+	# print $fh san($_) while <>;
+	print $fh san($body // "");
+	print $fh "</pre>";
+
+	print $fh $foot;
+
+	close($mshow);
+	close($fh);
+
+	# exit(0);
+}
blob - /dev/null
blob + 725f605f6ba88ebd61e5d2a49f24619b9d8f258f (mode 755)
--- /dev/null
+++ mkindex
@@ -0,0 +1,145 @@
+#!/usr/bin/env perl
+
+use open ":std", ":encoding(UTF-8)";
+use utf8;
+use strict;
+use warnings;
+use v5.32;
+
+my $outdir = $ENV{'OUTDIR'};
+die 'Set $OUTDIR' unless defined $outdir;
+mkdir $outdir;
+
+my $tfh;
+
+my $logo = <<EOF;
+<img srcset='/got-tiny.png, /got-tiny@2x.png 2x'
+     src='/got-tiny.png'
+     alt='"GOT", but the "O" is a cute, smiling sun' />
+EOF
+
+sub san {
+	my $str = shift;
+	$str =~ s/&/\&amp;/g;
+	$str =~ s/</\&lt;/g;
+	$str =~ s/>/\&gt;/g;
+	return $str;
+}
+
+sub initpage {
+	my ($fh, $title) = @_;
+	open(my $hdr, '<', 'head.html')
+	    or die "can't open head.html: $!";
+	while (<$hdr>) {
+		s/TITLE/$title/;
+		print $fh $_;
+	}
+}
+
+sub endpage {
+	my $fh = shift;
+	open(my $foot, '<', 'foot.html')
+	    or die "can't open foot.html: $!";
+	print $fh $_ while <$foot>;
+}
+
+sub nextthread {
+	endthread() if defined($tfh);
+	my ($mid, $subj) = @_;
+	my $dest = "$outdir/thread/$mid.html";
+	open($tfh, '>', $dest) or die "can't open $dest: $!";
+	initpage($tfh, $subj);
+
+	print $tfh "<header class='mail-header'>";
+	print $tfh "<p>";
+	print $tfh $logo;
+	print $tfh "<a href='/'>‹ Back to the index</a>";
+	print $tfh "</p>";
+	print $tfh "<dl><dt>Thread:</dt><dd>$subj</dd>";
+	print $tfh "</header>\n";
+	print $tfh "<div class='thread'>\n";
+}
+
+sub endthread {
+	print $tfh "</div>\n";
+	endpage($tfh);
+	close($tfh);
+}
+
+sub entry_raw {
+	my ($fh, $mid, $level, $date, $from, $subj) = @_;
+
+	my $class = "";
+	$class = "reply indent-$level" unless $level == 0;
+	print $fh "<div id='$mid' class='mail $class'>";
+	print $fh "<p class='mail-meta'>";
+	print $fh "<time>$date</time> ";
+	print $fh "<span class='from'>$from</span>";
+	print $fh "<span class='colon'>:</span>";
+	if ($fh != $tfh && $level == 0) {
+		print $fh " (<a href='/thread/$mid.html'>thread</a>)";
+	}
+	print $fh "</p>";
+	print $fh "<p class='subject'>";
+	print $fh "<a href='/$mid.html'>$subj</a>";
+	print $fh "</p>";
+	print $fh "</div>\n";
+}
+
+sub entry {
+	entry_raw(\*STDOUT, @_);
+	entry_raw($tfh, @_);
+}
+
+initpage(\*STDOUT, "Game of Trees Mail Archive");
+print <<EOF;
+<header class='index-header'>
+  <a href="https://gameoftrees.org" target="_blank">
+    <img src='/got.png'
+         srcset='/got.png, /got@2x.png 2x'
+         alt='"GOT" where the "O" is a cute smiling sun.' />
+  </a>
+  <h1>Game of Trees Mail Archive</h1>
+</header>
+<main>
+EOF
+
+my $threads_seen = 0;
+while (<>) {
+	chomp;
+	m/^[^ ]+ <([^>]+)> (.+)(\d{4}-\d{2}-\d{2} \d{2}:\d{2}) <([^>]+)> (.*)/;
+	die "woops; $_\n" unless defined $1;
+
+	my ($mid, $indent, $date, $from, $subj) = ($1, $2, $3, $4, $5);
+	$from =~ s/\s+$//;
+	$from = san($from);
+	$subj = san($subj);
+
+	my $level = length($indent) - 1;
+	$level = 10 if $indent =~ m/\.\.\d{2}\.\./;
+
+	$mid =~ s,_,__,g;
+	$mid =~ s,/,_,g;
+
+	$subj =~ s/\s+/ /g;
+	$subj =~ s/\s+$//g;
+
+	my $new_thread = $indent eq " ";
+	say "</section> <hr />" if $threads_seen && $new_thread;
+	if ($new_thread) {
+		nextthread($mid, $subj);
+		say "<section class='thread'>";
+	}
+
+	$threads_seen = 1;
+
+	entry($mid, $level, $date, $from, $subj);
+}
+
+if ($threads_seen) {
+	say "</section>" ;
+	endthread();
+}
+
+say "</main>";
+endpage(\*STDOUT);
blob - /dev/null
blob + 1fa1d9e9083db98c0fbfa60c84b44f5b7d78c50d (mode 644)
--- /dev/null
+++ style.css
@@ -0,0 +1,133 @@
+* {
+    box-sizing: border-box;
+}
+
+body {
+    margin: 0;
+    padding: 0;
+    border: 0;
+    font: 14px sans-serif;
+}
+
+header.mail-header {
+    background-color: #ced7e0;
+    padding: 5px 10px;
+    margin: 10px 0;
+}
+
+header.mail-header img {
+    vertical-align: middle;
+}
+
+header dl {
+    display: flex;
+    flex-wrap: wrap;
+    margin: 0;
+}
+
+header dt {
+    width: 70px;
+}
+
+header dd {
+    margin-left: auto;
+    width: calc(100% - 70px);
+    flex-grow: 2;
+}
+
+header dt, header dd {
+    padding: 3px 0;
+}
+
+header.index-header {
+    margin: 20px 0;
+    text-align: center;
+}
+
+h1 {
+    font-weight: normal;
+    font-size: 1.8rem;
+    margin: 0 0 1rem 0;
+}
+
+main {
+    padding: 5px;
+}
+
+pre {
+    font-family: monospace;
+    padding: 5px;
+    white-space: pre-wrap;
+}
+
+hr {
+    display: none;
+}
+
+.thread {
+    margin: 2rem 0;
+}
+
+.thread p {
+    margin: 0;
+}
+
+.thread a {
+    text-decoration: none;
+}
+
+.thread a:hover {
+    text-decoration: underline;
+}
+
+.mail {
+    /* font-family: monospace; */
+    margin: 0.5rem 0;
+    /* display: flex; */
+    /* flex-direction: row; */
+}
+
+.mail:target {
+    background-color: khaki;
+}
+
+.mail .mail-meta {
+    font-size: 13px;
+}
+
+.mail.reply .subject {
+    overflow: hidden;
+    text-overflow: ellipsis;
+    white-space: pre;
+}
+
+.mail.indent-1  { padding-left: 10px; }
+.mail.indent-2  { padding-left: 20px; }
+.mail.indent-3  { padding-left: 30px; }
+.mail.indent-4  { padding-left: 40px; }
+.mail.indent-5  { padding-left: 50px; }
+.mail.indent-6  { padding-left: 60px; }
+.mail.indent-7  { padding-left: 70px; }
+.mail.indent-8  { padding-left: 80px; }
+.mail.indent-9  { padding-left: 90px; }
+.mail.indent-10 { padding-left: 100px; }
+
+
+footer {
+    margin-top: 70px;
+    border-top: 1px solid black;
+}
+
+footer > p {
+    text-align: center;
+}
+
+footer ul {
+    padding: 0;
+    text-align: center;
+}
+
+footer li {
+    display: inline-block;
+    padding: 8px;
+}