Stencil 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.
- data/doc/README +2 -0
 - data/doc/Specifications +83 -0
 - data/lib/stencil/directives.rb +1 -0
 - data/lib/stencil/directives/base.rb +335 -0
 - data/lib/stencil/directives/term-style.rb +127 -0
 - data/lib/stencil/directives/text.rb +62 -0
 - data/lib/stencil/dynamic-template.rb +98 -0
 - data/lib/stencil/processor.rb +35 -0
 - data/lib/stencil/spec/view_matcher.rb +146 -0
 - data/lib/stencil/template.rb +269 -0
 - data/lib/stencil/view.rb +133 -0
 - metadata +70 -0
 
    
        data/doc/README
    ADDED
    
    
    
        data/doc/Specifications
    ADDED
    
    | 
         @@ -0,0 +1,83 @@ 
     | 
|
| 
      
 1 
     | 
    
         
            +
             
     | 
| 
      
 2 
     | 
    
         
            +
            Stencil::Template parsing an invalid template
         
     | 
| 
      
 3 
     | 
    
         
            +
            - should indicate the line and column of the error
         
     | 
| 
      
 4 
     | 
    
         
            +
             
     | 
| 
      
 5 
     | 
    
         
            +
            Stencil::RenderError
         
     | 
| 
      
 6 
     | 
    
         
            +
            - should has a useful string representation
         
     | 
| 
      
 7 
     | 
    
         
            +
             
     | 
| 
      
 8 
     | 
    
         
            +
            Stencil::Template with text directives to wrap lines
         
     | 
| 
      
 9 
     | 
    
         
            +
            - should transparently pass strings that don't need wrapping
         
     | 
| 
      
 10 
     | 
    
         
            +
            - should wrap long strings to width
         
     | 
| 
      
 11 
     | 
    
         
            +
            - should not re-wrap hard newlines
         
     | 
| 
      
 12 
     | 
    
         
            +
            - should not choke on unwrappable lines
         
     | 
| 
      
 13 
     | 
    
         
            +
             
     | 
| 
      
 14 
     | 
    
         
            +
            Stencil::Template with text directives to indent lines
         
     | 
| 
      
 15 
     | 
    
         
            +
            - should indent a single line
         
     | 
| 
      
 16 
     | 
    
         
            +
            - should indent multiple lines
         
     | 
| 
      
 17 
     | 
    
         
            +
             
     | 
| 
      
 18 
     | 
    
         
            +
            Stencil::Template with text directives to insert newlines
         
     | 
| 
      
 19 
     | 
    
         
            +
            - should insert a newline
         
     | 
| 
      
 20 
     | 
    
         
            +
             
     | 
| 
      
 21 
     | 
    
         
            +
            Stencil::ViewMatcher
         
     | 
| 
      
 22 
     | 
    
         
            +
            - should succeed with complex subview
         
     | 
| 
      
 23 
     | 
    
         
            +
            - should fail when structure is wrong
         
     | 
| 
      
 24 
     | 
    
         
            +
            - should fail when value doesn't match
         
     | 
| 
      
 25 
     | 
    
         
            +
            - should fail when matcher doesn't match
         
     | 
| 
      
 26 
     | 
    
         
            +
             
     | 
| 
      
 27 
     | 
    
         
            +
            Stencil::Template from a file
         
     | 
| 
      
 28 
     | 
    
         
            +
            - should sing and dance
         
     | 
| 
      
 29 
     | 
    
         
            +
             
     | 
| 
      
 30 
     | 
    
         
            +
            Stencil::Template with a file accessor and include
         
     | 
| 
      
 31 
     | 
    
         
            +
            - should work just fine
         
     | 
| 
      
 32 
     | 
    
         
            +
             
     | 
| 
      
 33 
     | 
    
         
            +
            Stencil::Template with simple value reference
         
     | 
| 
      
 34 
     | 
    
         
            +
            - should render numbers
         
     | 
| 
      
 35 
     | 
    
         
            +
            - should render strings
         
     | 
| 
      
 36 
     | 
    
         
            +
             
     | 
| 
      
 37 
     | 
    
         
            +
            Stencil::Template with a deep value reference
         
     | 
| 
      
 38 
     | 
    
         
            +
            - should render numbers
         
     | 
| 
      
 39 
     | 
    
         
            +
             
     | 
| 
      
 40 
     | 
    
         
            +
            Stencil::Template with a simple expression
         
     | 
| 
      
 41 
     | 
    
         
            +
            - should render numbers
         
     | 
| 
      
 42 
     | 
    
         
            +
             
     | 
| 
      
 43 
     | 
    
         
            +
            Stencil::Template with a 'with' directive
         
     | 
| 
      
 44 
     | 
    
         
            +
            - should render numbers
         
     | 
| 
      
 45 
     | 
    
         
            +
             
     | 
| 
      
 46 
     | 
    
         
            +
            Stencil::Template with a 'with' directive and a relative path
         
     | 
| 
      
 47 
     | 
    
         
            +
            - should render numbers
         
     | 
| 
      
 48 
     | 
    
         
            +
            - should render empty string if target is missing
         
     | 
| 
      
 49 
     | 
    
         
            +
             
     | 
| 
      
 50 
     | 
    
         
            +
            Stencil::Template with one list
         
     | 
| 
      
 51 
     | 
    
         
            +
            - should render combinations
         
     | 
| 
      
 52 
     | 
    
         
            +
             
     | 
| 
      
 53 
     | 
    
         
            +
            Stencil::Template with two lists
         
     | 
| 
      
 54 
     | 
    
         
            +
            - should render combinations
         
     | 
| 
      
 55 
     | 
    
         
            +
             
     | 
| 
      
 56 
     | 
    
         
            +
            Stencil::Template with a with-guarded list
         
     | 
| 
      
 57 
     | 
    
         
            +
            - should render an array
         
     | 
| 
      
 58 
     | 
    
         
            +
             
     | 
| 
      
 59 
     | 
    
         
            +
            Stencil::Template with an if block
         
     | 
| 
      
 60 
     | 
    
         
            +
            - should render true case
         
     | 
| 
      
 61 
     | 
    
         
            +
            - should render elseif case
         
     | 
| 
      
 62 
     | 
    
         
            +
            - should render false case
         
     | 
| 
      
 63 
     | 
    
         
            +
            - should raise error on missing case
         
     | 
| 
      
 64 
     | 
    
         
            +
             
     | 
| 
      
 65 
     | 
    
         
            +
            Stencil::Template nicely printing a deep list
         
     | 
| 
      
 66 
     | 
    
         
            +
            - should render list correctly
         
     | 
| 
      
 67 
     | 
    
         
            +
             
     | 
| 
      
 68 
     | 
    
         
            +
            Stencil::Template with whitespace absorbtion
         
     | 
| 
      
 69 
     | 
    
         
            +
            - should ignore extra whitespace in tags
         
     | 
| 
      
 70 
     | 
    
         
            +
             
     | 
| 
      
 71 
     | 
    
         
            +
            Stencil::DynamicTemplate for JSON
         
     | 
| 
      
 72 
     | 
    
         
            +
            - should render correctly
         
     | 
| 
      
 73 
     | 
    
         
            +
             
     | 
| 
      
 74 
     | 
    
         
            +
            Stencil::View
         
     | 
| 
      
 75 
     | 
    
         
            +
            - should render the correct viewset
         
     | 
| 
      
 76 
     | 
    
         
            +
             
     | 
| 
      
 77 
     | 
    
         
            +
            Stencil::TermStyleDirective
         
     | 
| 
      
 78 
     | 
    
         
            +
            - should change foreground color
         
     | 
| 
      
 79 
     | 
    
         
            +
            - should nest stylings
         
     | 
| 
      
 80 
     | 
    
         
            +
             
     | 
| 
      
 81 
     | 
    
         
            +
            Finished in 0.054271 seconds
         
     | 
| 
      
 82 
     | 
    
         
            +
             
     | 
| 
      
 83 
     | 
    
         
            +
            35 examples, 0 failures
         
     | 
| 
         @@ -0,0 +1 @@ 
     | 
|
| 
      
 1 
     | 
    
         
            +
            require 'stencil/directives/base'
         
     | 
| 
         @@ -0,0 +1,335 @@ 
     | 
|
| 
      
 1 
     | 
    
         
            +
            require 'stencil/processor'
         
     | 
| 
      
 2 
     | 
    
         
            +
            module Stencil
         
     | 
| 
      
 3 
     | 
    
         
            +
              class Directive
         
     | 
| 
      
 4 
     | 
    
         
            +
                class Source
         
     | 
| 
      
 5 
     | 
    
         
            +
                  def initialize(file, line, column)
         
     | 
| 
      
 6 
     | 
    
         
            +
                    @file, @line, @column = file, line, column
         
     | 
| 
      
 7 
     | 
    
         
            +
                  end
         
     | 
| 
      
 8 
     | 
    
         
            +
                
         
     | 
| 
      
 9 
     | 
    
         
            +
                  attr_accessor :file, :line, :column
         
     | 
| 
      
 10 
     | 
    
         
            +
             
     | 
| 
      
 11 
     | 
    
         
            +
                  def inspect
         
     | 
| 
      
 12 
     | 
    
         
            +
                    "#{@file}:#{@line},#{@column}"
         
     | 
| 
      
 13 
     | 
    
         
            +
                  end
         
     | 
| 
      
 14 
     | 
    
         
            +
             
     | 
| 
      
 15 
     | 
    
         
            +
                  def exception(based_on=nil)
         
     | 
| 
      
 16 
     | 
    
         
            +
                    message = case based_on
         
     | 
| 
      
 17 
     | 
    
         
            +
                              when String
         
     | 
| 
      
 18 
     | 
    
         
            +
                                based_on
         
     | 
| 
      
 19 
     | 
    
         
            +
                              when ::Exception
         
     | 
| 
      
 20 
     | 
    
         
            +
                                [based_on.class, based_on.message, based_on.backtrace.first].join(": ")
         
     | 
| 
      
 21 
     | 
    
         
            +
                              else
         
     | 
| 
      
 22 
     | 
    
         
            +
                                "resulting from #{based_on}.inspect"
         
     | 
| 
      
 23 
     | 
    
         
            +
                              end
         
     | 
| 
      
 24 
     | 
    
         
            +
                                
         
     | 
| 
      
 25 
     | 
    
         
            +
                    re = RenderError.new(message)
         
     | 
| 
      
 26 
     | 
    
         
            +
                    re.line = @line
         
     | 
| 
      
 27 
     | 
    
         
            +
                    re.column = @column
         
     | 
| 
      
 28 
     | 
    
         
            +
                    re.file = @file
         
     | 
| 
      
 29 
     | 
    
         
            +
                    re
         
     | 
| 
      
 30 
     | 
    
         
            +
                  end
         
     | 
| 
      
 31 
     | 
    
         
            +
                end
         
     | 
| 
      
 32 
     | 
    
         
            +
             
     | 
| 
      
 33 
     | 
    
         
            +
                @@directives = {}
         
     | 
| 
      
 34 
     | 
    
         
            +
             
     | 
| 
      
 35 
     | 
    
         
            +
                def self.create(location, string)
         
     | 
| 
      
 36 
     | 
    
         
            +
                  string = string.sub(/^\s*/,"").sub(/\s*$/,"")
         
     | 
| 
      
 37 
     | 
    
         
            +
                  name, arguments = string.split(/\s+/, 2)
         
     | 
| 
      
 38 
     | 
    
         
            +
                  klass = @@directives[name]
         
     | 
| 
      
 39 
     | 
    
         
            +
                  raise "Unrecognized directive: #{name}" if klass.nil?
         
     | 
| 
      
 40 
     | 
    
         
            +
                  return klass.new(location, arguments)
         
     | 
| 
      
 41 
     | 
    
         
            +
                end
         
     | 
| 
      
 42 
     | 
    
         
            +
             
     | 
| 
      
 43 
     | 
    
         
            +
                def self.register(as)
         
     | 
| 
      
 44 
     | 
    
         
            +
                  @@directives[as.to_s] = self
         
     | 
| 
      
 45 
     | 
    
         
            +
                end
         
     | 
| 
      
 46 
     | 
    
         
            +
             
     | 
| 
      
 47 
     | 
    
         
            +
                def interpret(code, data)
         
     | 
| 
      
 48 
     | 
    
         
            +
                  env = EvaluationHost.new(data)
         
     | 
| 
      
 49 
     | 
    
         
            +
                  return env.run(code)
         
     | 
| 
      
 50 
     | 
    
         
            +
                end
         
     | 
| 
      
 51 
     | 
    
         
            +
             
     | 
| 
      
 52 
     | 
    
         
            +
                def initialize(source, string)
         
     | 
| 
      
 53 
     | 
    
         
            +
                  @source = source
         
     | 
| 
      
 54 
     | 
    
         
            +
                  setup_parameters(string)
         
     | 
| 
      
 55 
     | 
    
         
            +
                end
         
     | 
| 
      
 56 
     | 
    
         
            +
             
     | 
| 
      
 57 
     | 
    
         
            +
                def setup_parameters(string)
         
     | 
| 
      
 58 
     | 
    
         
            +
                end
         
     | 
| 
      
 59 
     | 
    
         
            +
             
     | 
| 
      
 60 
     | 
    
         
            +
                def inspect
         
     | 
| 
      
 61 
     | 
    
         
            +
                  "[#{self.class.name.split("::").last} #{inspect_args}]"
         
     | 
| 
      
 62 
     | 
    
         
            +
                end
         
     | 
| 
      
 63 
     | 
    
         
            +
             
     | 
| 
      
 64 
     | 
    
         
            +
                def inspect_args
         
     | 
| 
      
 65 
     | 
    
         
            +
                  nil
         
     | 
| 
      
 66 
     | 
    
         
            +
                end
         
     | 
| 
      
 67 
     | 
    
         
            +
             
     | 
| 
      
 68 
     | 
    
         
            +
                def parsed(stack)
         
     | 
| 
      
 69 
     | 
    
         
            +
                  stack.last.add(self)
         
     | 
| 
      
 70 
     | 
    
         
            +
                end
         
     | 
| 
      
 71 
     | 
    
         
            +
             
     | 
| 
      
 72 
     | 
    
         
            +
                def prerender(state)
         
     | 
| 
      
 73 
     | 
    
         
            +
                end
         
     | 
| 
      
 74 
     | 
    
         
            +
             
     | 
| 
      
 75 
     | 
    
         
            +
                def checked_render(state)
         
     | 
| 
      
 76 
     | 
    
         
            +
                  begin
         
     | 
| 
      
 77 
     | 
    
         
            +
                    render(state)
         
     | 
| 
      
 78 
     | 
    
         
            +
                  rescue RenderError
         
     | 
| 
      
 79 
     | 
    
         
            +
                    raise
         
     | 
