mirror of
https://github.com/go-gitea/gitea.git
synced 2024-08-19 19:13:25 +02:00
185 lines
4.7 KiB
Go
185 lines
4.7 KiB
Go
package org
|
|
|
|
import (
|
|
"bytes"
|
|
"path/filepath"
|
|
"regexp"
|
|
"strings"
|
|
)
|
|
|
|
type Comment struct{ Content string }
|
|
|
|
type Keyword struct {
|
|
Key string
|
|
Value string
|
|
}
|
|
|
|
type NodeWithName struct {
|
|
Name string
|
|
Node Node
|
|
}
|
|
|
|
type NodeWithMeta struct {
|
|
Node Node
|
|
Meta Metadata
|
|
}
|
|
|
|
type Metadata struct {
|
|
Caption [][]Node
|
|
HTMLAttributes [][]string
|
|
}
|
|
|
|
type Include struct {
|
|
Keyword
|
|
Resolve func() Node
|
|
}
|
|
|
|
var keywordRegexp = regexp.MustCompile(`^(\s*)#\+([^:]+):(\s+(.*)|$)`)
|
|
var commentRegexp = regexp.MustCompile(`^(\s*)#(.*)`)
|
|
|
|
var includeFileRegexp = regexp.MustCompile(`(?i)^"([^"]+)" (src|example|export) (\w+)$`)
|
|
var attributeRegexp = regexp.MustCompile(`(?:^|\s+)(:[-\w]+)\s+(.*)$`)
|
|
|
|
func lexKeywordOrComment(line string) (token, bool) {
|
|
if m := keywordRegexp.FindStringSubmatch(line); m != nil {
|
|
return token{"keyword", len(m[1]), m[2], m}, true
|
|
} else if m := commentRegexp.FindStringSubmatch(line); m != nil {
|
|
return token{"comment", len(m[1]), m[2], m}, true
|
|
}
|
|
return nilToken, false
|
|
}
|
|
|
|
func (d *Document) parseComment(i int, stop stopFn) (int, Node) {
|
|
return 1, Comment{d.tokens[i].content}
|
|
}
|
|
|
|
func (d *Document) parseKeyword(i int, stop stopFn) (int, Node) {
|
|
k := parseKeyword(d.tokens[i])
|
|
switch k.Key {
|
|
case "NAME":
|
|
return d.parseNodeWithName(k, i, stop)
|
|
case "SETUPFILE":
|
|
return d.loadSetupFile(k)
|
|
case "INCLUDE":
|
|
return d.parseInclude(k)
|
|
case "CAPTION", "ATTR_HTML":
|
|
consumed, node := d.parseAffiliated(i, stop)
|
|
if consumed != 0 {
|
|
return consumed, node
|
|
}
|
|
fallthrough
|
|
default:
|
|
if _, ok := d.BufferSettings[k.Key]; ok {
|
|
d.BufferSettings[k.Key] = strings.Join([]string{d.BufferSettings[k.Key], k.Value}, "\n")
|
|
} else {
|
|
d.BufferSettings[k.Key] = k.Value
|
|
}
|
|
return 1, k
|
|
}
|
|
}
|
|
|
|
func (d *Document) parseNodeWithName(k Keyword, i int, stop stopFn) (int, Node) {
|
|
if stop(d, i+1) {
|
|
return 0, nil
|
|
}
|
|
consumed, node := d.parseOne(i+1, stop)
|
|
if consumed == 0 || node == nil {
|
|
return 0, nil
|
|
}
|
|
d.NamedNodes[k.Value] = node
|
|
return consumed + 1, NodeWithName{k.Value, node}
|
|
}
|
|
|
|
func (d *Document) parseAffiliated(i int, stop stopFn) (int, Node) {
|
|
start, meta := i, Metadata{}
|
|
for ; !stop(d, i) && d.tokens[i].kind == "keyword"; i++ {
|
|
switch k := parseKeyword(d.tokens[i]); k.Key {
|
|
case "CAPTION":
|
|
meta.Caption = append(meta.Caption, d.parseInline(k.Value))
|
|
case "ATTR_HTML":
|
|
attributes, rest := []string{}, k.Value
|
|
for {
|
|
if k, m := "", attributeRegexp.FindStringSubmatch(rest); m != nil {
|
|
k, rest = m[1], m[2]
|
|
attributes = append(attributes, k)
|
|
if v, m := "", attributeRegexp.FindStringSubmatchIndex(rest); m != nil {
|
|
v, rest = rest[:m[0]], rest[m[0]:]
|
|
attributes = append(attributes, v)
|
|
} else {
|
|
attributes = append(attributes, strings.TrimSpace(rest))
|
|
break
|
|
}
|
|
} else {
|
|
break
|
|
}
|
|
}
|
|
meta.HTMLAttributes = append(meta.HTMLAttributes, attributes)
|
|
default:
|
|
return 0, nil
|
|
}
|
|
}
|
|
if stop(d, i) {
|
|
return 0, nil
|
|
}
|
|
consumed, node := d.parseOne(i, stop)
|
|
if consumed == 0 || node == nil {
|
|
return 0, nil
|
|
}
|
|
i += consumed
|
|
return i - start, NodeWithMeta{node, meta}
|
|
}
|
|
|
|
func parseKeyword(t token) Keyword {
|
|
k, v := t.matches[2], t.matches[4]
|
|
return Keyword{strings.ToUpper(k), strings.TrimSpace(v)}
|
|
}
|
|
|
|
func (d *Document) parseInclude(k Keyword) (int, Node) {
|
|
resolve := func() Node {
|
|
d.Log.Printf("Bad include %#v", k)
|
|
return k
|
|
}
|
|
if m := includeFileRegexp.FindStringSubmatch(k.Value); m != nil {
|
|
path, kind, lang := m[1], m[2], m[3]
|
|
if !filepath.IsAbs(path) {
|
|
path = filepath.Join(filepath.Dir(d.Path), path)
|
|
}
|
|
resolve = func() Node {
|
|
bs, err := d.ReadFile(path)
|
|
if err != nil {
|
|
d.Log.Printf("Bad include %#v: %s", k, err)
|
|
return k
|
|
}
|
|
return Block{strings.ToUpper(kind), []string{lang}, d.parseRawInline(string(bs))}
|
|
}
|
|
}
|
|
return 1, Include{k, resolve}
|
|
}
|
|
|
|
func (d *Document) loadSetupFile(k Keyword) (int, Node) {
|
|
path := k.Value
|
|
if !filepath.IsAbs(path) {
|
|
path = filepath.Join(filepath.Dir(d.Path), path)
|
|
}
|
|
bs, err := d.ReadFile(path)
|
|
if err != nil {
|
|
d.Log.Printf("Bad setup file: %#v: %s", k, err)
|
|
return 1, k
|
|
}
|
|
setupDocument := d.Configuration.Parse(bytes.NewReader(bs), path)
|
|
if err := setupDocument.Error; err != nil {
|
|
d.Log.Printf("Bad setup file: %#v: %s", k, err)
|
|
return 1, k
|
|
}
|
|
for k, v := range setupDocument.BufferSettings {
|
|
d.BufferSettings[k] = v
|
|
}
|
|
return 1, k
|
|
}
|
|
|
|
func (n Comment) String() string { return orgWriter.WriteNodesAsString(n) }
|
|
func (n Keyword) String() string { return orgWriter.WriteNodesAsString(n) }
|
|
func (n NodeWithMeta) String() string { return orgWriter.WriteNodesAsString(n) }
|
|
func (n NodeWithName) String() string { return orgWriter.WriteNodesAsString(n) }
|
|
func (n Include) String() string { return orgWriter.WriteNodesAsString(n) }
|