adsl 0.0.3 → 0.1.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 (91) hide show
  1. checksums.yaml +4 -4
  2. data/Gemfile +2 -20
  3. data/README.md +14 -21
  4. data/bin/adsl-verify +8 -8
  5. data/lib/adsl.rb +3 -0
  6. data/lib/adsl/adsl.rb +3 -0
  7. data/lib/adsl/ds/data_store_spec.rb +339 -0
  8. data/lib/adsl/extract/instrumenter.rb +206 -0
  9. data/lib/adsl/extract/meta.rb +33 -0
  10. data/lib/adsl/extract/rails/action_block_builder.rb +233 -0
  11. data/lib/adsl/extract/rails/action_instrumenter.rb +400 -0
  12. data/lib/adsl/extract/rails/action_runner.rb +57 -0
  13. data/lib/adsl/extract/rails/active_record_metaclass_generator.rb +555 -0
  14. data/lib/adsl/extract/rails/callback_chain_simulator.rb +135 -0
  15. data/lib/adsl/extract/rails/invariant_extractor.rb +48 -0
  16. data/lib/adsl/extract/rails/invariant_instrumenter.rb +70 -0
  17. data/lib/adsl/extract/rails/other_meta.rb +57 -0
  18. data/lib/adsl/extract/rails/rails_extractor.rb +211 -0
  19. data/lib/adsl/extract/rails/rails_instrumentation_test_case.rb +34 -0
  20. data/lib/adsl/extract/rails/rails_special_gem_instrumentation.rb +120 -0
  21. data/lib/adsl/extract/rails/rails_test_helper.rb +140 -0
  22. data/lib/adsl/extract/sexp_utils.rb +54 -0
  23. data/lib/adsl/fol/first_order_logic.rb +261 -0
  24. data/lib/adsl/parser/adsl_parser.racc +159 -0
  25. data/lib/{parser → adsl/parser}/adsl_parser.rex +4 -4
  26. data/lib/{parser → adsl/parser}/adsl_parser.rex.rb +6 -6
  27. data/lib/adsl/parser/adsl_parser.tab.rb +1031 -0
  28. data/lib/adsl/parser/ast_nodes.rb +1410 -0
  29. data/lib/adsl/railtie.rb +67 -0
  30. data/lib/adsl/spass/bin.rb +230 -0
  31. data/lib/{spass → adsl/spass}/ruby_extensions.rb +0 -0
  32. data/lib/adsl/spass/spass_ds_extensions.rb +931 -0
  33. data/lib/adsl/spass/spass_translator.rb +393 -0
  34. data/lib/adsl/spass/util.rb +13 -0
  35. data/lib/adsl/util/csv_hash_formatter.rb +94 -0
  36. data/lib/adsl/util/general.rb +228 -0
  37. data/lib/adsl/util/test_helper.rb +71 -0
  38. data/lib/adsl/verification/formula_generators.rb +231 -0
  39. data/lib/adsl/verification/instrumentation_filter.rb +50 -0
  40. data/lib/adsl/verification/invariant.rb +19 -0
  41. data/lib/adsl/verification/rails_verification.rb +33 -0
  42. data/lib/adsl/verification/utils.rb +20 -0
  43. data/lib/adsl/verification/verification_case.rb +13 -0
  44. data/test/integration/rails/rails_branch_verification_test.rb +112 -0
  45. data/test/integration/rails/rails_verification_test.rb +253 -0
  46. data/test/integration/spass/basic_translation_test.rb +563 -0
  47. data/test/integration/spass/control_flow_translation_test.rb +421 -0
  48. data/test/unit/adsl/ds/data_store_spec_test.rb +54 -0
  49. data/test/unit/adsl/extract/instrumenter_test.rb +103 -0
  50. data/test/unit/adsl/extract/meta_test.rb +142 -0
  51. data/test/unit/adsl/extract/rails/action_block_builder_test.rb +178 -0
  52. data/test/unit/adsl/extract/rails/action_instrumenter_test.rb +68 -0
  53. data/test/unit/adsl/extract/rails/active_record_metaclass_generator_test.rb +336 -0
  54. data/test/unit/adsl/extract/rails/callback_chain_simulator_test.rb +76 -0
  55. data/test/unit/adsl/extract/rails/invariant_extractor_test.rb +92 -0
  56. data/test/unit/adsl/extract/rails/rails_extractor_test.rb +1380 -0
  57. data/test/unit/adsl/extract/rails/rails_test_helper_test.rb +25 -0
  58. data/test/unit/adsl/extract/sexp_utils_test.rb +100 -0
  59. data/test/unit/adsl/fol/first_order_logic_test.rb +227 -0
  60. data/test/unit/adsl/parser/action_parser_test.rb +1040 -0
  61. data/test/unit/adsl/parser/ast_nodes_test.rb +359 -0
  62. data/test/unit/adsl/parser/class_parser_test.rb +288 -0
  63. data/test/unit/adsl/parser/general_parser_test.rb +67 -0
  64. data/test/unit/adsl/parser/invariant_parser_test.rb +432 -0
  65. data/test/unit/adsl/parser/parser_util_test.rb +126 -0
  66. data/test/unit/adsl/spass/bin_test.rb +65 -0
  67. data/test/unit/adsl/spass/ruby_extensions_test.rb +39 -0
  68. data/test/unit/adsl/spass/spass_ds_extensions_test.rb +7 -0
  69. data/test/unit/adsl/spass/spass_translator_test.rb +342 -0
  70. data/test/unit/adsl/util/csv_hash_formatter_test.rb +68 -0
  71. data/test/unit/adsl/util/general_test.rb +303 -0
  72. data/test/unit/adsl/util/test_helper_test.rb +120 -0
  73. data/test/unit/adsl/verification/formula_generators_test.rb +200 -0
  74. data/test/unit/adsl/verification/instrumentation_filter_test.rb +39 -0
  75. data/test/unit/adsl/verification/utils_test.rb +39 -0
  76. data/test/unit/adsl/verification/verification_case_test.rb +8 -0
  77. metadata +229 -29
  78. data/lib/ds/data_store_spec.rb +0 -292
  79. data/lib/fol/first_order_logic.rb +0 -260
  80. data/lib/parser/adsl_ast.rb +0 -779
  81. data/lib/parser/adsl_parser.racc +0 -151
  82. data/lib/parser/adsl_parser.tab.rb +0 -976
  83. data/lib/parser/dsdl_parser.rex.rb +0 -196
  84. data/lib/parser/dsdl_parser.tab.rb +0 -976
  85. data/lib/spass/bin.rb +0 -164
  86. data/lib/spass/spass_ds_extensions.rb +0 -870
  87. data/lib/spass/spass_translator.rb +0 -388
  88. data/lib/spass/util.rb +0 -11
  89. data/lib/util/csv_hash_formatter.rb +0 -47
  90. data/lib/util/test_helper.rb +0 -33
  91. data/lib/util/util.rb +0 -114
