rgen 0.2.0
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/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
|