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 htmlroc" func main() { // Command line flags 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") flag.Parse() // 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 = "
" + str[idx+1:] } // Wrap other lines in
tags str = strings.ReplaceAll(str, "\n", "
") + "
" return template.HTML(str) } func tplHtmlify(str string) template.HTML { // Replace newlines with