| 
      
 80 
     | 
    
         
            +
                  rescue Object => ex
         
     | 
| 
      
 81 
     | 
    
         
            +
                    raise @source, ex
         
     | 
| 
      
 82 
     | 
    
         
            +
                  end
         
     | 
| 
      
 83 
     | 
    
         
            +
                end
         
     | 
| 
      
 84 
     | 
    
         
            +
             
     | 
| 
      
 85 
     | 
    
         
            +
                def render(data)
         
     | 
| 
      
 86 
     | 
    
         
            +
                  nil
         
     | 
| 
      
 87 
     | 
    
         
            +
                end
         
     | 
| 
      
 88 
     | 
    
         
            +
             
     | 
| 
      
 89 
     | 
    
         
            +
                def postrender(state)
         
     | 
| 
      
 90 
     | 
    
         
            +
                end
         
     | 
| 
      
 91 
     | 
    
         
            +
             
     | 
| 
      
 92 
     | 
    
         
            +
                def pre_end(state)
         
     | 
| 
      
 93 
     | 
    
         
            +
                end
         
     | 
| 
      
 94 
     | 
    
         
            +
             
     | 
| 
      
 95 
     | 
    
         
            +
                def render_end(data)
         
     | 
| 
      
 96 
     | 
    
         
            +
                  nil
         
     | 
| 
      
 97 
     | 
    
         
            +
                end
         
     | 
| 
      
 98 
     | 
    
         
            +
              end
         
     | 
| 
      
 99 
     | 
    
         
            +
             
     | 
| 
      
 100 
     | 
    
         
            +
              class Literal < Directive
         
     | 
| 
      
 101 
     | 
    
         
            +
                def setup_parameters(string)
         
     | 
| 
      
 102 
     | 
    
         
            +
                  @value = string
         
     | 
| 
      
 103 
     | 
    
         
            +
                end
         
     | 
| 
      
 104 
     | 
    
         
            +
             
     | 
| 
      
 105 
     | 
    
         
            +
                def render(data)
         
     | 
| 
      
 106 
     | 
    
         
            +
                  @value
         
     | 
| 
      
 107 
     | 
    
         
            +
                end
         
     | 
| 
      
 108 
     | 
    
         
            +
             
     | 
| 
      
 109 
     | 
    
         
            +
                def inspect_args
         
     | 
| 
      
 110 
     | 
    
         
            +
                  @value.inspect
         
     | 
| 
      
 111 
     | 
    
         
            +
                end
         
     | 
| 
      
 112 
     | 
    
         
            +
              end
         
     | 
| 
      
 113 
     | 
    
         
            +
             
     | 
| 
      
 114 
     | 
    
         
            +
              class Apply < Directive
         
     | 
| 
      
 115 
     | 
    
         
            +
                register "apply"
         
     | 
| 
      
 116 
     | 
    
         
            +
             
     | 
| 
      
 117 
     | 
    
         
            +
                def setup_parameters(string)
         
     | 
| 
      
 118 
     | 
    
         
            +
                  @type, @path = string.split(" ")
         
     | 
| 
      
 119 
     | 
    
         
            +
                end
         
     | 
| 
      
 120 
     | 
    
         
            +
             
     | 
| 
      
 121 
     | 
    
         
            +
                def render(state)
         
     | 
| 
      
 122 
     | 
    
         
            +
                  state.data.push_context(@path)
         
     | 
| 
      
 123 
     | 
    
         
            +
                  result = DynamicTemplate::formatter(@type).render_raw(state.data)
         
     | 
| 
      
 124 
     | 
    
         
            +
                  state.data.pop_context
         
     | 
| 
      
 125 
     | 
    
         
            +
                  return result
         
     | 
| 
      
 126 
     | 
    
         
            +
                end
         
     | 
| 
      
 127 
     | 
    
         
            +
              end
         
     | 
| 
      
 128 
     | 
    
         
            +
             
     | 
| 
      
 129 
     | 
    
         
            +
              class Include < Directive
         
     | 
| 
      
 130 
     | 
    
         
            +
                register "include"
         
     | 
| 
      
 131 
     | 
    
         
            +
             
     | 
| 
      
 132 
     | 
    
         
            +
                def setup_parameters(string)
         
     | 
| 
      
 133 
     | 
    
         
            +
                  @template_path, @data_path = string.split(" ")
         
     | 
| 
      
 134 
     | 
    
         
            +
                end
         
     | 
| 
      
 135 
     | 
    
         
            +
             
     | 
| 
      
 136 
     | 
    
         
            +
                def render(state)
         
     | 
| 
      
 137 
     | 
    
         
            +
                  template = state.get_template(@template_path)
         
     | 
| 
      
 138 
     | 
    
         
            +
                  state.data.with_context(@data_path) do |data|
         
     | 
| 
      
 139 
     | 
    
         
            +
                    template.render_raw(data)
         
     | 
| 
      
 140 
     | 
    
         
            +
                  end
         
     | 
| 
      
 141 
     | 
    
         
            +
                end
         
     | 
| 
      
 142 
     | 
    
         
            +
              end
         
     | 
| 
      
 143 
     | 
    
         
            +
             
     | 
| 
      
 144 
     | 
    
         
            +
              class Interpolate < Directive
         
     | 
| 
      
 145 
     | 
    
         
            +
                register "="
         
     | 
| 
      
 146 
     | 
    
         
            +
             
     | 
| 
      
 147 
     | 
    
         
            +
                def setup_parameters(string)
         
     | 
| 
      
 148 
     | 
    
         
            +
                  @code = string || "@"
         
     | 
| 
      
 149 
     | 
    
         
            +
                end
         
     | 
| 
      
 150 
     | 
    
         
            +
             
     | 
| 
      
 151 
     | 
    
         
            +
                def render(state)
         
     | 
| 
      
 152 
     | 
    
         
            +
                  return interpret(@code, state.data)
         
     | 
| 
      
 153 
     | 
    
         
            +
                end
         
     | 
| 
      
 154 
     | 
    
         
            +
             
     | 
| 
      
 155 
     | 
    
         
            +
                def inspect_args
         
     | 
| 
      
 156 
     | 
    
         
            +
                  @path
         
     | 
| 
      
 157 
     | 
    
         
            +
                end
         
     | 
| 
      
 158 
     | 
    
         
            +
              end
         
     | 
| 
      
 159 
     | 
    
         
            +
             
     | 
| 
      
 160 
     | 
    
         
            +
              class Block < Directive
         
     | 
| 
      
 161 
     | 
    
         
            +
                def initialize(location, string)
         
     | 
| 
      
 162 
     | 
    
         
            +
                  super
         
     | 
| 
      
 163 
     | 
    
         
            +
                  @apply = []
         
     | 
| 
      
 164 
     | 
    
         
            +
                end
         
     | 
| 
      
 165 
     | 
    
         
            +
             
     | 
| 
      
 166 
     | 
    
         
            +
                def inspect
         
     | 
| 
      
 167 
     | 
    
         
            +
                  "[#{self.class.name.split("::").last} #{inspect_args} {#{@apply.map{|dr| dr.inspect}.join(" ")}}]"
         
     | 
| 
      
 168 
     | 
    
         
            +
                end
         
     | 
| 
      
 169 
     | 
    
         
            +
             
     | 
| 
      
 170 
     | 
    
         
            +
                def add(directive)
         
     | 
| 
      
 171 
     | 
    
         
            +
                  @apply << directive 
         
     | 
| 
      
 172 
     | 
    
         
            +
                end
         
     | 
| 
      
 173 
     | 
    
         
            +
             
     | 
| 
      
 174 
     | 
    
         
            +
                def parsed(stack)
         
     | 
| 
      
 175 
     | 
    
         
            +
                  super
         
     | 
| 
      
 176 
     | 
    
         
            +
                  stack << self
         
     | 
| 
      
 177 
     | 
    
         
            +
                end
         
     | 
| 
      
 178 
     | 
    
         
            +
             
     | 
| 
      
 179 
     | 
    
         
            +
                def render(state)
         
     | 
| 
      
 180 
     | 
    
         
            +
                  @apply.map do |directive|
         
     | 
| 
      
 181 
     | 
    
         
            +
                    directive.checked_render(state)
         
     | 
| 
      
 182 
     | 
    
         
            +
                  end.flatten.compact
         
     | 
| 
      
 183 
     | 
    
         
            +
                end
         
     | 
| 
      
 184 
     | 
    
         
            +
             
     | 
| 
      
 185 
     | 
    
         
            +
                def ended(stack)
         
     | 
| 
      
 186 
     | 
    
         
            +
                  stack.pop
         
     | 
| 
      
 187 
     | 
    
         
            +
                end
         
     | 
| 
      
 188 
     | 
    
         
            +
              end
         
     | 
| 
      
 189 
     | 
    
         
            +
             
     | 
| 
      
 190 
     | 
    
         
            +
              class With < Block
         
     | 
| 
      
 191 
     | 
    
         
            +
                register :with
         
     | 
| 
      
 192 
     | 
    
         
            +
             
     | 
| 
      
 193 
     | 
    
         
            +
                def setup_parameters(path)
         
     | 
| 
      
 194 
     | 
    
         
            +
                  @path = path
         
     | 
| 
      
 195 
     | 
    
         
            +
                end
         
     | 
| 
      
 196 
     | 
    
         
            +
             
     | 
| 
      
 197 
     | 
    
         
            +
                def inspect_args
         
     | 
| 
      
 198 
     | 
    
         
            +
                  @path
         
     | 
| 
      
 199 
     | 
    
         
            +
                end
         
     | 
| 
      
 200 
     | 
    
         
            +
             
     | 
| 
      
 201 
     | 
    
         
            +
                def render(state)
         
     | 
| 
      
 202 
     | 
    
         
            +
                  if state.data.access(@path).nil?
         
     | 
| 
      
 203 
     | 
    
         
            +
                    return []
         
     | 
| 
      
 204 
     | 
    
         
            +
                  else
         
     | 
| 
      
 205 
     | 
    
         
            +
                    state.data.push_context(@path)
         
     | 
| 
      
 206 
     | 
    
         
            +
                    res = super
         
     | 
| 
      
 207 
     | 
    
         
            +
                    state.data.pop_context
         
     | 
| 
      
 208 
     | 
    
         
            +
             
     | 
| 
      
 209 
     | 
    
         
            +
                    return res
         
     | 
| 
      
 210 
     | 
    
         
            +
                  end
         
     | 
| 
      
 211 
     | 
    
         
            +
                end
         
     | 
| 
      
 212 
     | 
    
         
            +
              end
         
     | 
| 
      
 213 
     | 
    
         
            +
             
     | 
| 
      
 214 
     | 
    
         
            +
              class Each < Block
         
     | 
| 
      
 215 
     | 
    
         
            +
                register :each
         
     | 
| 
      
 216 
     | 
    
         
            +
             
     | 
| 
      
 217 
     | 
    
         
            +
             
     | 
| 
      
 218 
     | 
    
         
            +
                #XXX what if @list doesn't point to an array?
         
     | 
| 
      
 219 
     | 
    
         
            +
                def setup_parameters(string)
         
     | 
| 
      
 220 
     | 
    
         
            +
                  @path, @named_context = string.split(" ", 2)
         
     | 
| 
      
 221 
     | 
    
         
            +
                end
         
     | 
| 
      
 222 
     | 
    
         
            +
             
     | 
| 
      
 223 
     | 
    
         
            +
                def intercept(directive)
         
     | 
| 
      
 224 
     | 
    
         
            +
                  @apply << directive
         
     | 
| 
      
 225 
     | 
    
         
            +
                  nil
         
     | 
| 
      
 226 
     | 
    
         
            +
                end
         
     | 
| 
      
 227 
     | 
    
         
            +
             
     | 
| 
      
 228 
     | 
    
         
            +
                def inspect_args
         
     | 
| 
      
 229 
     | 
    
         
            +
                  "#@path => #@named_context"
         
     | 
| 
      
 230 
     | 
    
         
            +
                end
         
     | 
| 
      
 231 
     | 
    
         
            +
             
     | 
| 
      
 232 
     | 
    
         
            +
                def render(state)
         
     | 
| 
      
 233 
     | 
    
         
            +
                  data = state.data
         
     | 
| 
      
 234 
     | 
    
         
            +
                  list = data.access(@path)
         
     | 
| 
      
 235 
     | 
    
         
            +
                  data.push_context(@path, @named_context)
         
     | 
| 
      
 236 
     | 
    
         
            +
                  data.push_context(@path)
         
     | 
| 
      
 237 
     | 
    
         
            +
                  result = (0...list.length).map do |index|
         
     | 
| 
      
 238 
     | 
    
         
            +
                    data.push_context("##{index}", @named_context)
         
     | 
| 
      
 239 
     | 
    
         
            +
                    data.push_context("##{index}")
         
     | 
| 
      
 240 
     | 
    
         
            +
             
     | 
| 
      
 241 
     | 
    
         
            +
                    res = super
         
     | 
| 
      
 242 
     | 
    
         
            +
             
     | 
| 
      
 243 
     | 
    
         
            +
                    data.pop_context(@named_context)
         
     | 
| 
      
 244 
     | 
    
         
            +
                    data.pop_context
         
     | 
| 
      
 245 
     | 
    
         
            +
                    res
         
     | 
| 
      
 246 
     | 
    
         
            +
                  end.flatten.compact
         
     | 
| 
      
 247 
     | 
    
         
            +
                  data.pop_context(@named_context)
         
     | 
| 
      
 248 
     | 
    
         
            +
                  data.pop_context
         
     | 
| 
      
 249 
     | 
    
         
            +
             
     | 
| 
      
 250 
     | 
    
         
            +
                  result
         
     | 
| 
      
 251 
     | 
    
         
            +
                end
         
     | 
| 
      
 252 
     | 
    
         
            +
              end
         
     | 
| 
      
 253 
     | 
    
         
            +
             
     | 
| 
      
 254 
     | 
    
         
            +
              class If < Block
         
     | 
| 
      
 255 
     | 
    
         
            +
                register :if
         
     | 
| 
      
 256 
     | 
    
         
            +
             
     | 
| 
      
 257 
     | 
    
         
            +
                def setup_parameters(code)
         
     | 
| 
      
 258 
     | 
    
         
            +
                  @code = code
         
     | 
| 
      
 259 
     | 
    
         
            +
                  @truth = nil
         
     | 
| 
      
 260 
     | 
    
         
            +
                end
         
     | 
| 
      
 261 
     | 
    
         
            +
                attr_reader :truth
         
     | 
| 
      
 262 
     | 
    
         
            +
             
     | 
| 
      
 263 
     | 
    
         
            +
                def inspect_args
         
     | 
| 
      
 264 
     | 
    
         
            +
                  @code
         
     | 
