inversion 0.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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,35 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ begin
4
+ require 'inversion/tilt'
5
+ require 'sinatra'
6
+ require 'sinatra/base'
7
+ rescue LoadError => err
8
+ warn "Couldn't load Inversion Sinatra support: %s: %s" % [ err.class.name, err.message ]
9
+ end
10
+
11
+ # Add support for Tilt (https://github.com/rtomayko/tilt) if it's already been loaded.
12
+ if defined?( ::Sinatra ) # :nodoc:
13
+
14
+ # A mixin to add Inversion support to Sinatra::Base
15
+ module Inversion::SinatraTemplateHelpers
16
+
17
+ ### Add an 'inversion' helper method to Sinatra's template DSL:
18
+ ###
19
+ ### get '/' do
20
+ ### inversion :company_directory, :locals => { :people => People.all }
21
+ ### end
22
+ def inversion( template, options={}, locals={} )
23
+ render :inversion, template, options, locals
24
+ end
25
+
26
+ end # Inversion::SinatraTemplateHelpers
27
+
28
+ # Inject Inversion helpers as a mixin
29
+ # :stopdoc:
30
+ class Sinatra::Base
31
+ include Inversion::SinatraTemplateHelpers
32
+ end
33
+
34
+ end
35
+
@@ -0,0 +1,250 @@
1
+ #!/usr/bin/env ruby
2
+ # vim: set noet nosta sw=4 ts=4 :
3
+
4
+ require 'pathname'
5
+ require 'inversion' unless defined?( Inversion )
6
+
7
+ # Load the Configurability library if it's installed
8
+ begin
9
+ require 'configurability'
10
+ require 'configurability/config'
11
+ rescue LoadError
12
+ end
13
+
14
+
15
+ # The main template class. Instances of this class are created by parsing template
16
+ # source and combining the resulting node tree with a set of attributes that
17
+ # can be used to populate it when rendered.
18
+ class Inversion::Template
19
+ include Inversion::Loggable
20
+
21
+ # Configurability support -- load template configuration from the 'templates' section
22
+ # of the config.
23
+ if defined?( Configurability )
24
+ extend Configurability
25
+ config_key :templates if respond_to?( :config_key )
26
+ end
27
+
28
+
29
+ # Load subordinate classes
30
+ require 'inversion/template/parser'
31
+ require 'inversion/template/node'
32
+ require 'inversion/template/tag'
33
+ require 'inversion/renderstate'
34
+
35
+
36
+ # Valid actions for 'on_render_error'
37
+ VALID_ERROR_ACTIONS = [
38
+ :ignore,
39
+ :comment,
40
+ :propagate,
41
+ ]
42
+
43
+ ### Default config values
44
+ DEFAULT_CONFIG = {
45
+ :ignore_unknown_tags => true,
46
+ :on_render_error => :comment,
47
+ :debugging_comments => false,
48
+ :comment_start => '<!-- ',
49
+ :comment_end => ' -->',
50
+ :template_paths => [],
51
+ :escape_format => :html,
52
+ :strip_tag_lines => true,
53
+ }
54
+
55
+
56
+ ### Global config
57
+ @config = DEFAULT_CONFIG.dup
58
+ class << self; attr_accessor :config; end
59
+
60
+
61
+ ### Configure the templating system.
62
+ def self::configure( config )
63
+ Inversion.log.debug "Merging config %p with current config %p" % [ config, self.config ]
64
+ self.config = self.config.merge( config )
65
+ end
66
+
67
+
68
+ ### Read a template object from the specified +path+.
69
+ def self::load( path, parsestate=nil, opts={} )
70
+
71
+ # Shift the options hash over if there isn't a parse state
72
+ if parsestate.is_a?( Hash )
73
+ opts = parsestate
74
+ parsestate = nil
75
+ end
76
+
77
+ tmpl = nil
78
+ path = Pathname( path )
79
+ template_paths = self.config[:template_paths] + [ Dir.pwd ]
80
+
81
+ # Unrestricted template location.
82
+ if path.absolute?
83
+ tmpl = path
84
+
85
+ # Template files searched under paths specified in 'template_paths', then
86
+ # the current working directory. First match wins.
87
+ else
88
+ tmpl = template_paths.collect {|dir| Pathname(dir) + path }.find do |fullpath|
89
+ fullpath.exist?
90
+ end
91
+
92
+ raise RuntimeError, "Unable to find template %p within configured paths %p" %
93
+ [ path.to_s, template_paths ] if tmpl.nil?
94
+ end
95
+
96
+ # We trust files read from disk
97
+ source = tmpl.read
98
+ source.untaint
99
+
100
+ # Load the instance and set the path to the source
101
+ template = self.new( source, parsestate, opts )
102
+ template.source_file = tmpl.expand_path
103
+
104
+ return template
105
+ end
106
+
107
+
108
+ ### Create a new Inversion:Template with the given +source+.
109
+ def initialize( source, parsestate=nil, opts={} )
110
+ if parsestate.is_a?( Hash )
111
+ self.log.debug "Shifting template options: %p" % [ parsestate ]
112
+ opts = parsestate
113
+ parsestate = nil
114
+ else
115
+ self.log.debug "Parse state is: %p" % [ parsestate ]
116
+ end
117
+
118
+ @source = source
119
+ @parser = Inversion::Template::Parser.new( self, opts )
120
+ @node_tree = []
121
+ @options = self.class.config.merge( opts )
122
+
123
+ @attributes = {}
124
+ @source_file = nil
125
+
126
+ # Now parse the template source into a tree of nodes and pre-generate accessors
127
+ # for any attributes defined by them
128
+ @node_tree = @parser.parse( source, parsestate )
129
+ self.define_attribute_accessors
130
+ end
131
+
132
+
133
+
134
+ ######
135
+ public
136
+ ######
137
+
138
+ attr_reader :source
139
+
140
+ attr_accessor :source_file
141
+
142
+ attr_reader :attributes
143
+
144
+ attr_reader :options
145
+
146
+ attr_reader :node_tree
147
+
148
+
149
+ ### Render the template, optionally passing a render state (if, for example, the
150
+ ### template is being rendered inside another template).
151
+ def render( parentstate=nil, &block )
152
+ self.log.info "rendering template 0x%08x" % [ self.object_id/2 ]
153
+ state = Inversion::RenderState.new( parentstate, self.attributes, self.options, &block )
154
+
155
+ # Pre-render hook
156
+ self.walk_tree {|node| node.before_rendering(state) }
157
+
158
+ self.log.debug " rendering node tree: %p" % [ @node_tree ]
159
+ self.walk_tree {|node| state << node }
160
+
161
+ # Post-render hook
162
+ self.walk_tree {|node| node.after_rendering(state) }
163
+
164
+ self.log.info " done rendering template 0x%08x" % [ self.object_id/2 ]
165
+ return state.to_s
166
+ end
167
+ alias_method :to_s, :render
168
+
169
+
170
+ ### Return a human-readable representation of the template object suitable
171
+ ### for debugging.
172
+ def inspect
173
+ return "#<%s:%08x (loaded from %s) attributes: %p, node_tree: %p, options: %p>" % [
174
+ self.class.name,
175
+ self.object_id / 2,
176
+ self.source_file ? self.source_file : "memory",
177
+ self.attributes,
178
+ self.node_tree.map(&:as_comment_body),
179
+ self.options,
180
+ ]
181
+ end
182
+
183
+
184
+ #########
185
+ protected
186
+ #########
187
+
188
+ ### Proxy method: handle attribute readers/writers for attributes that aren't yet
189
+ ### defined.
190
+ def method_missing( sym, *args, &block )
191
+ return super unless sym.to_s =~ /^([a-z]\w+)=?$/i
192
+ attribute = $1
193
+ self.install_accessors( attribute )
194
+
195
+ # Call the new method via #method to avoid a method_missing loop.
196
+ return self.method( sym ).call( *args, &block )
197
+ end
198
+
199
+
200
+ ### Walk the template's node tree, yielding each node in turn to the given block.
201
+ def walk_tree( nodes=@node_tree, &block )
202
+ nodes.each do |node|
203
+ yield( node )
204
+ end
205
+ end
206
+
207
+
208
+ ### Search for identifiers in the template's node tree and declare an accessor
209
+ ### for each one that's found.
210
+ def define_attribute_accessors
211
+ self.walk_tree do |node|
212
+ self.add_attributes_from_node( node )
213
+ end
214
+
215
+ self.attributes.each do |key, _|
216
+ self.install_accessors( key )
217
+ end
218
+ end
219
+
220
+
221
+ ### Add attributes for the given +node+'s identifiers.
222
+ def add_attributes_from_node( node )
223
+ if node.respond_to?( :identifiers )
224
+ node.identifiers.each do |id|
225
+ next if @attributes.key?( id.to_sym )
226
+ @attributes[ id.to_sym ] = nil
227
+ end
228
+ end
229
+ end
230
+
231
+
232
+ ### Install reader and writer methods for the attribute associated with the specified +key+.
233
+ def install_accessors( key )
234
+ reader, writer = self.make_attribute_accessors( key )
235
+
236
+ self.singleton_class.send( :define_method, key, &reader )
237
+ self.singleton_class.send( :define_method, "#{key}=", &writer )
238
+ end
239
+
240
+
241
+ ### Make method bodies
242
+ def make_attribute_accessors( key )
243
+ key = key.to_sym
244
+ reader = lambda { self.attributes[key] }
245
+ writer = lambda {|newval| self.attributes[key] = newval }
246
+
247
+ return reader, writer
248
+ end
249
+ end # class Inversion::Template
250
+
@@ -0,0 +1,120 @@
1
+ #!/usr/bin/env ruby
2
+ # vim: set noet nosta sw=4 ts=4 :
3
+
4
+ require 'inversion/template/codetag'
5
+
6
+ # Inversion attribute tag.
7
+ #
8
+ # Attribute tags add an accessor to a template like 'attr_accessor' does for Ruby classes.
9
+ #
10
+ # == Syntax
11
+ #
12
+ # <?attr foo ?>
13
+ # <?attr "%0.2f" % foo ?>
14
+ #
15
+ class Inversion::Template::AttrTag < Inversion::Template::CodeTag
16
+
17
+ # <?attr foo ?>
18
+ tag_pattern '$(ident)' do |tag, match|
19
+ tag.send( :log ).debug " Identifier is: %p" % [ match.string(1) ]
20
+ tag.name = match.string( 1 ).untaint.to_sym
21
+ end
22
+
23
+ # <?attr "%s" % foo ?>
24
+ tag_pattern 'tstring_beg $(tstring_content) tstring_end sp* $(op) sp* $(ident)' do |tag, match|
25
+ op = match.string( 2 )
26
+ raise Inversion::ParseError, "expected '%%', got %p instead" % [ op ] unless op == '%'
27
+
28
+ tag.format = match.string( 1 )
29
+ tag.name = match.string( 3 ).untaint.to_sym
30
+ end
31
+
32
+ # <?attr foo.methodchain ?>
33
+ tag_pattern '$(ident) $( .+ )' do |tag, match|
34
+ tag.name = match.string( 1 ).untaint.to_sym
35
+ tag.methodchain = match.string( 2 )
36
+ end
37
+
38
+ # <?attr "%s" % foo.methodchain ?>
39
+ tag_pattern 'tstring_beg $(tstring_content) tstring_end sp* $(op) sp* $(ident) $( .+ )' do |tag, match|
40
+ op = match.string( 2 )
41
+ raise Inversion::ParseError, "expected '%%', got %p instead" % [ op ] unless op == '%'
42
+
43
+ tag.format = match.string( 1 )
44
+ tag.name = match.string( 3 ).untaint.to_sym
45
+ tag.methodchain = match.string( 4 )
46
+ end
47
+
48
+
49
+
50
+ ### Create a new AttrTag with the given +name+, which should be a valid
51
+ ### Ruby identifier. The +linenum+ and +colnum+ should be the line and column of
52
+ ### the tag in the template source, if available.
53
+ def initialize( body, linenum=nil, colnum=nil )
54
+ @name = nil
55
+ @format = nil
56
+ @methodchain = nil
57
+
58
+ super
59
+
60
+ # Add an identifier for the tag name
61
+ self.identifiers << self.name.untaint.to_sym
62
+ end
63
+
64
+
65
+ ######
66
+ public
67
+ ######
68
+
69
+ # the name of the attribute
70
+ attr_accessor :name
71
+
72
+ # the format string used to format the attribute in the template (if
73
+ # one was declared)
74
+ attr_accessor :format
75
+
76
+ # the chain of methods that should be called (if any).
77
+ attr_accessor :methodchain
78
+
79
+
80
+ ### Render the tag attributes of the specified +render_state+ and return them.
81
+ def render( render_state )
82
+ self.log.debug "Rendering %p with state: %p" % [ self, render_state ]
83
+
84
+ value = nil
85
+ attribute = render_state.attributes[ self.name.to_sym ]
86
+ self.log.debug " initial attribute: %p" % [ attribute ]
87
+
88
+ # Evaluate the method chain (if there is one) against the attribute
89
+ if self.methodchain
90
+ methodchain = "self" + self.methodchain
91
+ self.log.debug " evaling methodchain: %p on: %p" % [ methodchain, attribute ]
92
+ value = attribute.instance_eval( methodchain )
93
+ else
94
+ value = attribute
95
+ end
96
+ self.log.debug " evaluated value: %p" % [ value ]
97
+
98
+ return value unless value
99
+
100
+ # Apply the format if there is one
101
+ if self.format
102
+ return self.format % value
103
+ else
104
+ return value
105
+ end
106
+ end
107
+
108
+
109
+ ### Render the tag as the body of a comment, suitable for template debugging.
110
+ def as_comment_body
111
+ comment = "%s: { template.%s" % [ self.tagname, self.name ]
112
+ comment << self.methodchain if self.methodchain
113
+ comment << " }"
114
+ comment << " with format: %p" % [ self.format ] if self.format
115
+
116
+ return comment
117
+ end
118
+
119
+ end # class Inversion::Template::AttrTag
120
+
@@ -0,0 +1,16 @@
1
+ #!/usr/bin/env ruby
2
+ # vim: set noet nosta sw=4 ts=4 :
3
+
4
+ require 'inversion/template/attrtag'
5
+
6
+ # Inversion call tag.
7
+ #
8
+ # This just exists to make 'call' an alias for 'attr'.
9
+ #
10
+ # == Syntax
11
+ #
12
+ # <?call foo.bar ?>
13
+ # <?call "%0.2f" % foo.bar ?>
14
+
15
+ class Inversion::Template::CallTag < Inversion::Template::AttrTag; end
16
+
@@ -0,0 +1,164 @@
1
+ #!/usr/bin/env ruby
2
+ # vim: set noet nosta sw=4 ts=4 :
3
+
4
+ require 'ripper'
5
+ require 'inversion/template/tag'
6
+
7
+ # The base class for Inversion tags that parse the body section of the tag using
8
+ # a Ruby parser.
9
+ #
10
+ # It provides a +tag_pattern+ declarative method that is used to specify a pattern of
11
+ # tokens to match, and a block for handling tag instances that match the pattern.
12
+ #
13
+ # class Inversion::Template::MyTag < Inversion::Template::CodeTag
14
+ #
15
+ # # Match a tag that looks like: <?my "string of stuff" ?>
16
+ # tag_pattern 'tstring_beg $(tstring_content) tstring_end' do |tag, match|
17
+ # tag.string = match.string( 1 )
18
+ # end
19
+ #
20
+ # end
21
+ #
22
+ # The tokens in the +tag_pattern+ are Ruby token names used by the parser. If you're creating
23
+ # your own tag, you can dump the tokens for a particular snippet using the 'inversion'
24
+ # command-line tool that comes with the gem:
25
+ #
26
+ # $ inversion tagtokens 'attr.dump! {|thing| thing.length }'
27
+ # ident<"attr"> period<"."> ident<"dump!"> sp<" "> lbrace<"{"> op<"|"> \
28
+ # ident<"thing"> op<"|"> sp<" "> ident<"thing"> period<"."> \
29
+ # ident<"length"> sp<" "> rbrace<"}">
30
+ #
31
+ # :TODO: Finish the tag_pattern docs: placeholders, regex limitations, etc.
32
+ #
33
+ class Inversion::Template::CodeTag < Inversion::Template::Tag
34
+ include Inversion::Loggable,
35
+ Inversion::AbstractClass
36
+
37
+
38
+ ### A subclass of Ripper::TokenPattern that binds matches to the beginning and
39
+ ### end of the matched string.
40
+ class TokenPattern < Ripper::TokenPattern
41
+
42
+ # the token pattern's source string
43
+ attr_reader :source
44
+
45
+ #########
46
+ protected
47
+ #########
48
+
49
+ ### Compile the token pattern into a Regexp
50
+ def compile( pattern )
51
+ if m = /[^\w\s$()\[\]{}?*+\.]/.match( pattern )
52
+ raise Ripper::TokenPattern::CompileError,
53
+ "invalid char in pattern: #{m[0].inspect}"
54
+ end
55
+
56
+ buf = '^'
57
+ pattern.scan( /(?:\w+|\$\(|[()\[\]\{\}?*+\.]+)/ ) do |tok|
58
+ case tok
59
+ when /\w/
60
+ buf << map_token( tok )
61
+ when '$('
62
+ buf << '('
63
+ when '('
64
+ buf << '(?:'
65
+ when /[?*\[\])\.]/
66
+ buf << tok
67
+ else
68
+ raise ScriptError, "invalid token in pattern: %p" % [ tok ]
69
+ end
70
+ end
71
+ buf << '$'
72
+
73
+ Regexp.compile( buf )
74
+ rescue RegexpError => err
75
+ raise Ripper::TokenPattern::CompileError, err.message
76
+ end
77
+
78
+ end # class TokenPattern
79
+
80
+
81
+ #################################################################
82
+ ### C L A S S M E T H O D S
83
+ #################################################################
84
+
85
+ ### Return the tag patterns for this class, or those of its superclass
86
+ ### if it doesn't override them.
87
+ def self::tag_patterns
88
+ return @tag_patterns if defined?( @tag_patterns )
89
+ return self.superclass.tag_patterns
90
+ end
91
+
92
+
93
+ ### Declare a +token_pattern+ for tag bodies along with a +callback+ that will
94
+ ### be called when a tag matching the pattern is instantiated.
95
+ def self::tag_pattern( token_pattern, &callback ) #:yield:
96
+ pattern = TokenPattern.compile( token_pattern )
97
+ @tag_patterns = [] unless defined?( @tag_patterns )
98
+ @tag_patterns << [ pattern, callback ]
99
+ end
100
+
101
+
102
+ #################################################################
103
+ ### I N S T A N C E M E T H O D S
104
+ #################################################################
105
+
106
+ ### Initialize a new tag that expects Ruby code in its +body+. Calls the
107
+ ### tag's #parse_pi_body method with the specified +body+.
108
+ def initialize( body, linenum=nil, colnum=nil ) # :notnew:
109
+ super
110
+
111
+ @body = body.strip
112
+ @identifiers = []
113
+ @matched_pattern = self.match_tag_pattern( body )
114
+ end
115
+
116
+
117
+ ######
118
+ public
119
+ ######
120
+
121
+ # the body of the tag
122
+ attr_reader :body
123
+
124
+ # the identifiers in the code contained in the tag
125
+ attr_reader :identifiers
126
+
127
+
128
+ ### Render the node as text.
129
+ pure_virtual :render
130
+
131
+
132
+
133
+ #########
134
+ protected
135
+ #########
136
+
137
+ ### Match the given +body+ against one of the tag's tag patterns, calling the
138
+ ### block associated with the first one that matches and returning the matching
139
+ ### pattern.
140
+ def match_tag_pattern( body )
141
+
142
+ self.class.tag_patterns.each do |tp, callback|
143
+ if match = tp.match( body.strip )
144
+ self.log.debug "Matched tag pattern: %p, match is: %p" % [ tp, match ]
145
+ callback.call( self, match )
146
+ return tp
147
+ end
148
+ end
149
+
150
+ self.log.error "Failed to match %p with %d patterns." %
151
+ [ body, self.class.tag_patterns.length ]
152
+
153
+ valid_patterns = self.class.tag_patterns.map( &:first ).map( &:source ).join( "\n ")
154
+ tokenized_src = Ripper.lex( body ).collect do |tok|
155
+ self.log.debug " lexed token: #{tok.inspect}"
156
+ "%s<%s>" % [ tok[1].to_s[3..-1], tok[2] ]
157
+ end.join(' ')
158
+
159
+ raise Inversion::ParseError, "malformed %s: expected one of:\n %s\ngot:\n %s" %
160
+ [ self.tagname, valid_patterns, tokenized_src ]
161
+ end
162
+
163
+ end # class Inversion::Template::CodeTag
164
+