tanuki 0.3.1 → 0.4.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (63) hide show
  1. data/README.rdoc +5 -4
  2. data/app/tanuki/controller/{link.thtml → controller.link.thtml} +0 -0
  3. data/app/tanuki/controller/controller.page.thtml +14 -0
  4. data/app/tanuki/controller/controller.rb +1 -2
  5. data/app/tanuki/controller/controller.title.ttxt +1 -0
  6. data/app/tanuki/controller/controller.view.thtml +3 -0
  7. data/app/tanuki/fetcher/sequel/sequel.rb +34 -0
  8. data/app/tanuki/manager/controller/controller.rb +1 -1
  9. data/app/tanuki/manager/page/page.rb +1 -1
  10. data/app/tanuki/meta_model/{manager.ttxt → meta_model.manager.ttxt} +0 -0
  11. data/app/tanuki/meta_model/{manager_base.ttxt → meta_model.manager_base.ttxt} +0 -0
  12. data/app/tanuki/meta_model/{model.ttxt → meta_model.model.ttxt} +0 -0
  13. data/app/tanuki/meta_model/{model_base.ttxt → meta_model.model_base.ttxt} +0 -0
  14. data/app/tanuki/meta_model/meta_model.rb +1 -2
  15. data/app/tanuki/model/controller/controller.rb +1 -1
  16. data/app/tanuki/model/page/page.rb +1 -1
  17. data/app/tanuki/page/missing/{default.thtml → missing.page.thtml} +1 -1
  18. data/app/tanuki/page/missing/missing.rb +3 -2
  19. data/app/user/page/home/home.rb +2 -0
  20. data/app/user/page/home/home.title.thtml +1 -0
  21. data/app/user/page/home/home.view.css +88 -0
  22. data/app/user/page/home/home.view.thtml +22 -0
  23. data/bin/tanuki +2 -1
  24. data/config/common.rb +1 -0
  25. data/config/common_application.rb +3 -6
  26. data/config/development_application.rb +0 -3
  27. data/lib/tanuki.rb +8 -7
  28. data/lib/tanuki/application.rb +108 -81
  29. data/lib/tanuki/argument.rb +10 -5
  30. data/lib/tanuki/argument/integer_range.rb +4 -2
  31. data/lib/tanuki/{behavior/object_behavior.rb → base_behavior.rb} +21 -4
  32. data/lib/tanuki/configurator.rb +20 -8
  33. data/lib/tanuki/const.rb +32 -0
  34. data/lib/tanuki/context.rb +18 -7
  35. data/lib/tanuki/controller.rb +517 -0
  36. data/lib/tanuki/css_compressor.rb +50 -0
  37. data/lib/tanuki/extensions/module.rb +21 -5
  38. data/lib/tanuki/extensions/rack/frozen_route.rb +35 -0
  39. data/lib/tanuki/extensions/rack/static_dir.rb +1 -1
  40. data/lib/tanuki/extensions/sequel/model.rb +7 -0
  41. data/lib/tanuki/i18n.rb +8 -6
  42. data/lib/tanuki/loader.rb +166 -33
  43. data/lib/tanuki/meta_model.rb +176 -0
  44. data/lib/tanuki/{behavior/model_behavior.rb → model_behavior.rb} +7 -3
  45. data/lib/tanuki/model_generator.rb +49 -29
  46. data/lib/tanuki/template_compiler.rb +72 -41
  47. data/lib/tanuki/utility.rb +11 -4
  48. data/lib/tanuki/utility/create.rb +52 -11
  49. data/lib/tanuki/utility/generate.rb +16 -10
  50. data/lib/tanuki/utility/version.rb +1 -1
  51. data/lib/tanuki/version.rb +7 -2
  52. metadata +50 -66
  53. data/app/tanuki/controller/default.thtml +0 -5
  54. data/app/tanuki/controller/index.thtml +0 -1
  55. data/app/user/page/index/default.thtml +0 -121
  56. data/app/user/page/index/index.rb +0 -2
  57. data/config/test_application.rb +0 -2
  58. data/lib/tanuki/behavior/controller_behavior.rb +0 -366
  59. data/lib/tanuki/behavior/meta_model_behavior.rb +0 -160
  60. data/lib/tanuki/extensions/rack/builder.rb +0 -26
  61. data/lib/tanuki/extensions/rack/server.rb +0 -18
  62. data/lib/tanuki/launcher.rb +0 -21
  63. 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