| 
      
 265 
     | 
    
         
            +
                end
         
     | 
| 
      
 266 
     | 
    
         
            +
             
     | 
| 
      
 267 
     | 
    
         
            +
                def determine_truth(data)
         
     | 
| 
      
 268 
     | 
    
         
            +
                  @truth = interpret(@code, data)
         
     | 
| 
      
 269 
     | 
    
         
            +
                  return @truth
         
     | 
| 
      
 270 
     | 
    
         
            +
                end
         
     | 
| 
      
 271 
     | 
    
         
            +
             
     | 
| 
      
 272 
     | 
    
         
            +
                def render(state)
         
     | 
| 
      
 273 
     | 
    
         
            +
                  if determine_truth(state.data)
         
     | 
| 
      
 274 
     | 
    
         
            +
                    return super
         
     | 
| 
      
 275 
     | 
    
         
            +
                  end
         
     | 
| 
      
 276 
     | 
    
         
            +
                end
         
     | 
| 
      
 277 
     | 
    
         
            +
              end
         
     | 
| 
      
 278 
     | 
    
         
            +
             
     | 
| 
      
 279 
     | 
    
         
            +
              class Else < If
         
     | 
| 
      
 280 
     | 
    
         
            +
                register :else
         
     | 
| 
      
 281 
     | 
    
         
            +
                register :elif
         
     | 
| 
      
 282 
     | 
    
         
            +
             
     | 
| 
      
 283 
     | 
    
         
            +
                def setup_parameters(code)
         
     | 
| 
      
 284 
     | 
    
         
            +
                  super
         
     | 
| 
      
 285 
     | 
    
         
            +
                  @previous = nil
         
     | 
| 
      
 286 
     | 
    
         
            +
                  @a_truth = false
         
     | 
| 
      
 287 
     | 
    
         
            +
                end
         
     | 
| 
      
 288 
     | 
    
         
            +
             
     | 
| 
      
 289 
     | 
    
         
            +
                def truth
         
     | 
| 
      
 290 
     | 
    
         
            +
                  @a_truth
         
     | 
| 
      
 291 
     | 
    
         
            +
                end
         
     | 
| 
      
 292 
     | 
    
         
            +
             
     | 
| 
      
 293 
     | 
    
         
            +
                def determine_truth(data)
         
     | 
| 
      
 294 
     | 
    
         
            +
                  if @previous.truth
         
     | 
| 
      
 295 
     | 
    
         
            +
                    @a_truth = true
         
     | 
| 
      
 296 
     | 
    
         
            +
                    @truth = false
         
     | 
| 
      
 297 
     | 
    
         
            +
                  else
         
     | 
| 
      
 298 
     | 
    
         
            +
                    if @code.nil?
         
     | 
| 
      
 299 
     | 
    
         
            +
                      @truth = true
         
     | 
| 
      
 300 
     | 
    
         
            +
                    else
         
     | 
| 
      
 301 
     | 
    
         
            +
                      @truth = interpret(@code, data)
         
     | 
| 
      
 302 
     | 
    
         
            +
                    end
         
     | 
| 
      
 303 
     | 
    
         
            +
                    @a_truth = @truth
         
     | 
| 
      
 304 
     | 
    
         
            +
                  end
         
     | 
| 
      
 305 
     | 
    
         
            +
                  @truth
         
     | 
| 
      
 306 
     | 
    
         
            +
                end
         
     | 
| 
      
 307 
     | 
    
         
            +
             
     | 
| 
      
 308 
     | 
    
         
            +
                def parsed(stack)
         
     | 
| 
      
 309 
     | 
    
         
            +
                  @previous = stack.last
         
     | 
| 
      
 310 
     | 
    
         
            +
                  unless If === @previous 
         
     | 
| 
      
 311 
     | 
    
         
            +
                    raise @source, "else has to follow if"
         
     | 
| 
      
 312 
     | 
    
         
            +
                  end
         
     | 
| 
      
 313 
     | 
    
         
            +
                  @previous.ended(stack)
         
     | 
| 
      
 314 
     | 
    
         
            +
                  super
         
     | 
| 
      
 315 
     | 
    
         
            +
                end
         
     | 
| 
      
 316 
     | 
    
         
            +
                end
         
     | 
| 
      
 317 
     | 
    
         
            +
             
     | 
| 
      
 318 
     | 
    
         
            +
             
     | 
| 
      
 319 
     | 
    
         
            +
              class End < Directive
         
     | 
| 
      
 320 
     | 
    
         
            +
                register :end
         
     | 
| 
      
 321 
     | 
    
         
            +
                register "/"
         
     | 
| 
      
 322 
     | 
    
         
            +
             
     | 
| 
      
 323 
     | 
    
         
            +
                def setup_parameters(arg)
         
     | 
| 
      
 324 
     | 
    
         
            +
                  @ended = nil
         
     | 
| 
      
 325 
     | 
    
         
            +
                  @count = arg.to_i
         
     | 
| 
      
 326 
     | 
    
         
            +
                  @count = 1 if @count < 1
         
     | 
| 
      
 327 
     | 
    
         
            +
                end
         
     | 
| 
      
 328 
     | 
    
         
            +
             
     | 
| 
      
 329 
     | 
    
         
            +
                def parsed(stack)
         
     | 
| 
      
 330 
     | 
    
         
            +
                  @count.times do
         
     | 
| 
      
 331 
     | 
    
         
            +
                    stack.last.ended(stack)
         
     | 
| 
      
 332 
     | 
    
         
            +
                  end
         
     | 
| 
      
 333 
     | 
    
         
            +
                end
         
     | 
| 
      
 334 
     | 
    
         
            +
              end
         
     | 
| 
      
 335 
     | 
    
         
            +
            end
         
     | 
| 
         @@ -0,0 +1,127 @@ 
     | 
|
| 
      
 1 
     | 
    
         
            +
            module Stencil
         
     | 
| 
      
 2 
     | 
    
         
            +
              module Styles
         
     | 
| 
      
 3 
     | 
    
         
            +
                Foreground = { 
         
     | 
| 
      
 4 
     | 
    
         
            +
                  'black'   => 30,
         
     | 
| 
      
 5 
     | 
    
         
            +
                  'red'     => 31, 
         
     | 
| 
      
 6 
     | 
    
         
            +
                  'green'   => 32, 
         
     | 
| 
      
 7 
     | 
    
         
            +
                  'yellow'  => 33,
         
     | 
| 
      
 8 
     | 
    
         
            +
                  'blue'    => 34,
         
     | 
| 
      
 9 
     | 
    
         
            +
                  'magenta' => 35,
         
     | 
| 
      
 10 
     | 
    
         
            +
                  'cyan'    => 36,
         
     | 
| 
      
 11 
     | 
    
         
            +
                  'white'   => 37,
         
     | 
| 
      
 12 
     | 
    
         
            +
                  'default' => 39
         
     | 
| 
      
 13 
     | 
    
         
            +
                }
         
     | 
| 
      
 14 
     | 
    
         
            +
             
     | 
| 
      
 15 
     | 
    
         
            +
                Background = {}
         
     | 
| 
      
 16 
     | 
    
         
            +
             
     | 
| 
      
 17 
     | 
    
         
            +
                Foreground.each() do |name, value|
         
     | 
| 
      
 18 
     | 
    
         
            +
                  Background[name] = value + 10
         
     | 
| 
      
 19 
     | 
    
         
            +
                end
         
     | 
| 
      
 20 
     | 
    
         
            +
             
     | 
| 
      
 21 
     | 
    
         
            +
                Extra = {
         
     | 
| 
      
 22 
     | 
    
         
            +
                  'clear'     => 0, 
         
     | 
| 
      
 23 
     | 
    
         
            +
                  'bold'      => 1,
         
     | 
| 
      
 24 
     | 
    
         
            +
                  'unbold'      => 22,
         
     | 
| 
      
 25 
     | 
    
         
            +
                  'underline' => 4,
         
     | 
| 
      
 26 
     | 
    
         
            +
                  'ununderline' => 24,
         
     | 
| 
      
 27 
     | 
    
         
            +
                  'blink'     => 5,
         
     | 
| 
      
 28 
     | 
    
         
            +
                  'unblink'     => 25,
         
     | 
| 
      
 29 
     | 
    
         
            +
                  'inverse'   => 7,
         
     | 
| 
      
 30 
     | 
    
         
            +
                  'uninverse'   => 27
         
     | 
| 
      
 31 
     | 
    
         
            +
                }
         
     | 
| 
      
 32 
     | 
    
         
            +
              end
         
     | 
| 
      
 33 
     | 
    
         
            +
             
     | 
| 
      
 34 
     | 
    
         
            +
              class TermStyleDirective < Block
         
     | 
| 
      
 35 
     | 
    
         
            +
                def initialize(location, string)
         
     | 
| 
      
 36 
     | 
    
         
            +
                  @previous_style = nil
         
     | 
| 
      
 37 
     | 
    
         
            +
                  @style = nil
         
     | 
| 
      
 38 
     | 
    
         
            +
                  super
         
     | 
| 
      
 39 
     | 
    
         
            +
                end
         
     | 
| 
      
 40 
     | 
    
         
            +
             
     | 
| 
      
 41 
     | 
    
         
            +
                def code_for(kind, name)
         
     | 
| 
      
 42 
     | 
    
         
            +
                  if kind.has_key?(name.to_s)
         
     | 
| 
      
 43 
     | 
    
         
            +
                    "\e[#{kind[name.to_s]}m"
         
     | 
| 
      
 44 
     | 
    
         
            +
                  else
         
     | 
| 
      
 45 
     | 
    
         
            +
                    ""
         
     | 
| 
      
 46 
     | 
    
         
            +
                  end
         
     | 
| 
      
 47 
     | 
    
         
            +
                end
         
     | 
| 
      
 48 
     | 
    
         
            +
             
     | 
| 
      
 49 
     | 
    
         
            +
                def escape_codes(options)
         
     | 
| 
      
 50 
     | 
    
         
            +
                  return "\e[0m" if options.empty?
         
     | 
| 
      
 51 
     | 
    
         
            +
                  return code_for(Styles::Foreground, options[:foreground]) + 
         
     | 
| 
      
 52 
     | 
    
         
            +
                    code_for(Styles::Background, options[:background]) + 
         
     | 
| 
      
 53 
     | 
    
         
            +
                    code_for(Styles::Extra, options[:extra])
         
     | 
| 
      
 54 
     | 
    
         
            +
                end
         
     | 
| 
      
 55 
     | 
    
         
            +
             
     | 
| 
      
 56 
     | 
    
         
            +
                def parsed(stack)
         
     | 
| 
      
 57 
     | 
    
         
            +
                  reverse = Enumerable::Enumerator::new(stack, :reverse_each)
         
     | 
| 
      
 58 
     | 
    
         
            +
                  @previous_style = reverse.find {|dir| TermStyleDirective === dir}
         
     | 
| 
      
 59 
     | 
    
         
            +
                  super
         
     | 
| 
      
 60 
     | 
    
         
            +
                end
         
     | 
| 
      
 61 
     | 
    
         
            +
             
     | 
| 
      
 62 
     | 
    
         
            +
                def old_style(state)
         
     | 
| 
      
 63 
     | 
    
         
            +
                  @old_style ||= @previous_style.style(state) rescue {:foreground => 'default', :background => 'default'}
         
     | 
| 
      
 64 
     | 
    
         
            +
                end
         
     | 
| 
      
 65 
     | 
    
         
            +
             
     | 
| 
      
 66 
     | 
    
         
            +
                def style(state)
         
     | 
| 
      
 67 
     | 
    
         
            +
                  @style ||= update_style(old_style(state), state.data)
         
     | 
| 
      
 68 
     | 
    
         
            +
                end
         
     | 
| 
      
 69 
     | 
    
         
            +
             
     | 
| 
      
 70 
     | 
    
         
            +
                def render(state)
         
     | 
| 
      
 71 
     | 
    
         
            +
                  [escape_codes(style(state))] + super + [escape_codes(old_style(state))]
         
     | 
| 
      
 72 
     | 
    
         
            +
                end
         
     | 
| 
      
 73 
     | 
    
         
            +
             
     | 
| 
      
 74 
     | 
    
         
            +
                def update_style(style, state)
         
     | 
| 
      
 75 
     | 
    
         
            +
                  return style
         
     | 
| 
      
 76 
     | 
    
         
            +
                end
         
     | 
| 
      
 77 
     | 
    
         
            +
              end
         
     | 
| 
      
 78 
     | 
    
         
            +
             
     | 
| 
      
 79 
     | 
    
         
            +
              class ForegroundColor < TermStyleDirective
         
     | 
| 
      
 80 
     | 
    
         
            +
                register :fcolor
         
     | 
| 
      
 81 
     | 
    
         
            +
                def setup_parameters(color)
         
     | 
| 
      
 82 
     | 
    
         
            +
                  @color_text = color
         
     | 
| 
      
 83 
     | 
    
         
            +
                end
         
     | 
| 
      
 84 
     | 
    
         
            +
             
     | 
| 
      
 85 
     | 
    
         
            +
                def update_style(style, state)
         
     | 
| 
      
 86 
     | 
    
         
            +
                  return style.merge(:foreground => interpret(@color_text, state))
         
     | 
| 
      
 87 
     | 
    
         
            +
                end
         
     | 
| 
      
 88 
     | 
    
         
            +
              end
         
     | 
| 
      
 89 
     | 
    
         
            +
             
     | 
| 
      
 90 
     | 
    
         
            +
              class Bold < TermStyleDirective
         
     | 
| 
      
 91 
     | 
    
         
            +
                register :bold
         
     | 
| 
      
 92 
     | 
    
         
            +
             
     | 
| 
      
 93 
     | 
    
         
            +
                def render(state)
         
     | 
| 
      
 94 
     | 
    
         
            +
                  [code_for(Styles::Extra, 'bold')] + 
         
     | 
| 
      
 95 
     | 
    
         
            +
                    super + 
         
     | 
| 
      
 96 
     | 
    
         
            +
                    [code_for(Styles::Extra, 'unbold')]
         
     | 
| 
      
 97 
     | 
    
         
            +
                end
         
     | 
| 
      
 98 
     | 
    
         
            +
              end
         
     | 
| 
      
 99 
     | 
    
         
            +
             
     | 
| 
      
 100 
     | 
    
         
            +
              class Underline < TermStyleDirective
         
     | 
| 
      
 101 
     | 
    
         
            +
                register :underline
         
     | 
| 
      
 102 
     | 
    
         
            +
             
     | 
| 
      
 103 
     | 
    
         
            +
                def render(state)
         
     | 
| 
      
 104 
     | 
    
         
            +
                  [code_for(Styles::Extra, 'underline')] + 
         
     | 
