File: template_container.rb

package info (click to toggle)
ruby-rgen 0.10.2-1
  • links: PTS, VCS
  • area: main
  • in suites: forky, sid, trixie
  • size: 2,428 kB
  • sloc: ruby: 11,344; xml: 1,368; yacc: 72; makefile: 10
file content (244 lines) | stat: -rw-r--r-- 8,100 bytes parent folder | download
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
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
# RGen Framework
# (c) Martin Thiede, 2006

require 'erb'
require 'fileutils'
require 'rgen/template_language/output_handler'
require 'rgen/template_language/template_helper'

module RGen
  
  module TemplateLanguage
    
    class TemplateContainer
      include TemplateHelper
      
      def initialize(metamodels, output_path, parent, filename)
        @templates = {}
        @parent = parent
        @filename = filename
        @indent = 0
        @output_path = output_path
    		@metamodels = metamodels
    		@metamodels = [ @metamodels ] unless @metamodels.is_a?(Array)
      end
      
      def load
    		File.open(@filename,"rb") do |f|
          begin
  	      	@@metamodels = @metamodels
    	    	fileContent = f.read
      	  	_detectNewLinePattern(fileContent)
        		ERB.new(fileContent, eoutvar: '@output').result(binding)
          rescue Exception => e
            processAndRaise(e)
          end
    		end
      end
      
      def expand(template, *all_args)
        args, params = _splitArgsAndOptions(all_args)
    		if params.has_key?(:foreach)
      		raise StandardError.new("expand :foreach argument is not enumerable") \
        		unless params[:foreach].is_a?(Enumerable)
          _expand_foreach(template, args, params)
        else
          _expand(template, args, params)
        end
      end
      
      def evaluate(template, *all_args)
        args, params = _splitArgsAndOptions(all_args)
        raise StandardError.new(":foreach can not be used with evaluate") if params[:foreach]
        _expand(template, args, params.merge({:_evalOnly => true}))
      end
      
      def this
        @context
      end
      
      def method_missing(name, *args)
        @context.send(name, *args)
      end
      
      def self.const_missing(name)
        super unless @@metamodels
        @@metamodels.each do |mm|
          return mm.const_get(name) rescue NameError
        end
        super
      end
      
      private
      
      def nonl
        @output.ignoreNextNL
      end
      
      def nows
        @output.ignoreNextWS
      end
      
      def nl
    		_direct_concat(@newLinePattern)
      end
      
      def ws
        _direct_concat(" ", true)
      end
      
      def iinc
        @indent += 1
        @output.indent = @indent
      end
      
      def idec
        @indent -= 1 if @indent > 0
        @output.indent = @indent
      end
      
      TemplateDesc = Struct.new(:block, :local)
      
      def define(template, params={}, &block)
        _define(template, params, &block)
      end
      
      def define_local(template, params={}, &block)
        _define(template, params.merge({:local => true}), &block)
      end
      
      def file(name, indentString=nil)
        old_output, @output = @output, OutputHandler.new(@indent, indentString || @parent.indentString)
        begin
          yield
        rescue Exception => e
          processAndRaise(e)
        end
        path = ""
        path += @output_path+"/" if @output_path
        dirname = File.dirname(path+name)
        FileUtils.makedirs(dirname) unless File.exist?(dirname)
    		File.open(path+name,"wb") { |f| f.write(@output) }
        @output = old_output
      end
      
      # private private
      
      def _define(template, params={}, &block)
        @templates[template] ||= {}
        cls = params[:for] || Object
        @templates[template][cls] = TemplateDesc.new(block, params[:local])
      end
      
      def _expand_foreach(template, args, params)
        sep = params[:separator]
        params[:foreach].each_with_index {|e,i|
          _direct_concat(sep.to_s) if sep && i > 0 
          _expand(template, args, params.merge({:for => e}))
        }
      end
      
      LOCAL_TEMPLATE_REGEX = /^:*(\w+)$/
      
      def _expand(template, args, params)
        raise StandardError.new("expand :for argument evaluates to nil") if params.has_key?(:for) && params[:for].nil?
        context = params[:for]
        old_indent = @indent
        @indent = params[:indent] || @indent
        noIndentNextLine = params[:_noIndentNextLine] || 
          (@output.is_a?(OutputHandler) && @output.noIndentNextLine) || 
          # the following line actually defines the noIndentNextLine state:
          # we don't indent the next line if the previous line was not finished,
          # i.e. if output has been generated but is not terminated by a newline
          # BEWARE: the initial evaluation of the ERB template during template loading
          #         also writes to @output (it creates a String); we must ignore this
          (@output.is_a?(OutputHandler) && @output.to_s.size > 0 && @output.to_s[-1] != "\n"[0]) 
        caller = params[:_caller] || self
        old_context, @context = @context, context if context
        local_output = nil
        if template =~ LOCAL_TEMPLATE_REGEX
          tplname = $1
          raise StandardError.new("Template not found: #{$1}") unless @templates[tplname]
          old_output, @output = @output, OutputHandler.new(@indent, @parent.indentString)
          @output.noIndentNextLine = noIndentNextLine
          _call_template(tplname, @context, args, caller == self)
          old_output.noIndentNextLine = false if old_output.is_a?(OutputHandler) && !@output.noIndentNextLine
          local_output, @output = @output, old_output
        else
          local_output = @parent.expand(template, *(args.dup << {:for => @context, :indent => @indent, :_noIndentNextLine => noIndentNextLine, :_evalOnly => true, :_caller => caller}))
        end
        _direct_concat(local_output) unless params[:_evalOnly]
        @context = old_context if old_context
        @indent = old_indent
        local_output.to_s
      end
      
      def processAndRaise(e, tpl=nil)
        bt = e.backtrace.dup
        e.backtrace.each_with_index do |t,i|
          if t =~ /\(erb\):(\d+):/
            bt[i] = "#{@filename}:#{$1}"
            bt[i] += ":in '#{tpl}'" if tpl
            break
          end
        end
        raise e, e.to_s, bt
      end
      
      def _call_template(tpl, context, args, localCall)
        found = false
        @templates[tpl].each_pair do |key, value| 
          if context.is_a?(key)
            templateDesc = @templates[tpl][key]
            proc = templateDesc.block
            arity = proc.arity
            arity = 0 if arity == -1	# if no args are given
            raise StandardError.new("Wrong number of arguments calling template #{tpl}: #{args.size} for #{arity} "+
              "(Beware: Hashes as last arguments are taken as options and are ignored)") \
              if arity != args.size
            raise StandardError.new("Template can only be called locally: #{tpl}") \
              if templateDesc.local && !localCall
            begin
             	@@metamodels = @metamodels
              proc.call(*args) 
            rescue Exception => e
              processAndRaise(e, tpl)
            end
            found = true
          end
        end
        raise StandardError.new("Template class not matching: #{tpl} for #{context.class.name}") unless found
      end
        
      def _direct_concat(s, allow_indent=false)
        if @output.is_a? OutputHandler
          if allow_indent
            @output.direct_concat_allow_indent(s)
          else
            @output.direct_concat(s)
          end
        else
          @output << s
        end
      end

      def _detectNewLinePattern(text)
        tests = 0
        rnOccurances = 0
        text.scan(/(\r?)\n/) do |groups|
          tests += 1
          rnOccurances += 1 if groups[0] == "\r"
          break if tests >= 10
        end
        if rnOccurances > (tests / 2)
          @newLinePattern = "\r\n"
        else
          @newLinePattern = "\n"
        end
      end
              
    end
    
  end
  
end