Remember //nix/escapeExecline? Well, this is worse (for some possible meanings of the word). Instead of taking a list and escaping/rendering it to an execline script, Execline reimplements part of execlineb(1): It takes a (nested) list where any enclosed list signifies an execline block and produces the corresponding execline argv form as described in execline-block(7). This means the result of Execline can directly be executed using •SH without the need for execlineb(1). Consequently, execlineb(1)'s management of positional parameters and the environment are not available. This is fine for the intended purpose of Execline (glueing together shell commands efficiently without messing around with the FFI in BQN for pipe(2) etc.). Change-Id: Ief69b1bab919c16b6e39c3f5dc3db628766c5a8c Reviewed-on: https://cl.tvl.fyi/c/depot/+/13180 Reviewed-by: sterni <sternenseemann@systemli.org> Autosubmit: sterni <sternenseemann@systemli.org> Tested-by: BuildkiteCI
179 lines
5.5 KiB
BQN
Executable file
179 lines
5.5 KiB
BQN
Executable file
#!/usr/bin/env BQN
|
||
# SPDX-FileCopyrightText: Copyright © 2024-2025 sterni
|
||
# SPDX-License-Identifier: GPL-3.0-only
|
||
#
|
||
# blërg is a reimplementation of mblog in BQN. BQN is used as a sort of bespoke
|
||
# scripting languages so we can rely on external tools for certain tasks (e.g.
|
||
# transforming HTML and parsing MIME messages). A list of dependencies is
|
||
# maintained in README.md.
|
||
|
||
# Utilities
|
||
|
||
MkDirP ← •file.CreateDir⍟(¬•file.Exists)
|
||
|
||
AsciiDown ← ⊢ - ('A'-'a')⊸×⟜('A'⊸≤∧≤⟜'Z')
|
||
Slugify ← '-'⊸⊣⍟(('A'⊸≤ ∧ ≤⟜'z') ¬∘∨ "-_0123456789"⊸(⊑∊˜)⟜<)¨ AsciiDown
|
||
|
||
DropPrefix ← {𝕩(≠/⊣)𝕨↑˜≠𝕩}
|
||
StripLeft ← (¬ ∧`∘=)/⊢
|
||
StripRight ← ⌽ StripLeft⟜⌽
|
||
|
||
_join ← {(∾⟜(𝕗⊸∾))´𝕩;𝕨∾𝕗∾𝕩}
|
||
|
||
nl ← @+10
|
||
SplitChar ← (= (¯1˙⍟⊣)¨ +`∘=)⊔⊢
|
||
Lines ← nl⊸SplitChar
|
||
|
||
ReadPosInt ← {(𝕨⊸×+⊣)´ ⌽'0'-˜𝕩} # ty leah2
|
||
ReadPosDec ← 10⊸ReadPosInt
|
||
|
||
Chomp ← {⟨nl⟩≡¯1↑𝕩? ¯1↓𝕩; 𝕩}
|
||
|
||
Run ← {
|
||
𝕊 𝕩: 1 𝕊 𝕩;
|
||
doChomp 𝕊 cmd:
|
||
exit‿stdout‿stderr ← •SH cmd
|
||
exit∾Chomp⍟doChomp¨ stdout‿stderr
|
||
}
|
||
|
||
R ← {𝕊 exit‿stdout‿stderr: stderr!0=exit ⋄ stdout}∘Run
|
||
LR ← Lines∘R
|
||
|
||
# see execline-block(7)
|
||
Execline ← ∾ {
|
||
# Not a string nor any other list
|
||
𝕩: 0≠•Type𝕩? ⋈•Fmt 𝕩;
|
||
# List, but not a string (i.e. block)
|
||
𝕩: 2≠•Type⊑𝕩? ⟨""⟩∾˜∾(' '⊸∾¨ 𝕊)¨𝕩;
|
||
# String (i.e. arg)
|
||
⋈𝕩
|
||
}¨
|
||
|
||
GetEnv ← {R "importas"‿"env"‿𝕩‿"printf"‿"%s"‿"$env"}
|
||
|
||
RelPath ← •wdpath⊸•file.At
|
||
SplitExt ← (∊⌾⌽ (∨`∘∧ + ¯2⊸×∘∧) =⟜'.')⊔⊢
|
||
|
||
# 3p dependencies
|
||
|
||
j ← {
|
||
# Update README.md if dependency discovery changes
|
||
⟨Parse⟩ ⇐ •Import (GetEnv "BQN_LIBS") •file.At "json.bqn"
|
||
|
||
ObjGet ⇐ (⊏⊸(⊑∘⊐) ⊑ ⊏˜⟜1)⟜<
|
||
ObjGetPath ⇐ ObjGet˜´⟜⌽
|
||
|
||
_objGetDef ⇐ {𝕨 (⊏⊸(⊑∘⊐) ⊑ (∾⟜(⋈𝕗))∘(⊏˜⟜1)) <𝕩}
|
||
}
|
||
|
||
# (Apple) Mail Notes Backend
|
||
|
||
# TODO(sterni): avoid argv limit by chunking
|
||
Hdrs ← {LR "mhdr"‿"-dh"‿(':' _join 𝕨)∾𝕩}
|
||
Dates ← {ReadPosDec¨ LR "mhdr"‿"-Dh"‿"Date"∾𝕩}
|
||
headerNames ← "X-Uniform-Type-Identifier"‿"X-Universally-Unique-Identifier"‿"Subject"
|
||
|
||
MailNotesBackend ← {𝕊 config:
|
||
mailDir ← RelPath config j.ObjGet "maildir"
|
||
Entries ⇐ {𝕊:
|
||
ms ← LR "mlist"‿mailDir
|
||
th ← ⟨≠ms,≠headerNames⟩⥊headerNames Hdrs ms
|
||
dh ← Dates ms
|
||
ah ← (("com.apple.mail-note"⊸≡⊑)˘/⊢) th∾˘dh≍˘ms
|
||
{𝕊 ·‿uuid‿title‿time‿path:
|
||
title ⇐ ⋄ time ⇐
|
||
id ⇐ Slugify uuid
|
||
Render ⇐ {R "execline-cd"‿𝕩‿"mshow"‿"-x"‿path ⋄ R "mn2html"‿path}
|
||
}˘ ah
|
||
}
|
||
}
|
||
|
||
# Git Backend
|
||
|
||
converters ← ⍉>⟨
|
||
# TODO(sterni): avoid cat
|
||
⟨"html", ⋈"cat"⟩,
|
||
⟨"md", "lowdown"‿"-T"‿"html"‿"--html-no-skiphtml"‿"--html-no-escapehtml"‿"--html-callout-mdn"⟩,
|
||
# TODO(sterni): use emacs
|
||
⟨"org", "pandoc"‿"-f"‿"org"‿"-t"‿"html5"⟩,
|
||
⟩
|
||
|
||
# TODO(sterni): pipefail
|
||
PipelineCmd ← {Execline "pipeline"‿𝕨∾𝕩}
|
||
|
||
GitBackend ← {𝕊 config:
|
||
repo ← RelPath config j.ObjGet "repository"
|
||
path ← ∾⟜'/' '/' StripRight config "." j._ObjGetDef "path"
|
||
|
||
# We use zero separated fields when dealing with paths, so quoting is unnecessary
|
||
GitCmd ← {"git"‿"-c"‿"core.quotePath=false"‿"-C"‿repo∾𝕩}
|
||
rev ← R GitCmd "rev-parse"‿"HEAD"
|
||
# Use the author date of the latest commit on the file to establish the date
|
||
# of the file. The author date is easier to arbitrarily change and survives
|
||
# history rewrites. It could be interesting to ignore commits that touch
|
||
# multiple files (especially treewide ones).
|
||
PathDate ← {ReadPosDec R GitCmd "log"‿"--date=unix"‿"--pretty=tformat:%ad"‿"-1"‿rev‿"--"‿𝕩}
|
||
|
||
Entries ⇐ {𝕤⋄
|
||
blobs ← ∘‿2⥊@ SplitChar R GitCmd "ls-tree"‿"-zr"‿"--format=%(path)%x00%(objectname)"‿rev‿path
|
||
{𝕊 p‿b:
|
||
extlessp‿ext ← SplitExt p
|
||
id ⇐ Slugify path DropPrefix extlessp
|
||
# TODO(sterni): extract from file if possible
|
||
title ⇐ •file.Name extlessp
|
||
time ⇐ PathDate p
|
||
Render ⇐ {𝕤
|
||
conv ← converters j.ObjGet ext
|
||
R (GitCmd "cat-file"‿"blob"‿b) PipelineCmd conv
|
||
}
|
||
}˘blobs
|
||
}
|
||
}
|
||
|
||
backends ← ⍉>⟨"mail-notes"‿mailNotesBackend, "git"‿gitBackend⟩
|
||
|
||
# Rendering
|
||
|
||
RenderPage ← {
|
||
∾"<!doctype html>
|
||
<html lang=""en"">
|
||
<head>
|
||
<meta charset=""utf-8"">
|
||
<title>"‿𝕨‿"</title>
|
||
<body>
|
||
<h1>"‿𝕨‿"</h1>"‿𝕩
|
||
}
|
||
|
||
WriteEntry ← {outDir 𝕊 entry:
|
||
entryDir ← MkDirP outDir •file.At entry.id
|
||
(entryDir •file.At "index.html") •file.Chars entry.title RenderPage entry.Render entryDir
|
||
# TODO(sterni): urlencode
|
||
"<li><a href="""∾entry.id∾""">"∾entry.title∾"</a></li>"
|
||
}
|
||
|
||
# Main
|
||
|
||
configFile‿outDir ← {
|
||
# Usage: blërg <config file> <out dir>
|
||
! 2=≠•args
|
||
# TODO(sterni): expand ~/
|
||
RelPath¨ •args
|
||
}
|
||
|
||
config ← {
|
||
raw ← j.Parse •FChars configFile
|
||
|
||
[bns,bcs] ← raw j.ObjGet "backends"
|
||
bcs ↩ bcs ∾˘⟜{2‿1⥊"name"‿𝕩}¨ bns
|
||
bts ← j.ObjGet⟜"type"¨ bcs
|
||
backends ⇐ bcs {𝕏 𝕨}¨ backends⊸j.ObjGet¨ bts
|
||
|
||
title ⇐ raw j.ObjGet "title"
|
||
}
|
||
|
||
entries ← ((⍒ •ns.Get⟜"time"¨)⊏⊢) ∾{𝕩.Entries @}¨ config.backends
|
||
"All entry IDs must be unique"!(≠=≠∘⍷) •ns.Get⟜"id"¨ entries
|
||
|
||
MkDirP outDir
|
||
entryIndex ← outDir⊸WriteEntry¨ entries
|
||
(outDir •file.At "index.html") •file.Chars config.title RenderPage ∾"<ul>"∾entryIndex∾"</ul>"
|