diff options
-rw-r--r-- | cmd/pgset/main.go | 12 | ||||
-rw-r--r-- | go.mod | 6 | ||||
-rw-r--r-- | lib/parse/parse.go | 21 | ||||
-rw-r--r-- | lib/translate/translate.go | 306 |
4 files changed, 286 insertions, 59 deletions
diff --git a/cmd/pgset/main.go b/cmd/pgset/main.go index f871f2b..b58a85e 100644 --- a/cmd/pgset/main.go +++ b/cmd/pgset/main.go @@ -7,6 +7,7 @@ import ( "os" "pgset/lib/parse" "strings" + "pgset/lib/translate" ) func main() { @@ -22,8 +23,11 @@ func main() { if err != nil { log.Fatal(err) } - fmt.Println(document[0]) - //neatroff(tree) - //tex(tree) - //sile(tree) + if document == nil { + fmt.Println("o"); + } + err = translate.Translate(document, "Neatroff", os.Stdout) + if err != nil { + log.Fatal("wow") + } } @@ -1,3 +1,7 @@ module pgset -go 1.19 +go 1.23.0 + +toolchain go1.24.5 + +require golang.org/x/exp v0.0.0-20250711185948-6ae5c78190dc diff --git a/lib/parse/parse.go b/lib/parse/parse.go index a86e166..54a77c3 100644 --- a/lib/parse/parse.go +++ b/lib/parse/parse.go @@ -7,13 +7,13 @@ import ( ) type Tag struct { - name string - attributes map[string]string + Name string + Attributes map[string]string } func (t *Tag) String() string { - s := "<" + t.name - for k, v := range t.attributes { + s := "<" + t.Name + for k, v := range t.Attributes { s += " " + k + `="` + v + `"` } s += ">" @@ -52,14 +52,21 @@ func ReadUntil(r io.Reader, sentinels []byte) (string, byte, error) { func ReadTag(r io.Reader) (*Tag, error) { e := new(Tag) - e.attributes = make(map[string]string) + e.Attributes = make(map[string]string) var err error var foundSentinel byte - e.name, foundSentinel, err = ReadUntil(r, []byte{' ', '>'}) + e.Name, foundSentinel, err = ReadUntil(r, []byte{' ', '>'}) if err != nil { return nil, err } + if e.Name[0] == '!' { + // Not An Element + e.Name = "NAE" + // Consume rest of tag + _, _, err := ReadUntil(r, []byte{'>'}) + return e, err + } for { if foundSentinel == '>' { @@ -88,7 +95,7 @@ func ReadTag(r io.Reader) (*Tag, error) { value, foundSentinel, err = ReadUntil(r, []byte{' ', '>'}) value = string(peek) + value } - e.attributes[key] = value + e.Attributes[key] = value } return e, err diff --git a/lib/translate/translate.go b/lib/translate/translate.go index d02e8b4..613da3a 100644 --- a/lib/translate/translate.go +++ b/lib/translate/translate.go @@ -1,25 +1,116 @@ package translate import ( - "fmt" "io" "pgset/lib/parse" + "errors" + "strings" ) -func a(language string) string { - return "" +type State struct { + // Which type of anchor was last begun: href or id? + lastAnchor string + // Consume input until further notice? + consume bool + // Stay on the same line of output until further notice? + sameLine bool + // Stat on the same line of output at least for this input? + sameLineOnce bool + + // metadata + title string + language string + creator string + subject string +} + +func style(state *State) { + state.consume = true +} +func slashStyle(state *State) { + state.consume = false } -func img(language string) string { - return "" +func href(t *parse.Tag, language string) (string, error) { + switch language { + case "Neatroff": + // .post.url #chapter23\ntext\n\n + return ".post.url " + t.Attributes["href"], nil + default: + return "", errors.New("Unimplemented") + } +} + +func id(t *parse.Tag, language string) (string, error) { + switch language { + case "Neatroff": + // .post.name chapter23 + return ".post.name " + strings.Replace(t.Attributes["id"], "#", "", 1), nil + default: + return "", errors.New("Unimplemented") + } } -func header(language string) string { - return "" +func a(t *parse.Tag, language string, state *State) (string, error) { + // There are two kinds of anchors. + // href: refer to a resource, internal or external + // id: serve as an anchor to be referred to + types := []string{"href", "id"} + // Least bad expression of this idea in this language + m := map[string]func (*parse.Tag, string) (string, error) { + "href": href, + "id": id, + } + for _, i := range types { + if _, ok := t.Attributes[i]; ok { + state.lastAnchor = i + s, err := m[i](t, language) + if err != nil { + return "", err + } + return s, nil + } + } + return "", errors.New("Broken anchor. " + t.String()) +} +func slashA(t *parse.Tag, language string, state *State) (string, error) { + var s string + switch state.lastAnchor { + case "href": + fallthrough + case "id": + fallthrough + default: + s = "\n" + } + state.lastAnchor = "" + return s, nil } -func footer(language string) string { - return "" +func meta(t *parse.Tag, language string, state *State) (string, error) { + // Multiple types of this tag. Only those with "name" attribute are interesting + s := "" + if _, ok := t.Attributes["name"]; ok { + s += "name" + } + return s, nil +} + +func i(state *State) (string, error) { + state.sameLine = true + return `\fI`, nil +} +func slashI(state *State) (string, error) { + state.sameLine = false + state.sameLineOnce = true + return `\fP`, nil +} +func strong(state *State) (string, error) { + state.sameLine = true + return `\fB`, nil +} +func slashStrong(state *State) (string, error) { + return slashI(state) } // Among all languages, these are defined: @@ -32,28 +123,74 @@ func footer(language string) string { // - a: anchors to bookmarks and urls, switch on attribute // - img: images // - p: paragraphs -// - meta: metadata +// - meta, link: metadata // - h{1,2,3,4,5}: headings // - strong, i: emphasis // - table, tbody, tr, td: Table of Contents // - div: chapter separations // - br: line breaks +// - blockquote: block quotations +// - hr: horizontal rule // // All other tags are ignored. -// - span: Unwanted metadata, contents to be consumed +// - span: not meaningful, essentially empty +// - body: not meaningful +// - head: not meaningful +// - Non-standard and atypical extensions var Definitions = map[string]map[string]any{ "ConTeXt": { // Must generate. - "a": a, + "a": a, "/a": ``, - "meta": meta, + // Standardized metadata. + // charset: character set + // + "meta": func(t *parse.Tag) (string, error) { + var s string + for k, v := range t.Attributes { + s = k + v + } + return s, nil + }, "/meta": ``, - "div": div, + "div": func(t *parse.Tag) (string, error) { + var s string + for k, v := range t.Attributes { + s = k + v + } + return s, nil + }, "/div": ``, + "Header": func(t *parse.Tag) (string, error) { + var s string + for k, v := range t.Attributes { + s = k + v + } + return s, nil + }, + "Footer": func(t *parse.Tag) (string, error) { + var s string + for k, v := range t.Attributes { + s = k + v + } + return s, nil + }, + // link tags are empty + "link": func(t *parse.Tag) (string, error) { + var s string + for k, v := range t.Attributes { + s = k + v + } + return s, nil + }, // img tags are empty - "img": img, - "Header": header, - "Footer": footer, + "img": func(t *parse.Tag) (string, error) { + var s string + for k, v := range t.Attributes { + s = k + v + } + return s, nil + }, // Constants. "p": ``, @@ -80,10 +217,14 @@ var Definitions = map[string]map[string]any{ "/h4": ``, "h5": ``, "/h5": ``, + "blockquote": ``, + "/blockquote": ``, // br tags are empty "br": ``, + // hr tags are empty + "hr": ``, "Prefix": ``, - "Postfix", ``, + "Postfix": ``, }, "kerTeX": {}, "XeTeX": {}, @@ -95,6 +236,9 @@ var Definitions = map[string]map[string]any{ .ssh 15 .kn 1 .hlm 2 +.de empty +.. +.blm empty .ds margin 1.25i .po \*[margin] .de header @@ -103,6 +247,9 @@ var Definitions = map[string]map[string]any{ .de footer ' bp .. +.de pg +. ti +1m +.. .wh 0 header .wh -\*[margin] footer .ds measure 6i @@ -173,6 +320,65 @@ var Definitions = map[string]map[string]any{ . /h2 ..`, "Footer": ``, + "Prefix": "", + "Postfix": "", + "NAE": ``, + "a": a, + "/a": slashA, + // Constants. + "p": `.pg`, + "/p": ``, + "i": i, + "/i": slashI, + "strong": strong, + "/strong": slashStrong, + "table": ``, + "/table": ``, + "tr": ``, + "/tr": ``, + "tbody": ``, + "/tbody": ``, + "td": ``, + "/td": ``, + "h1": ``, + "/h1": ``, + "h2": ``, + "/h2": ``, + "h3": ``, + "/h3": ``, + "h4": ``, + "/h4": ``, + "h5": ``, + "/h5": ``, + "blockquote": ``, + "/blockquote": ``, + // br tags are empty + "br": ``, + // hr tags are empty + "hr": ``, + "head": ``, + "/head": ``, + "title": ``, + "/title": ``, + "style": style, + "/style": slashStyle, + "html": ``, + "/html": ``, + "link": ``, + "meta": ``, + "body": ``, + "/body": ``, + "div": ``, + "/div": ``, + "section": ``, + "/section": ``, + "span": ``, + "/span": ``, + "li": ``, + "/li": ``, + "ul": ``, + "/ul": ``, + "img": ``, }, "Utmac": {}, "groff": {}, @@ -402,39 +608,45 @@ It's a versatile family providing users with plenty of features and moods. It sp }, } -// Templates. -// Optically correct scaling. -// Some people need larger body type. - -func style(language, key, value string) (string, error) { - return "", nil -} - -func generate(mark, language string) (string, error) { - if Definitions[language] == nil { - return errors.New(language + " is a new language, define it") - } -} - -func Generate(language string, tag Tag) string { - switch t := Definitions[language][mark].(type) { - case string: - return t - case func(Tag): - t(tag) - } -} - -func Translate(document []any, language string, output io.Writer) { +func Translate(document []any, language string, output io.Writer) error { + output.Write([]byte(Definitions[language]["Header"].(string) + "\n")) + state := new(State) + var err error for _, i := range document { + var out string switch t := i.(type) { case string: - output.Write(generate(language, *Tag{name: "Prefix"})) - output.Write(definitions[language]["Prefix"]) - output.Write(t) - output.Write(generate(language, *Tag{name: "Postfix"})) + out = Definitions[language]["Prefix"].(string) + t + Definitions[language]["Postfix"].(string) case *parse.Tag: - fmt.Println(i.name) + switch u := Definitions[language][t.Name].(type) { + case string: + out = u + case func(*parse.Tag) (string, error): + out, err = u(t) + case func(*parse.Tag, string, *State) (string, error): + out, err = u(t, language, state) + case func(*State) (string, error): + out, err = u(state) + case func(*State): + u(state) + out = "" + case nil: + out = "dne" + t.String() + "\n" + } + } + if err != nil { + return err + } + if out != "" && !state.consume { + if !state.sameLine && !state.sameLineOnce { + out += "\n" + } + if state.sameLineOnce { + state.sameLineOnce = false + } + output.Write([]byte(out)) } } + output.Write([]byte(Definitions[language]["Footer"].(string) + "\n")) + return nil } |