public
Rubygem
Description: Merb Core: All you need. None you don't.
Homepage: http://www.merbivore.com
Clone URL: git://github.com/wycats/merb-core.git
ezmobius (author)
Sun Mar 23 15:07:39 -0700 2008
commit  312181aade867323834c5031b7e9707f931e9ded
tree    6280af600d4c35194850e2d3476d431de40c1d54
parent  142d85ca7cf7c7a4f5a93b09be12978b39059125
merb-core / lib / merb-core / controller / template.rb
100644 205 lines (180 sloc) 6.083 kb
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
module Merb::InlineTemplates
end
 
module Merb::Template
  
  EXTENSIONS = {} unless defined?(EXTENSIONS)
  METHOD_LIST = {} unless defined?(METHOD_LIST)
  MTIMES = {} unless defined?(MTIMES)
  
  class << self
    # Get the template's method name from a full path. This replaces
    # non-alphanumeric characters with __ and "." with "_"
    #
    # Collisions are potentially possible with something like:
    # ~foo.bar and __foo.bar or !foo.bar.
    #
    # ==== Parameters
    # path<String>:: A full path to convert to a valid Ruby method name
    #
    # ==== Returns
    # String:: The template name.
    #
    #---
    # We might want to replace this with something that varies the
    # character replaced based on the non-alphanumeric character
    # to avoid edge-case collisions.
    def template_name(path)
      path = File.expand_path(path)
      path.gsub(/[^\.a-zA-Z0-9]/, "__").gsub(/\./, "_")
    end
 
    # Get the name of the template method for a particular path.
    #
    # ==== Parameters
    # path<String>:: A full path to find a template method for.
    # template_stack<Array>:: The template stack. Not used.
    #
    # ==== Returns
    # DOC
    #---
    # @semipublic
    def template_for(path, template_stack = [])
      path = File.expand_path(path)
      
      ret =
      if Merb::Config[:reload_templates]
        file = Dir["#{path}.{#{Merb::Template::EXTENSIONS.keys.join(',')}}"].first
        METHOD_LIST[path] = file ? inline_template(file) : nil
      else
        METHOD_LIST[path] ||= begin
          file = Dir["#{path}.{#{Merb::Template::EXTENSIONS.keys.join(',')}}"].first
          file ? inline_template(file) : nil
        end
      end
      
      ret
    end
    
    # Takes a template at a particular path and inlines it into a module and
    # adds it to the METHOD_LIST table to speed lookup later.
    #
    # ==== Parameters
    # path<String>::
    # The full path of the template (minus the templating specifier) to
    # inline.
    # mod<Module>::
    # The module to put the compiled method into. Defaults to
    # Merb::InlineTemplates
    #
    # ==== Note
    # Even though this method supports inlining into any module, the method
    # must be available to instances of AbstractController that will use it.
    #---
    # @public
    def inline_template(path, mod = Merb::InlineTemplates)
      path = File.expand_path(path)
      METHOD_LIST[path.gsub(/\.[^\.]*$/, "")] =
        engine_for(path).compile_template(path, template_name(path), mod)
    end
    
    # Finds the engine for a particular path.
    #
    # ==== Parameters
    # path<String>:: The path of the file to find an engine for.
    #
    # ==== Returns
    # Class:: The engine.
    #---
    # @semipublic
    def engine_for(path)
      path = File.expand_path(path)
      EXTENSIONS[path.match(/\.([^\.]*)$/)[1]]
    end
    
    # Registers the extensions that will trigger a particular templating
    # engine.
    #
    # ==== Parameters
    # engine<Class>:: The class of the engine that is being registered
    # extensions<Array[String]>::
    # The list of extensions that will be registered with this templating
    # language
    #
    # ==== Raises
    # ArgumentError:: engine does not have a compile_template method.
    #
    # ==== Example
    # Merb::Template.register_extensions(Merb::Template::Erubis, ["erb"])
    #---
    # @public
    def register_extensions(engine, extensions)
      raise ArgumentError, "The class you are registering does not have a compile_template method" unless
        engine.respond_to?(:compile_template)
      extensions.each{|ext| EXTENSIONS[ext] = engine }
      Merb::AbstractController.class_eval <<-HERE
include #{engine}::Mixin
HERE
    end
  end
  
  require 'erubis'
 
  class Erubis
    # ==== Parameters
    # path<String>:: A full path to the template.
    # name<String>:: The name of the method that will be created.
    # mod<Module>:: The module that the compiled method will be placed into.
    def self.compile_template(path, name, mod)
      template = ::Erubis::Eruby.new(File.read(path))
      template.def_method(mod, name, path)
      name
    end
 
    module Mixin
      
      # Provides direct acccess to the buffer for this view context
      #
      # ==== Parameters
      # the_binding<Binding>:: The binding to pass to the buffer.
      #
      # ==== Returns
      # DOC
      def _erb_buffer( the_binding )
        @_buffer = eval( "_buf", the_binding, __FILE__, __LINE__)
      end
 
      # ==== Parameters
      # *args:: Arguments to pass to the block.
      # &block:: The template block to call.
      #
      # ==== Returns
      # String:: The output of the block.
      #
      # ==== Examples
      # Capture being used in a .html.erb page:
      #
      # <% @foo = capture do %>
      # <p>Some Foo content!</p>
      # <% end %>
      def capture_erb(*args, &block)
        # get the buffer from the block's binding
        buffer = _erb_buffer( block.binding ) rescue nil
 
        # If there is no buffer, just call the block and get the contents
        if buffer.nil?
          block.call(*args)
        # If there is a buffer, execute the block, then extract its contents
        else
          pos = buffer.length
          block.call(*args)
 
          # extract the block
          data = buffer[pos..-1]
 
          # replace it in the original with empty string
          buffer[pos..-1] = ''
 
          data
        end
      end
 
      # DOC
      def concat_erb(string, binding)
        _erb_buffer(binding) << string
      end
            
    end
  
    Merb::Template.register_extensions(self, %w[erb])
  end
  
end
 
module Erubis
  module RubyEvaluator
 
    # DOC
    def def_method(object, method_name, filename=nil)
      m = object.is_a?(Module) ? :module_eval : :instance_eval
      setup = "@_engine = 'erb'"
      object.__send__(m, "def #{method_name}(locals={}); #{setup}; #{@src}; end", filename || @filename || '(erubis)')
    end
   
  end
end