Scripts
The pile of scripts used to build the garden. Not claiming it’s any good.
b.nu
def ifdo [asd] {
if $in {
do $asd
}
return $in
}
# ensure there's an enclosing directory for "p"
def ensdir [p] {
$p | path dirname | mkdir $in
}
# goofy hack: files like garden/listing.md get deleted on clean and might not exist when i run ls,
# so this puts fake entries into the ls table just to make sure out/listing.html is generated
def ensure_names [...ns] {
let tbl = $in
$ns | reduce --fold $tbl {|name, tbl|
if ($tbl | where name == $name | is-empty) {
$tbl | append {name: $name}
} else {
$tbl
}
}
}
# "true" if "target" is older than anything in "srcs"
def older-than [target, ...srcs] {
if not ($target | path exists) {
print $"($target): doesn't exist"
return true
}
let mtime = ls $target | get 0.modified
$srcs
| where { path exists }
| ls ...$in
| where { $in.modified > $mtime }
| is-not-empty
| ifdo { print $"($target): outdated" }
}
# pipe something in and it will get saved to "stampfile"; returns "true" if the
# contents of stampfile were different
def stamp [stampfile] {
let new = $in | to nuon
if (not ($stampfile | path exists)) or ($new != (open --raw $stampfile)) {
$new | save --force $stampfile
return true
}
return false
}
##########################
let tool_out = "tmp/tool"
let garden_specials = [garden/nu.md garden/listing.md garden/blog/index.md]
let garden = ls garden/**/*.md
| ensure_names ...$garden_specials
| rename -c { name: in-name }
| insert out-name { $in.in-name | path split | update 0 out | str replace .md .html | path join }
let blog = ls blog/**/*.md
| rename -c { name: in-name }
# slightly different path math
| insert out-name { $in.in-name | path split | update 0 out/blog | str replace .md "" | append "index.html" | path join }
let statics = ls static/**/*
| where type == file
| rename -c { name: in-name }
| insert out-name { $in.in-name | path split | update 0 out | path join }
##########################
# get pandoc to cough up highlighting styles
older-than "tmp/dollar-highlighting-css-dollar.html" | ifdo {
mkdir tmp
"$highlighting-css$\n" | save --force "tmp/dollar-highlighting-css-dollar.html"
}
[kate, zenburn] | par-each { |style|
let outcss = $"out/highlighting-($style).css"
older-than $outcss | ifdo {
ensdir $outcss
"`a`{.c}" # just a markdown string which has syntax highlighting in it
| pandoc --highlight-style=($style) --template=tmp/dollar-highlighting-css-dollar.html --metadata title="dummy"
| save --force $outcss
}
}
# quine
older-than garden/nu.md b.nu Tool.java deploy.sh | ifdo {
"# Scripts\n\n" | save --force garden/nu.md
"The pile of scripts used to build the [garden](index). Not claiming it's any good.\n\n" | save --append garden/nu.md
def putscript [file, lang] {
$"## `($file)`\n\n```($lang)\n(open --raw $file)```\n\n" | save --append garden/nu.md
}
putscript b.nu sh
putscript Tool.java java
putscript deploy.sh sh
}
# java tool
# TODO: now that im using a real scripting language the java tool might not be necessary
older-than $"($tool_out)/Tool.class" Tool.java | ifdo {
mkdir $tool_out
javac Tool.java -d $tool_out
}
# special listing pages
$garden | get in-name | sort | stamp tmp/listing-stampfile | ifdo {
print "regenerating listing"
java -cp $tool_out Tool gardenListing garden/ garden/listing.md
}
$blog | get in-name | sort | stamp tmp/blog-stampfile | ifdo {
print "regenerating blog listing"
java -cp $tool_out Tool blogListing blog/ garden/blog/index.md
}
def git-time [md] {
git log --pretty='%as' -n 1 $md
}
def do-pandoc [md, htm, get_date] {
mut flags = [
$md,
"-o",
$htm,
"--from=markdown+autolink_bare_uris+raw_attribute",
"--template=mytemplate.html",
"--lua-filter=filter.lua",
"--mathml",
"--wrap=preserve",
"--highlight-style=kate",
$"--variable=quat_filename=($md)"
]
if $get_date {
$flags = $flags | append $"--variable=quat_last_updated=(git-time $md)"
}
ensdir $htm
pandoc ...$flags
}
# markdown parsin
($garden | append $blog) | par-each { |row|
older-than $row.out-name $row.in-name mytemplate.html filter.lua | ifdo {
do-pandoc $row.in-name $row.out-name ($row.in-name not-in $garden_specials)
}
}
# statics
$statics | par-each { |row|
older-than $row.out-name $row.in-name | ifdo {
ensdir $row.out-name
cp $row.in-name $row.out-name
}
}
# search index (but not on Android)
if (which "termux-setup-storage" | is-empty) {
ls out/**/*.html | stamp tmp/out-stamp | ifdo {
./precompiled-pagefind/pagefind --site out
}
}
exitTool.java
import java.io.*;
import java.util.*;
import java.nio.charset.*;
import java.nio.file.*;
import java.nio.file.attribute.*;
class Tool {
public static void main(String[] args) throws IOException {
System.out.println("hello");
if(args[0].equals("gardenListing")) {
writeGardenListing(get(args[1]), get(args[2]));
} else if(args[0].equals("blogListing")) {
writeBlogListing(get(args[1]), get(args[2]));
} else {
throw new RuntimeException("unknown op " + args[0]);
}
System.out.println("Done");
}
static Path get(String arg) {
return Paths.get(arg).toAbsolutePath().normalize();
}
static void writeGardenListing(Path gardenSrc, Path listingMd) throws IOException {
System.out.println("gardenSrc: " + gardenSrc);
System.out.println("listingMd: " + listingMd);
List<Meta> metas = readMetas(gardenSrc).stream()
.sorted(Meta::compareByTitle)
.toList();
List<String> out = new ArrayList<>();
out.add("# Listing");
out.add("");
out.add("There are currently <b>" + metas.size() + "</b> files in the garden.");
out.add("");
for(Meta m : metas) out.add("* " + m.mdLink());
Files.createDirectories(listingMd.getParent());
Files.write(listingMd, out, StandardCharsets.UTF_8);
//System.out.println(String.join("\n", out));
}
static void writeBlogListing(Path blogSrc, Path blogListingMd) throws IOException {
System.out.println("blogSrc: " + blogSrc);
System.out.println("blogListingMd: " + blogListingMd);
List<Meta> metas = readMetas(blogSrc).stream()
.peek(it -> {
if(it.date == null) throw new IllegalArgumentException("Post at '" + it.bareUrl + "' has no date");
})
.sorted(Meta::compareByDate)
.toList();
List<String> out = new ArrayList<>();
out.add("# Blog");
out.add("");
out.add("There are " + metas.size() + " posts, but I don't blog as often now that I have the [garden](index).\n\nPlease pardon my dust, still migrating stuff here.");
out.add("");
for(Meta m : metas) {
// \u2b50 -> star
String pre = m.good ? "\u2b50 **" : m.draft ? "*" : "";
String post = m.good ? "**" : m.draft ? " (draft)*" : "";
out.add("* " + m.date + " – " + pre + m.mdLink("blog/", "/") + post);
if(m.blurb != null) {
out.add(" ");
out.add(" > " + m.blurb);
}
out.add("");
}
Files.createDirectories(blogListingMd.getParent());
Files.write(blogListingMd, out, StandardCharsets.UTF_8);
//System.out.println(String.join("\n", out));
}
static class Meta {
String bareUrl;
String title;
String date;
String blurb;
boolean good;
boolean draft;
int compareByTitle(Meta other) {
String myTitle = title.toLowerCase(Locale.ROOT).replaceAll("[^a-z]", "");
String theirTitle = other.title.toLowerCase(Locale.ROOT).replaceAll("[^a-z]", "");
return myTitle.compareTo(theirTitle);
}
int compareByDate(Meta other) {
return -date.compareTo(other.date);
}
String mdLink() {
return mdLink("", "");
}
String mdLink(String pre, String post) {
return "[" + escapeForMdLink(title) + "](" + pre + escapeForMdLink(bareUrl) + post + ")";
}
}
static Meta readMeta(Path base, Path md) throws IOException {
Meta m = new Meta();
m.bareUrl = fwdString(chopExtension(chopStart(base, md)));
boolean yamlMode = false;
for(String line : Files.readAllLines(md)) {
if("---".equals(line)) {
yamlMode = !yamlMode;
continue;
}
if(yamlMode) {
if("...".equals(line)) {
yamlMode = false;
continue;
}
if(line.startsWith("title:")) m.title = line.substring(6).trim();
if(line.startsWith("date:")) m.date = line.substring(5).trim();
if(line.startsWith("blurb:")) m.blurb = line.substring(6).trim();
if(line.startsWith("good:")) m.good = true;
if(line.startsWith("draft:")) m.draft = true;
}
//parse titles out of the first heading in the document
if(m.title == null && !yamlMode && line.startsWith("#")) {
do { line = line.substring(1); } while(line.startsWith("#"));
m.title = line.trim();
}
}
if(m.title == null) m.title = m.bareUrl;
return m;
}
static List<Meta> readMetas(Path dir) throws IOException {
List<Meta> m = new ArrayList<>();
Files.walkFileTree(dir, new SimpleFileVisitor<Path>() {
@Override
public FileVisitResult visitFile(Path md, BasicFileAttributes attrs) throws IOException {
if(!md.toString().endsWith(".md")) return FileVisitResult.CONTINUE;
m.add(readMeta(dir, md));
return FileVisitResult.CONTINUE;
}
});
return m;
}
/// path math ///
// start: /a/b/c/
// sub: /a/b/c/something/foo.txt
// result: something/foo.txt
static Path chopStart(Path start, Path sub) {
return sub.subpath(start.getNameCount(), sub.getNameCount());
}
static Path chopExtension(Path p) {
String filename = p.getFileName().toString();
int dot = filename.indexOf('.');
if(dot == -1) return p;
else return p.resolveSibling(filename.substring(0, dot));
}
//Always use / as the path separator even on windows >.>
static String fwdString(Path p) {
StringBuilder b = new StringBuilder(p.getName(0).toString());
for(int i = 1; i < p.getNameCount(); i++) b.append('/').append(p.getName(i).toString());
return b.toString();
}
/// markdown gunk ///
static String escapeForMdLink(String s) {
return s.replace("(", "\\(").replace(")", "\\)").replace("[", "\\[").replace("]", "\\]");
}
}deploy.sh
# sync pagefind indices with --delete so old ones get culled
if [ -f out/pagefind/pagefind.js ]; then
echo syncing pagefind indices...
rsync -lpvrtz --delete out/pagefind/ root@door:/opt/notes/pagefind/
else
echo not syncing pagefind
fi
echo uploading everything else
rsync -lpvrtz out/ root@door:/opt/notes/
# rsync flags!
# -l, copy symlinks as symlinks (not really needed tbh)
# -p, copy permission bits (probably bad on android?)
# -v, be noisier
# -r, recursive
# -t, copy mtimes
# -z, use compression in transit
# Termux has fucked up umasks, rsync will dutifully copy them,
# and then the web server can't read anything anymore
if command -v "termux-setup-storage"
then
echo fixing perms...
ssh door "chmod +rX -R /opt/notes/; chown root:root -R /opt/notes/"
fi