PageRenderTime 48ms CodeModel.GetById 21ms RepoModel.GetById 1ms app.codeStats 0ms

/src/stencil/ast.clj

http://github.com/davidsantiago/stencil
Clojure | 122 lines | 80 code | 12 blank | 30 comment | 6 complexity | 2bb607abe3395fc4ba02eafdc22cb599 MD5 | raw file
Possible License(s): EPL-1.0
  1. (ns stencil.ast
  2. (:refer-clojure :exclude [partial])
  3. (:require [clojure.zip :as zip]
  4. [clojure.string :as string])
  5. (:use stencil.utils))
  6. ;;
  7. ;; Data structures
  8. ;;
  9. (defprotocol ASTZipper
  10. (branch? [this] "Returns true if this node can possibly have children,
  11. whether it currently does or not.")
  12. (children [this] "When called on a branch node, returns its children.")
  13. (make-node [this children] "Given a node (potentially with existing children)
  14. and a seq of children that should totally replace
  15. the existing children, make the new node."))
  16. (defprotocol ASTNode
  17. (render [this ^StringBuilder sb context-stack]
  18. "Given a StringBuilder and the current context-stack, render this node to
  19. the result string in the StringBuilder."))
  20. ;; Section and InvertedSection need to keep track of the raw source code of
  21. ;; their contents, since lambdas need access to that. The attrs field lets them
  22. ;; keep track of that, with fields
  23. ;; - content-start : position in source string of content start
  24. ;; - content-end : position in source string of end of content
  25. ;; - content : string holding the raw content
  26. (defrecord Section [name attrs contents]
  27. ASTZipper
  28. (branch? [this] true)
  29. (children [this] contents)
  30. (make-node [this children] (Section. name attrs (vec children))))
  31. ;; ASTNode IS implemented, but not here. To avoid Clojure's circular
  32. ;; dependency inadequacies, we have to implement ASTNode at the top of
  33. ;; core.clj.
  34. (defn section [name attrs contents]
  35. (Section. name attrs contents))
  36. (defrecord InvertedSection [name attrs contents]
  37. ASTZipper
  38. (branch? [this] true)
  39. (children [this] contents)
  40. (make-node [this children] (InvertedSection. name attrs (vec children)))
  41. ASTNode
  42. (render [this sb context-stack]
  43. ;; Only render the section if the value is not present, false, or
  44. ;; an empty list.
  45. (let [ctx (first context-stack)
  46. ctx-val (context-get context-stack name)]
  47. ;; Per the spec, a function is truthy, so we should not render.
  48. (if (and (not (instance? clojure.lang.Fn ctx-val))
  49. (or (not ctx-val)
  50. (and (sequential? ctx-val)
  51. (empty? ctx-val))))
  52. (render contents sb context-stack)))))
  53. (defn inverted-section [name attrs contents]
  54. (InvertedSection. name attrs contents))
  55. ;; Partials can be obligated to indent the entire contents of the sub-template's
  56. ;; output, so we hold on to any padding here and apply it after the sub-
  57. ;; template renders.
  58. (defrecord Partial [name padding]
  59. ASTZipper
  60. (branch? [this] false)
  61. (children [this] nil)
  62. (make-node [this children] nil))
  63. ;; ASTNode IS implemented, but not here. To avoid Clojure's circular
  64. ;; dependency inadequacies, we have to implement ASTNode at the end of
  65. ;; loader.clj.
  66. (defn partial [name padding] (Partial. name padding))
  67. (defrecord EscapedVariable [name]
  68. ASTZipper
  69. (branch? [this] false)
  70. (children [this] nil)
  71. (make-node [this children] nil))
  72. ;; ASTNode IS implemented, but not here. To avoid Clojure's circular
  73. ;; dependency inadequacies, we have to implement ASTNode at the top of
  74. ;; core.clj.
  75. (defn escaped-variable [name] (EscapedVariable. name))
  76. (defrecord UnescapedVariable [name]
  77. ASTZipper
  78. (branch? [this] false)
  79. (children [this] nil)
  80. (make-node [this children] nil))
  81. ;; ASTNode IS implemented, but not here. To avoid Clojure's circular
  82. ;; dependency inadequacies, we have to implement ASTNode at the top of
  83. ;; core.clj.
  84. (defn unescaped-variable [name] (UnescapedVariable. name))
  85. (extend-protocol ASTZipper
  86. ;; Want to be able to just stick Strings in the AST.
  87. java.lang.String
  88. (branch? [this] false)
  89. (children [this] nil)
  90. (make-node [this children] nil)
  91. ;; Want to be able to use vectors to create lists in the AST.
  92. clojure.lang.PersistentVector
  93. (branch? [this] true)
  94. (children [this] this)
  95. (make-node [this children] (vec children)))
  96. (extend-protocol ASTNode
  97. java.lang.String
  98. (render [this ^StringBuilder sb context-stack] (.append sb this))
  99. clojure.lang.PersistentVector
  100. (render [this sb context-stack]
  101. (dotimes [i (count this)]
  102. (render (nth this i) sb context-stack))))
  103. ;; Implement a Zipper over ASTZippers.
  104. (defn ast-zip
  105. "Returns a zipper for ASTZippers, given a root ASTZipper."
  106. [root]
  107. (zip/zipper branch?
  108. children
  109. make-node
  110. root))