my_enginery 0.2.8

Sign up to get free protection for your applications and to get access to all the features.
Files changed (118) hide show
  1. data/.gitignore +18 -0
  2. data/.travis.yml +9 -0
  3. data/CHANGELOG.md +14 -0
  4. data/Gemfile +12 -0
  5. data/LICENSE +19 -0
  6. data/README.md +957 -0
  7. data/Rakefile +48 -0
  8. data/app/base/.pryrc +1 -0
  9. data/app/base/Gemfile +25 -0
  10. data/app/base/Rakefile +4 -0
  11. data/app/base/app.rb +8 -0
  12. data/app/base/base/boot.rb +45 -0
  13. data/app/base/base/config.rb +127 -0
  14. data/app/base/base/controllers/rear-controllers/.gitkeep +0 -0
  15. data/app/base/base/database.rb +3 -0
  16. data/app/base/base/helpers/application_helpers.rb +3 -0
  17. data/app/base/base/migrations/.gitkeep +0 -0
  18. data/app/base/base/models/.gitkeep +0 -0
  19. data/app/base/base/specs/.gitkeep +0 -0
  20. data/app/base/base/views/.gitkeep +0 -0
  21. data/app/base/config.ru +4 -0
  22. data/app/base/config/config.yml +17 -0
  23. data/app/base/public/assets/Enginery.png +0 -0
  24. data/app/base/public/assets/Espresso.png +0 -0
  25. data/app/base/public/assets/application.css +13 -0
  26. data/app/base/public/assets/application.js +2 -0
  27. data/app/base/public/assets/bootstrap/css/bootstrap-responsive.min.css +9 -0
  28. data/app/base/public/assets/bootstrap/css/bootstrap.min.css +9 -0
  29. data/app/base/public/assets/bootstrap/img/glyphicons-halflings-white.png +0 -0
  30. data/app/base/public/assets/bootstrap/img/glyphicons-halflings.png +0 -0
  31. data/app/base/public/assets/bootstrap/js/bootstrap.min.js +6 -0
  32. data/app/base/public/assets/jquery.js +6 -0
  33. data/app/base/var/db/.gitkeep +0 -0
  34. data/app/base/var/log/.gitkeep +0 -0
  35. data/app/base/var/pid/.gitkeep +0 -0
  36. data/app/database/ActiveRecord.rb +11 -0
  37. data/app/database/DataMapper.rb +11 -0
  38. data/app/database/Sequel.rb +11 -0
  39. data/app/database/mysql.yml +24 -0
  40. data/app/database/postgres.yml +24 -0
  41. data/app/database/sqlite.yml +24 -0
  42. data/app/gemfiles/ActiveRecord.rb +1 -0
  43. data/app/gemfiles/BlueCloth.rb +1 -0
  44. data/app/gemfiles/DataMapper.rb +1 -0
  45. data/app/gemfiles/FastCGI.rb +1 -0
  46. data/app/gemfiles/Puma.rb +1 -0
  47. data/app/gemfiles/RDiscount.rb +1 -0
  48. data/app/gemfiles/RDoc.rb +1 -0
  49. data/app/gemfiles/RedCloth.rb +1 -0
  50. data/app/gemfiles/WEBrick.rb +1 -0
  51. data/app/gemfiles/WikiCloth.rb +1 -0
  52. data/app/gemfiles/mysql/ActiveRecord.rb +1 -0
  53. data/app/gemfiles/mysql/DataMapper.rb +1 -0
  54. data/app/gemfiles/mysql/Sequel.rb +1 -0
  55. data/app/gemfiles/postgres/ActiveRecord.rb +1 -0
  56. data/app/gemfiles/postgres/DataMapper.rb +1 -0
  57. data/app/gemfiles/postgres/Sequel.rb +1 -0
  58. data/app/gemfiles/sqlite/ActiveRecord.rb +1 -0
  59. data/app/gemfiles/sqlite/DataMapper.rb +1 -0
  60. data/app/gemfiles/sqlite/Sequel.rb +1 -0
  61. data/app/layouts/ERB/layout.erb +56 -0
  62. data/app/layouts/Erubis/layout.erb +56 -0
  63. data/app/layouts/Haml/layout.haml +38 -0
  64. data/app/layouts/Slim/layout.slim +38 -0
  65. data/app/migrations/ActiveRecord.erb +51 -0
  66. data/app/migrations/DataMapper.erb +61 -0
  67. data/app/migrations/Sequel.erb +54 -0
  68. data/app/migrations/tracking_table/ActiveRecord.rb +19 -0
  69. data/app/migrations/tracking_table/DataMapper.rb +26 -0
  70. data/app/migrations/tracking_table/Sequel.rb +18 -0
  71. data/app/rakefiles/ActiveRecord.rb +0 -0
  72. data/app/rakefiles/DataMapper.rb +1 -0
  73. data/app/rakefiles/Sequel.rb +0 -0
  74. data/app/rakefiles/Specular.rb +1 -0
  75. data/app/specfiles/Specular.erb +7 -0
  76. data/bin/my_enginery +235 -0
  77. data/lib/enginery.rb +23 -0
  78. data/lib/enginery/cli.rb +44 -0
  79. data/lib/enginery/configurator.rb +139 -0
  80. data/lib/enginery/delete.rb +116 -0
  81. data/lib/enginery/enginery.rb +41 -0
  82. data/lib/enginery/generator.rb +325 -0
  83. data/lib/enginery/helpers/app.rb +83 -0
  84. data/lib/enginery/helpers/generic.rb +145 -0
  85. data/lib/enginery/helpers/input.rb +123 -0
  86. data/lib/enginery/helpers/orm.rb +86 -0
  87. data/lib/enginery/helpers/validations.rb +101 -0
  88. data/lib/enginery/migrator.rb +371 -0
  89. data/lib/enginery/rake-tasks/data_mapper.rb +49 -0
  90. data/lib/enginery/rake-tasks/specular.rb +41 -0
  91. data/lib/enginery/registry.rb +101 -0
  92. data/lib/enginery/usage.rb +66 -0
  93. data/lib/enginery/version.rb +7 -0
  94. data/logo.png +0 -0
  95. data/my_enginery.gemspec +37 -0
  96. data/test/delete/test__admin.rb +49 -0
  97. data/test/delete/test__controller.rb +37 -0
  98. data/test/delete/test__helper.rb +49 -0
  99. data/test/delete/test__migration.rb +27 -0
  100. data/test/delete/test__model.rb +35 -0
  101. data/test/delete/test__route.rb +35 -0
  102. data/test/delete/test__spec.rb +59 -0
  103. data/test/delete/test__view.rb +51 -0
  104. data/test/generator/test__admin.rb +39 -0
  105. data/test/generator/test__controller.rb +123 -0
  106. data/test/generator/test__helper.rb +56 -0
  107. data/test/generator/test__model.rb +206 -0
  108. data/test/generator/test__project.rb +81 -0
  109. data/test/generator/test__route.rb +110 -0
  110. data/test/generator/test__spec.rb +56 -0
  111. data/test/generator/test__view.rb +85 -0
  112. data/test/migrator/test__auto_generation.rb +41 -0
  113. data/test/migrator/test__manual_generation.rb +59 -0
  114. data/test/migrator/test__migrations.rb +139 -0
  115. data/test/sandbox/.gitkeep +0 -0
  116. data/test/setup.rb +29 -0
  117. data/test/support/spec_helpers.rb +151 -0
  118. metadata +392 -0