| 
      
 105 
     | 
    
         
            +
                    super + 
         
     | 
| 
      
 106 
     | 
    
         
            +
                    [code_for(Styles::Extra, 'ununderline')]
         
     | 
| 
      
 107 
     | 
    
         
            +
                end
         
     | 
| 
      
 108 
     | 
    
         
            +
              end
         
     | 
| 
      
 109 
     | 
    
         
            +
             
     | 
| 
      
 110 
     | 
    
         
            +
              class Inverse < TermStyleDirective
         
     | 
| 
      
 111 
     | 
    
         
            +
                register :inverse
         
     | 
| 
      
 112 
     | 
    
         
            +
             
     | 
| 
      
 113 
     | 
    
         
            +
             
     | 
| 
      
 114 
     | 
    
         
            +
                def render(state)
         
     | 
| 
      
 115 
     | 
    
         
            +
                  [code_for(Styles::Extra, 'inverse')] + 
         
     | 
| 
      
 116 
     | 
    
         
            +
                    super + 
         
     | 
| 
      
 117 
     | 
    
         
            +
                    [code_for(Styles::Extra, 'uninverse')]
         
     | 
| 
      
 118 
     | 
    
         
            +
                end
         
     | 
| 
      
 119 
     | 
    
         
            +
              end
         
     | 
| 
      
 120 
     | 
    
         
            +
             
     | 
| 
      
 121 
     | 
    
         
            +
              class BackgroundColor < ForegroundColor
         
     | 
| 
      
 122 
     | 
    
         
            +
                register :bcolor
         
     | 
| 
      
 123 
     | 
    
         
            +
                def update_style(style, state)
         
     | 
| 
      
 124 
     | 
    
         
            +
                  return style.merge(:background => interpret(@color_text, state))
         
     | 
| 
      
 125 
     | 
    
         
            +
                end
         
     | 
| 
      
 126 
     | 
    
         
            +
              end
         
     | 
| 
      
 127 
     | 
    
         
            +
            end
         
     | 
| 
         @@ -0,0 +1,62 @@ 
     | 
|
| 
      
 1 
     | 
    
         
            +
             
     | 
| 
      
 2 
     | 
    
         
            +
            module Stencil
         
     | 
| 
      
 3 
     | 
    
         
            +
              class Newline < Literal
         
     | 
| 
      
 4 
     | 
    
         
            +
                register "nl" 
         
     | 
| 
      
 5 
     | 
    
         
            +
                def setup_parameters(dummy)
         
     | 
| 
      
 6 
     | 
    
         
            +
                  super("\n")
         
     | 
| 
      
 7 
     | 
    
         
            +
                end
         
     | 
| 
      
 8 
     | 
    
         
            +
              end
         
     | 
| 
      
 9 
     | 
    
         
            +
             
     | 
| 
      
 10 
     | 
    
         
            +
              class Wrap < Block
         
     | 
| 
      
 11 
     | 
    
         
            +
                register "wrap"
         
     | 
| 
      
 12 
     | 
    
         
            +
                def setup_parameters(width)
         
     | 
| 
      
 13 
     | 
    
         
            +
                  @width_text = width
         
     | 
| 
      
 14 
     | 
    
         
            +
                end
         
     | 
| 
      
 15 
     | 
    
         
            +
              
         
     | 
| 
      
 16 
     | 
    
         
            +
                def render(state)
         
     | 
| 
      
 17 
     | 
    
         
            +
                  env = EvaluationHost.new(state.data)
         
     | 
| 
      
 18 
     | 
    
         
            +
                  width = env.run(@width_text)
         
     | 
| 
      
 19 
     | 
    
         
            +
                  res = super.compact.join("")
         
     | 
| 
      
 20 
     | 
    
         
            +
                  scanner = StringScanner.new(res)
         
     | 
| 
      
 21 
     | 
    
         
            +
                  scanner.skip(/\s*/m)
         
     | 
| 
      
 22 
     | 
    
         
            +
             
     | 
| 
      
 23 
     | 
    
         
            +
                  result = []
         
     | 
| 
      
 24 
     | 
    
         
            +
                  line = scanner.scan(/\S+/m) || ""
         
     | 
| 
      
 25 
     | 
    
         
            +
             
     | 
| 
      
 26 
     | 
    
         
            +
                  until scanner.eos? 
         
     | 
| 
      
 27 
     | 
    
         
            +
                    white = scanner.scan(/\s*/m)
         
     | 
| 
      
 28 
     | 
    
         
            +
                    white.gsub(/\n/) do |nl|
         
     | 
| 
      
 29 
     | 
    
         
            +
                      result << line + "\n"
         
     | 
| 
      
 30 
     | 
    
         
            +
                      line = ""
         
     | 
| 
      
 31 
     | 
    
         
            +
                    end
         
     | 
| 
      
 32 
     | 
    
         
            +
                    word = scanner.scan(/\S+/m)
         
     | 
| 
      
 33 
     | 
    
         
            +
                    next if word.nil?
         
     | 
| 
      
 34 
     | 
    
         
            +
                    if word.length > (width - line.length)
         
     | 
| 
      
 35 
     | 
    
         
            +
                      result << line + "\n"
         
     | 
| 
      
 36 
     | 
    
         
            +
                      line = word
         
     | 
| 
      
 37 
     | 
    
         
            +
                    else
         
     | 
| 
      
 38 
     | 
    
         
            +
                      line << (" " + word)
         
     | 
| 
      
 39 
     | 
    
         
            +
                    end
         
     | 
| 
      
 40 
     | 
    
         
            +
                  end
         
     | 
| 
      
 41 
     | 
    
         
            +
             
     | 
| 
      
 42 
     | 
    
         
            +
                  result << line
         
     | 
| 
      
 43 
     | 
    
         
            +
             
     | 
| 
      
 44 
     | 
    
         
            +
                  return result
         
     | 
| 
      
 45 
     | 
    
         
            +
                end
         
     | 
| 
      
 46 
     | 
    
         
            +
              end
         
     | 
| 
      
 47 
     | 
    
         
            +
             
     | 
| 
      
 48 
     | 
    
         
            +
              class Indent < Block
         
     | 
| 
      
 49 
     | 
    
         
            +
                register "indent" 
         
     | 
| 
      
 50 
     | 
    
         
            +
             
     | 
| 
      
 51 
     | 
    
         
            +
                def setup_parameters(spaces)
         
     | 
| 
      
 52 
     | 
    
         
            +
                  @space_text = spaces
         
     | 
| 
      
 53 
     | 
    
         
            +
                end
         
     | 
| 
      
 54 
     | 
    
         
            +
             
     | 
| 
      
 55 
     | 
    
         
            +
                def render(state)
         
     | 
| 
      
 56 
     | 
    
         
            +
                  spaces = " " * interpret(@space_text, state.data)
         
     | 
| 
      
 57 
     | 
    
         
            +
                  res = super.compact.join("")
         
     | 
| 
      
 58 
     | 
    
         
            +
                  res = spaces + res.gsub(/\n(?!$)/, "\n" + spaces)
         
     | 
| 
      
 59 
     | 
    
         
            +
                  return res
         
     | 
| 
      
 60 
     | 
    
         
            +
                end
         
     | 
| 
      
 61 
     | 
    
         
            +
              end
         
     | 
| 
      
 62 
     | 
    
         
            +
            end
         
     | 
| 
         @@ -0,0 +1,98 @@ 
     | 
|
| 
      
 1 
     | 
    
         
            +
            require 'stencil/template'
         
     | 
| 
      
 2 
     | 
    
         
            +
             
     | 
| 
      
 3 
     | 
    
         
            +
            module Stencil
         
     | 
| 
      
 4 
     | 
    
         
            +
              class DynamicTemplate < Template
         
     | 
| 
      
 5 
     | 
    
         
            +
                @@formatters = Hash.new
         
     | 
| 
      
 6 
     | 
    
         
            +
                def self.formatter(kind, &file_opener)
         
     | 
| 
      
 7 
     | 
    
         
            +
                  file_opener ||= DefaultFileOpener
         
     | 
| 
      
 8 
     | 
    
         
            +
                  formatter = @@formatters[kind.to_s]
         
     | 
| 
      
 9 
     | 
    
         
            +
                  if Class === formatter
         
     | 
| 
      
 10 
     | 
    
         
            +
                    formatter = formatter.new(&file_opener)
         
     | 
| 
      
 11 
     | 
    
         
            +
                    @@formatters[kind] = formatter
         
     | 
| 
      
 12 
     | 
    
         
            +
                  end
         
     | 
| 
      
 13 
     | 
    
         
            +
                  formatter
         
     | 
| 
      
 14 
     | 
    
         
            +
                end
         
     | 
| 
      
 15 
     | 
    
         
            +
             
     | 
| 
      
 16 
     | 
    
         
            +
                def self.register(kind)
         
     | 
| 
      
 17 
     | 
    
         
            +
                  @@formatters[kind.to_s] = self
         
     | 
| 
      
 18 
     | 
    
         
            +
                  define_method(:my_name){kind.to_s}
         
     | 
| 
      
 19 
     | 
    
         
            +
                end
         
     | 
| 
      
 20 
     | 
    
         
            +
             
     | 
| 
      
 21 
     | 
    
         
            +
                def initialize(&file_opener)
         
     | 
| 
      
 22 
     | 
    
         
            +
                  @hash_template = parse(*self.class.hash_template)
         
     | 
| 
      
 23 
     | 
    
         
            +
                  @array_template = parse(*self.class.array_template)
         
     | 
| 
      
 24 
     | 
    
         
            +
                  @item_template = parse(*self.class.item_template)
         
     | 
| 
      
 25 
     | 
    
         
            +
                  @file_opener = file_opener
         
     | 
| 
      
 26 
     | 
    
         
            +
                end
         
     | 
| 
      
 27 
     | 
    
         
            +
             
     | 
| 
      
 28 
     | 
    
         
            +
                def parse(string, file, line)
         
     | 
