include.go 2.9 KB

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