#!/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>"