@@ -0,0 +1,135 @@
1
+ require 'adsl/parser/ast_nodes'
2
+
3
+ module ADSL
4
+ module Extract
5
+ module Rails
6
+ module CallbackChainSimulator
7
+
8
+ include ADSL::Parser
9
+
10
+ # returns true or false if the node will render or raise, or will not render or raise
11
+ # returns nil if the node may or may not render or raise
12
+ def halting_status_of(ast_node, is_action_body = false)
13
+ if ast_node.is_a?(ASTBlock)
14
+ sub_statuses = ast_node.statements.map do |stmt|
15
+ sub_rs = halting_status_of stmt, is_action_body
16
+ return true if sub_rs
17
+ sub_rs
18
+ end
19
+ return nil if sub_statuses.include? nil
20
+ return false
21
+ elsif ast_node.is_a?(ASTEither)
22
+ sub_statuses = ast_node.blocks.map{ |block| halting_status_of block, is_action_body }
23
+ return false if sub_statuses.uniq == [false]
24
+ return true if sub_statuses.uniq == [true]
25
+ return nil
26
+ elsif ast_node.is_a?(ASTDummyStmt) and [:render, :raise].include?(ast_node.type)
27
+ return false if ast_node.type == :render and is_action_body
28
+ return true
29
+ else
30
+ return false
31
+ end
32
+ end
33
+
34
+ # returns a hash of {:will_halt => block, :will_not_halt => block}
35
+ def split_into_paths_that_will_or_will_not_halt(block, is_action_body = false)
36
+ case halting_status_of block, is_action_body
37
+ when true; return { :will_halt => block, :will_not_halt => nil }
38
+ when false; return { :will_halt => nil, :will_not_halt => block }
39
+ end
40
+
41
+ paths = { :will_halt => [], :will_not_halt => [] }
42
+ block.statements.each do |stmt|
43
+ if stmt.is_a?(ASTBlock) && halting_status_of(stmt, is_action_body).nil?
44
+ possibilities = split_into_paths_that_will_or_will_not_halt stmt, is_action_body
45
+
46
+ paths[:will_halt] << possibilities[:will_halt]
47
+ paths[:will_not_halt] << possibilities[:will_not_halt]
48
+ elsif stmt.is_a?(ASTEither) && halting_status_of(stmt, is_action_body).nil?
49
+ rendering_paths = []
50
+ not_rendering_paths = []
51
+
52
+ stmt.blocks.each do |subblock|
53
+ possibilities = split_into_paths_that_will_or_will_not_halt subblock, is_action_body
54
+ rendering_paths << possibilities[:will_halt] unless possibilities[:will_halt].nil?
55
+ not_rendering_paths << possibilities[:will_not_halt] unless possibilities[:will_not_halt].nil?
56
+ end
57
+
58
+ if rendering_paths.length == 1
59
+ paths[:will_halt] << rendering_paths.first
60
+ else
61
+ paths[:will_halt] << ASTEither.new(:blocks => rendering_paths)
62
+ end
63
+
64
+ if not_rendering_paths.length == 1
65
+ paths[:will_not_halt] << not_rendering_paths.first
66
+ else
67
+ paths[:will_not_halt] << ASTEither.new(:blocks => not_rendering_paths)
68
+ end
69
+ else
70
+ paths[:will_halt] << stmt
71
+ paths[:will_not_halt] << stmt
72
+ end
73
+ end
74
+
75
+ paths[:will_halt] = ASTBlock.new(:statements => paths[:will_halt])
76
+ paths[:will_not_halt] = ASTBlock.new(:statements => paths[:will_not_halt])
77
+
78
+ paths
79
+ end
80
+
81
+ def split_into_callbacks(root_block)
82
+ pairs = []
83
+ root_block.statements.reverse_each do |stmt|
84
+ if stmt.is_a? ADSL::Parser::ASTDummyStmt
85
+ pairs << [stmt.type, []]
86
+ else
87
+ pairs.last[1] << stmt
88
+ end
89
+ end
90
+ pairs.reverse!
91
+ pairs.length.times do |index|
92
+ stmts = pairs[index][1]
93
+ pairs[index][1] = (stmts.length == 1 ? stmts.first : ASTBlock.new(:statements => stmts.reverse))
94
+ end
95
+
96
+ pairs
97
+ end
98
+
99
+ def interrupt_callback_chain_on_render(root_block, action_name)
100
+ callbacks = split_into_callbacks root_block
101
+
102
+ index = action_index = callbacks.index{ |callback_name, block| callback_name == action_name }
103
+ return if index.nil?
104
+
105
+ # skip the action and proceed to the most prior before block
106
+ until index < 0
107
+ block = callbacks[index][1]
108
+ render_halts = index != action_index
109
+
110
+ case halting_status_of block, !render_halts
111
+ when true
112
+ # will halt execution after this callback is done
113
+ callbacks = callbacks.first(index + 1)
114
+ when nil
115
+ # may render
116
+ paths = split_into_paths_that_will_or_will_not_halt block, !render_halts
117
+ what_happens_unless_renders = ASTBlock.new(:statements => callbacks[index+1..-1].map{ |c| c[1] })
118
+ callbacks = callbacks.first(index + 1)
119
+ callbacks.last[1] = ASTEither.new(:blocks => [
120
+ paths[:will_halt],
121
+ ASTBlock.new(:statements => [paths[:will_not_halt], *what_happens_unless_renders])
122
+ ])
123
+ else
124
+ # doesn't render, all good!
125
+ end
126
+ index -= 1
127
+ end
128
+
129
+ root_block.statements = callbacks.map{ |name, block| block }
130
+ end
131
+
132
+ end
133
+ end
134
+ end
135
+ end
@@ -0,0 +1,48 @@
1
+ require 'adsl/verification/invariant'
2
+ require 'adsl/verification/formula_generators'
3
+ require 'adsl/extract/rails/invariant_instrumenter'
4
+ require 'adsl/verification/instrumentation_filter'
5
+
6
+ module ADSL
7
+ module Extract
8
+ module Rails
9
+ class InvariantExtractor
10
+
11
+ include ADSL::Verification
12
+ include ADSL::Verification::FormulaGenerators
13
+ include ADSL::Verification::InstrumentationFilterGenerators
14
+
15
+ attr_reader :invariants
16
+
17
+ def initialize(ar_class_names)
18
+ @ar_class_names = ar_class_names
19
+ @invariants = []
20
+ @builder = nil
21
+ @stack_level = 0
22
+ end
23
+
24
+ def invariant(name = nil, builder)
25
+ @invariants << Invariant.new(:description => name, :formula => builder.adsl_ast)
26
+ end
27
+
28
+ def load_in_context(path)
29
+ file = File.open path, 'r'
30
+ ADSL::Extract::Rails::InvariantInstrumenter.new(@ar_class_names).instrument_and_execute_source self, file.read
31
+ ensure
32
+ file.close
33
+ end
34
+
35
+ def extract(param)
36
+ if param.is_a? Array
37
+ param.each do |path|
38
+ load_in_context path
39
+ end
40
+ else
41
+ ADSL::Extract::Rails::InvariantInstrumenter.new(@ar_class_names).instrument_and_execute_source self, param
42
+ end
43
+ @invariants
44
+ end
45
+ end
46
+ end
47
+ end
48
+ end
@@ -0,0 +1,70 @@
1
+ require 'adsl/extract/instrumenter'
2
+ require 'adsl/verification/formula_generators'
3
+ require 'adsl/extract/rails/active_record_metaclass_generator'
4
+
5
+ module ADSL
6
+ module Extract
7
+ module Rails
8
+ class InvariantInstrumenter < ADSL::Extract::Instrumenter
9
+
10
+ def instrument_and_execute_source object, source
11
+ instrumented_source = instrument_string source
12
+ exec_within do
13
+ object.instance_eval instrumented_source
14
+ end
15
+ end
16
+
17
+ def initialize(ar_class_names, *params)
18
+ super *params
19
+
20
+ # if a block is passed to the invariant call, add that block to the last of the parameters instead
21
+ replace :iter do |sexp|
22
+ next sexp unless (
23
+ sexp[1].sexp_type == :call and
24
+ sexp[1][1].nil? and
25
+ sexp[1][2] == :invariant and
26
+ sexp[1].last.sexp_type == :call
27
+ )
28
+
29
+ params_for_invariant = sexp[1][3..-1]
30
+ s(:call, nil, :invariant,
31
+ *params_for_invariant[0..-2],
32
+ s(:iter, params_for_invariant.last, *sexp[2..-1])
33
+ )
34
+ end
35
+
36
+ # replace and with self.and etc
37
+ [:and, :or].each do |operand|
38
+ replace operand do |sexp|
39
+ s(:if,
40
+ s(:call, nil, :respond_to?, s(:lit, operand)),
41
+ s(:call, s(:self), operand, *sexp.sexp_body),
42
+ s(operand, *sexp.sexp_body)
43
+ )
44
+ end
45
+ end
46
+
47
+ # replace not with self.not
48
+ replace :call do |sexp|
49
+ next sexp unless sexp[2] == :!
50
+ s(:if,
51
+ s(:call, nil, :respond_to?, s(:lit, :not)),
52
+ s(:call, s(:self), :not, sexp[1].dup),
53
+ sexp
54
+ )
55
+ end
56
+ end
57
+
58
+ def should_instrument?(object, method_name)
59
+ return false unless super
60
+
61
+ klass = object.class != Class ? object.class : object
62
+ method = object.method method_name
63
+
64
+ klass.name.match(/^ADSL::.*$/).nil? && !(method.source_location[0] =~ /.*lib\/adsl\/.*/)
65
+ end
66
+
67
+ end
68
+ end
69
+ end
70
+ end
@@ -0,0 +1,57 @@
1
+ require 'active_support'
2
+ require 'adsl/parser/ast_nodes'
3
+
4
+ class NilClass
5
+ def adsl_ast
6
+ ::ADSL::Parser::ASTEmptyObjset.new
7
+ end
8
+ end
9
+
10
+ module ADSL
11
+ module Extract
12
+ module Rails
13
+
14
+ class MetaUnknown
15
+ def method_missing(method, *args, &block)
16
+ self
17
+ end
18
+
19
+ def respond_to?(method_name, *args, &block)
20
+ return true unless method_name == :adsl_ast
21
+ end
22
+
23
+ def to_s
24
+ self.class.name
25
+ end
26
+
27
+ def adsl_ast
28
+ nil
29
+ end
30
+ end
31
+
32
+ class PartiallyUnknownHash < MetaUnknown
33
+ def initialize(options = {})
34
+ @options = options
35
+ end
36
+
37
+ def [](arg)
38
+ @options[arg] || MetaUnknown.new
39
+ end
40
+
41
+ def []=(key, val)
42
+ @options[key] = val
43
+ end
44
+
45
+ def method_missing(method, *args, &block)
46
+ return @options[method] if @options.include? method
47
+ if method.to_s =~ /^.*=$/
48
+ short_method = method.to_s[0..-2].to_sym
49
+ return @options[short_method] if @options.include? short_method
50
+ end
51
+ super
52
+ end
53
+ end
54
+
55
+ end
56
+ end
57
+ end
@@ -0,0 +1,211 @@
1
+ require 'adsl/extract/rails/action_instrumenter'
2
+ require 'adsl/extract/rails/invariant_extractor'
3
+ require 'adsl/extract/rails/callback_chain_simulator'
4
+ require 'adsl/extract/rails/rails_special_gem_instrumentation'
5
+ require 'adsl/extract/rails/other_meta'
6
+ require 'adsl/parser/ast_nodes'
7
+ require 'adsl/util/general'
8
+ require 'pathname'
9
+
10
+ module ADSL
11
+ module Extract
12
+ module Rails
13
+ class RailsExtractor
14
+
15
+ include ADSL::Extract::Rails::CallbackChainSimulator
16
+ include ADSL::Extract::Rails::RailsSpecialGemInstrumentation
17
+
18
+ attr_accessor :ar_classes, :actions, :invariants, :instrumentation_filters
19
+
20
+ def initialize(options = {})
21
+ options = Hash[
22
+ :ar_classes => default_activerecord_models,
23
+ :invariants => Dir['invariants/**/*_invs.rb'],
24
+ :instrumentation_filters => []
25
+ ].merge options
26
+
27
+ @ar_classes = options[:ar_classes].map do |ar_class|
28
+ generator = ActiveRecordMetaclassGenerator.new ar_class
29
+ generator.generate_class
30
+ end
31
+
32
+ ar_class_names = @ar_classes.map(&:adsl_ast_class_name)
33
+
34
+ @invariant_extractor = ADSL::Extract::Rails::InvariantExtractor.new ar_class_names
35
+ @invariants = @invariant_extractor.extract(options[:invariants]).map{ |inv| inv.adsl_ast }
36
+ @instrumentation_filters = @invariant_extractor.instrumentation_filters
37
+ @instrumentation_filters += options[:instrumentation_filters]
38
+
39
+ @action_instrumenter = ADSL::Extract::Rails::ActionInstrumenter.new ar_class_names
40
+ @action_instrumenter.instrumentation_filters = @instrumentation_filters
41
+ @actions = []
42
+ all_routes.each do |route|
43
+ translation = action_to_adsl_ast(route)
44
+ @actions << translation unless translation.nil?
45
+ end
46
+ end
47
+
48
+ def all_routes
49
+ ::Rails.application.routes.routes.map{ |route|
50
+ {
51
+ :request_method => request_method_for(route),
52
+ :url => url_for(route),
53
+ :controller => controller_of(route),
54
+ :action => action_of(route)
55
+ }
56
+ }.select{ |route|
57
+ !route[:action].nil? &&
58
+ !route[:controller].nil? &&
59
+ !route[:url].nil? &&
60
+ !route[:request_method].nil? &&
61
+ route[:controller].action_methods.include?(route[:action].to_s)
62
+ }.uniq{ |a|
63
+ [a[:controller], a[:action]]
64
+ }.sort{ |a, b| [a[:controller].to_s, a[:action]] <=> [b[:controller].to_s, b[:action]] }
65
+ end
66
+
67
+ def route_for(controller, action)
68
+ all_routes.select{ |a| a[:controller] == controller && a[:action] == action.to_sym}.first
69
+ end
70
+
71
+ def action_name_for(route)
72
+ "#{route[:controller]}__#{route[:action]}"
73
+ end
74
+
75
+ def callbacks(controller)
76
+ controller.respond_to?(:_process_action_callbacks) ? controller._process_action_callbacks : []
77
+ end
78
+
79
+ def prepare_instrumentation(controller_class, action)
80
+ controller_class.class_eval <<-ruby, __FILE__, __LINE__ + 1
81
+ def default_render(*args); end
82
+ def verify_authenticity_token; end
83
+ def params
84
+ ADSL::Extract::Rails::PartiallyUnknownHash.new(
85
+ :controller => '#{ controller_class.controller_name }',
86
+ :action => '#{ action }'
87
+ )
88
+ end
89
+ ruby
90
+
91
+ instrument_gems controller_class, action
92
+
93
+ controller = controller_class.new
94
+ @action_instrumenter.instrument controller, action
95
+ callbacks(controller_class).each do |callback|
96
+ next if callback.filter.is_a?(String)
97
+ @action_instrumenter.instrument controller, callback.filter
98
+ end
99
+ end
100
+
101
+ def action_to_adsl_ast(route)
102
+ instrumentation_allows = @instrumentation_filters.map do |f|
103
+ f.allow_instrumentation? route[:controller].new, route[:action]
104
+ end
105
+ return nil if instrumentation_allows.include? false
106
+
107
+ action_name = action_name_for route
108
+ potential_adsl_asts = @actions.select{ |action| action.name.text == action_name }
109
+ raise "Multiple actions with identical names" if potential_adsl_asts.length > 1
110
+ return potential_adsl_asts.first if potential_adsl_asts.length == 1
111
+
112
+ prepare_instrumentation route[:controller], route[:action]
113
+
114
+ session = ActionDispatch::Integration::Session.new(::Rails.application)
115
+ ::Rails.application.config.action_dispatch.show_exceptions = false
116
+
117
+ block = @action_instrumenter.exec_within do
118
+ @action_instrumenter.exec_within do
119
+ request_method = route[:request_method].to_s.downcase.split('|').first
120
+ session.send request_method, route[:url]
121
+ end
122
+ @action_instrumenter.abb.root_lvl_adsl_ast
123
+ end
124
+
125
+ interrupt_callback_chain_on_render block, route[:action]
126
+
127
+ action = ADSL::Parser::ASTAction.new({
128
+ :name => ADSL::Parser::ASTIdent.new(:text => action_name),
129
+ :arg_cardinalities => [],
130
+ :arg_names => [],
131
+ :arg_types => [],
132
+ :block => block
133
+ })
134
+
135
+ action = action.optimize
136
+ action.prepend_global_variables_by_signatures /^at__.*/, /^atat__.*/
137
+
138
+ action
139
+ end
140
+
141
+ def default_activerecord_models
142
+ models_dir = Rails.respond_to?(:root) ? Rails.root.join('app', 'models') : Pathname.new('app/models')
143
+ classes = Dir[models_dir.join '**', '*.rb'].map{ |path|
144
+ relative_path = /^#{Regexp.escape models_dir.to_s}\/(.*)\.rb$/.match(path)[1]
145
+ klass = nil
146
+ while klass.nil? && !relative_path.empty?
147
+ begin
148
+ klass = relative_path.camelize.constantize
149
+ rescue NameError, LoadError
150
+ end
151
+ if klass.nil?
152
+ relative_path = /^[^\/]+\/(.*)$/.match(relative_path)[1]
153
+ end
154
+ end
155
+ raise "Could not find class corresponding to path #{path}" if klass.nil?
156
+ klass
157
+ }.select{ |klass| klass < ActiveRecord::Base }
158
+ until_no_change(classes) do |classes|
159
+ all = classes.dup
160
+ classes.each do |c|
161
+ all << c.superclass unless classes.include?(c.superclass) || c.superclass == ActiveRecord::Base
162
+ end
163
+ all
164
+ end
165
+ end
166
+
167
+ def controller_of(route)
168
+ return nil unless route.defaults.include? :controller
169
+ possible_names = [
170
+ "#{route.defaults[:controller].pluralize}_controller".camelize,
171
+ "#{route.defaults[:controller]}_controller".camelize,
172
+ ]
173
+ possible_names.each do |name|
174
+ begin
175
+ return name.constantize
176
+ rescue NameError
177
+ end
178
+ end
179
+ raise "No controller class found for #{route.defaults}; attempted class names are #{possible_names}"
180
+ end
181
+
182
+ def action_of(route)
183
+ return nil unless route.defaults.include? :action
184
+ route.defaults[:action].to_sym
185
+ end
186
+
187
+ def request_method_for(route)
188
+ method_s = route.verb.source.match(/^\^?(.*?)\$?$/)[1]
189
+ return nil if method_s.empty?
190
+ method_s.to_sym
191
+ end
192
+
193
+ def url_for(route)
194
+ params = {}
195
+ route.required_parts.each do |part|
196
+ params[part] = 0
197
+ end
198
+ route.format(params)
199
+ end
200
+
201
+ def adsl_ast
202
+ ADSL::Parser::ASTSpec.new(
203
+ :classes => @ar_classes.map(&:adsl_ast),
204
+ :actions => @actions,
205
+ :invariants => @invariants
206
+ )
207
+ end
208
+ end
209
+ end
210
+ end
211
+ end