| 
      
 29 
     | 
    
         
            +
                  super(string.gsub(/\[;\s*reapply/, "[;apply #{my_name}"), file, line)
         
     | 
| 
      
 30 
     | 
    
         
            +
                end
         
     | 
| 
      
 31 
     | 
    
         
            +
             
     | 
| 
      
 32 
     | 
    
         
            +
                def array_render(data)
         
     | 
| 
      
 33 
     | 
    
         
            +
                  state = State.new(data, &@file_opener)
         
     | 
| 
      
 34 
     | 
    
         
            +
                  res = render_with(state, @array_template)
         
     | 
| 
      
 35 
     | 
    
         
            +
                  return res
         
     | 
| 
      
 36 
     | 
    
         
            +
                end
         
     | 
| 
      
 37 
     | 
    
         
            +
             
     | 
| 
      
 38 
     | 
    
         
            +
                def hash_render(data)
         
     | 
| 
      
 39 
     | 
    
         
            +
                  pairs = data.access("").map{|k,v| {"key" => k, "value" => v}}
         
     | 
| 
      
 40 
     | 
    
         
            +
                  state = State.new(data, &@file_opener) 
         
     | 
| 
      
 41 
     | 
    
         
            +
                  state.data.inject_dataset("pair", pairs)
         
     | 
| 
      
 42 
     | 
    
         
            +
                  render_with(state, @hash_template)
         
     | 
| 
      
 43 
     | 
    
         
            +
                end
         
     | 
| 
      
 44 
     | 
    
         
            +
             
     | 
| 
      
 45 
     | 
    
         
            +
                def item_render(data)
         
     | 
| 
      
 46 
     | 
    
         
            +
                  state = State.new(data, &@file_opener) 
         
     | 
| 
      
 47 
     | 
    
         
            +
                  res = render_with(state, @item_template)
         
     | 
| 
      
 48 
     | 
    
         
            +
                end
         
     | 
| 
      
 49 
     | 
    
         
            +
             
     | 
| 
      
 50 
     | 
    
         
            +
                def render_raw(data)
         
     | 
| 
      
 51 
     | 
    
         
            +
                  res = nil
         
     | 
| 
      
 52 
     | 
    
         
            +
                  case data.access("")
         
     | 
| 
      
 53 
     | 
    
         
            +
                  when Array
         
     | 
| 
      
 54 
     | 
    
         
            +
                    res = array_render(data)
         
     | 
| 
      
 55 
     | 
    
         
            +
                  when Hash
         
     | 
| 
      
 56 
     | 
    
         
            +
                    res = hash_render(data)
         
     | 
| 
      
 57 
     | 
    
         
            +
                  else
         
     | 
| 
      
 58 
     | 
    
         
            +
                    res = item_render(data)
         
     | 
| 
      
 59 
     | 
    
         
            +
                  end
         
     | 
| 
      
 60 
     | 
    
         
            +
                  return res
         
     | 
| 
      
 61 
     | 
    
         
            +
                end
         
     | 
| 
      
 62 
     | 
    
         
            +
             
     | 
| 
      
 63 
     | 
    
         
            +
                class << self
         
     | 
| 
      
 64 
     | 
    
         
            +
                  attr_reader :hash_template, :array_template, :item_template
         
     | 
| 
      
 65 
     | 
    
         
            +
             
     | 
| 
      
 66 
     | 
    
         
            +
                  def line_and_file
         
     | 
| 
      
 67 
     | 
    
         
            +
                    m = %r{^([^:]+):(\d+)}.match caller[1]
         
     | 
| 
      
 68 
     | 
    
         
            +
                    if m.nil?
         
     | 
| 
      
 69 
     | 
    
         
            +
                      return []
         
     | 
| 
      
 70 
     | 
    
         
            +
                    else
         
     | 
| 
      
 71 
     | 
    
         
            +
                      return [m[1], m[2].to_i]
         
     | 
| 
      
 72 
     | 
    
         
            +
                    end
         
     | 
| 
      
 73 
     | 
    
         
            +
                  end
         
     | 
| 
      
 74 
     | 
    
         
            +
             
     | 
| 
      
 75 
     | 
    
         
            +
                  def list(string)
         
     | 
| 
      
 76 
     | 
    
         
            +
                    @array_template = [string] + line_and_file
         
     | 
| 
      
 77 
     | 
    
         
            +
                  end
         
     | 
| 
      
 78 
     | 
    
         
            +
             
     | 
| 
      
 79 
     | 
    
         
            +
                  def hash(string)
         
     | 
| 
      
 80 
     | 
    
         
            +
                    @hash_template = [string] + line_and_file
         
     | 
| 
      
 81 
     | 
    
         
            +
                  end
         
     | 
| 
      
 82 
     | 
    
         
            +
             
     | 
| 
      
 83 
     | 
    
         
            +
                  def item(string)
         
     | 
| 
      
 84 
     | 
    
         
            +
                    @item_template = [string] + line_and_file
         
     | 
| 
      
 85 
     | 
    
         
            +
                  end
         
     | 
| 
      
 86 
     | 
    
         
            +
                end
         
     | 
| 
      
 87 
     | 
    
         
            +
              end
         
     | 
| 
      
 88 
     | 
    
         
            +
             
     | 
| 
      
 89 
     | 
    
         
            +
              class JSONTemplate < DynamicTemplate
         
     | 
| 
      
 90 
     | 
    
         
            +
                register :json
         
     | 
| 
      
 91 
     | 
    
         
            +
             
     | 
| 
      
 92 
     | 
    
         
            +
                hash "{[;nl;][;each pair h;][;indent 2;][;= @h:key;]: [;reapply @h:value;][;if @h+1;], [;/;][;/;][;nl;][;/;]}"
         
     | 
| 
      
 93 
     | 
    
         
            +
             
     | 
| 
      
 94 
     | 
    
         
            +
                list "[ [;each @ i;][;reapply @i;][;if @i+1;], [;/;][;/;] ]"
         
     | 
| 
      
 95 
     | 
    
         
            +
             
     | 
| 
      
 96 
     | 
    
         
            +
                item "[;=;]"
         
     | 
| 
      
 97 
     | 
    
         
            +
              end
         
     | 
| 
      
 98 
     | 
    
         
            +
            end
         
     | 
| 
         @@ -0,0 +1,35 @@ 
     | 
|
| 
      
 1 
     | 
    
         
            +
             
     | 
| 
      
 2 
     | 
    
         
            +
            module Stencil
         
     | 
| 
      
 3 
     | 
    
         
            +
              class EvaluationHost
         
     | 
| 
      
 4 
     | 
    
         
            +
                remove_methods = self.instance_methods(true)
         
     | 
| 
      
 5 
     | 
    
         
            +
                remove_methods -= %w{binding extend}
         
     | 
| 
      
 6 
     | 
    
         
            +
                old_verbose = $VERBOSE
         
     | 
| 
      
 7 
     | 
    
         
            +
                $VERBOSE = nil
         
     | 
| 
      
 8 
     | 
    
         
            +
                remove_methods.each do |method|
         
     | 
| 
      
 9 
     | 
    
         
            +
                  undef_method(method)
         
     | 
| 
      
 10 
     | 
    
         
            +
                end
         
     | 
| 
      
 11 
     | 
    
         
            +
                $VERBOSE = old_verbose
         
     | 
| 
      
 12 
     | 
    
         
            +
             
     | 
| 
      
 13 
     | 
    
         
            +
                remove_constants = constants
         
     | 
| 
      
 14 
     | 
    
         
            +
                remove_constants.each do |const|
         
     | 
| 
      
 15 
     | 
    
         
            +
                  remove_const(const)
         
     | 
| 
      
 16 
     | 
    
         
            +
                end
         
     | 
| 
      
 17 
     | 
    
         
            +
             
     | 
| 
      
 18 
     | 
    
         
            +
                def self.const_missing(name)
         
     | 
| 
      
 19 
     | 
    
         
            +
                  raise "Access to constants (including classes) is not allowed in template processing"
         
     | 
| 
      
 20 
     | 
    
         
            +
                end
         
     | 
| 
      
 21 
     | 
    
         
            +
             
     | 
| 
      
 22 
     | 
    
         
            +
                def initialize(data)
         
     | 
| 
      
 23 
     | 
    
         
            +
                  @data = data
         
     | 
| 
      
 24 
     | 
    
         
            +
                end
         
     | 
| 
      
 25 
     | 
    
         
            +
             
     | 
| 
      
 26 
     | 
    
         
            +
                def val(path)
         
     | 
| 
      
 27 
     | 
    
         
            +
                  @data.access(path)
         
     | 
| 
      
 28 
     | 
    
         
            +
                end
         
     | 
| 
      
 29 
     | 
    
         
            +
             
     | 
| 
      
 30 
     | 
    
         
            +
                def run(code)
         
     | 
| 
      
 31 
     | 
    
         
            +
                  code = code.gsub(/@([^\s.]*)/){|m| "val('#{$1}')"}
         
     | 
| 
      
 32 
     | 
    
         
            +
                  return ::Kernel::eval(code, binding)
         
     | 
| 
      
 33 
     | 
    
         
            +
                end
         
     | 
| 
      
 34 
     | 
    
         
            +
              end
         
     | 
| 
      
 35 
     | 
    
         
            +
            end
         
     | 
| 
         @@ -0,0 +1,146 @@ 
     | 
|
| 
      
 1 
     | 
    
         
            +
            module Kernel
         
     | 
| 
      
 2 
     | 
    
         
            +
              def at_path(*args)
         
     | 
| 
      
 3 
     | 
    
         
            +
                Stencil::ViewMatcher::AtPath::Expectation.new(args, self)
         
     | 
| 
      
 4 
     | 
    
         
            +
              end
         
     | 
| 
      
 5 
     | 
    
         
            +
            end
         
     | 
| 
      
 6 
     | 
    
         
            +
             
     | 
| 
      
 7 
     | 
    
         
            +
            module Stencil
         
     | 
| 
      
 8 
     | 
    
         
            +
              module ViewMatcher
         
     | 
| 
      
 9 
     | 
    
         
            +
                def have_subview(view)
         
     | 
| 
      
 10 
     | 
    
         
            +
                  return SubviewMatcher.new(view)
         
     | 
| 
      
 11 
     | 
    
         
            +
                end
         
     | 
| 
      
 12 
     | 
    
         
            +
             
     | 
| 
      
 13 
     | 
    
         
            +
                class SubviewMatcher
         
     | 
| 
      
 14 
     | 
    
         
            +
                  def initialize(subview)
         
     | 
| 
      
 15 
     | 
    
         
            +
                    @subview = subview
         
     | 
| 
      
 16 
     | 
    
         
            +
                  end
         
     | 
| 
      
 17 
     | 
    
         
            +
             
     | 
| 
      
 18 
     | 
    
         
            +
                  def matches?(view)
         
     | 
| 
      
 19 
     | 
    
         
            +
                    @view = view
         
     | 
| 
      
 20 
     | 
    
         
            +
                    create_submatches([], @subview)
         
     | 
| 
      
 21 
     | 
    
         
            +
                    return true
         
     | 
| 
      
 22 
     | 
    
         
            +
                  end
         
     | 
| 
      
 23 
     | 
    
         
            +
             
     | 
| 
      
 24 
     | 
    
         
            +
                  def create_submatches(path, subview)
         
     | 
| 
      
 25 
     | 
    
         
            +
                    case subview
         
     | 
| 
      
 26 
     | 
    
         
            +
                    when Hash
         
     | 
| 
      
 27 
     | 
    
         
            +
                      subview.each_pair do |name, value|
         
     | 
| 
      
 28 
     | 
    
         
            +
                        create_submatches(path + [name], value)
         
     | 
| 
      
 29 
     | 
    
         
            +
                      end
         
     | 
| 
      
 30 
     | 
    
         
            +
                    when Array
         
     | 
| 
      
 31 
     | 
    
         
            +
                      subview.each_with_index do |value, index|
         
     | 
| 
      
 32 
     | 
    
         
            +
                        create_submatches(path + [index], value)
         
     | 
| 
      
 33 
     | 
    
         
            +
                      end
         
     | 
| 
      
 34 
     | 
    
         
            +
                    else
         
     | 
| 
      
 35 
     | 
    
         
            +
                      if subview.respond_to?(:matches?)
         
     | 
| 
      
 36 
     | 
    
         
            +
                        @view.at_path(*path).should(subview)
         
     | 
| 
      
 37 
     | 
    
         
            +
                      else
         
     | 
| 
      
 38 
     | 
    
         
            +
                        @view.at_path(*path).should == subview
         
     | 
| 
      
 39 
     | 
    
         
            +
                      end
         
     | 
| 
      
 40 
     | 
    
         
            +
                    end
         
     | 
| 
      
 41 
     | 
    
         
            +
                  end
         
     | 
| 
      
 42 
     | 
    
         
            +
                end
         
     | 
| 
      
 43 
     | 
    
         
            +
             
     | 
| 
      
 44 
     | 
    
         
            +
                module AtPath
         
     | 
| 
      
 45 
     | 
    
         
            +
                  class PositiveOperatorMatcher < ::Spec::Matchers::PositiveOperatorMatcher
         
     | 
| 
      
 46 
     | 
    
         
            +
                    def initialize(path, actual)
         
     | 
| 
      
 47 
     | 
    
         
            +
                      @path = path
         
     | 
| 
      
 48 
     | 
    
         
            +
                      super(actual)
         
     | 
| 
      
 49 
     | 
    
         
            +
                    end
         
     | 
| 
      
 50 
     | 
    
         
            +
             
     | 
| 
      
 51 
     | 
    
         
            +
                    ['==', '===', '=~', '>', '>=', '<', '<='].each do |operator|
         
     | 
| 
      
 52 
     | 
    
         
            +
                      define_method(operator) do |expected|
         
     | 
| 
      
 53 
     | 
    
         
            +
                        begin
         
     | 
| 
      
 54 
     | 
    
         
            +
                          super
         
     | 
| 
      
 55 
     | 
    
         
            +
                        rescue ::Spec::Expectations::ExpectationNotMetError => enme
         
     | 
| 
      
 56 
     | 
    
         
            +
                          message = "At #{@path.inspect}\n" + enme.message
         
     | 
| 
      
 57 
     | 
    
         
            +
                          Kernel::raise(Spec::Expectations::ExpectationNotMetError.new(message))
         
     | 
| 
      
 58 
     | 
    
         
            +
                        end
         
     | 
| 
      
 59 
     | 
    
         
            +
                      end
         
     | 
| 
      
 60 
     | 
    
         
            +
                    end
         
     | 
| 
      
 61 
     | 
    
         
            +
                  end
         
     | 
| 
      
 62 
     | 
    
         
            +
             
     | 
| 
      
 63 
     | 
    
         
            +
                  class NegativeOperatorMatcher < ::Spec::Matchers::NegativeOperatorMatcher
         
     | 
| 
      
 64 
     | 
    
         
            +
                    def initialize(path, actual)
         
     | 
| 
      
 65 
     | 
    
         
            +
                      @path = path
         
     | 
| 
      
 66 
     | 
    
         
            +
                      super(actual)
         
     | 
| 
      
 67 
     | 
    
         
            +
                    end
         
     | 
| 
      
 68 
     | 
    
         
            +
             
     | 
| 
      
 69 
     | 
    
         
            +
                    ['==', '===', '=~', '>', '>=', '<', '<='].each do |operator|
         
     | 
| 
      
 70 
     | 
    
         
            +
                      define_method(operator) do |expected|
         
     | 
| 
      
 71 
     | 
    
         
            +
                        begin
         
     | 
| 
      
 72 
     | 
    
         
            +
                          super
         
     | 
| 
      
 73 
     | 
    
         
            +
                        rescue ::Spec::Expectations::ExpectationNotMetError => enme
         
     | 
| 
      
 74 
     | 
    
         
            +
                          message = "At #{@path.inspect}\n" + enme.message
         
     | 
| 
      
 75 
     | 
    
         
            +
                          Kernel::raise(Spec::Expectations::ExpectationNotMetError.new(message))
         
     | 
| 
      
 76 
     | 
    
         
            +
                        end
         
     | 
| 
      
 77 
     | 
    
         
            +
                      end
         
     | 
| 
      
 78 
     | 
    
         
            +
                    end
         
     | 
| 
      
 79 
     | 
    
         
            +
                  end
         
     | 
| 
      
 80 
     | 
    
         
            +
             
     | 
| 
      
 81 
     | 
    
         
            +
                  class Expectation
         
     | 
| 
      
 82 
     | 
    
         
            +
                    def initialize(path, actual)
         
     | 
| 
      
 83 
     | 
    
         
            +
                      @path, @actual = path, actual
         
     | 
| 
      
 84 
     | 
    
         
            +
                      @thumb = @actual
         
     | 
| 
      
 85 
     | 
    
         
            +
                      @into = []
         
     | 
| 
      
 86 
     | 
    
         
            +
                    end
         
     | 
| 
      
 87 
     | 
    
         
            +
             
     | 
| 
      
 88 
     | 
    
         
            +
                    def should(matcher=nil, &block)
         
     | 
| 
      
 89 
     | 
    
         
            +
                      dereference
         
     | 
| 
      
 90 
     | 
    
         
            +
                      if matcher.nil?
         
     | 
| 
      
 91 
     | 
    
         
            +
                        ::Spec::Matchers.last_should = :should
         
     | 
| 
      
 92 
     | 
    
         
            +
                        ::Spec::Matchers.last_matcher = matcher
         
     | 
| 
      
 93 
     | 
    
         
            +
                        return PositiveOperatorMatcher.new(@path, @thumb)
         
     | 
| 
      
 94 
     | 
    
         
            +
                      end
         
     | 
| 
      
 95 
     | 
    
         
            +
             
     | 
| 
      
 96 
     | 
    
         
            +
                      begin
         
     | 
| 
      
 97 
     | 
    
         
            +
                        Spec::Expectations::PositiveExpectationHandler.handle_matcher(@thumb, matcher, &block)
         
     | 
| 
      
 98 
     | 
    
         
            +
                      rescue ::Spec::Expectations::ExpectationNotMetError => enme
         
     | 
| 
      
 99 
     | 
    
         
            +
                        message = "At #{@path.inspect}\n" + enme.message
         
     | 
| 
      
 100 
     | 
    
         
            +
                        Kernel::raise(Spec::Expectations::ExpectationNotMetError.new(message))
         
     | 
| 
      
 101 
     | 
    
         
            +
                      end
         
     | 
| 
      
 102 
     | 
    
         
            +
                    end
         
     | 
| 
      
 103 
     | 
    
         
            +
             
     | 
| 
      
 104 
     | 
    
         
            +
                    def should_not(matcher=nil, &block)
         
     | 
| 
      
 105 
     | 
    
         
            +
                      dereference
         
     | 
| 
      
 106 
     | 
    
         
            +
             
     | 
| 
      
 107 
     | 
    
         
            +
                      if matcher.nil?
         
     | 
| 
      
 108 
     | 
    
         
            +
                        ::Spec::Matchers.last_should = :should_not
         
     | 
| 
      
 109 
     | 
    
         
            +
                        ::Spec::Matchers.last_matcher = matcher
         
     | 
| 
      
 110 
     | 
    
         
            +
                        return NegativeOperatorMatcher.new(@path, @thumb) 
         
     | 
| 
      
 111 
     | 
    
         
            +
                      end
         
     | 
| 
      
 112 
     | 
    
         
            +
             
     | 
| 
      
 113 
     | 
    
         
            +
                      begin
         
     | 
| 
      
 114 
     | 
    
         
            +
                        Spec::Expectations::NegativeExpectationHandler.handle_matcher(@thumb, matcher, &block)
         
     | 
| 
      
 115 
     | 
    
         
            +
                      rescue ::Spec::Expectations::ExpectationNotMetError => enme
         
     | 
| 
      
 116 
     | 
    
         
            +
                        message = "At #{@path.inspect}\n" + enme.message
         
     | 
| 
      
 117 
     | 
    
         
            +
                        Kernel::raise(Spec::Expectations::ExpectationNotMetError.new(message))
         
     | 
| 
      
 118 
     | 
    
         
            +
                      end
         
     | 
| 
      
 119 
     | 
    
         
            +
                    end
         
     | 
| 
      
 120 
     | 
    
         
            +
             
     | 
| 
      
 121 
     | 
    
         
            +
                    def dereference
         
     | 
| 
      
 122 
     | 
    
         
            +
                      @path.each do |segment|
         
     | 
| 
      
 123 
     | 
    
         
            +
                        @into << segment
         
     | 
| 
      
 124 
     | 
    
         
            +
                        case @thumb
         
     | 
| 
      
 125 
     | 
    
         
            +
                        when Array
         
     | 
| 
      
 126 
     | 
    
         
            +
                          unless Integer === segment
         
     | 
| 
      
 127 
     | 
    
         
            +
                            deref_fail
         
     | 
| 
      
 128 
     | 
    
         
            +
                          end
         
     | 
| 
      
 129 
     | 
    
         
            +
                        when Hash
         
     | 
| 
      
 130 
     | 
    
         
            +
                          unless @thumb.has_key?(segment)
         
     | 
| 
      
 131 
     | 
    
         
            +
                            deref_fail
         
     | 
| 
      
 132 
     | 
    
         
            +
                          end
         
     | 
| 
      
 133 
     | 
    
         
            +
                        else
         
     | 
| 
      
 134 
     | 
    
         
            +
                          deref_fail
         
     | 
| 
      
 135 
     | 
    
         
            +
                        end
         
     | 
| 
      
 136 
     | 
    
         
            +
                        @thumb = @thumb[segment] rescue deref_fail
         
     | 
| 
      
 137 
     | 
    
         
            +
                      end
         
     | 
| 
      
 138 
     | 
    
         
            +
                    end
         
     | 
| 
      
 139 
     | 
    
         
            +
             
     | 
| 
      
 140 
     | 
    
         
            +
                    def deref_fail
         
     | 
| 
      
 141 
     | 
    
         
            +
                      ::Spec::Expectations.fail_with("Member: #{@thumb.class.name} at (#{@into[0..-2].inspect}) has no index #{@into.last.inspect}")
         
     | 
| 
      
 142 
     | 
    
         
            +
                    end
         
     | 
| 
      
 143 
     | 
    
         
            +
                  end
         
     | 
| 
      
 144 
     | 
    
         
            +
                end
         
     | 
| 
      
 145 
     | 
    
         
            +
              end
         
     | 
| 
      
 146 
     | 
    
         
            +
            end
         
     | 
| 
         @@ -0,0 +1,269 @@ 
     | 
|
| 
      
 1 
     | 
    
         
            +
            require 'strscan'
         
     | 
| 
      
 2 
     | 
    
         
            +
            require 'stencil/processor'
         
     | 
| 
      
 3 
     | 
    
         
            +
            require 'stencil/directives'
         
     | 
| 
      
 4 
     | 
    
         
            +
             
     | 
| 
      
 5 
     | 
    
         
            +
            module Stencil
         
     | 
| 
      
 6 
     | 
    
         
            +
              class Exception < ::Exception; end
         
     | 
| 
      
 7 
     | 
    
         
            +
              class RenderError < Exception
         
     | 
| 
      
 8 
     | 
    
         
            +
                attr_accessor :line, :column, :file, :based_on
         
     | 
| 
      
 9 
     | 
    
         
            +
             
     | 
| 
      
 10 
     | 
    
         
            +
                def message
         
     | 
| 
      
 11 
     | 
    
         
            +
                  super + " at: #@file:#@line,#@column"
         
     | 
| 
      
 12 
     | 
    
         
            +
                end
         
     | 
| 
      
 13 
     | 
    
         
            +
              end
         
     | 
| 
      
 14 
     | 
    
         
            +
              class ParseError < Exception; end
         
     | 
| 
      
 15 
     | 
    
         
            +
             
     | 
| 
      
 16 
     | 
    
         
            +
             
     | 
| 
      
 17 
     | 
    
         
            +
              class Data
         
     | 
| 
      
 18 
     | 
    
         
            +
                def initialize(data)
         
     | 
| 
      
 19 
     | 
    
         
            +
                  @data = data
         
     | 
| 
      
 20 
     | 
    
         
            +
                  @path = []
         
     | 
| 
      
 21 
     | 
    
         
            +
                end
         
     | 
| 
      
 22 
     | 
    
         
            +
             
     | 
| 
      
 23 
     | 
    
         
            +
                def relative(string)
         
     | 
| 
      
 24 
     | 
    
         
            +
                  path = @path.dup
         
     | 
| 
      
 25 
     | 
    
         
            +
                  s = StringScanner.new(string||"")
         
     | 
| 
      
 26 
     | 
    
         
            +
             
     | 
| 
      
 27 
     | 
    
         
            +
                  goto = s.scan(/[^:#]*/)
         
     | 
| 
      
 28 
     | 
    
         
            +
                  g = StringScanner.new(goto)
         
     | 
| 
      
 29 
     | 
    
         
            +
             
     | 
| 
      
 30 
     | 
    
         
            +
                  while g.rest?
         
     | 
| 
      
 31 
     | 
    
         
            +
                    go = g.getch
         
     | 
| 
      
 32 
     | 
    
         
            +
             
     | 
| 
      
 33 
     | 
    
         
            +
                    case go
         
     | 
| 
      
 34 
     | 
    
         
            +
                    when "^"
         
     | 
| 
      
 35 
     | 
    
         
            +
                      path.pop
         
     | 
| 
      
 36 
     | 
    
         
            +
                    when "/"
         
     | 
| 
      
 37 
     | 
    
         
            +
                      path.clear
         
     | 
| 
      
 38 
     | 
    
         
            +
                    when "+"
         
     | 
| 
      
 39 
     | 
    
         
            +
                      number = g.scan(/\d+/).to_i
         
     | 
| 
      
 40 
     | 
    
         
            +
                      here = path.pop
         
     | 
| 
      
 41 
     | 
    
         
            +
                      path.push(here + number)
         
     | 
| 
      
 42 
     | 
    
         
            +
                    when "-"
         
     | 
| 
      
 43 
     | 
    
         
            +
                      number = g.scan(/\d+/).to_i
         
     | 
| 
      
 44 
     | 
    
         
            +
                      here = path.pop
         
     | 
| 
      
 45 
     | 
    
         
            +
                      path.push(here - number)
         
     | 
| 
      
 46 
     | 
    
         
            +
                    else 
         
     | 
| 
      
 47 
     | 
    
         
            +
                      raise "Unrecognized path offset: #{go}"
         
     | 
| 
      
 48 
     | 
    
         
            +
                    end
         
     | 
| 
      
 49 
     | 
    
         
            +
                  end
         
     | 
| 
      
 50 
     | 
    
         
            +
             
     | 
| 
      
 51 
     | 
    
         
            +
                  while s.rest?
         
     | 
| 
      
 52 
     | 
    
         
            +
                    delim = s.getch
         
     | 
| 
      
 53 
     | 
    
         
            +
                    segment = s.scan(/[^:#]*/)
         
     | 
| 
      
 54 
     | 
    
         
            +
                      case delim
         
     | 
| 
      
 55 
     | 
    
         
            +
                      when ":"
         
     | 
| 
      
 56 
     | 
    
         
            +
                        path << segment
         
     | 
| 
      
 57 
     | 
    
         
            +
                      when "#"
         
     | 
| 
      
 58 
     | 
    
         
            +
                        path << segment.to_i
         
     | 
| 
      
 59 
     | 
    
         
            +
                      end
         
     | 
| 
      
 60 
     | 
    
         
            +
                  end
         
     | 
| 
      
 61 
     | 
    
         
            +
             
     | 
| 
      
 62 
     | 
    
         
            +
                  path
         
     | 
| 
      
 63 
     | 
    
         
            +
                end
         
     | 
| 
      
 64 
     | 
    
         
            +
             
     | 
| 
      
 65 
     | 
    
         
            +
                def recontext(string)
         
     | 
| 
      
 66 
     | 
    
         
            +
                  result = Data.new(@data)
         
     | 
| 
      
 67 
     | 
    
         
            +
                  result.path = relative(string)
         
     | 
| 
      
 68 
     | 
    
         
            +
                  return result
         
     | 
| 
      
 69 
     | 
    
         
            +
                end
         
     | 
| 
      
 70 
     | 
    
         
            +
             
     | 
| 
      
 71 
     | 
    
         
            +
                attr_accessor :data, :point, :path
         
     | 
| 
      
 72 
     | 
    
         
            +
             
     | 
| 
      
 73 
     | 
    
         
            +
                def access(string)
         
     | 
| 
      
 74 
     | 
    
         
            +
                  data = @data
         
     | 
| 
      
 75 
     | 
    
         
            +
                  path = relative(string)
         
     | 
| 
      
 76 
     | 
    
         
            +
                  path.each do |segment|
         
     | 
| 
      
 77 
     | 
    
         
            +
                    data = data[segment]
         
     | 
| 
      
 78 
     | 
    
         
            +
                  end
         
     | 
| 
      
 79 
     | 
    
         
            +
                  return data
         
     | 
| 
      
 80 
     | 
    
         
            +
                end
         
     | 
| 
      
 81 
     | 
    
         
            +
              end
         
     | 
| 
      
 82 
     | 
    
         
            +
             
     | 
| 
      
 83 
     | 
    
         
            +
             
     | 
| 
      
 84 
     | 
    
         
            +
              class Template
         
     | 
| 
      
 85 
     | 
    
         
            +
                class DataContext
         
     | 
| 
      
 86 
     | 
    
         
            +
                  def initialize(data)
         
     | 
| 
      
 87 
     | 
    
         
            +
                    @datasets = Hash.new{|h,k| h[k]=[]}
         
     | 
| 
      
 88 
     | 
    
         
            +
                    @datasets[nil] << Data.new(data)
         
     | 
| 
      
 89 
     | 
    
         
            +
                  end
         
     | 
| 
      
 90 
     | 
    
         
            +
             
     | 
| 
      
 91 
     | 
    
         
            +
                  def context_name(path)
         
     | 
| 
      
 92 
     | 
    
         
            +
                    m = /^@?([a-z]*)/.match(path)
         
     | 
| 
      
 93 
     | 
    
         
            +
                    which = m[1]
         
     | 
| 
      
 94 
     | 
    
         
            +
                    which = nil if which.empty?
         
     | 
| 
      
 95 
     | 
    
         
            +
                    path = m.post_match
         
     | 
| 
      
 96 
     | 
    
         
            +
                    return which,path
         
     | 
| 
      
 97 
     | 
    
         
            +
                  end
         
     | 
| 
      
 98 
     | 
    
         
            +
             
     | 
| 
      
 99 
     | 
    
         
            +
                  def push_context(path, onto=nil)
         
     | 
| 
      
 100 
     | 
    
         
            +
                    which, path = context_name(path)
         
     | 
| 
      
 101 
     | 
    
         
            +
                    @datasets[onto] << @datasets[which].last.recontext(path)
         
     | 
| 
      
 102 
     | 
    
         
            +
                  end
         
     | 
| 
      
 103 
     | 
    
         
            +
             
     | 
| 
      
 104 
     | 
    
         
            +
                  def pop_context(from=nil)
         
     | 
| 
      
 105 
     | 
    
         
            +
                    @datasets[from].pop
         
     | 
| 
      
 106 
     | 
    
         
            +
                  end
         
     | 
| 
      
 107 
     | 
    
         
            +
             
     | 
| 
      
 108 
     | 
    
         
            +
                  def with_context(path, onto = nil)
         
     | 
| 
      
 109 
     | 
    
         
            +
                    push_context(path, onto)
         
     | 
| 
      
 110 
     | 
    
         
            +
                    result = yield(self)
         
     | 
| 
      
 111 
     | 
    
         
            +
                    pop_context(onto)
         
     | 
| 
      
 112 
     | 
    
         
            +
                    return result
         
     | 
| 
      
 113 
     | 
    
         
            +
                  end
         
     | 
| 
      
 114 
     | 
    
         
            +
             
     | 
| 
      
 115 
     | 
    
         
            +
                  def inject_dataset(name, dataset)
         
     | 
| 
      
 116 
     | 
    
         
            +
                    @datasets[name] << Data.new(dataset)
         
     | 
| 
      
 117 
     | 
    
         
            +
                  end
         
     | 
| 
      
 118 
     | 
    
         
            +
             
     | 
| 
      
 119 
     | 
    
         
            +
                  def access(path)
         
     | 
| 
      
 120 
     | 
    
         
            +
                    which, path = context_name(path)
         
     | 
| 
      
 121 
     | 
    
         
            +
                    @datasets[which].last.access(path)
         
     | 
| 
      
 122 
     | 
    
         
            +
                  end
         
     | 
| 
      
 123 
     | 
    
         
            +
                end
         
     | 
| 
      
 124 
     | 
    
         
            +
             
     | 
| 
      
 125 
     | 
    
         
            +
                class State
         
     | 
| 
      
 126 
     | 
    
         
            +
                  def initialize(data, &file_opener)
         
     | 
| 
      
 127 
     | 
    
         
            +
                    @data = data
         
     | 
| 
      
 128 
     | 
    
         
            +
                    @file_opener = file_opener
         
     | 
| 
      
 129 
     | 
    
         
            +
                  end
         
     | 
| 
      
 130 
     | 
    
         
            +
             
     | 
| 
      
 131 
     | 
    
         
            +
                  def get_template(path)
         
     | 
| 
      
 132 
     | 
    
         
            +
                    Template::file(path, &@file_opener)
         
     | 
| 
      
 133 
     | 
    
         
            +
                  end
         
     | 
| 
      
 134 
     | 
    
         
            +
             
     | 
| 
      
 135 
     | 
    
         
            +
                  attr_accessor :data
         
     | 
| 
      
 136 
     | 
    
         
            +
                end
         
     | 
| 
      
 137 
     | 
    
         
            +
             
     | 
| 
      
 138 
     | 
    
         
            +
                DefaultFileOpener = proc do |path|
         
     | 
| 
      
 139 
     | 
    
         
            +
                    File.open(path) do |file|
         
     | 
| 
      
 140 
     | 
    
         
            +
                      file.read
         
     | 
| 
      
 141 
     | 
    
         
            +
                    end
         
     | 
| 
      
 142 
     | 
    
         
            +
                  end
         
     | 
| 
      
 143 
     | 
    
         
            +
             
     | 
| 
      
 144 
     | 
    
         
            +
             
     | 
| 
      
 145 
     | 
    
         
            +
                class << self
         
     | 
| 
      
 146 
     | 
    
         
            +
                  def string(data, file=nil, line=nil, &file_open)
         
     | 
| 
      
 147 
     | 
    
         
            +
                    m = %r{^([^:]+):(\d+)}.match caller[0]
         
     | 
| 
      
 148 
     | 
    
         
            +
                    file ||= m[1] unless m.nil?
         
     | 
| 
      
 149 
     | 
    
         
            +
                    line ||= m[2].to_i unless m.nil?
         
     | 
| 
      
 150 
     | 
    
         
            +
             
     | 
| 
      
 151 
     | 
    
         
            +
                    file ||= ""
         
     | 
| 
      
 152 
     | 
    
         
            +
                    line ||= 1
         
     | 
| 
      
 153 
     | 
    
         
            +
                    template = self.new(data, file, line)
         
     | 
| 
      
 154 
     | 
    
         
            +
                    template.file_opener = file_open unless file_open.nil?
         
     | 
| 
      
 155 
     | 
    
         
            +
                    return template
         
     | 
| 
      
 156 
     | 
    
         
            +
                  end
         
     | 
| 
      
 157 
     | 
    
         
            +
             
     | 
| 
      
 158 
     | 
    
         
            +
                  def file(path, &file_open)
         
     | 
| 
      
 159 
     | 
    
         
            +
                    file_open ||= DefaultFileOpener
         
     | 
| 
      
 160 
     | 
    
         
            +
                    template = self.new(file_open[path], path)
         
     | 
| 
      
 161 
     | 
    
         
            +
                    template.file_opener = file_open
         
     | 
| 
      
 162 
     | 
    
         
            +
                    return template
         
     | 
| 
      
 163 
     | 
    
         
            +
                  end
         
     | 
| 
      
 164 
     | 
    
         
            +
                end
         
     | 
| 
      
 165 
     | 
    
         
            +
             
     | 
| 
      
 166 
     | 
    
         
            +
                def initialize(template, file="", line=1, column=1)
         
     | 
| 
      
 167 
     | 
    
         
            +
                  @template = parse(template, file, line, column)
         
     | 
| 
      
 168 
     | 
    
         
            +
                  @file_opener = DefaultFileOpener
         
     | 
| 
      
 169 
     | 
    
         
            +
                end
         
     | 
| 
      
 170 
     | 
    
         
            +
             
     | 
| 
      
 171 
     | 
    
         
            +
                class DocumentScanner
         
     | 
| 
      
 172 
     | 
    
         
            +
                  def initialize(string, file="", line=1, column=1)
         
     | 
| 
      
 173 
     | 
    
         
            +
                    @file = file
         
     | 
| 
      
 174 
     | 
    
         
            +
                    @begin_line, @begin_column = line,column
         
     | 
| 
      
 175 
     | 
    
         
            +
                    @end_line, @end_column = line,column
         
     | 
| 
      
 176 
     | 
    
         
            +
                    @scanner = StringScanner.new(string)
         
     | 
| 
      
 177 
     | 
    
         
            +
                  end
         
     | 
| 
      
 178 
     | 
    
         
            +
                
         
     | 
| 
      
 179 
     | 
    
         
            +
                  attr_accessor :file, :line, :col
         
     | 
| 
      
 180 
     | 
    
         
            +
             
     | 
| 
      
 181 
     | 
    
         
            +
                  def scan(pattern)
         
     | 
| 
      
 182 
     | 
    
         
            +
                    result = @scanner.scan(pattern)
         
     | 
| 
      
 183 
     | 
    
         
            +
                    return nil if result.nil?
         
     | 
| 
      
 184 
     | 
    
         
            +
             
     | 
| 
      
 185 
     | 
    
         
            +
                    lines = result.split("\n")
         
     | 
| 
      
 186 
     | 
    
         
            +
                    @begin_line = @end_line
         
     | 
| 
      
 187 
     | 
    
         
            +
                    @begin_column = @end_column
         
     | 
| 
      
 188 
     | 
    
         
            +
                    if lines.length > 1
         
     | 
| 
      
 189 
     | 
    
         
            +
                      @end_line += lines.length - 1
         
     | 
| 
      
 190 
     | 
    
         
            +
                      @end_column = lines.last.length + 1
         
     | 
| 
      
 191 
     | 
    
         
            +
                    else
         
     | 
| 
      
 192 
     | 
    
         
            +
                      @end_column += result.length
         
     | 
| 
      
 193 
     | 
    
         
            +
                    end
         
     | 
| 
      
 194 
     | 
    
         
            +
                    return result
         
     | 
| 
      
 195 
     | 
    
         
            +
                  end
         
     | 
| 
      
 196 
     | 
    
         
            +
             
     | 
| 
      
 197 
     | 
    
         
            +
                  def rest
         
     | 
| 
      
 198 
     | 
    
         
            +
                    @scanner.rest
         
     | 
| 
      
 199 
     | 
    
         
            +
                  end
         
     | 
| 
      
 200 
     | 
    
         
            +
             
     | 
| 
      
 201 
     | 
    
         
            +
                  def rest?
         
     | 
| 
      
 202 
     | 
    
         
            +
                    @scanner.rest?
         
     | 
| 
      
 203 
     | 
    
         
            +
                  end
         
     | 
| 
      
 204 
     | 
    
         
            +
             
     | 
| 
      
 205 
     | 
    
         
            +
                  def location
         
     | 
| 
      
 206 
     | 
    
         
            +
                    Directive::Source.new(@file, @begin_line, @begin_column)
         
     | 
| 
      
 207 
     | 
    
         
            +
                  end
         
     | 
| 
      
 208 
     | 
    
         
            +
                end
         
     | 
| 
      
 209 
     | 
    
         
            +
                attr_writer :file_opener
         
     | 
| 
      
 210 
     | 
    
         
            +
             
     | 
| 
      
 211 
     | 
    
         
            +
                BeginDirective = %r{\[;}m
         
     | 
| 
      
 212 
     | 
    
         
            +
                  EndDirective = %r{;\]}m
         
     | 
| 
      
 213 
     | 
    
         
            +
                    UntilDirective = %r{.*?(?=#{BeginDirective})}m
         
     | 
| 
      
 214 
     | 
    
         
            +
                    FinishDirective = %r{.*?(?=#{EndDirective})}m
         
     | 
| 
      
 215 
     | 
    
         
            +
             
     | 
| 
      
 216 
     | 
    
         
            +
             
     | 
| 
      
 217 
     | 
    
         
            +
                    def parse(template, file="", line=1, column=1)
         
     | 
| 
      
 218 
     | 
    
         
            +
                      scanner = DocumentScanner.new(template, file, line, column)
         
     | 
| 
      
 219 
     | 
    
         
            +
                      result = [Block.new(scanner.location, "")]
         
     | 
| 
      
 220 
     | 
    
         
            +
                      last_newline = 0
         
     | 
| 
      
 221 
     | 
    
         
            +
             
     | 
| 
      
 222 
     | 
    
         
            +
                      while scanner.rest?
         
     | 
| 
      
 223 
     | 
    
         
            +
                        literal = scanner.scan(UntilDirective)
         
     | 
| 
      
 224 
     | 
    
         
            +
             
     | 
| 
      
 225 
     | 
    
         
            +
                        unless literal.nil? or literal.empty? 
         
     | 
| 
      
 226 
     | 
    
         
            +
                          result.last.add(Literal.new(scanner.location, literal))
         
     | 
| 
      
 227 
     | 
    
         
            +
                        end
         
     | 
| 
      
 228 
     | 
    
         
            +
             
     | 
| 
      
 229 
     | 
    
         
            +
                        delim = scanner.scan(BeginDirective)
         
     | 
| 
      
 230 
     | 
    
         
            +
             
     | 
| 
      
 231 
     | 
    
         
            +
                        if delim.nil?
         
     | 
| 
      
 232 
     | 
    
         
            +
                          result.last.add(Literal.new(scanner.location, scanner.rest))
         
     | 
| 
      
 233 
     | 
    
         
            +
                          break
         
     | 
| 
      
 234 
     | 
    
         
            +
                        end
         
     | 
| 
      
 235 
     | 
    
         
            +
             
     | 
| 
      
 236 
     | 
    
         
            +
                        directive = scanner.scan(FinishDirective)
         
     | 
| 
      
 237 
     | 
    
         
            +
             
     | 
| 
      
 238 
     | 
    
         
            +
                        directive = Directive.create(scanner.location, directive)
         
     | 
| 
      
 239 
     | 
    
         
            +
                        directive.parsed(result)
         
     | 
| 
      
 240 
     | 
    
         
            +
             
     | 
| 
      
 241 
     | 
    
         
            +
                        scanner.scan(EndDirective)
         
     | 
| 
      
 242 
     | 
    
         
            +
                      end
         
     | 
| 
      
 243 
     | 
    
         
            +
             
     | 
| 
      
 244 
     | 
    
         
            +
                      raise "Unterminated directives: #{result[1..-1].inspect}" unless result.length == 1
         
     | 
| 
      
 245 
     | 
    
         
            +
                      result.last
         
     | 
| 
      
 246 
     | 
    
         
            +
                    end
         
     | 
| 
      
 247 
     | 
    
         
            +
             
     | 
| 
      
 248 
     | 
    
         
            +
                    def render(data)
         
     | 
| 
      
 249 
     | 
    
         
            +
                      data_context = DataContext.new(data)
         
     | 
| 
      
 250 
     | 
    
         
            +
                      render_raw(data_context).compact.join("")
         
     | 
| 
      
 251 
     | 
    
         
            +
                    end
         
     | 
| 
      
 252 
     | 
    
         
            +
             
     | 
| 
      
 253 
     | 
    
         
            +
                    def render_raw(data)
         
     | 
| 
      
 254 
     | 
    
         
            +
                      state = State.new(data, &@file_opener)
         
     | 
| 
      
 255 
     | 
    
         
            +
                      render_with(state, @template)
         
     | 
| 
      
 256 
     | 
    
         
            +
                    end
         
     | 
| 
      
 257 
     | 
    
         
            +
             
     | 
| 
      
 258 
     | 
    
         
            +
                    def render_with(state, template)
         
     | 
| 
      
 259 
     | 
    
         
            +
                      begin
         
     | 
| 
      
 260 
     | 
    
         
            +
                        result = template.checked_render(state)
         
     | 
| 
      
 261 
     | 
    
         
            +
                        return result
         
     | 
| 
      
 262 
     | 
    
         
            +
                      rescue RenderError
         
     | 
| 
      
 263 
     | 
    
         
            +
                        raise
         
     | 
| 
      
 264 
     | 
    
         
            +
                      rescue Object => ex
         
     | 
| 
      
 265 
     | 
    
         
            +
                        raise RenderError, [ex.class, ex.message].join(" ")
         
     | 
| 
      
 266 
     | 
    
         
            +
                      end
         
     | 
| 
      
 267 
     | 
    
         
            +
                    end
         
     | 
| 
      
 268 
     | 
    
         
            +
              end
         
     | 
| 
      
 269 
     | 
    
         
            +
            end
         
     | 
    
        data/lib/stencil/view.rb
    ADDED
    
    | 
         @@ -0,0 +1,133 @@ 
     | 
|
| 
      
 1 
     | 
    
         
            +
            module Stencil
         
     | 
| 
      
 2 
     | 
    
         
            +
              module ViewHost 
         
     | 
| 
      
 3 
     | 
    
         
            +
                class Renderer
         
     | 
| 
      
 4 
     | 
    
         
            +
                  def initialize(data, capsule)
         
     | 
| 
      
 5 
     | 
    
         
            +
                    @data = data
         
     | 
| 
      
 6 
     | 
    
         
            +
                    @capsule = capsule
         
     | 
| 
      
 7 
     | 
    
         
            +
                  end
         
     | 
| 
      
 8 
     | 
    
         
            +
             
     | 
| 
      
 9 
     | 
    
         
            +
                  def go
         
     | 
| 
      
 10 
     | 
    
         
            +
                    instance_eval(&@capsule.block)
         
     | 
| 
      
 11 
     | 
    
         
            +
                  end
         
     | 
| 
      
 12 
     | 
    
         
            +
             
     | 
| 
      
 13 
     | 
    
         
            +
                  attr_reader :data
         
     | 
| 
      
 14 
     | 
    
         
            +
                end
         
     | 
| 
      
 15 
     | 
    
         
            +
             
     | 
| 
      
 16 
     | 
    
         
            +
                class EvalRenderer < Renderer
         
     | 
| 
      
 17 
     | 
    
         
            +
                  def go
         
     | 
| 
      
 18 
     | 
    
         
            +
                    @data.instance_eval(&@capsule.block)
         
     | 
| 
      
 19 
     | 
    
         
            +
                  end
         
     | 
| 
      
 20 
     | 
    
         
            +
                end
         
     | 
| 
      
 21 
     | 
    
         
            +
             
     | 
| 
      
 22 
     | 
    
         
            +
                def view(mod = nil, &block)
         
     | 
| 
      
 23 
     | 
    
         
            +
                  render_class = EvalRenderer
         
     | 
| 
      
 24 
     | 
    
         
            +
                  unless mod.nil?
         
     | 
| 
      
 25 
     | 
    
         
            +
                    render_class = Class.new(EvalRenderer) do 
         
     | 
| 
      
 26 
     | 
    
         
            +
                      include(mod)
         
     | 
| 
      
 27 
     | 
    
         
            +
                    end
         
     | 
| 
      
 28 
     | 
    
         
            +
                  end
         
     | 
| 
      
 29 
     | 
    
         
            +
                  sub_view(render_class, &block)
         
     | 
| 
      
 30 
     | 
    
         
            +
                end
         
     | 
| 
      
 31 
     | 
    
         
            +
                module_function :view
         
     | 
| 
      
 32 
     | 
    
         
            +
             
     | 
| 
      
 33 
     | 
    
         
            +
                def sub_view(render_class, &block)
         
     | 
| 
      
 34 
     | 
    
         
            +
                  template = ViewDefinition.create_template(render_class, &block)
         
     | 
| 
      
 35 
     | 
    
         
            +
                  return View.new(template)
         
     | 
| 
      
 36 
     | 
    
         
            +
                end
         
     | 
| 
      
 37 
     | 
    
         
            +
                module_function :sub_view
         
     | 
| 
      
 38 
     | 
    
         
            +
              end
         
     | 
| 
      
 39 
     | 
    
         
            +
             
     | 
| 
      
 40 
     | 
    
         
            +
              class ViewDefinition
         
     | 
| 
      
 41 
     | 
    
         
            +
                def self.create_template(render_class, &block)
         
     | 
| 
      
 42 
     | 
    
         
            +
                  definer = self.new(render_class)
         
     | 
| 
      
 43 
     | 
    
         
            +
                  return definer.instance_eval(&block)
         
     | 
| 
      
 44 
     | 
    
         
            +
                end
         
     | 
| 
      
 45 
     | 
    
         
            +
             
     | 
| 
      
 46 
     | 
    
         
            +
                def initialize(render_class)
         
     | 
| 
      
 47 
     | 
    
         
            +
                  @render_class = render_class
         
     | 
| 
      
 48 
     | 
    
         
            +
                end
         
     | 
| 
      
 49 
     | 
    
         
            +
             
     | 
| 
      
 50 
     | 
    
         
            +
                def item(&block)
         
     | 
| 
      
 51 
     | 
    
         
            +
                  Capsule.new(@render_class, &block)
         
     | 
| 
      
 52 
     | 
    
         
            +
                end
         
     | 
| 
      
 53 
     | 
    
         
            +
             
     | 
| 
      
 54 
     | 
    
         
            +
                def list(&block)
         
     | 
| 
      
 55 
     | 
    
         
            +
                  ListCapsule.new(@render_class, &block)
         
     | 
| 
      
 56 
     | 
    
         
            +
                end
         
     | 
| 
      
 57 
     | 
    
         
            +
              end
         
     | 
| 
      
 58 
     | 
    
         
            +
             
     | 
| 
      
 59 
     | 
    
         
            +
              class Capsule
         
     | 
| 
      
 60 
     | 
    
         
            +
                def initialize(render_class, &block)
         
     | 
| 
      
 61 
     | 
    
         
            +
                  @render_class = render_class
         
     | 
| 
      
 62 
     | 
    
         
            +
                  @block = block
         
     | 
| 
      
 63 
     | 
    
         
            +
                end
         
     | 
| 
      
 64 
     | 
    
         
            +
                attr_reader :block
         
     | 
| 
      
 65 
     | 
    
         
            +
             
     | 
| 
      
 66 
     | 
    
         
            +
                def render(data)
         
     | 
| 
      
 67 
     | 
    
         
            +
                  @render_class.new(data, self).go
         
     | 
| 
      
 68 
     | 
    
         
            +
                end
         
     | 
| 
      
 69 
     | 
    
         
            +
              end
         
     | 
| 
      
 70 
     | 
    
         
            +
             
     | 
| 
      
 71 
     | 
    
         
            +
              class ListCapsule < Capsule
         
     | 
| 
      
 72 
     | 
    
         
            +
                def initialize(render_class, &block)
         
     | 
| 
      
 73 
     | 
    
         
            +
                  super
         
     | 
| 
      
 74 
     | 
    
         
            +
                  @mapping = nil
         
     | 
| 
      
 75 
     | 
    
         
            +
                end
         
     | 
| 
      
 76 
     | 
    
         
            +
             
     | 
| 
      
 77 
     | 
    
         
            +
                def map(&block)
         
     | 
| 
      
 78 
     | 
    
         
            +
                  @mapping = ViewHost.sub_view(@render_class, &block)
         
     | 
| 
      
 79 
     | 
    
         
            +
                  self
         
     | 
| 
      
 80 
     | 
    
         
            +
                end
         
     | 
| 
      
 81 
     | 
    
         
            +
             
     | 
| 
      
 82 
     | 
    
         
            +
                def render(data)
         
     | 
| 
      
 83 
     | 
    
         
            +
                  list = super
         
     | 
| 
      
 84 
     | 
    
         
            +
                  rendered = list.map do |item|
         
     | 
| 
      
 85 
     | 
    
         
            +
                    @mapping.viewset(item)
         
     | 
| 
      
 86 
     | 
    
         
            +
                  end
         
     | 
| 
      
 87 
     | 
    
         
            +
                  rendered
         
     | 
| 
      
 88 
     | 
    
         
            +
                end
         
     | 
| 
      
 89 
     | 
    
         
            +
              end
         
     | 
| 
      
 90 
     | 
    
         
            +
             
     | 
| 
      
 91 
     | 
    
         
            +
              class View
         
     | 
| 
      
 92 
     | 
    
         
            +
                def initialize(template)
         
     | 
| 
      
 93 
     | 
    
         
            +
                  @template = template
         
     | 
| 
      
 94 
     | 
    
         
            +
                end
         
     | 
| 
      
 95 
     | 
    
         
            +
             
     | 
| 
      
 96 
     | 
    
         
            +
                attr_reader :template
         
     | 
| 
      
 97 
     | 
    
         
            +
             
     | 
| 
      
 98 
     | 
    
         
            +
                def render_map(data, hash)
         
     | 
| 
      
 99 
     | 
    
         
            +
                  rendered = {}
         
     | 
| 
      
 100 
     | 
    
         
            +
                  hash.each_pair do |key, value|
         
     | 
| 
      
 101 
     | 
    
         
            +
                    rendered[key.to_s] = render_sub(data, value)
         
     | 
| 
      
 102 
     | 
    
         
            +
                  end
         
     | 
| 
      
 103 
     | 
    
         
            +
                  rendered
         
     | 
| 
      
 104 
     | 
    
         
            +
                end
         
     | 
| 
      
 105 
     | 
    
         
            +
             
     | 
| 
      
 106 
     | 
    
         
            +
                def render_list(data, list)
         
     | 
| 
      
 107 
     | 
    
         
            +
                  list.map do |item|
         
     | 
| 
      
 108 
     | 
    
         
            +
                    render_sub(data, item)
         
     | 
| 
      
 109 
     | 
    
         
            +
                  end
         
     | 
| 
      
 110 
     | 
    
         
            +
                end
         
     | 
| 
      
 111 
     | 
    
         
            +
             
     | 
| 
      
 112 
     | 
    
         
            +
                def render_item(data, item)
         
     | 
| 
      
 113 
     | 
    
         
            +
                  item
         
     | 
| 
      
 114 
     | 
    
         
            +
                end
         
     | 
| 
      
 115 
     | 
    
         
            +
             
     | 
| 
      
 116 
     | 
    
         
            +
                def render_sub(data, template)
         
     | 
| 
      
 117 
     | 
    
         
            +
                  case template
         
     | 
| 
      
 118 
     | 
    
         
            +
                  when Hash
         
     | 
| 
      
 119 
     | 
    
         
            +
                    render_map(data, template)
         
     | 
| 
      
 120 
     | 
    
         
            +
                  when Array
         
     | 
| 
      
 121 
     | 
    
         
            +
                    render_list(data, template)
         
     | 
| 
      
 122 
     | 
    
         
            +
                  when Capsule
         
     | 
| 
      
 123 
     | 
    
         
            +
                    render_sub(data, template.render(data))
         
     | 
| 
      
 124 
     | 
    
         
            +
                  else 
         
     | 
| 
      
 125 
     | 
    
         
            +
                    render_item(data, template)
         
     | 
| 
      
 126 
     | 
    
         
            +
                  end
         
     | 
| 
      
 127 
     | 
    
         
            +
                end
         
     | 
| 
      
 128 
     | 
    
         
            +
             
     | 
| 
      
 129 
     | 
    
         
            +
                def viewset(data)
         
     | 
| 
      
 130 
     | 
    
         
            +
                  render_sub(data, @template)
         
     | 
| 
      
 131 
     | 
    
         
            +
                end
         
     | 
| 
      
 132 
     | 
    
         
            +
              end
         
     | 
| 
      
 133 
     | 
    
         
            +
            end
         
     | 
    
        metadata
    ADDED
    
    | 
         @@ -0,0 +1,70 @@ 
     | 
|
| 
      
 1 
     | 
    
         
            +
            --- !ruby/object:Gem::Specification 
         
     | 
| 
      
 2 
     | 
    
         
            +
            name: Stencil
         
     | 
| 
      
 3 
     | 
    
         
            +
            version: !ruby/object:Gem::Version 
         
     | 
| 
      
 4 
     | 
    
         
            +
              version: "0.1"
         
     | 
| 
      
 5 
     | 
    
         
            +
            platform: ruby
         
     | 
| 
      
 6 
     | 
    
         
            +
            authors: 
         
     | 
| 
      
 7 
     | 
    
         
            +
            - Judson Lester
         
     | 
| 
      
 8 
     | 
    
         
            +
            autorequire: 
         
     | 
| 
      
 9 
     | 
    
         
            +
            bindir: bin
         
     | 
| 
      
 10 
     | 
    
         
            +
            cert_chain: []
         
     | 
| 
      
 11 
     | 
    
         
            +
             
     | 
| 
      
 12 
     | 
    
         
            +
            date: 2009-12-28 00:00:00 -08:00
         
     | 
| 
      
 13 
     | 
    
         
            +
            default_executable: 
         
     | 
| 
      
 14 
     | 
    
         
            +
            dependencies: []
         
     | 
| 
      
 15 
     | 
    
         
            +
             
     | 
| 
      
 16 
     | 
    
         
            +
            description: "  Stencil is a templating library with a number of design goals.\n\n  *  Limited code in templates.  This isn't meant to embed ruby in anything - \n  it allows for simple control structures, since that's typically what you need \n  in a template, but full access to the Ruby\n  interpreter is just a tempatation into sin. (From a separation of concerns \n  standpoint.)  There's a certain amount of code available in conditionals and \n  interpolations, since otherwise they're much harder to do...\n\n  *  Easy to extend.  If you do need something extra from a template, not \n  having it in the templating language is frustrating.  It's easy to add \n  features to stencil, since they're described in as well-designed classes.\n\n  * Generic output.  Not everything is a website or a mime-encoded email.  It's \n  nice to be able to spit out generic text from time to time.\n\n  * Data sourced from simple datatypes - hashes and array, referenced with data \n  paths.  Views can be extracted from any object, or built up in code.\n"
         
     | 
| 
      
 17 
     | 
    
         
            +
            email: nyarly@gmail.com
         
     | 
| 
      
 18 
     | 
    
         
            +
            executables: []
         
     | 
| 
      
 19 
     | 
    
         
            +
             
     | 
| 
      
 20 
     | 
    
         
            +
            extensions: []
         
     | 
| 
      
 21 
     | 
    
         
            +
             
     | 
| 
      
 22 
     | 
    
         
            +
            extra_rdoc_files: 
         
     | 
| 
      
 23 
     | 
    
         
            +
            - doc/README
         
     | 
| 
      
 24 
     | 
    
         
            +
            - doc/Specifications
         
     | 
| 
      
 25 
     | 
    
         
            +
            files: 
         
     | 
| 
      
 26 
     | 
    
         
            +
            - lib/stencil/spec/view_matcher.rb
         
     | 
| 
      
 27 
     | 
    
         
            +
            - lib/stencil/processor.rb
         
     | 
| 
      
 28 
     | 
    
         
            +
            - lib/stencil/directives.rb
         
     | 
| 
      
 29 
     | 
    
         
            +
            - lib/stencil/template.rb
         
     | 
| 
      
 30 
     | 
    
         
            +
            - lib/stencil/dynamic-template.rb
         
     | 
| 
      
 31 
     | 
    
         
            +
            - lib/stencil/directives/text.rb
         
     | 
| 
      
 32 
     | 
    
         
            +
            - lib/stencil/directives/base.rb
         
     | 
| 
      
 33 
     | 
    
         
            +
            - lib/stencil/directives/term-style.rb
         
     | 
| 
      
 34 
     | 
    
         
            +
            - lib/stencil/view.rb
         
     | 
| 
      
 35 
     | 
    
         
            +
            - doc/README
         
     | 
| 
      
 36 
     | 
    
         
            +
            - doc/Specifications
         
     | 
| 
      
 37 
     | 
    
         
            +
            has_rdoc: true
         
     | 
| 
      
 38 
     | 
    
         
            +
            homepage: http://stencil-templ.rubyforge.org/
         
     | 
| 
      
 39 
     | 
    
         
            +
            licenses: []
         
     | 
| 
      
 40 
     | 
    
         
            +
             
     | 
| 
      
 41 
     | 
    
         
            +
            post_install_message: Another tidy package brought to you by Judson
         
     | 
| 
      
 42 
     | 
    
         
            +
            rdoc_options: 
         
     | 
| 
      
 43 
     | 
    
         
            +
            - --inline-source
         
     | 
| 
      
 44 
     | 
    
         
            +
            - --main
         
     | 
| 
      
 45 
     | 
    
         
            +
            - doc/README
         
     | 
| 
      
 46 
     | 
    
         
            +
            - --title
         
     | 
| 
      
 47 
     | 
    
         
            +
            - Stencil-0.1 RDoc
         
     | 
| 
      
 48 
     | 
    
         
            +
            require_paths: 
         
     | 
| 
      
 49 
     | 
    
         
            +
            - lib
         
     | 
| 
      
 50 
     | 
    
         
            +
            required_ruby_version: !ruby/object:Gem::Requirement 
         
     | 
| 
      
 51 
     | 
    
         
            +
              requirements: 
         
     | 
| 
      
 52 
     | 
    
         
            +
              - - ">="
         
     | 
| 
      
 53 
     | 
    
         
            +
                - !ruby/object:Gem::Version 
         
     | 
| 
      
 54 
     | 
    
         
            +
                  version: "0"
         
     | 
| 
      
 55 
     | 
    
         
            +
              version: 
         
     | 
| 
      
 56 
     | 
    
         
            +
            required_rubygems_version: !ruby/object:Gem::Requirement 
         
     | 
| 
      
 57 
     | 
    
         
            +
              requirements: 
         
     | 
| 
      
 58 
     | 
    
         
            +
              - - ">="
         
     | 
| 
      
 59 
     | 
    
         
            +
                - !ruby/object:Gem::Version 
         
     | 
| 
      
 60 
     | 
    
         
            +
                  version: "0"
         
     | 
| 
      
 61 
     | 
    
         
            +
              version: 
         
     | 
| 
      
 62 
     | 
    
         
            +
            requirements: []
         
     | 
| 
      
 63 
     | 
    
         
            +
             
     | 
| 
      
 64 
     | 
    
         
            +
            rubyforge_project: stencil-templ
         
     | 
| 
      
 65 
     | 
    
         
            +
            rubygems_version: 1.3.5
         
     | 
| 
      
 66 
     | 
    
         
            +
            signing_key: 
         
     | 
| 
      
 67 
     | 
    
         
            +
            specification_version: 3
         
     | 
| 
      
 68 
     | 
    
         
            +
            summary: Output agnostic templating engine
         
     | 
| 
      
 69 
     | 
    
         
            +
            test_files: []
         
     | 
| 
      
 70 
     | 
    
         
            +
             
     |