summaryrefslogtreecommitdiffstats
path: root/vendor/github.com/gomarkdown/markdown/parser/include.go
blob: 2448a6854381b3bac0c017fd4f07f15b67c183af (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
package parser

import (
	"bytes"
	"path"
	"path/filepath"
)

// isInclude parses {{...}}[...], that contains a path between the {{, the [...] syntax contains
// an address to select which lines to include. It is treated as an opaque string and just given
// to readInclude.
func (p *Parser) isInclude(data []byte) (filename string, address []byte, consumed int) {
	i := skipCharN(data, 0, ' ', 3) // start with up to 3 spaces
	if len(data[i:]) < 3 {
		return "", nil, 0
	}
	if data[i] != '{' || data[i+1] != '{' {
		return "", nil, 0
	}
	start := i + 2

	// find the end delimiter
	i = skipUntilChar(data, i, '}')
	if i+1 >= len(data) {
		return "", nil, 0
	}
	end := i
	i++
	if data[i] != '}' {
		return "", nil, 0
	}
	filename = string(data[start:end])

	if i+1 < len(data) && data[i+1] == '[' { // potential address specification
		start := i + 2

		end = skipUntilChar(data, start, ']')
		if end >= len(data) {
			return "", nil, 0
		}
		address = data[start:end]
		return filename, address, end + 1
	}

	return filename, address, i + 1
}

func (p *Parser) readInclude(from, file string, address []byte) []byte {
	if p.Opts.ReadIncludeFn != nil {
		return p.Opts.ReadIncludeFn(from, file, address)
	}

	return nil
}

// isCodeInclude parses <{{...}} which is similar to isInclude the returned bytes are, however wrapped in a code block.
func (p *Parser) isCodeInclude(data []byte) (filename string, address []byte, consumed int) {
	i := skipCharN(data, 0, ' ', 3) // start with up to 3 spaces
	if len(data[i:]) < 3 {
		return "", nil, 0
	}
	if data[i] != '<' {
		return "", nil, 0
	}
	start := i

	filename, address, consumed = p.isInclude(data[i+1:])
	if consumed == 0 {
		return "", nil, 0
	}
	return filename, address, start + consumed + 1
}

// readCodeInclude acts like include except the returned bytes are wrapped in a fenced code block.
func (p *Parser) readCodeInclude(from, file string, address []byte) []byte {
	data := p.readInclude(from, file, address)
	if data == nil {
		return nil
	}
	ext := path.Ext(file)
	buf := &bytes.Buffer{}
	buf.Write([]byte("```"))
	if ext != "" { // starts with a dot
		buf.WriteString(" " + ext[1:] + "\n")
	} else {
		buf.WriteByte('\n')
	}
	buf.Write(data)
	buf.WriteString("```\n")
	return buf.Bytes()
}

// incStack hold the current stack of chained includes. Each value is the containing
// path of the file being parsed.
type incStack struct {
	stack []string
}

func newIncStack() *incStack {
	return &incStack{stack: []string{}}
}

// Push updates i with new.
func (i *incStack) Push(new string) {
	if path.IsAbs(new) {
		i.stack = append(i.stack, path.Dir(new))
		return
	}
	last := ""
	if len(i.stack) > 0 {
		last = i.stack[len(i.stack)-1]
	}
	i.stack = append(i.stack, path.Dir(filepath.Join(last, new)))
}

// Pop pops the last value.
func (i *incStack) Pop() {
	if len(i.stack) == 0 {
		return
	}
	i.stack = i.stack[:len(i.stack)-1]
}

func (i *incStack) Last() string {
	if len(i.stack) == 0 {
		return ""
	}
	return i.stack[len(i.stack)-1]
}