diff --git a/hypha.go b/hypha.go
index 0e6e583..e412c14 100644
--- a/hypha.go
+++ b/hypha.go
@@ -32,6 +32,7 @@ func init() {
}
return
}
+ markup.HyphaIterate = IterateHyphaNamesWith
}
// GetHyphaData finds a hypha addressed by `hyphaName` and returns its `hyphaData`. `hyphaData` is set to a zero value if this hypha does not exist. `isOld` is false if this hypha does not exist.
@@ -190,7 +191,7 @@ func binaryHtmlBlock(hyphaName string, hd *HyphaData) string {
case ".jpg", ".gif", ".png", ".webp", ".svg", ".ico":
return fmt.Sprintf(`
-
+
`, hyphaName)
case ".ogg", ".webm", ".mp4":
return fmt.Sprintf(`
diff --git a/main.go b/main.go
index aa29c7f..51d7610 100644
--- a/main.go
+++ b/main.go
@@ -23,7 +23,7 @@ import (
var WikiDir string
// HyphaPattern is a pattern which all hyphae must match.
-var HyphaPattern = regexp.MustCompile(`[^?!:#@><*|"\'&%]+`)
+var HyphaPattern = regexp.MustCompile(`[^?!:#@><*|"\'&%{}]+`)
// HyphaStorage is a mapping between canonical hypha names and their meta information.
var HyphaStorage = make(map[string]*HyphaData)
diff --git a/markup/img.go b/markup/img.go
index 244451b..6445ed1 100644
--- a/markup/img.go
+++ b/markup/img.go
@@ -13,53 +13,169 @@ func MatchesImg(line string) bool {
}
type imgEntry struct {
- path string
- sizeH string
- sizeV string
- desc string
+ trimmedPath string
+ path strings.Builder
+ sizeW strings.Builder
+ sizeH strings.Builder
+ desc strings.Builder
}
+func (entry *imgEntry) descriptionAsHtml(hyphaName string) (html string) {
+ if entry.desc.Len() == 0 {
+ return ""
+ }
+ lines := strings.Split(entry.desc.String(), "\n")
+ for _, line := range lines {
+ if line = strings.TrimSpace(line); line != "" {
+ if html != "" {
+ html += ` `
+ }
+ html += ParagraphToHtml(hyphaName, line)
+ }
+ }
+ return `` + html + ``
+}
+
+func (entry *imgEntry) sizeWAsAttr() string {
+ if entry.sizeW.Len() == 0 {
+ return ""
+ }
+ return ` width="` + entry.sizeW.String() + `"`
+}
+
+func (entry *imgEntry) sizeHAsAttr() string {
+ if entry.sizeH.Len() == 0 {
+ return ""
+ }
+ return ` height="` + entry.sizeH.String() + `"`
+}
+
+type imgState int
+
+const (
+ inRoot imgState = iota
+ inName
+ inDimensionsW
+ inDimensionsH
+ inDescription
+)
+
type Img struct {
entries []imgEntry
- inDesc bool
+ currEntry imgEntry
hyphaName string
+ state imgState
+}
+
+func (img *Img) pushEntry() {
+ if strings.TrimSpace(img.currEntry.path.String()) != "" {
+ img.entries = append(img.entries, img.currEntry)
+ img.currEntry = imgEntry{}
+ img.currEntry.path.Reset()
+ }
}
func (img *Img) Process(line string) (shouldGoBackToNormal bool) {
- if img.inDesc {
- rightBraceIndex := strings.IndexRune(line, '}')
- if cnt := len(img.entries); rightBraceIndex == -1 && cnt != 0 {
- img.entries[cnt-1].desc += "\n" + line
- } else if rightBraceIndex != -1 && cnt != 0 {
- img.entries[cnt-1].desc += "\n" + line[:rightBraceIndex]
- img.inDesc = false
- }
- if strings.Count(line, "}") > 1 {
+ stateToProcessor := map[imgState]func(rune) bool{
+ inRoot: img.processInRoot,
+ inName: img.processInName,
+ inDimensionsW: img.processInDimensionsW,
+ inDimensionsH: img.processInDimensionsH,
+ inDescription: img.processInDescription,
+ }
+ for _, r := range line {
+ if shouldReturnTrue := stateToProcessor[img.state](r); shouldReturnTrue {
return true
}
- } else if s := strings.TrimSpace(line); s != "" {
- if s[0] == '}' {
- return true
- }
- img.parseStartOfEntry(line)
}
return false
}
-func ImgFromFirstLine(line, hyphaName string) Img {
- img := Img{
+func (img *Img) processInDescription(r rune) (shouldReturnTrue bool) {
+ switch r {
+ case '}':
+ img.state = inName
+ default:
+ img.currEntry.desc.WriteRune(r)
+ }
+ return false
+}
+
+func (img *Img) processInRoot(r rune) (shouldReturnTrue bool) {
+ switch r {
+ case '}':
+ img.pushEntry()
+ return true
+ case '\n', '\r':
+ img.pushEntry()
+ case ' ', '\t':
+ default:
+ img.state = inName
+ img.currEntry = imgEntry{}
+ img.currEntry.path.Reset()
+ img.currEntry.path.WriteRune(r)
+ }
+ return false
+}
+
+func (img *Img) processInName(r rune) (shouldReturnTrue bool) {
+ switch r {
+ case '}':
+ img.pushEntry()
+ return true
+ case '|':
+ img.state = inDimensionsW
+ case '{':
+ img.state = inDescription
+ case '\n', '\r':
+ img.pushEntry()
+ img.state = inRoot
+ default:
+ img.currEntry.path.WriteRune(r)
+ }
+ return false
+}
+
+func (img *Img) processInDimensionsW(r rune) (shouldReturnTrue bool) {
+ switch r {
+ case '}':
+ img.pushEntry()
+ return true
+ case '*':
+ img.state = inDimensionsH
+ case ' ', '\t', '\n':
+ case '{':
+ img.state = inDescription
+ default:
+ img.currEntry.sizeW.WriteRune(r)
+ }
+ return false
+}
+
+func (img *Img) processInDimensionsH(r rune) (shouldGoBackToNormal bool) {
+ switch r {
+ case '}':
+ img.pushEntry()
+ return true
+ case ' ', '\t', '\n':
+ case '{':
+ img.state = inDescription
+ default:
+ img.currEntry.sizeH.WriteRune(r)
+ }
+ return false
+}
+
+func ImgFromFirstLine(line, hyphaName string) (img *Img, shouldGoBackToNormal bool) {
+ img = &Img{
hyphaName: hyphaName,
entries: make([]imgEntry, 0),
}
- line = line[strings.IndexRune(line, '{'):]
- if len(line) == 1 { // if { only
- } else {
- line = line[1:] // Drop the {
- }
- return img
+ line = line[strings.IndexRune(line, '{')+1:]
+ return img, img.Process(line)
}
-func (img *Img) canonicalPathFor(path string) string {
+func (img *Img) binaryPathFor(path string) string {
path = strings.TrimSpace(path)
if strings.IndexRune(path, ':') != -1 || strings.IndexRune(path, '/') == 0 {
return path
@@ -68,73 +184,71 @@ func (img *Img) canonicalPathFor(path string) string {
}
}
-func (img *Img) parseStartOfEntry(line string) (entry imgEntry, followedByDesc bool) {
- pipeIndex := strings.IndexRune(line, '|')
- if pipeIndex == -1 { // If no : in string
- entry.path = img.canonicalPathFor(line)
+func (img *Img) pagePathFor(path string) string {
+ path = strings.TrimSpace(path)
+ if strings.IndexRune(path, ':') != -1 || strings.IndexRune(path, '/') == 0 {
+ return path
} else {
- entry.path = img.canonicalPathFor(line[:pipeIndex])
- line = strings.TrimPrefix(line, line[:pipeIndex+1])
-
- var (
- leftBraceIndex = strings.IndexRune(line, '{')
- rightBraceIndex = strings.IndexRune(line, '}')
- dimensions string
- )
-
- if leftBraceIndex == -1 {
- dimensions = line
- } else {
- dimensions = line[:leftBraceIndex]
- }
-
- sizeH, sizeV := parseDimensions(dimensions)
- entry.sizeH = sizeH
- entry.sizeV = sizeV
-
- if leftBraceIndex != -1 && rightBraceIndex == -1 {
- img.inDesc = true
- followedByDesc = true
- entry.desc = strings.TrimPrefix(line, line[:leftBraceIndex+1])
- } else if leftBraceIndex != -1 && rightBraceIndex != -1 {
- entry.desc = line[leftBraceIndex+1 : rightBraceIndex]
- }
+ return "/page/" + xclCanonicalName(img.hyphaName, path)
}
- img.entries = append(img.entries, entry)
- return
}
-func parseDimensions(dimensions string) (sizeH, sizeV string) {
+func parseDimensions(dimensions string) (sizeW, sizeH string) {
xIndex := strings.IndexRune(dimensions, '*')
if xIndex == -1 { // If no x in dimensions
- sizeH = strings.TrimSpace(dimensions)
+ sizeW = strings.TrimSpace(dimensions)
} else {
- sizeH = strings.TrimSpace(dimensions[:xIndex])
- sizeV = strings.TrimSpace(strings.TrimPrefix(dimensions, dimensions[:xIndex+1]))
+ sizeW = strings.TrimSpace(dimensions[:xIndex])
+ sizeH = strings.TrimSpace(strings.TrimPrefix(dimensions, dimensions[:xIndex+1]))
}
return
}
-func (img Img) ToHtml() (html string) {
- for _, entry := range img.entries {
- html += fmt.Sprintf(`
-
-`, entry.path, entry.sizeH, entry.sizeV)
- if entry.desc != "" {
- html += ` `
- for i, line := range strings.Split(entry.desc, "\n") {
- if line != "" {
- if i > 0 {
- html += ` `
- }
- html += ParagraphToHtml(img.hyphaName, line)
- }
- }
- html += ``
+func (img *Img) checkLinks() map[string]bool {
+ m := make(map[string]bool)
+ for i, entry := range img.entries {
+ // Also trim them for later use
+ entry.trimmedPath = strings.TrimSpace(entry.path.String())
+ isAbsoluteUrl := strings.ContainsRune(entry.trimmedPath, ':')
+ if !isAbsoluteUrl {
+ entry.trimmedPath = canonicalName(entry.trimmedPath)
}
+ img.entries[i] = entry
+ m[entry.trimmedPath] = isAbsoluteUrl
+ }
+ HyphaIterate(func(hyphaName string) {
+ for _, entry := range img.entries {
+ if hyphaName == entry.trimmedPath {
+ m[entry.trimmedPath] = true
+ }
+ }
+ })
+ return m
+}
+
+func (img *Img) ToHtml() (html string) {
+ linkAvailabilityMap := img.checkLinks()
+ isOneImageOnly := len(img.entries) == 1 && img.entries[0].desc.Len() == 0
+ if isOneImageOnly {
+ html += ``
+ } else {
+ html += ``
+ }
+
+ for _, entry := range img.entries {
+ html += ``
+ // If is existing hypha or an external path
+ if linkAvailabilityMap[entry.trimmedPath] {
+ html += fmt.Sprintf(
+ ``,
+ img.pagePathFor(entry.trimmedPath),
+ img.binaryPathFor(entry.trimmedPath),
+ entry.sizeWAsAttr(), entry.sizeHAsAttr())
+ } else { // If is a non-existent hypha
+ html += fmt.Sprintf(`Hypha %s does not exist`, img.pagePathFor(entry.trimmedPath), entry.trimmedPath)
+ }
+ html += entry.descriptionAsHtml(img.hyphaName)
html += ``
}
- return `
-` + html + `
-`
+ return html + ``
}
diff --git a/markup/lexer.go b/markup/lexer.go
index a98848c..08476d6 100644
--- a/markup/lexer.go
+++ b/markup/lexer.go
@@ -12,6 +12,9 @@ var HyphaExists func(string) bool
// HyphaAccess holds function that accesses a hypha by its name.
var HyphaAccess func(string) (rawText, binaryHtml string, err error)
+// HyphaIterate is a function that iterates all hypha names existing.
+var HyphaIterate func(func(string))
+
// GemLexerState is used by markup parser to remember what is going on.
type GemLexerState struct {
// Name of hypha being parsed
@@ -21,7 +24,7 @@ type GemLexerState struct {
id int
buf string
// Temporaries
- img Img
+ img *Img
}
type Line struct {
@@ -84,7 +87,7 @@ func geminiLineToAST(line string, state *GemLexerState, ast *[]Line) {
imgState:
if shouldGoBackToNormal := state.img.Process(line); shouldGoBackToNormal {
state.where = ""
- addLine(state.img)
+ addLine(*state.img)
}
return
@@ -174,8 +177,13 @@ normalState:
case line == "----":
*ast = append(*ast, Line{id: -1, contents: ""})
case MatchesImg(line):
- state.where = "img"
- state.img = ImgFromFirstLine(line, state.name)
+ img, shouldGoBackToNormal := ImgFromFirstLine(line, state.name)
+ if shouldGoBackToNormal {
+ addLine(*img)
+ } else {
+ state.where = "img"
+ state.img = img
+ }
default:
addLine(fmt.Sprintf("