- @_original[attribute] = self[attribute] unless @_original.include? attribute
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] # extract_key is generated ad hoc by model compiler!
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) # get is generated Ad Hoc by model compiler
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 where
7
- # the first nesting level represents namespaces
8
- # and the second one contains named model bodies +Hash+.
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
- # +Hash+ for collecting the template render-time info for models.
12
- # Keys in this hash are full qualified names of models (+namespace_model_name+).
13
- # Values are hashes in the form of +{:generated => [], :failed => [], :skipped => []}+
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\.{1,2}\Z/ }.each do |namespace_path|
28
- namespace_name = namespace_path.split('_').map {|s| s.capitalize }.join
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
- Dir.glob(File.join(schema_root, namespace_path, 'models', '*.yml')) do |file_path|
31
- model_name = File.basename(file_path, '.yml').split('_').map {|s| s.capitalize }.join
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
- if @tried.include? namespace_model_name = "#{namespace_name}.#{model_name}"
40
- next
41
- else
42
- @tried[namespace_model_name] = {:generated => [], :failed => [], :skipped => []}
43
- @tried["#{namespace_model_name} (base)"] = {:generated => [], :failed => [], :skipped => []}
44
- end
45
- end
46
- end
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+), which resides in +ctx.gen_root+.
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
- path = Tanuki::Loader.class_path(class_name, base ? @ctx.gen_root : @ctx.app_root)
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 path, 'w' do |file|
72
- writer = proc {|out| file.print out.to_s }
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 => false,
92
- :model_base => true,
93
- :manager => false,
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 to meet each other
103
- # and make any cross-model assumptions (like names for foreign keys).
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+ for method +sym+ in class +klass+.
24
- def compile_template(ios, src, klass, sym)
25
- ios << "# encoding: #{src.encoding}\nclass #{klass}\n" << TEMPLATE_HEADER % sym
26
- compile(ios, src, true)
27
- ios << TEMPLATE_FOOTER % sym << "\nend"
28
- end
29
-
30
- # Compiles a wikified template from a given +src+ string to method +sym+ for a given object +obj+.
31
- def compile_wiki(src, obj, sym)
32
- code = StringIO.new
33
- code << TEMPLATE_HEADER % sym
34
- parse_wiki src
35
- compile(code, src, true)
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 +[[controller?attribute:link#template]]+ with corresponding code in template tags +<%! %>+.
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
- code << ".model#{$~[:model].split('.').map {|attr| "[:#{attr}]"}.join}" if $~[:model]
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 '.default_view'
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
- if new_index = src[index..-1].index(pattern = expect_pattern(state))
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
- process_code_state(ios, code_buf << src[index...new_index], state)
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
- ios << "\n_.('',ctx)" if ensure_output && !(PRINT_STATES.include? last_state)
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
- TEMPLATE_HEADER = "def %1$s_view(*args,&block)\nproc do|_,ctx|\nif _has_tpl ctx,self.class,:%1$s\nctx=_ctx(ctx)"
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
- TEMPLATE_FOOTER = "\nelse\n(_run_tpl ctx,self,:%s,*args,&block).(_,ctx)\nend\nend\nend"
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+ for a given +state+.
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 :code_line, :code_span then
179
+ when /code_(?:line_)?span/ then
153
180
  ios << "\n#{src}"
154
- when :code_print then
181
+ when /code_(?:line_)?print/ then
155
182
  ios << "\n_.((#{src}),ctx)"
156
- when :code_template then
183
+ when /code_(?:line_)?template/ then
157
184
  ios << "\n(#{src}).(_,ctx)"
158
- when :code_visitor
159
- inner_m = src.match(/^([^ \(]+)?(\([^\)]*\))?\s*(.*)$/)
160
- ios << "\n#{inner_m[1]}_result=(#{inner_m[3]}).(#{inner_m[1]}_visitor#{inner_m[2]},ctx)"
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{^\s*%%?|<%[=!_#%]?|<l10n>}
170
- when :code_line then %r{\n|\Z}
171
- when :code_span, :code_print, :code_template, :code_visitor, :code_comment then %r{[-%]?%>}
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 :code_line then :outer
192
- when :code_span, :code_print, :code_template, :code_visitor, :code_comment then
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