inversion 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (78) hide show
  1. data.tar.gz.sig +2 -0
  2. data/.gemtest +0 -0
  3. data/ChangeLog +836 -0
  4. data/History.md +4 -0
  5. data/Manifest.txt +74 -0
  6. data/README.rdoc +171 -0
  7. data/Rakefile +55 -0
  8. data/bin/inversion +276 -0
  9. data/lib/inversion.rb +98 -0
  10. data/lib/inversion/exceptions.rb +21 -0
  11. data/lib/inversion/mixins.rb +236 -0
  12. data/lib/inversion/monkeypatches.rb +20 -0
  13. data/lib/inversion/renderstate.rb +337 -0
  14. data/lib/inversion/sinatra.rb +35 -0
  15. data/lib/inversion/template.rb +250 -0
  16. data/lib/inversion/template/attrtag.rb +120 -0
  17. data/lib/inversion/template/calltag.rb +16 -0
  18. data/lib/inversion/template/codetag.rb +164 -0
  19. data/lib/inversion/template/commenttag.rb +54 -0
  20. data/lib/inversion/template/conditionaltag.rb +49 -0
  21. data/lib/inversion/template/configtag.rb +60 -0
  22. data/lib/inversion/template/containertag.rb +45 -0
  23. data/lib/inversion/template/elsetag.rb +62 -0
  24. data/lib/inversion/template/elsiftag.rb +49 -0
  25. data/lib/inversion/template/endtag.rb +55 -0
  26. data/lib/inversion/template/escapetag.rb +26 -0
  27. data/lib/inversion/template/fortag.rb +120 -0
  28. data/lib/inversion/template/iftag.rb +69 -0
  29. data/lib/inversion/template/importtag.rb +70 -0
  30. data/lib/inversion/template/includetag.rb +51 -0
  31. data/lib/inversion/template/node.rb +102 -0
  32. data/lib/inversion/template/parser.rb +297 -0
  33. data/lib/inversion/template/pptag.rb +28 -0
  34. data/lib/inversion/template/publishtag.rb +72 -0
  35. data/lib/inversion/template/subscribetag.rb +88 -0
  36. data/lib/inversion/template/tag.rb +150 -0
  37. data/lib/inversion/template/textnode.rb +43 -0
  38. data/lib/inversion/template/unlesstag.rb +60 -0
  39. data/lib/inversion/template/uriencodetag.rb +30 -0
  40. data/lib/inversion/template/yieldtag.rb +51 -0
  41. data/lib/inversion/tilt.rb +82 -0
  42. data/lib/inversion/utils.rb +235 -0
  43. data/spec/data/sinatra/hello.inversion +1 -0
  44. data/spec/inversion/mixins_spec.rb +177 -0
  45. data/spec/inversion/monkeypatches_spec.rb +35 -0
  46. data/spec/inversion/renderstate_spec.rb +291 -0
  47. data/spec/inversion/sinatra_spec.rb +59 -0
  48. data/spec/inversion/template/attrtag_spec.rb +216 -0
  49. data/spec/inversion/template/calltag_spec.rb +30 -0
  50. data/spec/inversion/template/codetag_spec.rb +51 -0
  51. data/spec/inversion/template/commenttag_spec.rb +84 -0
  52. data/spec/inversion/template/configtag_spec.rb +105 -0
  53. data/spec/inversion/template/containertag_spec.rb +54 -0
  54. data/spec/inversion/template/elsetag_spec.rb +105 -0
  55. data/spec/inversion/template/elsiftag_spec.rb +87 -0
  56. data/spec/inversion/template/endtag_spec.rb +78 -0
  57. data/spec/inversion/template/escapetag_spec.rb +59 -0
  58. data/spec/inversion/template/fortag_spec.rb +98 -0
  59. data/spec/inversion/template/iftag_spec.rb +241 -0
  60. data/spec/inversion/template/importtag_spec.rb +106 -0
  61. data/spec/inversion/template/includetag_spec.rb +108 -0
  62. data/spec/inversion/template/node_spec.rb +81 -0
  63. data/spec/inversion/template/parser_spec.rb +170 -0
  64. data/spec/inversion/template/pptag_spec.rb +51 -0
  65. data/spec/inversion/template/publishtag_spec.rb +69 -0
  66. data/spec/inversion/template/subscribetag_spec.rb +60 -0
  67. data/spec/inversion/template/tag_spec.rb +97 -0
  68. data/spec/inversion/template/textnode_spec.rb +86 -0
  69. data/spec/inversion/template/unlesstag_spec.rb +84 -0
  70. data/spec/inversion/template/uriencodetag_spec.rb +49 -0
  71. data/spec/inversion/template/yieldtag_spec.rb +54 -0
  72. data/spec/inversion/template_spec.rb +269 -0
  73. data/spec/inversion/tilt_spec.rb +47 -0
  74. data/spec/inversion_spec.rb +95 -0
  75. data/spec/lib/constants.rb +9 -0
  76. data/spec/lib/helpers.rb +160 -0
  77. metadata +316 -0
  78. metadata.gz.sig +0 -0
