rgen 0.2.0
Sign up to get free protection for your applications and to get access to all the features.
- data/CHANGELOG +9 -0
- data/MIT-LICENSE +20 -0
- data/README +73 -0
- data/lib/ea/xmi_class_instantiator.rb +45 -0
- data/lib/ea/xmi_helper.rb +26 -0
- data/lib/ea/xmi_metamodel.rb +19 -0
- data/lib/ea/xmi_object_instantiator.rb +42 -0
- data/lib/ea/xmi_to_classmodel.rb +78 -0
- data/lib/ea/xmi_to_objectmodel.rb +89 -0
- data/lib/mmgen/metamodel_generator.rb +19 -0
- data/lib/mmgen/mm_ext/uml_classmodel_ext.rb +71 -0
- data/lib/mmgen/mmgen.rb +21 -0
- data/lib/mmgen/templates/uml_classmodel.tpl +63 -0
- data/lib/rgen/array_extensions.rb +23 -0
- data/lib/rgen/auto_class_creator.rb +56 -0
- data/lib/rgen/environment.rb +57 -0
- data/lib/rgen/metamodel_builder.rb +102 -0
- data/lib/rgen/metamodel_builder/build_helper.rb +29 -0
- data/lib/rgen/metamodel_builder/builder_extensions.rb +191 -0
- data/lib/rgen/metamodel_builder/builder_runtime.rb +67 -0
- data/lib/rgen/name_helper.rb +18 -0
- data/lib/rgen/template_language.rb +169 -0
- data/lib/rgen/template_language/directory_template_container.rb +51 -0
- data/lib/rgen/template_language/output_handler.rb +84 -0
- data/lib/rgen/template_language/template_container.rb +153 -0
- data/lib/rgen/template_language/template_helper.rb +26 -0
- data/lib/rgen/transformer.rb +316 -0
- data/lib/rgen/xml_instantiator/dependency_resolver.rb +23 -0
- data/lib/rgen/xml_instantiator/xml_instantiator.rb +78 -0
- data/lib/rgen/xml_instantiator/xml_parser.rb +39 -0
- data/lib/uml/objectmodel_instantiator.rb +53 -0
- data/lib/uml/uml_classmodel.rb +92 -0
- data/lib/uml/uml_objectmodel.rb +65 -0
- data/test/array_extensions_test.rb +54 -0
- data/test/environment_test.rb +47 -0
- data/test/metamodel_builder_test.rb +175 -0
- data/test/metamodel_generator_test.rb +45 -0
- data/test/metamodel_generator_test/TestModel.rb +40 -0
- data/test/metamodel_generator_test/expected_result.txt +40 -0
- data/test/output_handler_test.rb +40 -0
- data/test/rgen_test.rb +13 -0
- data/test/template_language_test.rb +46 -0
- data/test/template_language_test/expected_result.txt +10 -0
- data/test/template_language_test/templates/content/chapter.tpl +5 -0
- data/test/template_language_test/templates/index/c/cmod.tpl +1 -0
- data/test/template_language_test/templates/index/chapter.tpl +3 -0
- data/test/template_language_test/templates/root.tpl +22 -0
- data/test/template_language_test/testout.txt +10 -0
- data/test/transformer_test.rb +176 -0
- data/test/xmi_class_instantiator_test.rb +107 -0
- data/test/xmi_instantiator_test/testmodel.eap +0 -0
- data/test/xmi_instantiator_test/testmodel.xml +962 -0
- data/test/xmi_object_instantiator_test.rb +65 -0
- metadata +117 -0
@@ -0,0 +1,67 @@
|
|
1
|
+
# RGen Framework
|
2
|
+
# (c) Martin Thiede, 2006
|
3
|
+
|
4
|
+
require 'rgen/name_helper'
|
5
|
+
|
6
|
+
module RGen
|
7
|
+
|
8
|
+
module MetamodelBuilder
|
9
|
+
|
10
|
+
# This module is mixed into MetamodelBuilder::MMBase.
|
11
|
+
# The methods provided by this module are used by the methods generated
|
12
|
+
# by the class methods of MetamodelBuilder::BuilderExtensions
|
13
|
+
module BuilderRuntime
|
14
|
+
include NameHelper
|
15
|
+
|
16
|
+
def addGeneric(role, value)
|
17
|
+
send("add#{firstToUpper(role)}",value)
|
18
|
+
end
|
19
|
+
|
20
|
+
def removeGeneric(role, value)
|
21
|
+
send("remove#{firstToUpper(role)}",value)
|
22
|
+
end
|
23
|
+
|
24
|
+
def setGeneric(role, value)
|
25
|
+
send("#{role}=",value)
|
26
|
+
end
|
27
|
+
|
28
|
+
def getGeneric(role)
|
29
|
+
send("#{role}")
|
30
|
+
end
|
31
|
+
|
32
|
+
def _unregister(element, target, target_role, kind)
|
33
|
+
return unless element and target and target_role
|
34
|
+
if kind == 'one'
|
35
|
+
target.send("#{target_role}=",nil)
|
36
|
+
elsif kind == 'many'
|
37
|
+
target.send("remove#{firstToUpper(target_role)}",element)
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
def _register(element, target, target_role, kind)
|
42
|
+
return unless element and target and target_role
|
43
|
+
if kind == 'one'
|
44
|
+
target.send("#{target_role}=",element)
|
45
|
+
elsif kind == 'many'
|
46
|
+
target.send("add#{firstToUpper(target_role)}",element)
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
def _assignmentTypeError(target, value, expected)
|
51
|
+
text = ""
|
52
|
+
if target
|
53
|
+
targetId = target.class.name
|
54
|
+
targetId += "(" + target.name + ")" if target.respond_to?(:name) and target.name
|
55
|
+
text += "In #{targetId} : "
|
56
|
+
end
|
57
|
+
valueId = value.class.name
|
58
|
+
valueId += "(" + value.name + ")" if value.respond_to?(:name) and value.name
|
59
|
+
text += "Can not put a #{valueId} where a #{expected} is expected"
|
60
|
+
StandardError.new(text)
|
61
|
+
end
|
62
|
+
|
63
|
+
end
|
64
|
+
|
65
|
+
end
|
66
|
+
|
67
|
+
end
|
@@ -0,0 +1,18 @@
|
|
1
|
+
# RGen Framework
|
2
|
+
# (c) Martin Thiede, 2006
|
3
|
+
|
4
|
+
module RGen
|
5
|
+
|
6
|
+
module NameHelper
|
7
|
+
def normalizeName(name)
|
8
|
+
name.gsub(/[\.:]/,'_')
|
9
|
+
end
|
10
|
+
def className(object)
|
11
|
+
object.class.name =~ /::(\w+)$/; $1
|
12
|
+
end
|
13
|
+
def firstToUpper(str)
|
14
|
+
str[0..0].upcase + ( str[1..-1] || "" )
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
end
|
@@ -0,0 +1,169 @@
|
|
1
|
+
# RGen Framework
|
2
|
+
# (c) Martin Thiede, 2006
|
3
|
+
|
4
|
+
require 'rgen/template_language/directory_template_container'
|
5
|
+
require 'rgen/template_language/template_container'
|
6
|
+
|
7
|
+
module RGen
|
8
|
+
|
9
|
+
# The RGen template language has been designed to build complex generators.
|
10
|
+
# It is very similar to the EXPAND language of the Java based
|
11
|
+
# OpenArchitectureWare framework.
|
12
|
+
#
|
13
|
+
# =Templates
|
14
|
+
#
|
15
|
+
# The basic idea is to allow "templates" not only being template files
|
16
|
+
# but smaller parts. Those parts can be expanded from other parts very
|
17
|
+
# much like Ruby methods are called from other methods.
|
18
|
+
# Thus the term "template" refers to such a part within a "template file".
|
19
|
+
#
|
20
|
+
# Template files used by the RGen template language should have a
|
21
|
+
# filename with the postfix ".tpl". Those files can reside within (nested)
|
22
|
+
# template file directories.
|
23
|
+
#
|
24
|
+
# As an example a template directory could look like the following:
|
25
|
+
#
|
26
|
+
# templates/root.tpl
|
27
|
+
# templates/dbaccess/dbaccess.tpl
|
28
|
+
# templates/dbaccess/schema.tpl
|
29
|
+
# templates/headers/generic_headers.tpl
|
30
|
+
# templates/headers/specific/component.tpl
|
31
|
+
#
|
32
|
+
# A template is always called for a <i>context object</i>. The context object
|
33
|
+
# serves as the receiver of methods called within the template. Details are given
|
34
|
+
# below.
|
35
|
+
#
|
36
|
+
#
|
37
|
+
# =Defining Templates
|
38
|
+
#
|
39
|
+
# One or more templates can be defined in a template file using the +define+
|
40
|
+
# keyword as in the following example:
|
41
|
+
#
|
42
|
+
# <% define 'GenerateDBAdapter', :for => DBDescription do |dbtype| %>
|
43
|
+
# Content to be generated; use ERB syntax here
|
44
|
+
# <% end %>
|
45
|
+
#
|
46
|
+
# The template definition takes three kinds of parameters:
|
47
|
+
# 1. The name of the template within the template file as a String or Symbol
|
48
|
+
# 2. An optional class object describing the class of context objects for which
|
49
|
+
# this template is valid.
|
50
|
+
# 3. An arbitrary number of template parameters
|
51
|
+
# See RGen::TemplateLanguage::TemplateContainer for details about the syntax of +define+.
|
52
|
+
#
|
53
|
+
# Within a template, regular ERB syntax can be used. This is
|
54
|
+
# * <code><%</code> and <code>%></code> are used to embed Ruby code
|
55
|
+
# * <code><%=</code> and <code>%></code> are used to embed Ruby expressions with
|
56
|
+
# the expression result being written to the template output
|
57
|
+
# * <code><%#</code> and <code>%></code> are used for comments
|
58
|
+
# All content not within these tags is written to the template output verbatim.
|
59
|
+
# See below for details about output files and output formatting.
|
60
|
+
#
|
61
|
+
# All methods which are called from within the template are sent to the context
|
62
|
+
# object.
|
63
|
+
#
|
64
|
+
#
|
65
|
+
# =Expanding Templates
|
66
|
+
#
|
67
|
+
# Templates are normally expanded from within other templates. The only
|
68
|
+
# exception is the root template, which is expanded from the surrounding code.
|
69
|
+
#
|
70
|
+
# Template names can be specified in the following ways:
|
71
|
+
# * Non qualified name: use the template with the given name in the current template file
|
72
|
+
# * Relative qualified name: use the template within the template file specified by the relative path
|
73
|
+
# * Absolute qualified name: use the template within the template file specified by the absolute path
|
74
|
+
#
|
75
|
+
# The +expand+ keyword is used to expand templates.
|
76
|
+
#
|
77
|
+
# Here are some examples:
|
78
|
+
#
|
79
|
+
# <% expand 'GenerateDBAdapter', dbtype, :for => dbDesc %>
|
80
|
+
#
|
81
|
+
# <i>Non qualified</i>. Must be called within the file where 'GenerateDBAdapter' is defined.
|
82
|
+
# There is one template parameter passed in via variable +dbtype+.
|
83
|
+
# The context object is provided in variable +dbDesc+.
|
84
|
+
#
|
85
|
+
# <% expand 'dbaccess::ExampleSQL' %>
|
86
|
+
#
|
87
|
+
# <i>Qualified with filename</i>. Must be called from a file in the same directory as 'dbaccess.tpl'
|
88
|
+
# There are no parameters. The current context object will be used as the context
|
89
|
+
# object for this template expansion.
|
90
|
+
#
|
91
|
+
# <% expand '../headers/generic_headers::CHeader', :foreach => modules %>
|
92
|
+
#
|
93
|
+
# <i>Relatively qualified</i>. Must be called from a location from which the file
|
94
|
+
# 'generic_headers.tpl' is accessible via the relative path '../headers'.
|
95
|
+
# The template is expanded for each module in +modules+ (which has to be an Array).
|
96
|
+
# Each element of +modules+ will be the context object in turn.
|
97
|
+
#
|
98
|
+
# <% expand '/headers/generic_headers::CHeader', :foreach => modules %>
|
99
|
+
#
|
100
|
+
# Absolutely qualified: The same behaviour as before but with an absolute path from
|
101
|
+
# the template directory root (which in this example is 'templates', see above)
|
102
|
+
#
|
103
|
+
#
|
104
|
+
# =Output Files and Formatting
|
105
|
+
#
|
106
|
+
# Normally the generated content is to be written into one or more output files.
|
107
|
+
# The RGen template language facilitates this by means of the +file+ keyword.
|
108
|
+
#
|
109
|
+
# When the +file+ keyword is used to define a block, all output generated
|
110
|
+
# from template code within this block will be written to the specified file.
|
111
|
+
# This includes output generated from template expansions.
|
112
|
+
# Thus all output from templates expanded within this block is written to
|
113
|
+
# the same file as long as those templates do not use the +file+ keyword to
|
114
|
+
# define a new file context.
|
115
|
+
#
|
116
|
+
# Here is an example:
|
117
|
+
#
|
118
|
+
# <% file 'dbadapter/'+adapter.name+'.c' do %>
|
119
|
+
# all content within this block will be written to the specified file
|
120
|
+
# <% end %>
|
121
|
+
#
|
122
|
+
# Note that the filename itself can be calculated dynamically by an arbitrary
|
123
|
+
# Ruby expression.
|
124
|
+
#
|
125
|
+
# The absolute position where the output file is created depends on the output
|
126
|
+
# root directory passed to DirectoryTemplateContainer as described below.
|
127
|
+
#
|
128
|
+
# =Setting up the Generator
|
129
|
+
#
|
130
|
+
# Setting up the generator consists of 3 steps:
|
131
|
+
# * Instantiate DirectoryTemplateContainer passing the metamodel and the output
|
132
|
+
# directory to the constructor.
|
133
|
+
# * Load the templates into the template container
|
134
|
+
# * Expand the root template to start generation
|
135
|
+
#
|
136
|
+
# Here is an example:
|
137
|
+
#
|
138
|
+
# module MyMM
|
139
|
+
# # metaclasses are defined here, e.g. using RGen::MetamodelBuilder
|
140
|
+
# end
|
141
|
+
#
|
142
|
+
# OUTPUT_DIR = File.dirname(__FILE__)+"/output"
|
143
|
+
# TEMPLATES_DIR = File.dirname(__FILE__)+"/templates"
|
144
|
+
#
|
145
|
+
# tc = RGen::TemplateLanguage::DirectoryTemplateContainer.new(MyMM, OUTPUT_DIR)
|
146
|
+
# tc.load(TEMPLATES_DIR)
|
147
|
+
# # testModel should hold an instance of the metamodel class expected by the root template
|
148
|
+
# # the following line starts generation
|
149
|
+
# tc.expand('root::Root', :for => testModel, :indent => 1)
|
150
|
+
#
|
151
|
+
# The metamodel is the Ruby module which contains the metaclasses.
|
152
|
+
# This information is required for the template container in order to resolve the
|
153
|
+
# metamodel classes used within the template file.
|
154
|
+
#
|
155
|
+
# The output path is prepended to the relative paths provided to the +file+
|
156
|
+
# definitions in the template files.
|
157
|
+
#
|
158
|
+
# The template directory should contain template files as described above.
|
159
|
+
#
|
160
|
+
# Finally the generation process is started by calling +expand+ in the same way as it
|
161
|
+
# is used from within templates.
|
162
|
+
#
|
163
|
+
# Also see the unit tests for more examples.
|
164
|
+
#
|
165
|
+
module TemplateLanguage
|
166
|
+
|
167
|
+
end
|
168
|
+
|
169
|
+
end
|
@@ -0,0 +1,51 @@
|
|
1
|
+
# RGen Framework
|
2
|
+
# (c) Martin Thiede, 2006
|
3
|
+
|
4
|
+
require 'rgen/template_language/template_container'
|
5
|
+
require 'rgen/template_language/template_helper'
|
6
|
+
|
7
|
+
module RGen
|
8
|
+
|
9
|
+
module TemplateLanguage
|
10
|
+
|
11
|
+
class DirectoryTemplateContainer
|
12
|
+
include TemplateHelper
|
13
|
+
|
14
|
+
def initialize(metamodel=nil, output_path=nil, parent=nil)
|
15
|
+
@containers = {}
|
16
|
+
@parent = parent
|
17
|
+
@metamodel = metamodel
|
18
|
+
@output_path = output_path
|
19
|
+
end
|
20
|
+
|
21
|
+
def load(dir)
|
22
|
+
#print "Loading templates in #{dir} ...\n"
|
23
|
+
Dir.foreach(dir) { |f|
|
24
|
+
qf = dir+"/"+f
|
25
|
+
if !File.directory?(qf) && f =~ /^(.*)\.tpl$/
|
26
|
+
(@containers[$1] = TemplateContainer.dup.new(@metamodel, @output_path, self)).load(qf)
|
27
|
+
elsif File.directory?(qf) && f != "." && f != ".."
|
28
|
+
(@containers[f] = DirectoryTemplateContainer.new(@metamodel, @output_path, self)).load(qf)
|
29
|
+
end
|
30
|
+
}
|
31
|
+
end
|
32
|
+
|
33
|
+
def expand(template, *all_args)
|
34
|
+
args, params = _splitArgsAndOptions(all_args)
|
35
|
+
element = params[:for]
|
36
|
+
if template =~ /^\// && @parent
|
37
|
+
@parent.expand(template, *all_args)
|
38
|
+
elsif template =~ /^[\/]*(\w+)[:\/]+(.*)/
|
39
|
+
throw "Template not found: #{$1}" unless @containers[$1]
|
40
|
+
@containers[$1].expand($2, *all_args)
|
41
|
+
elsif @parent
|
42
|
+
@parent.expand(template, *all_args)
|
43
|
+
else
|
44
|
+
throw "Template not found: #{template}"
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
end
|
50
|
+
|
51
|
+
end
|
@@ -0,0 +1,84 @@
|
|
1
|
+
# RGen Framework
|
2
|
+
# (c) Martin Thiede, 2006
|
3
|
+
|
4
|
+
module RGen
|
5
|
+
|
6
|
+
module TemplateLanguage
|
7
|
+
|
8
|
+
class OutputHandler
|
9
|
+
attr_writer :indent
|
10
|
+
|
11
|
+
def initialize(indent=0, mode=:explicit)
|
12
|
+
self.mode = mode
|
13
|
+
@indent = indent
|
14
|
+
@state = :wait_for_nonws
|
15
|
+
@output = ""
|
16
|
+
end
|
17
|
+
|
18
|
+
def concat(s)
|
19
|
+
return @output.concat(s) if s.is_a? OutputHandler
|
20
|
+
s = s.to_str.gsub(/^[\t ]*\r?\n/,'') if @ignoreNextNL
|
21
|
+
s = s.to_str.gsub(/^\s+/,'') if @ignoreNextWS
|
22
|
+
@ignoreNextNL = @ignoreNextWS = false if s =~ /\S/
|
23
|
+
if @mode == :direct
|
24
|
+
@output.concat(s)
|
25
|
+
elsif @mode == :explicit
|
26
|
+
while s.size > 0
|
27
|
+
#puts "DEGUB: #{@state} #{s.dump}"
|
28
|
+
# s starts with whitespace
|
29
|
+
if s =~ /\A(\s+)(.*)/m
|
30
|
+
ws = $1; rest = $2
|
31
|
+
#puts "DEGUB: ws #{ws.dump} rest #{rest.dump}"
|
32
|
+
if @state == :wait_for_nl
|
33
|
+
# ws contains a newline
|
34
|
+
if ws =~ /\A[\t ]*(\r?\n)(\s*)/m
|
35
|
+
@output.concat($1)
|
36
|
+
@state = :wait_for_nonws
|
37
|
+
s = $2 + rest
|
38
|
+
else
|
39
|
+
@output.concat(ws)
|
40
|
+
s = rest
|
41
|
+
end
|
42
|
+
else
|
43
|
+
s = rest
|
44
|
+
end
|
45
|
+
# s starts with non-whitespace
|
46
|
+
elsif s =~ /\A(\S+)(.*)/m
|
47
|
+
nonws = $1; rest = $2
|
48
|
+
#puts "DEGUB: nonws #{nonws.dump} rest #{rest.dump}"
|
49
|
+
@output.concat(" "*@indent) if @state == :wait_for_nonws
|
50
|
+
@output.concat(nonws)
|
51
|
+
@state = :wait_for_nl
|
52
|
+
s = rest
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
57
|
+
alias << concat
|
58
|
+
|
59
|
+
def to_str
|
60
|
+
@output
|
61
|
+
end
|
62
|
+
alias to_s to_str
|
63
|
+
|
64
|
+
def direct_concat(s)
|
65
|
+
@output.concat(s)
|
66
|
+
end
|
67
|
+
|
68
|
+
def ignoreNextNL
|
69
|
+
@ignoreNextNL = true
|
70
|
+
end
|
71
|
+
|
72
|
+
def ignoreNextWS
|
73
|
+
@ignoreNextWS = true
|
74
|
+
end
|
75
|
+
|
76
|
+
def mode=(m)
|
77
|
+
raise StandardError.new("Unknown mode: #{m}") unless [:direct, :explicit].include?(m)
|
78
|
+
@mode = m
|
79
|
+
end
|
80
|
+
end
|
81
|
+
|
82
|
+
end
|
83
|
+
|
84
|
+
end
|
@@ -0,0 +1,153 @@
|
|
1
|
+
# RGen Framework
|
2
|
+
# (c) Martin Thiede, 2006
|
3
|
+
|
4
|
+
require 'erb'
|
5
|
+
require 'rgen/template_language/output_handler'
|
6
|
+
require 'rgen/template_language/template_helper'
|
7
|
+
|
8
|
+
module RGen
|
9
|
+
|
10
|
+
module TemplateLanguage
|
11
|
+
|
12
|
+
class TemplateContainer
|
13
|
+
include TemplateHelper
|
14
|
+
|
15
|
+
def initialize(metamodel, output_path, parent)
|
16
|
+
@templates = {}
|
17
|
+
@parent = parent
|
18
|
+
@indent = 0
|
19
|
+
@output_path = output_path
|
20
|
+
raise StandardError.new("Can not set metamodel, dup class first") if self.class == TemplateContainer
|
21
|
+
@@metamodel = metamodel
|
22
|
+
end
|
23
|
+
|
24
|
+
def load(filename)
|
25
|
+
#print "Loading templates in #{filename} ...\n"
|
26
|
+
File.open(filename) { |f|
|
27
|
+
ERB.new(f.read,nil,nil,'@output').result(binding)
|
28
|
+
}
|
29
|
+
end
|
30
|
+
|
31
|
+
# if this container can handle the call, the expansion result is returned
|
32
|
+
# otherwise expand is called on the appropriate container and the result is added to @output
|
33
|
+
def expand(template, *all_args)
|
34
|
+
args, params = _splitArgsAndOptions(all_args)
|
35
|
+
if params[:foreach].is_a? Enumerable
|
36
|
+
_expand_foreach(template, args, params)
|
37
|
+
else
|
38
|
+
_expand(template, args, params)
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
def this
|
43
|
+
@context
|
44
|
+
end
|
45
|
+
|
46
|
+
def method_missing(name, *args)
|
47
|
+
@context.send(name, *args)
|
48
|
+
end
|
49
|
+
|
50
|
+
def self.const_missing(name)
|
51
|
+
super unless @@metamodel
|
52
|
+
@@metamodel.const_get(name)
|
53
|
+
end
|
54
|
+
|
55
|
+
private
|
56
|
+
|
57
|
+
def nonl
|
58
|
+
@output.ignoreNextNL
|
59
|
+
end
|
60
|
+
|
61
|
+
def nows
|
62
|
+
@output.ignoreNextWS
|
63
|
+
end
|
64
|
+
|
65
|
+
def nl
|
66
|
+
_direct_concat("\n")
|
67
|
+
end
|
68
|
+
|
69
|
+
def iinc
|
70
|
+
@indent += 1
|
71
|
+
@output.indent = @indent
|
72
|
+
end
|
73
|
+
|
74
|
+
def idec
|
75
|
+
@indent -= 1 if @indent > 0
|
76
|
+
@output.indent = @indent
|
77
|
+
end
|
78
|
+
|
79
|
+
def define(template, params={}, &block)
|
80
|
+
@templates[template] ||= {}
|
81
|
+
cls = params[:for] || Object
|
82
|
+
@templates[template][cls] = block
|
83
|
+
end
|
84
|
+
|
85
|
+
def file(name)
|
86
|
+
old_output, @output = @output, OutputHandler.new(@indent)
|
87
|
+
yield
|
88
|
+
path = ""
|
89
|
+
path += @output_path+"/" if @output_path
|
90
|
+
File.open(path+name,"w") { |f| f.write(@output) }
|
91
|
+
@output = old_output
|
92
|
+
end
|
93
|
+
|
94
|
+
# private private
|
95
|
+
|
96
|
+
def _expand_foreach(template, args, params)
|
97
|
+
params[:foreach].each {|e|
|
98
|
+
single_params = params.dup
|
99
|
+
single_params[:for] = e
|
100
|
+
_expand(template, args, single_params)
|
101
|
+
}
|
102
|
+
end
|
103
|
+
|
104
|
+
LOCAL_TEMPLATE_REGEX = /^:*(\w+)$/
|
105
|
+
|
106
|
+
def _expand(template, args, params)
|
107
|
+
context = params[:for]
|
108
|
+
@indent = params[:indent] || @indent
|
109
|
+
old_context, @context = @context, context if context
|
110
|
+
local_output = nil
|
111
|
+
if template =~ LOCAL_TEMPLATE_REGEX
|
112
|
+
throw "Template not found: #{$1}" unless @templates[$1]
|
113
|
+
old_output, @output = @output, OutputHandler.new(@indent)
|
114
|
+
_call_template($1, @context, args)
|
115
|
+
local_output, @output = @output, old_output
|
116
|
+
else
|
117
|
+
local_output = @parent.expand(template, *(args.dup << {:for => @context, :indent => @indent}))
|
118
|
+
end
|
119
|
+
_direct_concat(local_output)
|
120
|
+
@context = old_context if old_context
|
121
|
+
local_output
|
122
|
+
end
|
123
|
+
|
124
|
+
def _call_template(tpl, context, args)
|
125
|
+
found = false
|
126
|
+
@templates[tpl].each_pair { |key, value|
|
127
|
+
if context.is_a?(key)
|
128
|
+
proc = @templates[tpl][key]
|
129
|
+
arity = proc.arity
|
130
|
+
arity = 0 if arity == -1 # if no args are given
|
131
|
+
raise StandardError.new("Wrong number of arguments calling template #{tpl}: #{args.size} for #{arity} "+
|
132
|
+
"(Beware: Hashes as last arguments are taken as options and are ignored)") \
|
133
|
+
if arity != args.size
|
134
|
+
proc.call(*args)
|
135
|
+
found = true
|
136
|
+
end
|
137
|
+
}
|
138
|
+
raise StandardError.new("Template class not matching: #{tpl} for #{context.class.name}") unless found
|
139
|
+
end
|
140
|
+
|
141
|
+
def _direct_concat(s)
|
142
|
+
if @output.is_a? OutputHandler
|
143
|
+
@output.direct_concat(s)
|
144
|
+
else
|
145
|
+
@output << s
|
146
|
+
end
|
147
|
+
end
|
148
|
+
|
149
|
+
end
|
150
|
+
|
151
|
+
end
|
152
|
+
|
153
|
+
end
|