@@ -0,0 +1,371 @@
1
+ module Enginery
2
+ class Migrator
3
+ include Helpers
4
+
5
+ TIME_FORMAT = '%Y-%m-%d_%H-%M-%S'.freeze
6
+ NAME_REGEXP = /\A(\d+)\.(\d+\-\d+\-\d+_\d+\-\d+\-\d+)\.(.*)#{Regexp.escape MIGRATION_SUFFIX}\Z/.freeze
7
+
8
+ def initialize dst_root, setups = {}
9
+ @dst_root, @setups = dst_root, setups
10
+ @migrations = Dir[dst_path(:migrations, '**/*%s' % MIGRATION_SUFFIX)].inject([]) do |map,f|
11
+ step, time, name = File.basename(f).scan(NAME_REGEXP).flatten
12
+ step && time && name && map << [step.to_i, time, name, f.sub(dst_path.migrations, '')]
13
+ map
14
+ end.sort {|a,b| a.first <=> b.first}.freeze
15
+ end
16
+
17
+ # generate new migration.
18
+ # it will create a [n].[timestamp].[name].rb migration file in base/migrations/
19
+ # and column_transitions.yml file in base/migrations/track/
20
+ # migration file will contain "up" and "down" sections.
21
+ # column_transitions file will keep track of column type changes.
22
+ #
23
+ def new name
24
+ (name.nil? || name.empty?) && fail("Please provide migration name via second argument")
25
+ (name =~ /[^\w|\d|\-|\.|\:]/) && fail("Migration name can contain only alphanumerics, dashes, semicolons and dots")
26
+ @migrations.any? {|m| m[2] == name} && fail('"%s" migration already exists' % name)
27
+
28
+ max = (@migrations.max {|m| m.first}||[0]).first
29
+ model = @setups[:create_table] || @setups[:update_table]
30
+ context = {model: model, name: name, step: max + 1}
31
+
32
+ [:create_table, :update_table].each do |o|
33
+ context[o] = (m = constant_defined?(@setups[o])) ? model_to_table(m) : nil
34
+ end
35
+ table = context[:create_table] || context[:update_table] ||
36
+ fail('No model provided or provided one does not exists!')
37
+
38
+ [:create_columns, :update_columns].each do |o|
39
+ context[o] = transitions(table, (@setups[o]||[]).map {|(n,t)| [n, opted_column_type(t)]})
40
+ end
41
+ context[:rename_columns] = @setups[:rename_columns]||[]
42
+
43
+ engine = Tenjin::Engine.new(path: [src_path.migrations], cache: false)
44
+ source_code = engine.render('%s.erb' % guess_orm, context.merge(context: context))
45
+
46
+ o
47
+ o '--- %s model - generating "%s" migration ---' % [model, name]
48
+ o
49
+ o ' Serial Number: %s' % context[:step]
50
+ o
51
+ time = Time.now.strftime(TIME_FORMAT)
52
+ path = dst_path(:migrations, class_to_route(model))
53
+ FileUtils.mkdir_p(path)
54
+ file = File.join(path, [context[:step], time, name, 'rb']*'.')
55
+ write_file file, source_code
56
+ output_source_code source_code.split("\n")
57
+ name
58
+ end
59
+
60
+ # convert given range or a single migration into files to be run
61
+ # ex: 1-5 will run migrations from one to 5 inclusive
62
+ # 1 2 4 will run 1st, 2nd, and 4th migrations
63
+ # 2 will run only 2nd migration
64
+ def serials_to_files vector, *serials
65
+ vector = validate_vector(vector)
66
+ serials.map do |serial|
67
+ if serial =~ /\-/
68
+ a, z = serial.split('-')
69
+ (a..z).to_a
70
+ else
71
+ serial
72
+ end
73
+ end.flatten.map do |e|
74
+ @migrations.find {|m| m.first == e.to_i} ||
75
+ fail('Wrong range provided. "%s" is not a recognized migration step' % e)
76
+ end.sort do |a,b|
77
+ vector == :up ? a.first <=> b.first : b.first <=> a.first
78
+ end.map(&:last)
79
+ end
80
+
81
+ # - validate migration file name
82
+ # - apply migration in given direction if migration was not previously performed
83
+ # in given direction or :force option given
84
+ # - create a track in TRACKING_TABLE
85
+ # so on consequent requests we may know whether migration was already performed
86
+ def run vector, file, force_run = nil
87
+ vector = validate_vector(vector)
88
+
89
+ (migration = @migrations.find {|m| m.last == file}) ||
90
+ fail('"%s" is not a valid migration file' % file)
91
+
92
+ create_tracking_table_if_needed
93
+
94
+ track = track_exists?(file, vector)
95
+ if track && !force_run
96
+ o
97
+ o '*** Skipping "%s: %s" migration ***' % [migration[0], migration[2]]
98
+ o ' It was already performed %s on %s' % [track.vector.upcase, track.performed_at]
99
+ o ' Use :force option to run it anyway - enginery m:%s:force ...' % vector
100
+ o
101
+ return
102
+ end
103
+ apply!(migration, vector) && persist_track(file, vector)
104
+ end
105
+
106
+ # list available migrations with date of last run, if any
107
+ def list
108
+ create_tracking_table_if_needed
109
+ o indent('--'), '-=---'
110
+ @migrations.each do |(step,time,name,file)|
111
+ track = track_exists?(file)
112
+ last_perform = track ? '%s on %s' % [track.vector, track.performed_at] : 'none'
113
+ o indent(step), ' : ', name
114
+ o indent('created at'), ' : ', DateTime.strptime(time, TIME_FORMAT).rfc2822
115
+ o indent('last performed'), ' : ', last_perform
116
+ o indent('--'), '-=---'
117
+ end
118
+ end
119
+
120
+ def outstanding_migrations vector
121
+ create_tracking_table_if_needed
122
+ serials = @migrations.inject([]) do |l,(step,time,name,file)|
123
+ track_exists?(File.basename(file), vector) ? l : l.push(step)
124
+ end
125
+ serials_to_files(vector, *serials)
126
+ end
127
+
128
+ def last_run file
129
+ create_tracking_table_if_needed
130
+ return unless track = track_exists?(file)
131
+ [track.vector, track.performed_at]
132
+ end
133
+
134
+ def update_model_file context, vector
135
+ model = context[:model]
136
+ file = dst_path(:models, class_to_route(model) + MODEL_SUFFIX)
137
+ return unless File.file?(file)
138
+
139
+ lines, properties = File.readlines(file), []
140
+ lines.each_with_index do |l,i|
141
+ property = l.scan(/(\s+)?property\s+[\W]?(\w+)\W+(\w+)(.*)/).flatten
142
+ properties << (property << i) if property[1] && property[2]
143
+ end
144
+ return if properties.empty?
145
+
146
+ new_lines = case vector.to_s.downcase.to_sym
147
+ when :up
148
+ add_properties(lines, properties, context)
149
+ when :down
150
+ remove_properties(lines, properties, context)
151
+ end
152
+
153
+ return unless new_lines
154
+ File.open(file, 'w') {|f| f << new_lines.join}
155
+ end
156
+
157
+ def guess_orm
158
+ orm = (@setups[:orm] || Cfg[:orm] || fail('No project-wide ORM detected.
159
+ Please update config/config.yml by adding
160
+ orm: [DataMapper|ActiveRecord|Sequel]')).to_s.strip
161
+ (ORM_MATCHERS.find {|o,m| orm =~ m} || fail('"%s" ORM not supported')).first
162
+ end
163
+
164
+ private
165
+
166
+ # load migration file and call corresponding methods that will run migration up/down
167
+ def apply! migration, vector, orm = guess_orm
168
+ o
169
+ o '*** Performing %s step #%s ***' % [vector, migration.first]
170
+ o ' Label: %s' % migration[2]
171
+ o ' ORM: %s' % orm
172
+ begin
173
+
174
+ load dst_path(:migrations, migration.last)
175
+
176
+ case orm
177
+ when :DataMapper
178
+
179
+ update_model_file(MigratorContext, vector)
180
+
181
+ mj, mn, pt = DataMapper::VERSION.scan(/\d+/).map(&:to_i)
182
+ if MigratorContext[:rename_columns].any? && [1,2,0] == [mj,mn,pt]
183
+ o ' status: Skipped as renaming columns is broken on DataMapper 1.2.0'
184
+ return false
185
+ end
186
+
187
+ MigratorInstance.instance_exec do
188
+ # when using perform_up/down DataMapper will create a tracking table
189
+ # and decide whether migration should be run, based on needs_up? and needs_down?
190
+ # Enginery keeps own tracks and does not need DataMapper's tracking table
191
+ # nor decisions on running migrations,
192
+ # so using instance_exec to apply migrations directly.
193
+ if action = instance_variable_get('@%s_action' % vector)
194
+ action.call
195
+ end
196
+ end
197
+ when :ActiveRecord
198
+ MigratorInstance.new.send vector
199
+ when :Sequel
200
+ model = constant_defined?(MigratorContext[:model])
201
+ MigratorInstance.apply model.db, vector
202
+ end
203
+ o ' status: OK'
204
+ true
205
+ rescue => e
206
+ fail e.message, *e.backtrace
207
+ end
208
+ end
209
+
210
+ def add_properties lines, properties, context
211
+ property_setup, new_properties = nil, []
212
+
213
+ context[:create_columns].each do |(n,t)|
214
+ next if properties.find {|p| p[1].to_s == n.to_s}
215
+ property_setup = [properties.last.first, n, t.to_s.split('::').last]
216
+ new_properties << '%sproperty :%s, %s' % property_setup
217
+ end
218
+ if new_properties.any?
219
+ lines[properties.last.last] += (new_properties.join("\n") + "\n")
220
+ end
221
+
222
+ context[:rename_columns].each do |(cn,nn)|
223
+ next unless property = properties.find {|p| p[1].to_s == cn.to_s}
224
+ property_setup = [property[0], nn, *property[2..3]]
225
+ lines[property.last] = "%sproperty :%s, %s%s\n" % property_setup
226
+ end
227
+
228
+ context[:update_columns].each do |(n,t)|
229
+ next unless property = properties.find {|p| p[1].to_s == n.to_s}
230
+ property_setup = [*property[0..1], t.to_s.split('::').last, property[3]]
231
+ lines[property.last] = "%sproperty :%s, %s%s\n" % property_setup
232
+ end
233
+
234
+ property_setup ? lines : nil
235
+ end
236
+
237
+
238
+ def remove_properties lines, properties, context
239
+ property = nil
240
+
241
+ context[:create_columns].each do |(n)|
242
+ next unless property = properties.find {|p| p[1].to_s == n.to_s}
243
+ lines[property.last] = nil
244
+ end
245
+
246
+ context[:rename_columns].each do |(cn,nn)|
247
+ next unless property = properties.find {|p| p[1].to_s == nn.to_s}
248
+ property[1] = cn
249
+ lines[property.last] = "%sproperty :%s, %s%s\n" % property
250
+ end
251
+
252
+ context[:update_columns].each do |(n,nt,ot)|
253
+ next unless property = properties.find {|p| p[1].to_s == n.to_s}
254
+ property[2] = ot.to_s.split('::').last
255
+ lines[property.last] = "%sproperty :%s, %s%s\n" % property
256
+ end
257
+
258
+ property ? lines : nil
259
+ end
260
+
261
+ def create_tracking_table_if_needed
262
+ require src_path(:migrations, 'tracking_table/%s.rb' % guess_orm)
263
+ case guess_orm
264
+ when :DataMapper
265
+ TracksMigrator.instance_exec { @up_action.call }
266
+ when :ActiveRecord
267
+ TracksMigrator.new.up
268
+ when :Sequel
269
+ TracksMigrator.apply Sequel::Model.db, :up
270
+ end
271
+ end
272
+
273
+ def track_exists? migration, vector = nil
274
+ conditions = {migration: migration}
275
+ conditions[:vector] = vector.to_s if vector # #to_s required on Sequel
276
+ case guess_orm
277
+ when :ActiveRecord, :DataMapper
278
+ TracksModel.first(conditions: conditions)
279
+ when :Sequel
280
+ TracksModel.first(conditions)
281
+ end
282
+ end
283
+
284
+ def persist_track migration, vector
285
+ key = {migration: migration}
286
+ row = key.merge(performed_at: DateTime.now.rfc2822, vector: vector.to_s)
287
+ case guess_orm
288
+ when :DataMapper
289
+ TracksModel.all(key).destroy!
290
+ TracksModel.create(row)
291
+ when :ActiveRecord
292
+ TracksModel.delete_all(key)
293
+ TracksModel.create(row)
294
+ when :Sequel
295
+ TracksModel.where(key).delete
296
+ TracksModel.insert(row)
297
+ end
298
+ end
299
+
300
+ # get the actual db table of a given model
301
+ def model_to_table model
302
+ case guess_orm
303
+ when :DataMapper
304
+ model.repository.adapter.resource_naming_convention.call(model)
305
+ when :ActiveRecord, :Sequel
306
+ model.table_name
307
+ end
308
+ end
309
+
310
+ def default_column_type orm = guess_orm
311
+ case orm
312
+ when :ActiveRecord
313
+ 'string'
314
+ when :DataMapper, :Sequel
315
+ 'String'
316
+ end
317
+ end
318
+
319
+ # convert given string into column type suitable for migration file
320
+ def opted_column_type type, orm = nil
321
+ orm ||= guess_orm
322
+ type ||= default_column_type(orm)
323
+ case orm
324
+ when :DataMapper
325
+ 'DataMapper::Property::%s' % capitalize(type)
326
+ when :Sequel
327
+ type.to_s =~ /text/i ? "String, text: true" : capitalize(type)
328
+ else
329
+ type
330
+ end
331
+ end
332
+
333
+ # someString.capitalize will return Somestring.
334
+ # we need SomeString instead, which is returned by this method
335
+ def capitalize smth
336
+ smth.to_s.match(/(\w)(.*)/) {|m| m[1].upcase << m[2]}
337
+ end
338
+
339
+ def validate_vector vector
340
+ invalid_vector!(vector) unless vector.is_a?(String)
341
+ (vector =~ /\Au/i) && (vector = :up)
342
+ (vector =~ /\Ad/i) && (vector = :down)
343
+ invalid_vector!(vector) unless vector.is_a?(Symbol)
344
+ vector
345
+ end
346
+
347
+ def invalid_vector! vector
348
+ fail('%s is a unrecognized vector. Use either "up" or "down"' % vector.inspect)
349
+ end
350
+
351
+ def indent smth
352
+ string = smth.to_s
353
+ ident_size = 20 - string.size
354
+ ident_size = 0 if ident_size < 0
355
+ INDENT + ' '*ident_size + string
356
+ end
357
+
358
+ def transitions table, columns
359
+ transitions_file = dst_path(:migrations, 'transitions.yml')
360
+ transitions = File.file?(transitions_file) ? (YAML.load(File.read(transitions_file)) rescue {}) : {}
361
+ transitions[table] ||= {}
362
+ columns.each do |column|
363
+ column[2] = transitions[table][column.first]
364
+ transitions[table][column.first] = column[1]
365
+ end
366
+ File.open(transitions_file, 'w') {|f| f << YAML.dump(transitions)}
367
+ columns
368
+ end
369
+
370
+ end
371
+ end
@@ -0,0 +1,49 @@
1
+
2
+ namespace :dm do
3
+ %w[auto_migrate auto_upgrade].each do |t|
4
+ alert = t == 'auto_migrate' ? 'ACHTUNG! This is a DESTRUCTIVE action!' : nil
5
+
6
+ ObjectSpace.each_object(Class).select do |c|
7
+ c.ancestors.include?(DataMapper::Resource)
8
+ end.each do |m|
9
+ run = lambda do
10
+ puts '', ' Running %s.%s!' % [m.name, t], ''
11
+ m.send(t + '!')
12
+ end
13
+
14
+ desc 'Run %s.%s! %s' % [m.name, t, alert]
15
+ task [t, m.name]*':' do
16
+ if alert
17
+ puts '', alert
18
+ puts ' The table for %s model will be destroyed and recreated from ground up.' % m.name
19
+ puts ' Any data will be lost, so please consider to take some backups before continuing.', ''
20
+ puts ' Type Y and press enter to continue'
21
+ puts ' Press enter to cancel'
22
+ answer = STDIN.gets.strip
23
+ (puts 'exiting...'; exit(0)) unless answer == 'Y'
24
+ end
25
+ run.call
26
+ end
27
+ task([t, m.name ,'y']*':') { run.call }
28
+ end
29
+
30
+ run = lambda do
31
+ puts '', ' Running DataMapper.%s!' % t, ''
32
+ DataMapper.send(t + '!')
33
+ end
34
+ desc 'Run DataMapper.%s! %s' % [t, alert]
35
+ task t do
36
+ if alert
37
+ puts '', alert
38
+ puts ' ALL tables will be destroyed and recreated from ground up.'
39
+ puts ' Any data will be lost, so please consider to take some backups before continuing.', ''
40
+ puts ' Type Y and press enter to continue'
41
+ puts ' Press enter to cancel'
42
+ answer = STDIN.gets.strip
43
+ (puts 'exiting...'; exit(0)) unless answer == 'Y'
44
+ end
45
+ run.call
46
+ end
47
+ task(t + ':y'){ run.call }
48
+ end
49
+ end
@@ -0,0 +1,41 @@
1
+
2
+ Dir[Cfg.specs_path('**/*_spec.rb')].each {|f| require f}
3
+
4
+ specular_session = Specular.new do
5
+ boot do
6
+ include Sonar
7
+ end
8
+ before do
9
+ app App
10
+ map App.base_url
11
+ end
12
+ end
13
+
14
+ specular_tasks = []
15
+ App.mounted_controllers.reject do |c|
16
+ next if c.name == 'RearHomeController' || c.name =~ /::RearController\Z/
17
+ task_name = 'test:%s' % c.name
18
+ desc 'Run tests for "%s" controller' % c
19
+ task task_name do
20
+ puts specular_session.run /\A#{c}\W/
21
+ specular_session.exit_code == 0 || fail
22
+ end
23
+ c.public_actions.each do |a|
24
+ task 'test:%s#%s' % [c.name, a] do
25
+ puts specular_session.run /\A#{c}##{a}\Z/
26
+ specular_session.exit_code == 0 || fail
27
+ end
28
+ end
29
+ specular_tasks << task_name
30
+ end
31
+
32
+ task 'test:controllers' => specular_tasks
33
+
34
+ desc 'Run all tests'
35
+ task :test do
36
+ specular_session.run
37
+ puts specular_session.failures if specular_session.failed?
38
+ puts specular_session.summary
39
+ specular_session.exit_code == 0 || fail
40
+ end
41
+ task default: :test