@@ -0,0 +1,69 @@
1
+ #!/usr/bin/env ruby
2
+ # vim: set noet nosta sw=4 ts=4 :
3
+
4
+ require 'inversion/mixins'
5
+ require 'inversion/template/attrtag'
6
+ require 'inversion/template/containertag'
7
+ require 'inversion/template/conditionaltag'
8
+ require 'inversion/template/elsiftag'
9
+ require 'inversion/template/elsetag'
10
+
11
+ # Inversion 'if' tag.
12
+ #
13
+ # This tag causes a section of the template to be rendered only if its methodchain or attribute
14
+ # is a true value.
15
+ #
16
+ # == Syntax
17
+ #
18
+ # <?if attr ?>...<?end?>
19
+ # <?if obj.method ?>...<?end?>
20
+ #
21
+ class Inversion::Template::IfTag < Inversion::Template::AttrTag
22
+ include Inversion::Loggable,
23
+ Inversion::Template::ContainerTag,
24
+ Inversion::Template::ConditionalTag
25
+
26
+ # Inherits AttrTag's tag patterns
27
+
28
+ ### Render the tag's contents if the condition is true, or any else or elsif sections
29
+ ### if the condition isn't true.
30
+ def render( state )
31
+ self.enable_rendering if super
32
+ self.render_subnodes( state )
33
+ return nil
34
+ end
35
+
36
+
37
+ ### Render the tag's subnodes according to the tag's logical state.
38
+ def render_subnodes( renderstate )
39
+ self.log.debug "Rendering subnodes. Rendering initially %s" %
40
+ [ self.rendering_enabled? ? "enabled" : "disabled" ]
41
+
42
+ # walk the subtree, modifying the logic flags for else and elsif tags,
43
+ # and rendering nodes if rendering is enabled
44
+ self.subnodes.each do |node|
45
+ case node
46
+ when Inversion::Template::ElsifTag
47
+ self.log.debug " logic switch: %p..." % [ node ]
48
+ if !self.rendering_was_enabled? && node.render( renderstate )
49
+ self.enable_rendering
50
+ else
51
+ self.disable_rendering
52
+ end
53
+
54
+ when Inversion::Template::ElseTag
55
+ self.log.debug " logic switch: %p..." % [ node ]
56
+ if !self.rendering_was_enabled?
57
+ self.enable_rendering
58
+ else
59
+ self.disable_rendering
60
+ end
61
+
62
+ else
63
+ renderstate << node if self.rendering_enabled?
64
+ end
65
+ end
66
+ end
67
+
68
+ end # class Inversion::Template::IfTag
69
+
@@ -0,0 +1,70 @@
1
+ #!/usr/bin/env ruby
2
+ # vim: set noet nosta sw=4 ts=4 :
3
+
4
+ require 'inversion/template/tag'
5
+
6
+ # Inversion import tag.
7
+ #
8
+ # The import tag copies one or more attributes from an enclosing template so they
9
+ # only need to be set once.
10
+ #
11
+ # == Syntax
12
+ #
13
+ # <?import txn ?>
14
+ # <?import foo, bar ?>
15
+ #
16
+ class Inversion::Template::ImportTag < Inversion::Template::Tag
17
+
18
+ ### Create a new ImportTag with the given +name+, which should be a valid
19
+ ### Ruby identifier.
20
+ def initialize( body, linenum=nil, colnum=nil )
21
+ super
22
+ @attributes = body.split( /\s*,\s*/ ).collect {|name| name.untaint.strip.to_sym }
23
+ end
24
+
25
+
26
+ ######
27
+ public
28
+ ######
29
+
30
+ # the names of the attributes to import
31
+ attr_reader :attributes
32
+
33
+
34
+ ### Merge the inherited renderstate into the current template's +renderstate+.
35
+ def render( renderstate )
36
+ if (( cstate = renderstate.containerstate ))
37
+ self.log.debug "Importing inherited attributes: %p from %p" % [ @attributes, cstate.attributes ]
38
+
39
+ # Pick out the attributes that are being imported
40
+ inherited_attrs = @attributes.inject( {} ) do |attrs, key|
41
+ attrs[ key ] = cstate.attributes[ key ]
42
+ attrs
43
+ end
44
+
45
+ # Merge, but overwrite unset values with inherited ones
46
+ renderstate.attributes.merge!( inherited_attrs ) do |key, oldval, newval|
47
+ if oldval.nil?
48
+ self.log.debug "Importing attribute %p: %p" % [ key, newval ]
49
+ newval
50
+ else
51
+ self.log.debug "Not importing attribute %p: already set to %p" % [ key, oldval ]
52
+ oldval
53
+ end
54
+ end
55
+
56
+ else
57
+ self.log.debug "No-op import: no parent attributes set."
58
+ end
59
+
60
+ return nil
61
+ end
62
+
63
+
64
+ ### Render the tag as the body of a comment, suitable for template debugging.
65
+ def as_comment_body
66
+ return "Import %s" % [ self.attributes.join(', ') ]
67
+ end
68
+
69
+ end # class Inversion::Template::ImportTag
70
+
@@ -0,0 +1,51 @@
1
+ #!/usr/bin/env ruby
2
+ # vim: set noet nosta sw=4 ts=4 :
3
+
4
+ require 'pathname'
5
+ require 'inversion/mixins'
6
+ require 'inversion/template/tag'
7
+
8
+
9
+ # Inversion 'include' tag.
10
+ #
11
+ # A tag that inserts other template files into the current template.
12
+ #
13
+ # == Example
14
+ #
15
+ # <?include /an/absolute/path/to/a/different/template.tmpl ?>
16
+ # <?include a/relative/path/to/a/different/template.tmpl ?>
17
+ #
18
+ #
19
+ class Inversion::Template::IncludeTag < Inversion::Template::Tag
20
+ include Inversion::Loggable
21
+
22
+
23
+ ### Create a new IncludeTag with the specified +path+.
24
+ def initialize( path, linenum=nil, colnum=nil )
25
+ super
26
+ self.log.debug "Body is: %p" % [ @body ]
27
+ @path = @body
28
+ @included_template = nil
29
+ end
30
+
31
+
32
+ ######
33
+ public
34
+ ######
35
+
36
+ # The path of the included template
37
+ attr_reader :path
38
+
39
+
40
+ ### Parser callback -- Load the included template and check for recursive includes.
41
+ def before_appending( parsestate )
42
+ @included_template = parsestate.load_subtemplate( self.path )
43
+ end
44
+
45
+
46
+ def after_appending( parsestate )
47
+ parsestate.append_tree( @included_template.node_tree )
48
+ end
49
+
50
+ end # class Inversion::Template::IncludeTag
51
+
@@ -0,0 +1,102 @@
1
+ #!/usr/bin/env ruby
2
+ # vim: set noet nosta sw=4 ts=4 :
3
+
4
+ require 'inversion/mixins'
5
+ require 'inversion/template' unless defined?( Inversion::Template )
6
+
7
+ # Inversion template node base class. Template text is parsed by the
8
+ # Inversion::Template::Parser into nodes, and appended to a tree
9
+ # that is later walked when the template is rendered.
10
+ #
11
+ # This class is abstract; it just defines the API that other nodes
12
+ # are expected to implement.
13
+ class Inversion::Template::Node
14
+ include Inversion::AbstractClass
15
+
16
+
17
+ ### Create a new TextNode with the specified +source+.
18
+ def initialize( body, linenum=nil, colnum=nil )
19
+ @body = body
20
+ @linenum = linenum
21
+ @colnum = colnum
22
+ end
23
+
24
+
25
+ ######
26
+ public
27
+ ######
28
+
29
+ # The line number the node was parsed from in the template source (if known)
30
+ attr_reader :linenum
31
+
32
+ # The column number the node was parsed from in the template source (if known)
33
+ attr_reader :colnum
34
+
35
+
36
+ ### Render the node using the given +render_state+. By default, rendering a node
37
+ ### returns +nil+.
38
+ def render( render_state )
39
+ return nil
40
+ end
41
+
42
+
43
+ ### Render the node as a comment
44
+ def as_comment_body
45
+ return self.inspect
46
+ end
47
+
48
+
49
+ ### Returns +true+ if the node introduces a new parsing/rendering scope.
50
+ def is_container?
51
+ return false
52
+ end
53
+ alias_method :container?, :is_container?
54
+
55
+
56
+ ### Return the location of the tag in the template, if it was parsed from one (i.e.,
57
+ ### if it was created with a StringScanner)
58
+ def location
59
+ return "line %s, column %s" % [
60
+ self.linenum || '??',
61
+ self.colnum || '??',
62
+ ]
63
+ end
64
+
65
+
66
+ ### Default (no-op) implementation of the before_appending callback. This exists so defining
67
+ ### the append callbacks are optional for Node's subclasses.
68
+ def before_appending( state )
69
+ # Nothing to do
70
+ return nil
71
+ end
72
+ alias_method :before_append, :before_appending
73
+
74
+
75
+ ### Default (no-op) implementation of the after_appending callback. This exists so defining
76
+ ### the append callbacks are optional for Node's subclasses.
77
+ def after_appending( state )
78
+ # Nothing to do
79
+ return nil
80
+ end
81
+ alias_method :after_append, :after_appending
82
+
83
+
84
+ ### Default (no-op) implementation of the before_rendering callback. This exists so defining
85
+ ### the rendering callbacks are optional for Node's subclasses.
86
+ def before_rendering( state=nil )
87
+ # Nothing to do
88
+ return nil
89
+ end
90
+ alias_method :before_render, :before_rendering
91
+
92
+
93
+ ### Default (no-op) implementation of the after_rendering callback. This exists so defining
94
+ ### the rendering callbacks are optional for Node's subclasses.
95
+ def after_rendering( state=nil )
96
+ # Nothing to do
97
+ return nil
98
+ end
99
+ alias_method :after_render, :after_rendering
100
+
101
+ end # class Inversion::Template::Node
102
+
@@ -0,0 +1,297 @@
1
+ #!/usr/bin/env ruby
2
+ # encoding: utf-8
3
+ # vim: set noet nosta sw=4 ts=4 :
4
+
5
+ require 'inversion/template' unless defined?( Inversion::Template )
6
+ require 'inversion/mixins'
7
+ require 'inversion/template/textnode'
8
+ require 'inversion/template/tag'
9
+ require 'inversion/template/endtag'
10
+
11
+ # This is the parser for Inversion templates. It takes template source and
12
+ # returns a tree of Inversion::Template::Node objects (if parsing is successful).
13
+ class Inversion::Template::Parser
14
+ include Inversion::Loggable
15
+
16
+ # The pattern for matching a tag opening
17
+ TAG_OPEN = /[\[<]\?/
18
+
19
+ # The pattern for matching a tag.
20
+ TAG_PATTERN = %r{
21
+ (?<tagstart>#{TAG_OPEN}) # Tag opening: either <? or [?
22
+ (?<tagname>[a-z]\w*) # The name of the tag
23
+ (?:\s+ # At least once whitespace character between the tagname and body
24
+ (?<body>.+?) # The body of the tag
25
+ )?
26
+ \s*
27
+ (?<tagend>\?[\]>]) # Tag closing: either ?] or ?>
28
+ }x
29
+
30
+ # Valid tagends by tagstart
31
+ MATCHING_BRACKETS = {
32
+ '<?' => '?>',
33
+ '[?' => '?]',
34
+ }
35
+
36
+ # Default values for parser configuration options.
37
+ DEFAULT_OPTIONS = {
38
+ :ignore_unknown_tags => true,
39
+ }
40
+
41
+
42
+ ### Create a new Inversion::Template::Parser with the specified config +options+.
43
+ def initialize( template, options={} )
44
+ @template = template
45
+ @options = DEFAULT_OPTIONS.merge( options )
46
+ end
47
+
48
+
49
+ ######
50
+ public
51
+ ######
52
+
53
+ # The parser's config options
54
+ attr_reader :options
55
+
56
+
57
+ ### Parse the given +source+ into one or more Inversion::Template::Nodes and return
58
+ ### it as an Array.
59
+ def parse( source, inherited_state=nil )
60
+ state = nil
61
+
62
+ if inherited_state
63
+ inherited_state.template = @template
64
+ state = inherited_state
65
+ else
66
+ state = Inversion::Template::Parser::State.new( @template, self.options )
67
+ end
68
+
69
+ self.log.debug "Starting parse of template source (%0.2fK, %s)" %
70
+ [ source.bytesize/1024.0, source.encoding ]
71
+
72
+ last_pos = last_linenum = last_colnum = 0
73
+ source.scan( TAG_PATTERN ) do |*|
74
+ match = Regexp.last_match
75
+ start_pos, end_pos = match.offset( 0 )
76
+ linenum = match.pre_match.count( "\n" ) + 1
77
+ colnum = match.pre_match.length - (match.pre_match.rindex("\n") || -1)
78
+
79
+ # Error on <?...?] and vice-versa.
80
+ unless match[:tagend] == MATCHING_BRACKETS[match[:tagstart]]
81
+ raise Inversion::ParseError,
82
+ "malformed tag %p: mismatched start and end brackets at line %d, column %d" %
83
+ [ match[0], linenum, colnum ]
84
+ end
85
+
86
+ # Check for nested tags
87
+ if match[0].index( TAG_OPEN, 2 )
88
+ raise Inversion::ParseError, "unclosed or nested tag %p at line %d, column %d" %
89
+ [ match[0], linenum, colnum ]
90
+ end
91
+
92
+ self.log.debug " found a tag at offset: %d (%p) (line %d, col %d)" %
93
+ [ start_pos, abbrevstring(match[0]), linenum, colnum ]
94
+
95
+ # If there were characters between the end of the last match and
96
+ # the beginning of the tag, create a text node with them
97
+ unless last_pos == start_pos
98
+ text = match.pre_match[ last_pos..-1 ]
99
+ self.log.debug " adding literal text node: %p" % [ abbrevstring(text) ]
100
+ state << Inversion::Template::TextNode.new( text, last_linenum, last_colnum )
101
+ end
102
+
103
+ self.log.debug " creating tag with tagname: %p, body: %p" %
104
+ [ match[:tagname], match[:body] ]
105
+
106
+ tag = Inversion::Template::Tag.create( match[:tagname], match[:body], linenum, colnum )
107
+ if tag.nil?
108
+ unless state.options[ :ignore_unknown_tags ]
109
+ raise Inversion::ParseError, "Unknown tag %p at line %d, column %d" %
110
+ [ match[:tagname], linenum, colnum ]
111
+ end
112
+
113
+ tag = Inversion::Template::TextNode.new( match[0], linenum, colnum )
114
+ end
115
+
116
+ self.log.debug " created tag: %p" % [ tag ]
117
+ state << tag
118
+
119
+ # Keep offsets for the next match
120
+ last_pos = end_pos
121
+ last_linenum = linenum + match[0].count( "\n" )
122
+ last_colnum = match[0].length - ( match[0].rindex("\n") || -1 )
123
+ end
124
+
125
+ # If there are any characters left over after the last tag
126
+ remainder = source[ last_pos..-1 ]
127
+ if remainder && !remainder.empty?
128
+ self.log.debug "Remainder after last tag: %p" % [ abbrevstring(remainder) ]
129
+
130
+ # Detect unclosed tags
131
+ if remainder.index( "<?" ) || remainder.index( "[?" )
132
+ raise Inversion::ParseError,
133
+ "unclosed tag after line %d, column %d" % [ last_linenum, last_colnum ]
134
+ end
135
+
136
+ # Add any remaining text as a text node
137
+ state << Inversion::Template::TextNode.new( remainder, last_linenum, last_colnum )
138
+ end
139
+
140
+ return state.tree
141
+ end
142
+
143
+
144
+ #######
145
+ private
146
+ #######
147
+
148
+ ### Return at most +length+ characters long from the given +string+, appending +ellipsis+
149
+ ### at the end if it was truncated.
150
+ def abbrevstring( string, length=30, ellipsis='…' )
151
+ return string if string.length < length
152
+ length -= ellipsis.length
153
+ return string[ 0, length ] + ellipsis
154
+ end
155
+
156
+
157
+ # Parse state object class. State objects keep track of where in the parse tree
158
+ # new nodes should be appended, and manages inclusion.
159
+ class State
160
+ include Inversion::Loggable
161
+
162
+ ### Create a new State object
163
+ def initialize( template, options={} )
164
+ @template = template
165
+ @options = options.dup
166
+ @tree = []
167
+ @node_stack = [ @tree ]
168
+ @include_stack = []
169
+ end
170
+
171
+
172
+ ### Copy constructor -- duplicate inner structures.
173
+ def initialize_copy( original )
174
+ @template = original.template
175
+ @options = original.options.dup
176
+ @tree = @tree.map( &:dup )
177
+ @node_stack = [ @tree ]
178
+ @include_stack = original.include_stack.dup
179
+ end
180
+
181
+
182
+ ######
183
+ public
184
+ ######
185
+
186
+ # The parse options in effect for this parse state
187
+ attr_reader :options
188
+
189
+ # The template object for this parser state
190
+ attr_accessor :template
191
+
192
+ # The stack of templates that have been loaded for this state; for loop detection.
193
+ attr_reader :include_stack
194
+
195
+ # The stack of containers
196
+ attr_reader :node_stack
197
+
198
+
199
+ ### Append operator: add nodes to the correct part of the parse tree.
200
+ def <<( node )
201
+ self.log.debug "Appending %p" % [ node ]
202
+
203
+ node.before_appending( self )
204
+ self.node_stack.last << node
205
+
206
+ if node.is_container?
207
+ # Containers get pushed onto the stack so they get appended to
208
+ self.node_stack.push( node )
209
+ else
210
+ # Container nodes' #after_appending gets called in #pop
211
+ node.after_appending( self )
212
+ end
213
+
214
+ return self
215
+ rescue Inversion::ParseError => err
216
+ raise err, "%s at %s" % [ err.message, node.location ]
217
+ end
218
+
219
+
220
+ ### Append another Array of nodes onto this state's node tree.
221
+ def append_tree( newtree )
222
+ newtree.each do |node|
223
+ self.node_stack.last << node
224
+ end
225
+ end
226
+
227
+
228
+ ### Returns the tree if it's well formed.
229
+ def tree
230
+ unless self.is_well_formed?
231
+ raise Inversion::ParseError, "Unclosed container tag: %s, from %s" %
232
+ [ self.node_stack.last.tagname, self.node_stack.last.location ]
233
+ end
234
+ return @tree
235
+ end
236
+
237
+
238
+ ### Check to see if all open tags have been closed.
239
+ def is_well_formed?
240
+ return self.node_stack.length == 1
241
+ end
242
+ alias_method :well_formed?, :is_well_formed?
243
+
244
+
245
+ ### Pop one level off of the node stack and return it.
246
+ def pop
247
+ closed_node = self.node_stack.pop
248
+
249
+ # If there's nothing on the node stack, we've popped the top-level
250
+ # Array, which means there wasn't an opening container.
251
+ raise Inversion::ParseError, "unbalanced end: no open tag" if
252
+ self.node_stack.empty?
253
+
254
+ closed_node.after_appending( self )
255
+
256
+ return closed_node
257
+ end
258
+
259
+
260
+ ### Return the node that is currently being appended to, or +nil+ if there aren't any
261
+ ### opened container nodes.
262
+ def current_node
263
+ return self.node_stack.last
264
+ end
265
+
266
+
267
+ ### Clear any parsed nodes from the state, leaving the options and include_stack intact.
268
+ def clear_nodes
269
+ @tree = []
270
+ @node_stack = [ @tree ]
271
+ end
272
+
273
+
274
+ ### Load a subtemplate from the specified +path+, checking for recursive-dependency.
275
+ def load_subtemplate( path )
276
+ if self.include_stack.include?( path )
277
+ stack_desc = ( self.include_stack + [path] ).join( ' --> ' )
278
+ msg = "Recursive load of %p detected: from %s" % [ path, stack_desc ]
279
+
280
+ self.log.error( msg )
281
+ raise Inversion::StackError, msg
282
+ end
283
+
284
+ self.log.debug "Include stack is: %p" % [ self.include_stack ]
285
+
286
+ substate = self.dup
287
+ substate.clear_nodes
288
+ substate.include_stack.push( path )
289
+
290
+ return Inversion::Template.load( path, substate, self.options )
291
+ end
292
+
293
+ end # class Inversion::Template::Parser::State
294
+
295
+ end # class Inversion::Template::Parser
296
+
297
+