You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

keyword.go 4.7KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184
  1. package org
  2. import (
  3. "bytes"
  4. "path/filepath"
  5. "regexp"
  6. "strings"
  7. )
  8. type Comment struct{ Content string }
  9. type Keyword struct {
  10. Key string
  11. Value string
  12. }
  13. type NodeWithName struct {
  14. Name string
  15. Node Node
  16. }
  17. type NodeWithMeta struct {
  18. Node Node
  19. Meta Metadata
  20. }
  21. type Metadata struct {
  22. Caption [][]Node
  23. HTMLAttributes [][]string
  24. }
  25. type Include struct {
  26. Keyword
  27. Resolve func() Node
  28. }
  29. var keywordRegexp = regexp.MustCompile(`^(\s*)#\+([^:]+):(\s+(.*)|$)`)
  30. var commentRegexp = regexp.MustCompile(`^(\s*)#(.*)`)
  31. var includeFileRegexp = regexp.MustCompile(`(?i)^"([^"]+)" (src|example|export) (\w+)$`)
  32. var attributeRegexp = regexp.MustCompile(`(?:^|\s+)(:[-\w]+)\s+(.*)$`)
  33. func lexKeywordOrComment(line string) (token, bool) {
  34. if m := keywordRegexp.FindStringSubmatch(line); m != nil {
  35. return token{"keyword", len(m[1]), m[2], m}, true
  36. } else if m := commentRegexp.FindStringSubmatch(line); m != nil {
  37. return token{"comment", len(m[1]), m[2], m}, true
  38. }
  39. return nilToken, false
  40. }
  41. func (d *Document) parseComment(i int, stop stopFn) (int, Node) {
  42. return 1, Comment{d.tokens[i].content}
  43. }
  44. func (d *Document) parseKeyword(i int, stop stopFn) (int, Node) {
  45. k := parseKeyword(d.tokens[i])
  46. switch k.Key {
  47. case "NAME":
  48. return d.parseNodeWithName(k, i, stop)
  49. case "SETUPFILE":
  50. return d.loadSetupFile(k)
  51. case "INCLUDE":
  52. return d.parseInclude(k)
  53. case "CAPTION", "ATTR_HTML":
  54. consumed, node := d.parseAffiliated(i, stop)
  55. if consumed != 0 {
  56. return consumed, node
  57. }
  58. fallthrough
  59. default:
  60. if _, ok := d.BufferSettings[k.Key]; ok {
  61. d.BufferSettings[k.Key] = strings.Join([]string{d.BufferSettings[k.Key], k.Value}, "\n")
  62. } else {
  63. d.BufferSettings[k.Key] = k.Value
  64. }
  65. return 1, k
  66. }
  67. }
  68. func (d *Document) parseNodeWithName(k Keyword, i int, stop stopFn) (int, Node) {
  69. if stop(d, i+1) {
  70. return 0, nil
  71. }
  72. consumed, node := d.parseOne(i+1, stop)
  73. if consumed == 0 || node == nil {
  74. return 0, nil
  75. }
  76. d.NamedNodes[k.Value] = node
  77. return consumed + 1, NodeWithName{k.Value, node}
  78. }
  79. func (d *Document) parseAffiliated(i int, stop stopFn) (int, Node) {
  80. start, meta := i, Metadata{}
  81. for ; !stop(d, i) && d.tokens[i].kind == "keyword"; i++ {
  82. switch k := parseKeyword(d.tokens[i]); k.Key {
  83. case "CAPTION":
  84. meta.Caption = append(meta.Caption, d.parseInline(k.Value))
  85. case "ATTR_HTML":
  86. attributes, rest := []string{}, k.Value
  87. for {
  88. if k, m := "", attributeRegexp.FindStringSubmatch(rest); m != nil {
  89. k, rest = m[1], m[2]
  90. attributes = append(attributes, k)
  91. if v, m := "", attributeRegexp.FindStringSubmatchIndex(rest); m != nil {
  92. v, rest = rest[:m[0]], rest[m[0]:]
  93. attributes = append(attributes, v)
  94. } else {
  95. attributes = append(attributes, strings.TrimSpace(rest))
  96. break
  97. }
  98. } else {
  99. break
  100. }
  101. }
  102. meta.HTMLAttributes = append(meta.HTMLAttributes, attributes)
  103. default:
  104. return 0, nil
  105. }
  106. }
  107. if stop(d, i) {
  108. return 0, nil
  109. }
  110. consumed, node := d.parseOne(i, stop)
  111. if consumed == 0 || node == nil {
  112. return 0, nil
  113. }
  114. i += consumed
  115. return i - start, NodeWithMeta{node, meta}
  116. }
  117. func parseKeyword(t token) Keyword {
  118. k, v := t.matches[2], t.matches[4]
  119. return Keyword{strings.ToUpper(k), strings.TrimSpace(v)}
  120. }
  121. func (d *Document) parseInclude(k Keyword) (int, Node) {
  122. resolve := func() Node {
  123. d.Log.Printf("Bad include %#v", k)
  124. return k
  125. }
  126. if m := includeFileRegexp.FindStringSubmatch(k.Value); m != nil {
  127. path, kind, lang := m[1], m[2], m[3]
  128. if !filepath.IsAbs(path) {
  129. path = filepath.Join(filepath.Dir(d.Path), path)
  130. }
  131. resolve = func() Node {
  132. bs, err := d.ReadFile(path)
  133. if err != nil {
  134. d.Log.Printf("Bad include %#v: %s", k, err)
  135. return k
  136. }
  137. return Block{strings.ToUpper(kind), []string{lang}, d.parseRawInline(string(bs))}
  138. }
  139. }
  140. return 1, Include{k, resolve}
  141. }
  142. func (d *Document) loadSetupFile(k Keyword) (int, Node) {
  143. path := k.Value
  144. if !filepath.IsAbs(path) {
  145. path = filepath.Join(filepath.Dir(d.Path), path)
  146. }
  147. bs, err := d.ReadFile(path)
  148. if err != nil {
  149. d.Log.Printf("Bad setup file: %#v: %s", k, err)
  150. return 1, k
  151. }
  152. setupDocument := d.Configuration.Parse(bytes.NewReader(bs), path)
  153. if err := setupDocument.Error; err != nil {
  154. d.Log.Printf("Bad setup file: %#v: %s", k, err)
  155. return 1, k
  156. }
  157. for k, v := range setupDocument.BufferSettings {
  158. d.BufferSettings[k] = v
  159. }
  160. return 1, k
  161. }
  162. func (n Comment) String() string { return orgWriter.WriteNodesAsString(n) }
  163. func (n Keyword) String() string { return orgWriter.WriteNodesAsString(n) }
  164. func (n NodeWithMeta) String() string { return orgWriter.WriteNodesAsString(n) }
  165. func (n NodeWithName) String() string { return orgWriter.WriteNodesAsString(n) }
  166. func (n Include) String() string { return orgWriter.WriteNodesAsString(n) }