2023-08-03 21:50:47 +00:00
|
|
|
|
// This is all because I don't want to deal with double-escaping regex special
|
|
|
|
|
// characters in sh.
|
|
|
|
|
package main
|
|
|
|
|
|
|
|
|
|
import (
|
2023-08-04 00:46:41 +00:00
|
|
|
|
"bytes"
|
2023-08-04 01:19:07 +00:00
|
|
|
|
"fmt"
|
2023-08-03 21:50:47 +00:00
|
|
|
|
"os"
|
|
|
|
|
"regexp"
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
func mapSlice[A, B any](f func(A) B, a []A) []B {
|
|
|
|
|
b := make([]B, len(a))
|
|
|
|
|
for i := range b {
|
|
|
|
|
b[i] = f(a[i])
|
|
|
|
|
}
|
|
|
|
|
return b
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
type Replacer struct {
|
|
|
|
|
re *regexp.Regexp
|
|
|
|
|
replacement string
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func compile(raw [2]string) Replacer {
|
|
|
|
|
return Replacer{
|
|
|
|
|
re: regexp.MustCompile(raw[0]),
|
|
|
|
|
replacement: raw[1],
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
var replacers = mapSlice(compile, [][2]string{
|
|
|
|
|
// Chapter titles
|
|
|
|
|
{`CHAPTER [IVX]+\.` + "\n\n" + `(.*)\.`, `# $1`},
|
|
|
|
|
|
|
|
|
|
// Section titles
|
|
|
|
|
{`(?ms)SECTION [IVX]+\.\n\n_([^_]+)\._`, `## $1`},
|
|
|
|
|
|
|
|
|
|
// Untitled sections
|
2023-08-03 22:35:42 +00:00
|
|
|
|
{`SECTION [IVX]+\.`, `\section{}`},
|
2023-08-03 21:50:47 +00:00
|
|
|
|
|
|
|
|
|
// Em dashes
|
|
|
|
|
{"--", "—"},
|
|
|
|
|
|
|
|
|
|
// Left double typographical quote
|
|
|
|
|
{`"(\w|_\w)`, `“$1`},
|
|
|
|
|
// Right double typographical quote
|
|
|
|
|
{`"`, `”`},
|
|
|
|
|
|
|
|
|
|
// Left single typographical quote
|
|
|
|
|
{`([^\pL])'(\pL|_)`, `$1‘$2`},
|
|
|
|
|
{`(?m)^'`, `‘`},
|
|
|
|
|
// Right single typographical quote
|
|
|
|
|
{`'`, `’`},
|
|
|
|
|
|
|
|
|
|
// Block quotes
|
|
|
|
|
{`(?m)^ +(\S)`, `> $1`},
|
2023-08-04 00:46:41 +00:00
|
|
|
|
|
|
|
|
|
// Footnote superscript
|
|
|
|
|
{`\[(\d+)\]`, `[^$1]`},
|
2023-08-03 21:50:47 +00:00
|
|
|
|
})
|
|
|
|
|
|
2023-08-04 00:46:41 +00:00
|
|
|
|
var footnoteRE = regexp.MustCompile(`\[Footnote \d+: ([^\[\]]|(\[\^\d+\]))+\]`)
|
|
|
|
|
var footnoteNumberRE = regexp.MustCompile(`\[Footnote (\d+):`)
|
|
|
|
|
|
|
|
|
|
func replaceFootnote(src []byte) []byte {
|
|
|
|
|
var formatted = footnoteNumberRE.ReplaceAll(src, []byte("[^$1]:"))
|
|
|
|
|
// Indent footnote paragraphs.
|
|
|
|
|
// This only works because footnotes are stacked at the end of each chapter.
|
|
|
|
|
formatted = bytes.ReplaceAll(formatted, []byte("\n"), []byte("\n "))
|
|
|
|
|
// Strip terminal "]".
|
|
|
|
|
return formatted[:len(formatted)-1]
|
|
|
|
|
}
|
|
|
|
|
|
2023-08-03 21:50:47 +00:00
|
|
|
|
func run() error {
|
2023-08-04 01:19:07 +00:00
|
|
|
|
if len(os.Args) != 3 {
|
|
|
|
|
fmt.Fprintf(os.Stderr, "usage: %s <infile> <outfile>\n", os.Args[0])
|
|
|
|
|
os.Exit(64) // command line usage error
|
|
|
|
|
}
|
|
|
|
|
inpath := os.Args[1]
|
|
|
|
|
outpath := os.Args[2]
|
|
|
|
|
|
|
|
|
|
text, err := os.ReadFile(inpath)
|
2023-08-03 21:50:47 +00:00
|
|
|
|
if err != nil {
|
|
|
|
|
return err
|
|
|
|
|
}
|
2023-08-04 01:19:07 +00:00
|
|
|
|
|
|
|
|
|
for _, r := range replacers {
|
|
|
|
|
text = r.re.ReplaceAll(text, []byte(r.replacement))
|
|
|
|
|
}
|
|
|
|
|
text = footnoteRE.ReplaceAllFunc(text, replaceFootnote)
|
|
|
|
|
|
|
|
|
|
err = os.WriteFile(outpath, text, 0666)
|
|
|
|
|
if err != nil {
|
|
|
|
|
return err
|
2023-08-03 21:50:47 +00:00
|
|
|
|
}
|
2023-08-04 01:19:07 +00:00
|
|
|
|
|
2023-08-03 21:50:47 +00:00
|
|
|
|
return nil
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func main() {
|
|
|
|
|
err := run()
|
|
|
|
|
if err != nil {
|
2023-08-04 01:19:07 +00:00
|
|
|
|
fmt.Fprintln(os.Stderr, err)
|
|
|
|
|
os.Exit(1)
|
2023-08-03 21:50:47 +00:00
|
|
|
|
}
|
|
|
|
|
}
|