tanuki 0.3.1 → 0.4.0
Sign up to get free protection for your applications and to get access to all the features.
- data/README.rdoc +5 -4
- data/app/tanuki/controller/{link.thtml → controller.link.thtml} +0 -0
- data/app/tanuki/controller/controller.page.thtml +14 -0
- data/app/tanuki/controller/controller.rb +1 -2
- data/app/tanuki/controller/controller.title.ttxt +1 -0
- data/app/tanuki/controller/controller.view.thtml +3 -0
- data/app/tanuki/fetcher/sequel/sequel.rb +34 -0
- data/app/tanuki/manager/controller/controller.rb +1 -1
- data/app/tanuki/manager/page/page.rb +1 -1
- data/app/tanuki/meta_model/{manager.ttxt → meta_model.manager.ttxt} +0 -0
- data/app/tanuki/meta_model/{manager_base.ttxt → meta_model.manager_base.ttxt} +0 -0
- data/app/tanuki/meta_model/{model.ttxt → meta_model.model.ttxt} +0 -0
- data/app/tanuki/meta_model/{model_base.ttxt → meta_model.model_base.ttxt} +0 -0
- data/app/tanuki/meta_model/meta_model.rb +1 -2
- data/app/tanuki/model/controller/controller.rb +1 -1
- data/app/tanuki/model/page/page.rb +1 -1
- data/app/tanuki/page/missing/{default.thtml → missing.page.thtml} +1 -1
- data/app/tanuki/page/missing/missing.rb +3 -2
- data/app/user/page/home/home.rb +2 -0
- data/app/user/page/home/home.title.thtml +1 -0
- data/app/user/page/home/home.view.css +88 -0
- data/app/user/page/home/home.view.thtml +22 -0
- data/bin/tanuki +2 -1
- data/config/common.rb +1 -0
- data/config/common_application.rb +3 -6
- data/config/development_application.rb +0 -3
- data/lib/tanuki.rb +8 -7
- data/lib/tanuki/application.rb +108 -81
- data/lib/tanuki/argument.rb +10 -5
- data/lib/tanuki/argument/integer_range.rb +4 -2
- data/lib/tanuki/{behavior/object_behavior.rb → base_behavior.rb} +21 -4
- data/lib/tanuki/configurator.rb +20 -8
- data/lib/tanuki/const.rb +32 -0
- data/lib/tanuki/context.rb +18 -7
- data/lib/tanuki/controller.rb +517 -0
- data/lib/tanuki/css_compressor.rb +50 -0
- data/lib/tanuki/extensions/module.rb +21 -5
- data/lib/tanuki/extensions/rack/frozen_route.rb +35 -0
- data/lib/tanuki/extensions/rack/static_dir.rb +1 -1
- data/lib/tanuki/extensions/sequel/model.rb +7 -0
- data/lib/tanuki/i18n.rb +8 -6
- data/lib/tanuki/loader.rb +166 -33
- data/lib/tanuki/meta_model.rb +176 -0
- data/lib/tanuki/{behavior/model_behavior.rb → model_behavior.rb} +7 -3
- data/lib/tanuki/model_generator.rb +49 -29
- data/lib/tanuki/template_compiler.rb +72 -41
- data/lib/tanuki/utility.rb +11 -4
- data/lib/tanuki/utility/create.rb +52 -11
- data/lib/tanuki/utility/generate.rb +16 -10
- data/lib/tanuki/utility/version.rb +1 -1
- data/lib/tanuki/version.rb +7 -2
- metadata +50 -66
- data/app/tanuki/controller/default.thtml +0 -5
- data/app/tanuki/controller/index.thtml +0 -1
- data/app/user/page/index/default.thtml +0 -121
- data/app/user/page/index/index.rb +0 -2
- data/config/test_application.rb +0 -2
- data/lib/tanuki/behavior/controller_behavior.rb +0 -366
- data/lib/tanuki/behavior/meta_model_behavior.rb +0 -160
- data/lib/tanuki/extensions/rack/builder.rb +0 -26
- data/lib/tanuki/extensions/rack/server.rb +0 -18
- data/lib/tanuki/launcher.rb +0 -21
- data/lib/tanuki/utility/server.rb +0 -23
@@ -0,0 +1,176 @@
|
|
1
|
+
module Tanuki
|
2
|
+
|
3
|
+
# Tanuki::MetaModel contains all methods for the meta-model.
|
4
|
+
class MetaModel
|
5
|
+
|
6
|
+
include Tanuki::BaseBehavior
|
7
|
+
|
8
|
+
# Creates new meta-model +name+ in +namespace+.
|
9
|
+
# Model schema is passed as +data+.
|
10
|
+
# Stucture +models+ contains all models being generated.
|
11
|
+
def initialize(namespace, name, data, models)
|
12
|
+
@namespace = namespace
|
13
|
+
@name = name
|
14
|
+
@data = data
|
15
|
+
@models = models
|
16
|
+
end
|
17
|
+
|
18
|
+
# Returns class name for a given class type.
|
19
|
+
def class_name_for(class_type)
|
20
|
+
case class_type
|
21
|
+
when :model, :model_base then "#{@namespace}::Model::#{@name}"
|
22
|
+
when :manager, :manager_base then "#{@namespace}::Manager::#{@name}"
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
# Returns an array of code for alias-column name pair.
|
27
|
+
def key
|
28
|
+
@key.inspect
|
29
|
+
end
|
30
|
+
|
31
|
+
# Prepares data for template generation.
|
32
|
+
# Processes own keys, fields, etc.
|
33
|
+
def process
|
34
|
+
process_source
|
35
|
+
process_key
|
36
|
+
process_joins
|
37
|
+
process_filter
|
38
|
+
process_order
|
39
|
+
end
|
40
|
+
|
41
|
+
# Extracts the model firts-source information form the YAML @data
|
42
|
+
# and performs
|
43
|
+
def process_source
|
44
|
+
guess_table = @name.pluralize
|
45
|
+
@data ||= {}
|
46
|
+
@source = @data['source'] || guess_table
|
47
|
+
@source = {'table' => @source} if @source.is_a? String
|
48
|
+
@first_source = (@source['table'] || guess_table).downcase.to_sym
|
49
|
+
end
|
50
|
+
|
51
|
+
def process_key
|
52
|
+
if @source.include? 'key' && @source['key'].nil?
|
53
|
+
@key = []
|
54
|
+
else
|
55
|
+
@key = @source['key'] || 'id'
|
56
|
+
end
|
57
|
+
@key = [@key] if @key.is_a? String
|
58
|
+
raise 'invalid key' unless @key.is_a? Array
|
59
|
+
@key.map! do |k|
|
60
|
+
parts = k.split('.').map {|p| p.to_sym }
|
61
|
+
raise "invalid key field #{k}" if parts.count > 2
|
62
|
+
if parts.count == 2
|
63
|
+
if parts[0] != @first_source.to_s
|
64
|
+
raise 'all key fields should belong to the first-source'
|
65
|
+
end
|
66
|
+
parts
|
67
|
+
else
|
68
|
+
[@first_source, parts[0]]
|
69
|
+
end
|
70
|
+
end
|
71
|
+
end
|
72
|
+
|
73
|
+
def process_joins
|
74
|
+
@joins = {}
|
75
|
+
@joins[@first_source] = nil
|
76
|
+
joins = @source['joins'] || {}
|
77
|
+
joins = [joins] if joins.is_a? String
|
78
|
+
if joins.is_a? Array
|
79
|
+
joins = Hash[*joins.collect {|v| [v, nil] }.flatten]
|
80
|
+
end
|
81
|
+
if joins.is_a? Hash
|
82
|
+
joins.each_pair do |table_alias, join|
|
83
|
+
table_alias = table_alias.to_sym
|
84
|
+
if @joins.include? table_alias
|
85
|
+
raise "#{table_alias} is already in use"
|
86
|
+
end
|
87
|
+
if join && (join['on'].is_a? Hash)
|
88
|
+
table_name = join['table'] || table_alias
|
89
|
+
on = join['on']
|
90
|
+
join_type = (join['type'] || 'inner').to_sym
|
91
|
+
else
|
92
|
+
on = join
|
93
|
+
table_name = table_alias
|
94
|
+
join_type = :inner
|
95
|
+
end
|
96
|
+
if on
|
97
|
+
on.map! do |lhs, rhs|
|
98
|
+
[
|
99
|
+
[lhs, table_alias],
|
100
|
+
[rhs, @first_source]
|
101
|
+
].map do |side, table_alias|
|
102
|
+
if side.is_a? String
|
103
|
+
if m = side.match(/^\(('|")(.*)\1\)$/)
|
104
|
+
m[2]
|
105
|
+
else
|
106
|
+
parts = side.split('.').map {|x| x.to_sym }
|
107
|
+
case parts.count
|
108
|
+
when 1
|
109
|
+
[table_alias, parts[0]]
|
110
|
+
when 2
|
111
|
+
unless @joins.include? parts[0]
|
112
|
+
raise "unknown alias #{parts[0]}"
|
113
|
+
end
|
114
|
+
parts
|
115
|
+
else
|
116
|
+
raise "invalid column specification #{lhs}"
|
117
|
+
end # case
|
118
|
+
end # if match
|
119
|
+
else
|
120
|
+
side
|
121
|
+
end # if
|
122
|
+
end # map
|
123
|
+
end # map!
|
124
|
+
on = Hash[*on.flatten]
|
125
|
+
else
|
126
|
+
on = {}
|
127
|
+
@key.each do |k|
|
128
|
+
on[[
|
129
|
+
table_alias,
|
130
|
+
(@first_source.to_s.singularize << '_' << k[1].to_s).to_sym
|
131
|
+
]] = k
|
132
|
+
end # each
|
133
|
+
end # if
|
134
|
+
@joins[table_alias] = {
|
135
|
+
:type => join_type,
|
136
|
+
:table => table_name,
|
137
|
+
:alias => table_alias,
|
138
|
+
:on => on
|
139
|
+
}
|
140
|
+
end # each_pair
|
141
|
+
else
|
142
|
+
raise "`joins' should be either nil or string or array or hash"
|
143
|
+
end # if
|
144
|
+
end
|
145
|
+
|
146
|
+
# Prepares data for building a Sequel +where+ clause.
|
147
|
+
def process_filter
|
148
|
+
# TODO
|
149
|
+
end
|
150
|
+
|
151
|
+
# Prepares data for building a Sequel +order+ clause.
|
152
|
+
def process_order
|
153
|
+
# TODO
|
154
|
+
end
|
155
|
+
|
156
|
+
# Prepares data for template generation.
|
157
|
+
# Processes foreign keys, fields, etc.
|
158
|
+
def process_relations
|
159
|
+
# TODO
|
160
|
+
end
|
161
|
+
|
162
|
+
# Returns code for alias-column name pair for field +field_name+.
|
163
|
+
def qualified_name(field_name)
|
164
|
+
parts = field_name.split('.')
|
165
|
+
if parts.length == 1
|
166
|
+
"%w{:#{@first_source} :#{field_name}}"
|
167
|
+
elsif parts.length == 2
|
168
|
+
"%w{:#{parts[0]} :#{parts[1]}}"
|
169
|
+
else
|
170
|
+
raise "field name for model #{@namespace}.#{@name} is invalid"
|
171
|
+
end
|
172
|
+
end
|
173
|
+
|
174
|
+
end # MetaModel
|
175
|
+
|
176
|
+
end # Tanuki
|
@@ -22,7 +22,9 @@ module Tanuki
|
|
22
22
|
@_errors ||= {}
|
23
23
|
@_original ||= {}
|
24
24
|
begin
|
25
|
-
|
25
|
+
unless @_original.include? attribute
|
26
|
+
@_original[attribute] = self[attribute]
|
27
|
+
end
|
26
28
|
self.class[attribute].set(@_data, value)
|
27
29
|
@_errors.delete(attribute)
|
28
30
|
rescue
|
@@ -79,11 +81,13 @@ module Tanuki
|
|
79
81
|
# Creates new model, or returns existing one.
|
80
82
|
def get(data, ctx, lazy=false) # IDENTITY TRACKING AND LAZY LOADING
|
81
83
|
entity_key = extract_key(data)
|
82
|
-
key = [self, entity_key]
|
84
|
+
key = [self, entity_key]
|
85
|
+
# extract_key is generated ad-hoc by model compiler!
|
83
86
|
if cached = ctx.entity_cache[key]
|
84
87
|
cached
|
85
88
|
else
|
86
|
-
ctx.entity_cache[key] = get(*entity_key)
|
89
|
+
ctx.entity_cache[key] = get(*entity_key)
|
90
|
+
# get is generated ad-hoc by model compiler
|
87
91
|
end
|
88
92
|
end
|
89
93
|
|
@@ -3,14 +3,16 @@ module Tanuki
|
|
3
3
|
# Tanuki::ModelGenerator is used for, well, generating models
|
4
4
|
class ModelGenerator
|
5
5
|
|
6
|
-
# A collection of all model definitions represented as hash of hashes
|
7
|
-
# the first nesting level represents namespaces
|
8
|
-
# and the second one contains named model bodies
|
6
|
+
# A collection of all model definitions represented as hash of hashes
|
7
|
+
# where the first nesting level represents namespaces
|
8
|
+
# and the second one contains named model bodies hash.
|
9
9
|
attr_reader :models
|
10
10
|
|
11
|
-
#
|
12
|
-
# Keys in this hash are full qualified names of models
|
13
|
-
#
|
11
|
+
# Hash for collecting the template render-time info for models.
|
12
|
+
# Keys in this hash are full qualified names of models
|
13
|
+
# (+namespace_model_name+). Base models are marked with +(base)+ suffix.
|
14
|
+
# Values are hashes in the form of
|
15
|
+
# +{:generated => [], :failed => [], :skipped => []}+
|
14
16
|
# where all subentries correspond to template generation statuses.
|
15
17
|
attr_reader :tried
|
16
18
|
|
@@ -24,26 +26,41 @@ module Tanuki
|
|
24
26
|
# Loads all models into memory from a given +schema_root+ and prepares
|
25
27
|
# their own properties to be rendered in class templates.
|
26
28
|
def read_models(schema_root)
|
27
|
-
Dir.entries(schema_root).reject {|path| path =~ /\A
|
28
|
-
|
29
|
+
paths = Dir.entries(schema_root).reject {|path| path =~ /\A\..*\Z/ }
|
30
|
+
paths.each do |namespace_path|
|
31
|
+
namespace_name = namespace_path.camelize
|
29
32
|
namespace = @models[namespace_name] = {}
|
30
|
-
|
31
|
-
|
33
|
+
schema_glob = "#{schema_root}/#{namespace_path}/models/*.yml"
|
34
|
+
p schema_glob
|
35
|
+
Dir.glob(schema_glob) do |file_path|
|
36
|
+
|
37
|
+
# Create meta model for namespace and model name from schema
|
38
|
+
model_name = File.basename(file_path, '.yml').camelize
|
32
39
|
meta_model = namespace[model_name] = Tanuki::MetaModel.new(
|
33
40
|
namespace_name,
|
34
41
|
model_name,
|
35
42
|
YAML.load_file(file_path),
|
36
43
|
@models
|
37
44
|
)
|
38
|
-
meta_model.process
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
45
|
+
meta_model.process
|
46
|
+
namespace_model_name = "#{namespace_name}.#{model_name}"
|
47
|
+
|
48
|
+
# TODO is this necessary?
|
49
|
+
next if @tried.include? namespace_model_name
|
50
|
+
|
51
|
+
# Register namespace and model name
|
52
|
+
@tried[namespace_model_name] = {
|
53
|
+
:generated => [],
|
54
|
+
:failed => [],
|
55
|
+
:skipped => []
|
56
|
+
}
|
57
|
+
@tried["#{namespace_model_name} (base)"] = {
|
58
|
+
:generated => [],
|
59
|
+
:failed => [],
|
60
|
+
:skipped => []
|
61
|
+
}
|
62
|
+
end # glob
|
63
|
+
end # each
|
47
64
|
end
|
48
65
|
|
49
66
|
# Generates all models that were read from a given +schema_root+.
|
@@ -57,19 +74,21 @@ module Tanuki
|
|
57
74
|
# Renders a model class by applying +class_type+ template
|
58
75
|
# to a given +meta_model+. Classes are splitted in two parts:
|
59
76
|
# * user-extendable part (+base=false+), which resides in +ctx.app_root+,
|
60
|
-
# * other part with framework-specific code (+base=true+),
|
77
|
+
# * other part with framework-specific code (+base=true+),
|
78
|
+
# which resides in +ctx.gen_root+.
|
61
79
|
# +namespace_model_name+ is used as a label for error report collection
|
62
80
|
# in Tanuki::ModelGenerator#tried hash.
|
63
81
|
def generate_class(meta_model, namespace_model_name, class_type, base)
|
64
82
|
class_name = meta_model.class_name_for class_type
|
65
83
|
namespace_model_name += ' (base)' if base
|
66
|
-
|
84
|
+
root = base ? @ctx.gen_root : @ctx.app_root
|
85
|
+
path = Tanuki::Loader.class_path(class_name, root)
|
67
86
|
if base || !(File.exists? path)
|
68
87
|
begin
|
69
88
|
dirname = File.dirname(path)
|
70
89
|
FileUtils.mkdir_p dirname unless File.directory? dirname
|
71
|
-
File.open
|
72
|
-
writer = proc {|out| file
|
90
|
+
File.open(path, 'w') do |file|
|
91
|
+
writer = proc {|out| file << out.to_s }
|
73
92
|
Loader.run_template({}, meta_model, class_type).call(writer, @ctx)
|
74
93
|
end
|
75
94
|
@tried[namespace_model_name][:generated] << class_name
|
@@ -88,9 +107,9 @@ module Tanuki
|
|
88
107
|
namespace.each do |model_name, meta_model|
|
89
108
|
namespace_model_name = "#{namespace_name}.#{model_name}"
|
90
109
|
{
|
91
|
-
:model
|
92
|
-
:model_base
|
93
|
-
:manager
|
110
|
+
:model => false,
|
111
|
+
:model_base => true,
|
112
|
+
:manager => false,
|
94
113
|
:manager_base => true
|
95
114
|
}.each do |class_type, base|
|
96
115
|
generate_class meta_model, namespace_model_name, class_type, base
|
@@ -99,12 +118,13 @@ module Tanuki
|
|
99
118
|
end
|
100
119
|
end
|
101
120
|
|
102
|
-
# Iterates over all loaded model definitions, giving them a chance
|
103
|
-
# and make any cross-model assumptions
|
121
|
+
# Iterates over all loaded model definitions, giving them a chance
|
122
|
+
# to meet each other and make any cross-model assumptions
|
123
|
+
# (like names for foreign keys).
|
104
124
|
def process_models
|
105
125
|
@models.each do |namespace_name, namespace |
|
106
126
|
namespace.each do |model_name, meta_model|
|
107
|
-
meta_model.process_relations
|
127
|
+
meta_model.process_relations
|
108
128
|
end
|
109
129
|
end
|
110
130
|
end
|
@@ -8,36 +8,45 @@ module Tanuki
|
|
8
8
|
# The following tags are recognized:
|
9
9
|
#
|
10
10
|
# <% Ruby code -- output to stdout %>
|
11
|
+
# <%~ Ruby code -- output to stdout %>
|
11
12
|
# <%= Ruby expression -- replace with result %>
|
12
13
|
# <%# comment -- ignored -- useful in testing %>
|
13
|
-
# % a line of Ruby code -- treated as <% line %>
|
14
|
-
# %% replaced with % if first thing on a line
|
15
14
|
# <%% or %%> -- replace with <% or %> respectively
|
16
15
|
# <%! Ruby expression -- must return a template %> -- renders a template
|
17
16
|
# <%_visitor Ruby code %> -- see Tanuki::Application::visitor for details
|
18
17
|
# <l10n><en>English text</en> ... -- other localizations </l10n>
|
18
|
+
#
|
19
|
+
# All of these tags, except +l10n+, have a single line syntax:
|
20
|
+
#
|
21
|
+
# % a line of Ruby code -- treated as <% line -%>
|
22
|
+
# %~ a line of Ruby code -- treated as <% line -%>
|
23
|
+
# %= Ruby expression -- treated as <%= line -%>
|
24
|
+
# %# comment -- ignored -- treated as <%# line -%>
|
25
|
+
# %% -- replace with % if first thing on a line
|
26
|
+
# %! Ruby expression that returns a template -- treated as <%! line -%>
|
27
|
+
# %_visitor Ruby code -- treated as <%_visitor line -%>
|
19
28
|
class TemplateCompiler
|
20
29
|
|
21
30
|
class << self
|
22
31
|
|
23
|
-
# Compiles a template from a given +src+ string to +ios+
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
code << TEMPLATE_FOOTER % sym
|
37
|
-
obj.instance_eval code
|
32
|
+
# Compiles a template from a given +src+ string to +ios+
|
33
|
+
# for method +sym+ in class +klass+.
|
34
|
+
# If +development+ is false, then no encoding and class declarations,
|
35
|
+
# as well as runtime template checks are generated.
|
36
|
+
def compile_template(ios, src, klass, sym, development=true)
|
37
|
+
ios << TEMPLATE_HEADERS[:class] % [src.encoding, klass] if development
|
38
|
+
ios << TEMPLATE_HEADERS[:method] % sym
|
39
|
+
ios << TEMPLATE_HEADERS[:dev] % sym if development
|
40
|
+
ios << TEMPLATE_HEADERS[:context] % [klass, sym]
|
41
|
+
compile(ios, parse_wiki(src.chomp), true)
|
42
|
+
ios << TEMPLATE_FOOTERS[:dev] % sym if development
|
43
|
+
ios << TEMPLATE_FOOTERS[:method]
|
44
|
+
ios << TEMPLATE_FOOTERS[:class] if development
|
38
45
|
end
|
39
46
|
|
40
|
-
# Replaces all wiki inserts like
|
47
|
+
# Replaces all wiki inserts like
|
48
|
+
# +[[controller?attribute:link#template]]+
|
49
|
+
# with corresponding code in template tags +<%! %>+.
|
41
50
|
def parse_wiki(s)
|
42
51
|
s.gsub WIKI_SYNTAX do
|
43
52
|
code = '<%! self'
|
@@ -52,12 +61,15 @@ module Tanuki
|
|
52
61
|
end
|
53
62
|
}.join
|
54
63
|
|
55
|
-
|
64
|
+
if $~[:model]
|
65
|
+
attrs = $~[:model].split('.').map {|attr| "[:#{attr}]"}.join
|
66
|
+
code << ".model#{attrs}"
|
67
|
+
end
|
56
68
|
code << ".link_to(:#{$~[:link]})" if $~[:link]
|
57
69
|
|
58
70
|
# Template
|
59
71
|
code << case $~[:template]
|
60
|
-
when '' then '.
|
72
|
+
when '' then '.view'
|
61
73
|
when nil then '.link_view'
|
62
74
|
else ".#{$~[:template]}_view"
|
63
75
|
end
|
@@ -76,7 +88,8 @@ module Tanuki
|
|
76
88
|
begin
|
77
89
|
|
78
90
|
# Find out state for expected pattern
|
79
|
-
|
91
|
+
pattern = expect_pattern(state)
|
92
|
+
if new_index = src[index..-1].index(pattern)
|
80
93
|
new_index += index
|
81
94
|
match = src[index..-1].match(pattern)[0]
|
82
95
|
new_state = next_state(state, match)
|
@@ -105,7 +118,8 @@ module Tanuki
|
|
105
118
|
if new_index
|
106
119
|
unless state != :outer && new_state == :code_skip
|
107
120
|
if new_state == :outer
|
108
|
-
|
121
|
+
code_buf << src[index...new_index]
|
122
|
+
process_code_state(ios, code_buf, state)
|
109
123
|
code_buf = ''
|
110
124
|
end
|
111
125
|
index = new_index + match.length
|
@@ -119,20 +133,32 @@ module Tanuki
|
|
119
133
|
end
|
120
134
|
|
121
135
|
end until new_index.nil?
|
122
|
-
|
136
|
+
|
137
|
+
if ensure_output && !(PRINT_STATES.include? last_state)
|
138
|
+
ios << "\n_.('',ctx)"
|
139
|
+
end
|
123
140
|
last_state
|
124
141
|
end
|
125
142
|
|
126
143
|
private
|
127
144
|
|
128
145
|
# Scanner states that output the evaluated result.
|
129
|
-
PRINT_STATES = [:outer, :code_print]
|
146
|
+
PRINT_STATES = [:outer, :code_print].freeze
|
130
147
|
|
131
148
|
# Template header code. Sent to output before compilation.
|
132
|
-
|
149
|
+
TEMPLATE_HEADERS = {
|
150
|
+
:class => "# encoding: %s\nclass %s\n",
|
151
|
+
:method => "def %s_view(*args,&block)\nproc do|_,ctx|\n",
|
152
|
+
:dev => "if _has_tpl ctx,self.class,:%s\n",
|
153
|
+
:context => %{ctx=_ctx(ctx,"%s#%s")}
|
154
|
+
}.freeze
|
133
155
|
|
134
156
|
# Template footer code. Sent to output after compilation.
|
135
|
-
|
157
|
+
TEMPLATE_FOOTERS = {
|
158
|
+
:dev => "\nelse\n(_run_tpl ctx,self,:%s,*args,&block).(_,ctx)\nend\n",
|
159
|
+
:method => "end\nend\n",
|
160
|
+
:class => "end\n"
|
161
|
+
}.freeze
|
136
162
|
|
137
163
|
# Wiki insert syntax
|
138
164
|
WIKI_SYNTAX = %r{
|
@@ -142,22 +168,23 @@ module Tanuki
|
|
142
168
|
(?::(?<link>[a-z_]+))?
|
143
169
|
(?:\#(?<template>[a-z_]*))?
|
144
170
|
\]\]
|
145
|
-
}x
|
171
|
+
}x.freeze
|
146
172
|
|
147
|
-
# Generates code for Ruby template bits from a given +src+ to +ios+
|
173
|
+
# Generates code for Ruby template bits from a given +src+ to +ios+
|
174
|
+
# for a given +state+.
|
148
175
|
def process_code_state(ios, src, state)
|
149
176
|
src.strip!
|
150
177
|
src.gsub!(/^[ \t]+/, '')
|
151
178
|
case state
|
152
|
-
when
|
179
|
+
when /code_(?:line_)?span/ then
|
153
180
|
ios << "\n#{src}"
|
154
|
-
when
|
181
|
+
when /code_(?:line_)?print/ then
|
155
182
|
ios << "\n_.((#{src}),ctx)"
|
156
|
-
when
|
183
|
+
when /code_(?:line_)?template/ then
|
157
184
|
ios << "\n(#{src}).(_,ctx)"
|
158
|
-
when
|
159
|
-
|
160
|
-
ios << "\n#{
|
185
|
+
when /code_(?:line_)?visitor/
|
186
|
+
m = src.match(/^([^ \(]+)?(\([^\)]*\))?\s*(.*)$/)
|
187
|
+
ios << "\n#{m[1]}_result=(#{m[3]}).(#{m[1]}_visitor#{m[2]},ctx)"
|
161
188
|
when :l10n then
|
162
189
|
localize(ios, src)
|
163
190
|
end
|
@@ -166,9 +193,9 @@ module Tanuki
|
|
166
193
|
# Returns the next expected pattern for a given +state+.
|
167
194
|
def expect_pattern(state)
|
168
195
|
case state
|
169
|
-
when :outer then %r{
|
170
|
-
when
|
171
|
-
when
|
196
|
+
when :outer then %r{(?:^\s*|<)%[~=!_#%]?|<l10n>}
|
197
|
+
when /\Acode_line/ then %r{\n|\Z}
|
198
|
+
when /\Acode_(?:span|print|template|visitor|comment)/ then %r{[-%]?%>}
|
172
199
|
when :l10n then %r{<\/l10n>}
|
173
200
|
end
|
174
201
|
end
|
@@ -178,8 +205,6 @@ module Tanuki
|
|
178
205
|
case state
|
179
206
|
when :outer then
|
180
207
|
case match
|
181
|
-
when /\A\s*%\Z/ then :code_line
|
182
|
-
when /\A\s*%%\Z/ then :code_skip
|
183
208
|
when '<%' then :code_span
|
184
209
|
when '<%=' then :code_print
|
185
210
|
when '<%!' then :code_template
|
@@ -187,9 +212,15 @@ module Tanuki
|
|
187
212
|
when '<%#' then :code_comment
|
188
213
|
when '<%%' then :code_skip
|
189
214
|
when '<l10n>' then :l10n
|
215
|
+
when /\A\s*%~?\Z/ then :code_line_span
|
216
|
+
when /\A\s*%=\Z/ then :code_line_print
|
217
|
+
when /\A\s*%!\Z/ then :code_line_template
|
218
|
+
when /\A\s*%_\Z/ then :code_line_visitor
|
219
|
+
when /\A\s*%#\Z/ then :code_line_comment
|
220
|
+
when /\A\s*%%\Z/ then :code_skip
|
190
221
|
end
|
191
|
-
when
|
192
|
-
when
|
222
|
+
when /\Acode_line/ then :outer
|
223
|
+
when /\Acode_(?:span|print|template|visitor|comment)/ then
|
193
224
|
case match
|
194
225
|
when '%%>' then :code_skip
|
195
226
|
else :outer
|