192 lines
4.8 KiB
Go
192 lines
4.8 KiB
Go
package main // import "git.fromouter.space/mcg/htmlroc"
|
|
import (
|
|
"flag"
|
|
"fmt"
|
|
"html/template"
|
|
"io/ioutil"
|
|
"os"
|
|
"strings"
|
|
)
|
|
|
|
// Where are we in the file
|
|
const (
|
|
FStart = "start" // File start
|
|
InTOC = "intoc" // In Table of content
|
|
AfterTOC = "aftertoc" // After Table of content
|
|
)
|
|
|
|
// TemplateData is the data that is filled and passed to the HTML template
|
|
type TemplateData struct {
|
|
Title string
|
|
Credits string
|
|
Sections []*TemplateSection
|
|
}
|
|
|
|
// TemplateSection is a section of the rules
|
|
type TemplateSection struct {
|
|
ID string
|
|
Title string
|
|
Extra bool
|
|
Rules []TemplateRule
|
|
Text string
|
|
}
|
|
|
|
// TemplateRule is a single rule
|
|
type TemplateRule struct {
|
|
ID string
|
|
Text string
|
|
Depth int
|
|
}
|
|
|
|
var funmap = template.FuncMap{
|
|
"toCredits": tplToCredits,
|
|
"htmlify": tplHtmlify,
|
|
}
|
|
|
|
// HTMLrocCredits is the credit string to append to the other credits
|
|
const HTMLrocCredits = "Converted to HTML using <a href=\"https://git.fromouter.space/mcg/htmlroc\">htmlroc</a>"
|
|
|
|
func main() {
|
|
txtfile := flag.String("in", "rules.txt", "Path to rules.txt file")
|
|
tplfile := flag.String("template", "template.html", "Path to template file")
|
|
outname := flag.String("out", "out.html", "Output file")
|
|
|
|
// Read template file
|
|
tpl, err := template.New(*tplfile).Funcs(funmap).ParseFiles(*tplfile)
|
|
checkErr(err, "Could not load template file \"%s\"", *tplfile)
|
|
|
|
tpldata := TemplateData{}
|
|
|
|
// Read rules file
|
|
filebytes, err := ioutil.ReadFile(*txtfile)
|
|
checkErr(err, "Could not read rules file \"%s\"", *txtfile)
|
|
|
|
filestr := string(filebytes)
|
|
|
|
// Use table of content as delimiter between credits and rule text
|
|
toc := strings.Index(filestr, "Table of Contents")
|
|
if toc < 0 {
|
|
fmt.Fprintln(os.Stderr, "could not find TOC")
|
|
os.Exit(1)
|
|
}
|
|
|
|
// Extract credits
|
|
tpldata.Credits, filestr = strings.TrimSpace(filestr[:toc]), filestr[toc:]
|
|
|
|
// Add our own credits
|
|
tpldata.Credits += "\n" + HTMLrocCredits
|
|
|
|
// Rules are after TOC
|
|
tocend := strings.Index(filestr, "\n\n")
|
|
if tocend < 0 {
|
|
fmt.Fprintln(os.Stderr, "could not find end of TOC")
|
|
os.Exit(1)
|
|
}
|
|
|
|
// Strip TOC out
|
|
filestr = filestr[tocend+2:]
|
|
|
|
// Every rule is a line (pretty handy!)
|
|
lines := strings.Split(filestr, "\n")
|
|
var currentSection *TemplateSection
|
|
for _, line := range lines {
|
|
// Trim line (used for several purposes)
|
|
trimmed := strings.TrimSpace(line)
|
|
|
|
// Skip empty lines
|
|
if len(trimmed) < 1 {
|
|
continue
|
|
}
|
|
|
|
// All rules begin with "(ID)"
|
|
isRule := trimmed[0] == '('
|
|
|
|
// All sections begin with "N. "
|
|
// for sake of not making this check extra complicated we're going to
|
|
// assume it has to be within the first 5 characters
|
|
isSection := strings.Index(trimmed[:5], ". ") > 0
|
|
|
|
// Calculate depth based on number of whitespace (/2)
|
|
depth := len(line) - len(trimmed)
|
|
|
|
switch {
|
|
// It's a section
|
|
case isSection:
|
|
parts := strings.SplitN(trimmed, ". ", 2)
|
|
if len(parts) < 2 {
|
|
// ???
|
|
fmt.Fprintf(os.Stderr, "Found unexpected line: \"%s\"\n", trimmed)
|
|
continue
|
|
}
|
|
// Create new section
|
|
currentSection = &TemplateSection{
|
|
ID: parts[0],
|
|
Title: parts[1],
|
|
Rules: []TemplateRule{},
|
|
Extra: false,
|
|
}
|
|
tpldata.Sections = append(tpldata.Sections, currentSection)
|
|
|
|
// It's a new rule
|
|
case isRule:
|
|
|
|
// ID is between ()s
|
|
endID := strings.IndexRune(trimmed, ')')
|
|
if endID < 0 {
|
|
// ???
|
|
fmt.Fprintf(os.Stderr, "Found unexpected or malformed line: \"%s\"\n", trimmed)
|
|
continue
|
|
}
|
|
|
|
currentSection.Rules = append(currentSection.Rules, TemplateRule{
|
|
ID: trimmed[1:endID],
|
|
Text: strings.TrimSpace(trimmed[endID+1:]),
|
|
Depth: depth,
|
|
})
|
|
|
|
// It's something else
|
|
default:
|
|
// If there is at least a rule, assume it's a continuation of the last rule
|
|
if len(currentSection.Rules) > 0 {
|
|
currentSection.Rules[len(currentSection.Rules)-1].Text += "\n" + line
|
|
} else {
|
|
// No rules? Might be an extra
|
|
currentSection.Extra = true
|
|
currentSection.Text += line + "\n"
|
|
}
|
|
}
|
|
}
|
|
|
|
// Create output file
|
|
outfile, err := os.Create(*outname)
|
|
checkErr(err, "Could not create output file \"%s\"", *outname)
|
|
defer outfile.Close()
|
|
|
|
// Run template on output file stream
|
|
checkErr(tpl.Execute(outfile, tpldata), "Error running template")
|
|
}
|
|
|
|
func checkErr(err error, fmtstr string, args ...interface{}) {
|
|
if err != nil {
|
|
fmt.Fprintf(os.Stderr, "FATAL: "+fmtstr+":\n ", args...)
|
|
fmt.Fprintln(os.Stderr, err.Error())
|
|
os.Exit(1)
|
|
}
|
|
}
|
|
|
|
func tplToCredits(str string) template.HTML {
|
|
// Make first line an heading
|
|
idx := strings.IndexRune(str, '\n')
|
|
if idx > 0 {
|
|
str = "<h1>" + str[:idx] + "</h1><p>" + str[idx+1:]
|
|
}
|
|
// Wrap other lines in <p> tags
|
|
str = strings.ReplaceAll(str, "\n", "</p><p>") + "</p>"
|
|
return template.HTML(str)
|
|
}
|
|
|
|
func tplHtmlify(str string) template.HTML {
|
|
// Replace newlines with <br />
|
|
str = strings.ReplaceAll(str, "\n", "<br />")
|
|
return template.HTML(str)
|
|
}
|