my_enginery 0.2.8

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 (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