106 lines
3.5 KiB
Go
106 lines
3.5 KiB
Go
// Package gemtext provides a variety of tools for processing gemtext files.
|
|
package gemtext
|
|
|
|
import (
|
|
"fmt"
|
|
"strings"
|
|
)
|
|
|
|
type ObjectType int64
|
|
|
|
const (
|
|
TEXT ObjectType = iota
|
|
LINK
|
|
PREFORMATTED_TOGGLE
|
|
PREFORMATTED_TEXT
|
|
HEADING
|
|
LIST
|
|
QUOTE
|
|
)
|
|
|
|
// type GemtextObject represents a gemtext object.
|
|
type GemtextObject struct {
|
|
Type ObjectType
|
|
Text string // Contains the text of the element. For a link this is the label.
|
|
Literal string // Contains the line as it exists in the file
|
|
Path string // Populated if object is a link
|
|
Level int // Populated if object is a heading
|
|
}
|
|
|
|
// type GemtextPage represents a gemtext page. It is equivalent to a slice of GemtextObjects.
|
|
type GemtextPage []GemtextObject
|
|
|
|
// ParseLink takes a gemini link as a string and returns a GemtextObject and an error.
|
|
func ParseLink(l string) (GemtextObject, error) {
|
|
if !strings.HasPrefix(l, "=>") {
|
|
return GemtextObject{}, fmt.Errorf("Not a gemtext link!")
|
|
} else {
|
|
f := strings.Fields(l[2:])
|
|
link := GemtextObject{Type: LINK, Literal: l}
|
|
link.Path = f[0]
|
|
link.Text = strings.TrimSpace(l[3+len(f[0]):]) // text that remains after removing the URL and link marker
|
|
return link, nil
|
|
}
|
|
|
|
}
|
|
|
|
// ParseHeading parses a gemtext heading, returns a HEADING GemtextObject if it is between levels 1-3 and TEXT otherwise.
|
|
func ParseHeading(l string) (GemtextObject, error) {
|
|
switch {
|
|
case strings.HasPrefix(l, "####"): // Fake headings
|
|
return GemtextObject{Type: TEXT, Text: l, Literal: l}, nil
|
|
case strings.HasPrefix(l, "###"):
|
|
return GemtextObject{Type: HEADING, Level: 3, Text: strings.TrimSpace(l[3:]), Literal: l}, nil
|
|
case strings.HasPrefix(l, "##"):
|
|
return GemtextObject{Type: HEADING, Level: 2, Text: strings.TrimSpace(l[2:]), Literal: l}, nil
|
|
case strings.HasPrefix(l, "#"):
|
|
return GemtextObject{Type: HEADING, Level: 1, Text: strings.TrimSpace(l[1:]), Literal: l}, nil
|
|
}
|
|
return GemtextObject{Type: TEXT, Text: l, Literal: l}, nil
|
|
|
|
}
|
|
|
|
// ParseLine parses a gemtext line, and returns a GemtextObject representing it.
|
|
// "isPreformatted" takes a true if the line is in a preformatted block, else otherwise.
|
|
func ParseLine(line string, isPreformatted bool) (GemtextObject, error) {
|
|
if !isPreformatted {
|
|
switch {
|
|
case strings.HasPrefix(line, "=>"):
|
|
return ParseLink(line)
|
|
case strings.HasPrefix(line, "```"):
|
|
return GemtextObject{Type: PREFORMATTED_TOGGLE, Literal: line, Text: line[3:]}, nil
|
|
case strings.HasPrefix(line, "#"):
|
|
return ParseHeading(line)
|
|
case strings.HasPrefix(line, ">"):
|
|
return GemtextObject{Type: QUOTE, Text: strings.TrimSpace(line[1:]), Literal: line}, nil
|
|
case strings.HasPrefix(line, "* "): // Bullets require a space in the spec
|
|
return GemtextObject{Type: LIST, Text: line[2:], Literal: line}, nil
|
|
}
|
|
return GemtextObject{Type: TEXT, Text: line, Literal: line}, nil
|
|
} else {
|
|
if strings.HasPrefix(line, "```") { // toggles are the only special line type in preformatted mode
|
|
return GemtextObject{Type: PREFORMATTED_TOGGLE, Literal: line, Text: line[3:]}, nil
|
|
}
|
|
return GemtextObject{Type: PREFORMATTED_TEXT, Text: line, Literal: line}, nil
|
|
}
|
|
|
|
}
|
|
|
|
// ParsePage takes a string containing the contents of a gemtext page and returns a GemtextPage.
|
|
func ParsePage(p string) (GemtextPage, error) {
|
|
preformatted := false
|
|
lines := strings.Split(p, "\n")
|
|
var page GemtextPage
|
|
for _, line := range lines {
|
|
l, err := ParseLine(line, preformatted)
|
|
page = append(page, l)
|
|
if err != nil {
|
|
return GemtextPage{}, err
|
|
}
|
|
if l.Type == PREFORMATTED_TOGGLE {
|
|
preformatted = !preformatted
|
|
}
|
|
}
|
|
return page, nil
|
|
}
|