package main import( "fmt" "os" "io" "strings" "strconv" p "github.com/mcecode/gemtext-parser" ) type State struct { out io.Writer maxLineLength int insertionMap []string insertionIndex int isInCodeBlock bool wasPreviousLink bool isWebOnly bool } func main() { if len(os.Args) < 3 { fmt.Printf("Usage: %s input.gmi output_gophermap [max line length (default 70)] [preprocessor insertion map file]\n", os.Args[0]) return } var asl p.ASL var err error if os.Args[1] != "-" { asl, err = p.ParseFile(os.Args[1]) if err != nil { fmt.Fprintln(os.Stderr, err) return } } else { stdin, err := io.ReadAll(os.Stdin) if err != nil { fmt.Fprintln(os.Stderr, err) return } asl = p.Parse(string(stdin)) } var outputWriter io.Writer if os.Args[2] != "-" { file, err := os.Create(os.Args[2]) if err != nil { fmt.Fprintln(os.Stderr, err) return } defer file.Close() outputWriter = file } else { outputWriter = os.Stdout } preprocessorMap := []string{} if len(os.Args) >= 5 { data, err := os.ReadFile(os.Args[4]) if err != nil { fmt.Fprintln(os.Stderr, err) os.Exit(1) } preprocessorMap = strings.Split(string(data), "") } s := State{outputWriter, 70, preprocessorMap, 0, false, false, false} if len(os.Args) >= 4 { maxLineLength, err := strconv.Atoi(os.Args[3]) if err != nil { fmt.Fprintln(os.Stderr, err) return } s.maxLineLength = maxLineLength } asl.Visit(s.handleElement) } var gopherTypeCharParams = GopherTypeCharParams{ GophermapFiles: []string{"gophermap"}, GophermapExtensions: []string{}, PromptFileExtensions: []string{}, } func (s *State) handleElement(e p.Element) { if strings.HasPrefix(e.Text(), "") { s.isWebOnly = !s.isWebOnly return } if s.isWebOnly { return } if strings.HasPrefix(e.Text(), "") { s.out.Write([]byte(s.insertionMap[s.insertionIndex])) s.insertionIndex += 1 return } switch (e.Type()) { case p.ElementTypes.Text(): if e.Text() == "" { return } if !s.isInCodeBlock { s.out.Write([]byte("\n")) } s.out.Write([]byte(JoinGophermapLines("i", WrapLines(e.Text(), s.maxLineLength, " "), "\t/FAKE\tNULL\t0"))) s.wasPreviousLink = false case p.ElementTypes.Link(): if !s.wasPreviousLink { s.out.Write([]byte("\n")) } isUrl := strings.Contains(e.URL(), "://") linkPrefix := "" if isUrl { linkPrefix = "URL:" } typeChar := gopherTypeCharParams.GopherTypeChar(e.URL(), strings.HasSuffix(e.URL(), "/")) s.out.Write([]byte(typeChar + e.Text() + "\t" + linkPrefix + e.URL())) s.wasPreviousLink = true case p.ElementTypes.Heading(): s.out.Write([]byte("\n")) if e.Level() == p.HeadingLevels.Heading() || e.Level() == p.HeadingLevels.SubHeading() { s.out.Write([]byte("\n\n")) } if e.Level() == p.HeadingLevels.SubSubHeading() { s.out.Write([]byte("\n")) } s.out.Write([]byte(JoinGophermapLines("i", WrapLines(e.Text(), s.maxLineLength, " "), "\t/FAKE\tNULL\t0"))) s.wasPreviousLink = false case p.ElementTypes.List(): s.out.Write([]byte(JoinGophermapLines("i- ", WrapLines(e.Text(), s.maxLineLength - 2, " "), "\t/FAKE\tNULL\t0"))) s.wasPreviousLink = false case p.ElementTypes.Quote(): s.out.Write([]byte(JoinGophermapLines("i> ", WrapLines(e.Text(), s.maxLineLength - 2, " "), "\t/FAKE\tNULL\t0"))) s.wasPreviousLink = false case p.ElementTypes.PreformatToggle(): s.isInCodeBlock = !s.isInCodeBlock s.wasPreviousLink = false } if e.Text() != "" { s.out.Write([]byte("\n")) } }