tanuki 0.3.1 → 0.4.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.
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