include.go

  1package parser
  2
  3import (
  4	"bytes"
  5	"path"
  6	"path/filepath"
  7)
  8
  9// isInclude parses {{...}}[...], that contains a path between the {{, the [...] syntax contains
 10// an address to select which lines to include. It is treated as an opaque string and just given
 11// to readInclude.
 12func (p *Parser) isInclude(data []byte) (filename string, address []byte, consumed int) {
 13	i := skipCharN(data, 0, ' ', 3) // start with up to 3 spaces
 14	if len(data[i:]) < 3 {
 15		return "", nil, 0
 16	}
 17	if data[i] != '{' || data[i+1] != '{' {
 18		return "", nil, 0
 19	}
 20	start := i + 2
 21
 22	// find the end delimiter
 23	i = skipUntilChar(data, i, '}')
 24	if i+1 >= len(data) {
 25		return "", nil, 0
 26	}
 27	end := i
 28	i++
 29	if data[i] != '}' {
 30		return "", nil, 0
 31	}
 32	filename = string(data[start:end])
 33
 34	if i+1 < len(data) && data[i+1] == '[' { // potential address specification
 35		start := i + 2
 36
 37		end = skipUntilChar(data, start, ']')
 38		if end >= len(data) {
 39			return "", nil, 0
 40		}
 41		address = data[start:end]
 42		return filename, address, end + 1
 43	}
 44
 45	return filename, address, i + 1
 46}
 47
 48func (p *Parser) readInclude(from, file string, address []byte) []byte {
 49	if p.Opts.ReadIncludeFn != nil {
 50		return p.Opts.ReadIncludeFn(from, file, address)
 51	}
 52
 53	return nil
 54}
 55
 56// isCodeInclude parses <{{...}} which is similar to isInclude the returned bytes are, however wrapped in a code block.
 57func (p *Parser) isCodeInclude(data []byte) (filename string, address []byte, consumed int) {
 58	i := skipCharN(data, 0, ' ', 3) // start with up to 3 spaces
 59	if len(data[i:]) < 3 {
 60		return "", nil, 0
 61	}
 62	if data[i] != '<' {
 63		return "", nil, 0
 64	}
 65	start := i
 66
 67	filename, address, consumed = p.isInclude(data[i+1:])
 68	if consumed == 0 {
 69		return "", nil, 0
 70	}
 71	return filename, address, start + consumed + 1
 72}
 73
 74// readCodeInclude acts like include except the returned bytes are wrapped in a fenced code block.
 75func (p *Parser) readCodeInclude(from, file string, address []byte) []byte {
 76	data := p.readInclude(from, file, address)
 77	if data == nil {
 78		return nil
 79	}
 80	ext := path.Ext(file)
 81	buf := &bytes.Buffer{}
 82	buf.Write([]byte("```"))
 83	if ext != "" { // starts with a dot
 84		buf.WriteString(" " + ext[1:] + "\n")
 85	} else {
 86		buf.WriteByte('\n')
 87	}
 88	buf.Write(data)
 89	buf.WriteString("```\n")
 90	return buf.Bytes()
 91}
 92
 93// incStack hold the current stack of chained includes. Each value is the containing
 94// path of the file being parsed.
 95type incStack struct {
 96	stack []string
 97}
 98
 99func newIncStack() *incStack {
100	return &incStack{stack: []string{}}
101}
102
103// Push updates i with new.
104func (i *incStack) Push(new string) {
105	if path.IsAbs(new) {
106		i.stack = append(i.stack, path.Dir(new))
107		return
108	}
109	last := ""
110	if len(i.stack) > 0 {
111		last = i.stack[len(i.stack)-1]
112	}
113	i.stack = append(i.stack, path.Dir(filepath.Join(last, new)))
114}
115
116// Pop pops the last value.
117func (i *incStack) Pop() {
118	if len(i.stack) == 0 {
119		return
120	}
121	i.stack = i.stack[:len(i.stack)-1]
122}
123
124func (i *incStack) Last() string {
125	if len(i.stack) == 0 {
126		return ""
127	}
128	return i.stack[len(i.stack)-1]
129}