Class: LtdTemplate
Defined Under Namespace
Modules: Consumer, Method_Handler, Univalue, Value Classes: Code, Proxy, ResourceLimitExceeded
Constant Summary collapse
- VERSION =
'1.0.1'
- TOKEN_MAP =
{ ?. => :dot, # method separator '..' => :dotdot, # begin named values ?( => :lparen, # begin call parameters ?, => :comma, # next call parameter ?) => :rparen, # end call parameters ?[ => :lbrack, # begin array subscripts ?] => :rbrack, # end array subscripts ?{ => :lbrace, # begin code block ?} => :rbrace # end code block }
- @@classes =
@@classes contains the default factory classes. These can be overridden globally using the #set_classes class method or per-template using the #set_classes instance method.
NOTE: Factory types are also used for resource tracking, and therefore must be unique among resource usage symbols.
{ # # These represent storable values. # :array => 'Sarah', :array_splat => 'LtdTemplate::Value::Array_Splat', :code_block => 'LtdTemplate::Value::Code_Block', :namespace => 'LtdTemplate::Value::Namespace', # # These proxies provide template methods for native values. # :array_proxy => 'LtdTemplate::Proxy::Array', :boolean_proxy => 'LtdTemplate::Proxy::Boolean', :match_proxy => 'LtdTemplate::Proxy::Match', :nil_proxy => 'LtdTemplate::Proxy::Nil', :number_proxy => 'LtdTemplate::Proxy::Number', :regexp_proxy => 'LtdTemplate::Proxy::Regexp', :string_proxy => 'LtdTemplate::Proxy::String', # # These only occur as part of code blocks. # :call => 'LtdTemplate::Code::Call', :code_seq => 'LtdTemplate::Code::Sequence', :parameters => 'LtdTemplate::Code::Parameters', :subscript => 'LtdTemplate::Code::Subscript', :variable => 'LtdTemplate::Code::Variable', }
- @@proxies =
@@proxies contains the default mapping of native Ruby classes to factory symbols for the corresponding proxy classes. These can be overridden globally using the #set_proxies class method or per-template using the #set_proxies instance method. Unproxied native types will be invisible.
{ 'Array' => :array_proxy, 'FalseClass' => :boolean_proxy, 'Hash' => :array_proxy, 'MatchData' => :match_proxy, 'NilClass' => :nil_proxy, 'Numeric' => :number_proxy, 'Regexp' => :regexp_proxy, 'Sarah' => :array_proxy, 'String' => :string_proxy, 'TrueClass' => :boolean_proxy, }
Instance Attribute Summary collapse
-
#exceeded ⇒ Symbol?
readonly
The resource whose limit was being exceeded when an LtdTemplate::ResourceLimitExceeded exception was raised.
-
#limits ⇒ Hash
readonly
A hash of resource limits to enforce during parsing and rendering.
-
#namespace ⇒ LtdTemplate::Value::Namespace?
readonly
The current namespace (at the bottom of the rendering namespace stack).
-
#options ⇒ Hash
readonly
Instance initialization options.
-
#usage ⇒ Hash
readonly
A hash of resource usage.
-
#used ⇒ Hash
readonly
The resources already $.use’d for this template.
Class Method Summary collapse
-
.set_classes(mapping) ⇒ Hash
Change default factory classes globally.
-
.set_proxies(mapping) ⇒ Hash
Change default proxy types globally.
Instance Method Summary collapse
-
#check_limit(resource) ⇒ Object
Throw an exception if a resource limit has been exceeded.
-
#factory(type, *args) ⇒ Object
Generate new code, value, or proxy objects.
-
#get_tokens(str) ⇒ Array<Array>
Convert a string into an array of parser tokens.
-
#initialize(options = {}) ⇒ LtdTemplate
constructor
Constructor.
-
#inspect ⇒ Object
Don’t “spill our guts” upon inspection.
-
#parse(template) ⇒ LtdTemplate
Parse a template from a string.
-
#parse_block(tokens, stops = []) ⇒ LtdTemplate::Code
Parse a code block, stopping at any stop token.
-
#parse_list(tokens, stops, resume = []) ⇒ Array<LtdTemplate::Code>
Common code for parsing various lists.
-
#parse_parameters(tokens) ⇒ LtdTemplate::Code::Parameters
Parse a positional and/or named parameter list.
-
#parse_strlit(raw) ⇒ String
Parse escape sequences in string literals.
-
#parse_subscripts(tokens) ⇒ Array<LtdTemplate::Code>
Parse subscripts after the opening left bracket.
-
#parse_template(tokens) ⇒ Object
This is the top-level token parser.
-
#pop_namespace ⇒ Object
Pop the current namespace from the stack.
-
#proxy_type(type) ⇒ Object
Return the factory type symbol for a value’s method handler class.
-
#push_namespace(method, parameters = nil, opts = {}) ⇒ Object
Push a new namespace onto the namespace stack.
-
#render(options = {}) ⇒ String
Render the template.
-
#rubyverse_new(object) ⇒ Object
Return an object to handle template methods for the supplied object.
-
#set_classes(mapping) ⇒ LtdTemplate
Override the default factory class mapping for this template instance.
-
#set_proxies(mapping) ⇒ LtdTemplate
Override the default proxy types for this template instance.
-
#use(resource, amount = 1) ⇒ Object
Track incremental usage of a resource.
-
#using(resource, amount) ⇒ Object
Track peak usage of a resource.
Constructor Details
#initialize(options = {}) ⇒ LtdTemplate
Constructor
162 163 164 165 166 167 |
# File 'lib/ltdtemplate.rb', line 162 def initialize ( = {}) @classes, @proxies = {}, {} @code, @namespace = nil, nil @limits, @usage, @used = {}, {}, {} @options = end |
Instance Attribute Details
#exceeded ⇒ Symbol? (readonly)
The resource whose limit was being exceeded when an LtdTemplate::ResourceLimitExceeded exception was raised.
118 119 120 |
# File 'lib/ltdtemplate.rb', line 118 def exceeded @exceeded end |
#limits ⇒ Hash (readonly)
A hash of resource limits to enforce during parsing and rendering.
106 107 108 |
# File 'lib/ltdtemplate.rb', line 106 def limits @limits end |
#namespace ⇒ LtdTemplate::Value::Namespace? (readonly)
The current namespace (at the bottom of the rendering namespace stack).
128 129 130 |
# File 'lib/ltdtemplate.rb', line 128 def namespace @namespace end |
#options ⇒ Hash (readonly)
Instance initialization options.
123 124 125 |
# File 'lib/ltdtemplate.rb', line 123 def @options end |
#usage ⇒ Hash (readonly)
A hash of resource usage. It is updated after calls to #parse and #render.
112 113 114 |
# File 'lib/ltdtemplate.rb', line 112 def usage @usage end |
#used ⇒ Hash (readonly)
The resources already $.use’d for this template
133 134 135 |
# File 'lib/ltdtemplate.rb', line 133 def used @used end |
Class Method Details
.set_classes(mapping) ⇒ Hash
Change default factory classes globally
140 141 142 |
# File 'lib/ltdtemplate.rb', line 140 def self.set_classes (mapping) @@classes.merge! mapping end |
.set_proxies(mapping) ⇒ Hash
Change default proxy types globally
149 150 151 |
# File 'lib/ltdtemplate.rb', line 149 def self.set_proxies (mapping) @@proxies.merge! mapping end |
Instance Method Details
#check_limit(resource) ⇒ Object
Throw an exception if a resource limit has been exceeded.
174 175 176 177 178 179 180 181 |
# File 'lib/ltdtemplate.rb', line 174 def check_limit (resource) if @limits[resource] && @usage[resource] && @usage[resource] > @limits[resource] @exceeded = resource raise LtdTemplate::ResourceLimitExceeded.new( "Exceeded #{resource} #{@limits[resource]}") end end |
#factory(type, *args) ⇒ Object
Generate new code, value, or proxy objects.
189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 |
# File 'lib/ltdtemplate.rb', line 189 def factory (type, *args) use :factory spec = @classes[type] || @@classes[type] case spec when nil raise ArgumentError, "Unknown template factory type #{type}" when String # Convert the class name to a class on first use and cache it require spec.downcase.gsub('::', File::SEPARATOR) @classes[type] = klass = eval(spec) else klass = spec end use type args.unshift self if klass.is_a? LtdTemplate::Consumer if klass.respond_to? :new then klass.new *args else klass end end |
#get_tokens(str) ⇒ Array<Array>
Convert a string into an array of parser tokens.
212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 |
# File 'lib/ltdtemplate.rb', line 212 def get_tokens (str) tokens = [] str.split(%r{( /\*.*?\*/ # /* comment */ | '(?:\\.|[^\.,\[\](){}\s])+# 'string | "(?:\\"|[^"])*" # "string" | [@^][a-zA-Z0-9_]+ # root or parent identifier | [a-zA-Z_][a-zA-Z0-9_]*# alphanumeric identifier | -?\d+(?:\.\d+)? # integer or real numeric literal | \.\. # begin keyed values | [\.(,)\[\]{}] # methods, calls, elements, blocks | \s+ )}mx).grep(/\S/).each do |token| if token =~ %r{^/\*.*\*/$}s # Ignore comment elsif token =~ /^'(.*)/s or token =~ /^"(.*)"$/s # String literal #tokens.push [ :string, $1.gsub(/\\(.)/, '\1') ] tokens.push [ :string, parse_strlit($1) ] elsif token =~ /^-?\d+(?:\.\d+)?/ # Numeric literal tokens.push [ :number, token =~ /\./ ? token.to_f : token.to_i ] elsif TOKEN_MAP[token] # Delimiter tokens.push [ TOKEN_MAP[token] ] elsif token =~ /^[@^][a-z0-9_]|^[a-z_]|^[@^\$]$/i # Variable or alphanumeric method name tokens.push [ :name, token ] else # Punctuated method name tokens.push [ :method, token ] end end tokens end |
#inspect ⇒ Object
Don’t “spill our guts” upon inspection.
259 |
# File 'lib/ltdtemplate.rb', line 259 def inspect; "#<#{self.class.name}##{self.object_id}>"; end |
#parse(template) ⇒ LtdTemplate
Parse a template from a string.
266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 |
# File 'lib/ltdtemplate.rb', line 266 def parse (template) @usage = {} tokens = [] literal = true trim_next = false # Template "text<<code>>text<<code>>..." => # (text, code, text, code, ...) template.split(/<<(?!<)((?:[^<>]|<[^<]|>[^>])*)>>/s).each do |part| if part.length > 0 if literal part.sub!(/^\s+/s, '') if trim_next tokens.push [ :ext_string, part ] else if part[0] == '.' tokens[-1][1].sub!(/\s+$/s, '') if tokens[0] and tokens[-1][0] == :ext_string part = part[1..-1] if part.length > 1 end part = part[0..-2] if trim_next = (part[-1] == '.') tokens += get_tokens part end else trim_next = false end literal = !literal end @code = parse_template tokens self end |
#parse_block(tokens, stops = []) ⇒ LtdTemplate::Code
Parse a code block, stopping at any stop token.
304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 |
# File 'lib/ltdtemplate.rb', line 304 def parse_block (tokens, stops = []) code = [] while tokens[0] break if stops.include? tokens[0][0] token = tokens.shift# Consume the current token case token[0] when :string, :ext_string, :number # string and numeric literals code.push token[1] when :name # variable code.push factory(:variable, token[1]) when :lbrack # variable element subscripts subs = parse_subscripts tokens code.push factory(:subscript, code.pop, subs) if code[0] when :dot # method call w/ or w/out parameters case tokens[0][0] when :name, :method, :string method = tokens.shift if tokens[0] and tokens[0][0] == :lparen tokens.shift # Consume ( params = parse_parameters tokens else params = factory :parameters end code.push factory(:call, code.pop, method[1], params) if code[0] end if tokens[0] when :method # punctuated method call # Insert the implied dot and re-parse tokens.unshift [ :dot ], token when :lparen # call params = parse_parameters tokens code.push factory(:call, code.pop, 'call', params) if code[0] when :lbrace # explicit code block code.push factory(:code_block, parse_block(tokens, [ :rbrace ])) tokens.shift if tokens[0] # Consume } end end (code.size == 1) ? code[0] : factory(:code_seq, code) end |
#parse_list(tokens, stops, resume = []) ⇒ Array<LtdTemplate::Code>
Common code for parsing various lists.
356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 |
# File 'lib/ltdtemplate.rb', line 356 def parse_list (tokens, stops, resume = []) list = [] block_stops = stops + [ :comma ] while tokens[0] if !stops.include? tokens[0][0] block = parse_block tokens, block_stops list.push block if block tokens.shift if tokens[0] and tokens[0][0] == :comma elsif tokens[1] and resume.include? tokens[1][0] tokens.shift 2 # Consume stop and resume tokens else break end end list end |
#parse_parameters(tokens) ⇒ LtdTemplate::Code::Parameters
Parse a positional and/or named parameter list
378 379 380 381 382 383 384 385 386 387 388 389 390 |
# File 'lib/ltdtemplate.rb', line 378 def parse_parameters (tokens) positional = parse_list tokens, [ :dotdot, :rparen ] if tokens[0] and tokens[0][0] == :dotdot tokens.shift # Consume .. named = parse_list tokens, [ :rparen ] else named = nil end tokens.shift # Consume ) factory :parameters, positional, named end |
#parse_strlit(raw) ⇒ String
Parse escape sequences in string literals.
These are the same as in Ruby double-quoted strings:
\M-\C-x meta-control-x
\M-x meta-x
\C-x, \cx control-x
\udddd Unicode four-digit, 16-bit hexadecimal code point dddd
\xdd two-digit, 8-bit hexadecimal dd
\ddd one-, two-, or three-digit, 8-bit octal ddd
\c any other character c is just itself
405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 |
# File 'lib/ltdtemplate.rb', line 405 def parse_strlit (raw) raw.split(%r{(\\M-\\C-.|\\M-.|\\C-.|\\c.| \\u[0-9a-fA-F]{4}|\\x[0-9a-fA-f]{2}|\\[0-7]{1,3}|\\.)}x). map do |part| case part when /\\M-\\C-(.)/ then ($1.ord & 31 | 128).chr when /\\M-(.)/ then ($1.ord | 128).chr when /\\C-(.)/, /\\c(.)/ then ($1.ord & 31).chr when /\\u(.*)/ then $1.to_i(16).chr(Encoding::UTF_16BE) when /\\x(..)/ then $1.to_i(16).chr when /\\([0-7]+)/ then $1.to_i(8).chr when /\\(.)/ then "\a\b\e\f\n\r\s\t\v"["abefnrstv". index($1) || 9] || $1 else part end end.join '' end |
#parse_subscripts(tokens) ⇒ Array<LtdTemplate::Code>
Parse subscripts after the opening left bracket
427 428 429 430 431 |
# File 'lib/ltdtemplate.rb', line 427 def parse_subscripts (tokens) subs = parse_list tokens, [ :rbrack ], [ :lbrack ] tokens.shift # Consume ] subs end |
#parse_template(tokens) ⇒ Object
This is the top-level token parser.
436 |
# File 'lib/ltdtemplate.rb', line 436 def parse_template (tokens); parse_block tokens; end |
#pop_namespace ⇒ Object
Pop the current namespace from the stack.
439 440 441 442 443 444 |
# File 'lib/ltdtemplate.rb', line 439 def pop_namespace if @namespace.parent @namespace = @namespace.parent use :namespace_depth, -1 end end |
#proxy_type(type) ⇒ Object
Return the factory type symbol for a value’s method handler class.
450 451 452 453 454 455 456 457 458 459 460 461 462 463 |
# File 'lib/ltdtemplate.rb', line 450 def proxy_type (type) if type type_name = type.name if !@proxies.has_key? type_name # No local proxy class for this type; check the global # settings and the proxy type for the superclass. @proxies[type_name] = @@proxies[type_name] || proxy_type(type.superclass) end @proxies[type_name] else nil end end |
#push_namespace(method, parameters = nil, opts = {}) ⇒ Object
Push a new namespace onto the namespace stack.
474 475 476 477 478 479 |
# File 'lib/ltdtemplate.rb', line 474 def push_namespace (method, parameters = nil, opts = {}) use :namespaces use :namespace_depth @namespace = factory :namespace, method, parameters, @namespace @namespace.target = opts[:target] if opts[:target] end |
#render(options = {}) ⇒ String
Render the template.
The options hash may include :parameters, which may be an array or hash. These values will form the parameter array “_” in the root namespace.
493 494 495 496 497 498 499 500 501 502 503 504 505 506 507 508 509 510 511 512 |
# File 'lib/ltdtemplate.rb', line 493 def render ( = {}) # # Reset for each rendering # @exceeded, @usage, @used = nil, {}, {} @namespace = nil # # Create the root namespace and evaluate the template code. # if @code params = Sarah.new params.set *[:parameters] if [:parameters] push_namespace 'render', params rubyversed(@code).evaluate.in_rubyverse(self).tpl_text else '' end ensure self.rubyverse_map.clear unless [:cleanup] == false end |
#rubyverse_new(object) ⇒ Object
Return an object to handle template methods for the supplied object.
Unrecognized object types will be treated as nil.
517 518 519 520 521 522 523 524 |
# File 'lib/ltdtemplate.rb', line 517 def rubyverse_new (object) if object.is_a? LtdTemplate::Method_Handler object # The object handles its own template methods. elsif type = proxy_type(object.class) factory type, object else rubyversed nil end end |
#set_classes(mapping) ⇒ LtdTemplate
Override the default factory class mapping for this template instance.
531 532 533 534 |
# File 'lib/ltdtemplate.rb', line 531 def set_classes (mapping) @classes.merge! mapping self end |
#set_proxies(mapping) ⇒ LtdTemplate
Override the default proxy types for this template instance.
541 542 543 544 |
# File 'lib/ltdtemplate.rb', line 541 def set_proxies (mapping) @proxies.merge! mapping self end |
#use(resource, amount = 1) ⇒ Object
Track incremental usage of a resource.
551 552 553 554 555 |
# File 'lib/ltdtemplate.rb', line 551 def use (resource, amount = 1) @usage[resource] ||= 0 @usage[resource] += amount check_limit resource end |
#using(resource, amount) ⇒ Object
Track peak usage of a resource.
562 563 564 565 566 567 568 |
# File 'lib/ltdtemplate.rb', line 562 def using (resource, amount) @usage[resource] ||= 0 if amount > @usage[resource] @usage[resource] = amount check_limit resource end end |