package main import ( "fmt" "log" "os" "strings" "sort" "io" ) const ( bufsize = 512 prefix = `.\"s ` end = `.\"e` section = `.\"§` //tableHead = ".TS\nexpand;\nl1w(1.5i) l1w(4.5i)." //tableFoot = ".TE" mac = ".ref" //delim = "()" ) var tableHead, tableFoot, delim, extraPrefix, extraSuffix string type Reference struct { name string contents string index int } func interpEscape(str string) string { var rt string for i := 0; i < len(str); i++ { if str[i] == '\\' { i++ if str[i] == 'n' { rt += "\n" } else { rt += "\\n" } } else { rt += string(str[i]) } } return rt } func getRefTxt(lines []string) string { var txt string for _, line := range lines { if strings.HasPrefix(line, end) { break } txt += line + "\n" } return string(txt) } func findIndex(refs []Reference, name string) int { for _, ref := range refs { if ref.name == name { return ref.index } } return -1 } func checkLetter(ch rune) bool { if ch >= 'A' && ch <= 'Z' || ch >= 'a' && ch <= 'z' { return true } return false } func checkAllCaps(word string) bool { for _, ch := range word { if !checkLetter(ch) { continue } if ch < 'A' || ch > 'Z' { return false } } return true } // Typographic niceties. func extraFmt(ref string) string { words := strings.Split(ref, " ") if checkAllCaps(words[0]) { words[0] = extraPrefix + words[0] + extraSuffix } words[1] = extraPrefix + words[1] + extraSuffix return words[0] + " " + words[1] } func main() { in := os.Stdin if len(os.Args) > 1 { var err error in, err = os.Open(os.Args[1]) if err != nil { log.Fatal(err) } } bytes, err := io.ReadAll(in) if err != nil { log.Fatal(err) } contents := string(bytes) /* Iterate through the contents of the file twice: Once to find and index all references. Again to refer to each reference. */ var refs []Reference div := -1 lines := strings.Split(contents, "\n") for i, line := range lines { if strings.HasPrefix(line, section) { div = i words := strings.Split(line, "#") wc := len(words) if wc < 4 { log.Fatal(`Provide 3 arguments in the § split, delimited by #. • A tbl(7) header • A tbl(7) footer • A pair of delimiters for references, such as "()" Below is an example. .\"§#.TS\nexpand;\nl1w(1.5i) l1w(4.5i).#.TE#()#\fC#\fP The final two arguments in that example are optional.`) } tableHead = interpEscape(words[1] + "\n") tableFoot = interpEscape(words[2]) delim = words[3] if wc > 4 { extraPrefix = words[4] } if wc > 5 { extraSuffix = words[5] } } } // If no split, quietly return contents. if div == -1 { fmt.Print(contents) return } for i, line := range lines[div:] { if strings.HasPrefix(line, prefix) { var ref Reference ref.name = line[len(prefix):] // i is relative to the lines after div. ref.contents = getRefTxt(lines[div+i+1:]) refs = append(refs, ref) } } /* Sort references alphabetically. */ sort.Slice(refs, func(i, j int) bool { return strings.Compare(refs[i].name, refs[j].name) < 0 }) for i := range refs { refs[i].index = i } // Second pass. for _, line := range lines[:div] { if strings.HasPrefix(line, mac) { var name string var start, end int for i := range line { if line[i] == delim[0] { start = i+1 } if line[i] == delim[1] { end = i break } } name = line[start:end] index := findIndex(refs, name) fmt.Printf(".post.url #%d \"%s\"\n\n", index, extraFmt(line[start-1:])) } else { fmt.Println(line) } } // Format References into AMS Style bibiography. /* As it turns out, in order to be referenced individually, each reference needs its own table. */ for _, ref := range refs { fmt.Printf(".post.name %d\n", ref.index) fmt.Printf(tableHead) fmt.Printf("T{\n%c%s%c\nT}", delim[0], extraFmt(ref.name), delim[1]) fmt.Printf("\tT{\n%sT}\n", ref.contents) fmt.Println(tableFoot) } }