padrino-gen 0.17.0 → 0.18.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.
- checksums.yaml +4 -4
- data/README.rdoc +5 -5
- data/lib/padrino-gen/generators/actions.rb +145 -14
- data/lib/padrino-gen/generators/app/app.rb.tt +4 -2
- data/lib/padrino-gen/generators/cli.rb +9 -3
- data/lib/padrino-gen/generators/component.rb +19 -15
- data/lib/padrino-gen/generators/components/actions.rb +4 -3
- data/lib/padrino-gen/generators/components/mocks/mocha.rb +15 -9
- data/lib/padrino-gen/generators/components/mocks/rr.rb +24 -6
- data/lib/padrino-gen/generators/components/orms/activerecord.rb +47 -40
- data/lib/padrino-gen/generators/components/orms/mongoid.rb +28 -2
- data/lib/padrino-gen/generators/components/orms/sequel.rb +79 -36
- data/lib/padrino-gen/generators/components/renderers/liquid.rb +1 -2
- data/lib/padrino-gen/generators/components/scripts/dojo.rb +1 -1
- data/lib/padrino-gen/generators/components/scripts/jquery.rb +1 -1
- data/lib/padrino-gen/generators/components/scripts/mootools.rb +1 -1
- data/lib/padrino-gen/generators/components/scripts/prototype.rb +1 -1
- data/lib/padrino-gen/generators/components/stylesheets/sass.rb +1 -1
- data/lib/padrino-gen/generators/components/stylesheets/scss.rb +1 -1
- data/lib/padrino-gen/generators/components/tests/bacon.rb +3 -1
- data/lib/padrino-gen/generators/components/tests/cucumber.rb +13 -8
- data/lib/padrino-gen/generators/components/tests/minitest.rb +1 -1
- data/lib/padrino-gen/generators/components/tests/rspec.rb +1 -1
- data/lib/padrino-gen/generators/components/tests/shoulda.rb +1 -1
- data/lib/padrino-gen/generators/components/tests/testunit.rb +1 -1
- data/lib/padrino-gen/generators/project/config/apps.rb.tt +4 -1
- data/lib/padrino-gen/generators/project.rb +19 -5
- data/lib/padrino-gen/generators/runner.rb +3 -1
- data/lib/padrino-gen/padrino-tasks/activerecord.rb +62 -18
- data/lib/padrino-gen/padrino-tasks/sequel.rb +37 -14
- data/lib/padrino-gen/padrino-tasks/sql-helpers.rb +172 -44
- data/test/test_activerecord_tasks.rb +172 -2
- data/test/test_app_generator.rb +15 -2
- data/test/test_cli.rb +57 -0
- data/test/test_component_generator.rb +307 -3
- data/test/test_controller_generator.rb +3 -2
- data/test/test_generator.rb +24 -0
- data/test/test_helper_generator.rb +3 -2
- data/test/test_mailer_generator.rb +4 -3
- data/test/test_migration_generator.rb +124 -4
- data/test/test_model_generator.rb +106 -64
- data/test/test_plugin_generator.rb +45 -7
- data/test/test_project_generator.rb +168 -71
- data/test/test_sequel_tasks.rb +156 -0
- data/test/test_sql_helpers.rb +139 -64
- data/test/test_sql_helpers_dump.rb +328 -0
- data/test/test_task_generator.rb +4 -3
- metadata +12 -37
- data/lib/padrino-gen/generators/components/orms/dynamoid.rb +0 -67
- data/lib/padrino-gen/generators/components/orms/minirecord.rb +0 -184
- data/lib/padrino-gen/generators/components/orms/ohm.rb +0 -67
- data/lib/padrino-gen/generators/components/stylesheets/less.rb +0 -25
- data/lib/padrino-gen/padrino-tasks/minirecord.rb +0 -19
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: '039917b81dc2bfbb846774da7452682d6f29af8bc1f4d76609b2ec10d8392912'
|
|
4
|
+
data.tar.gz: 451d8a83d39747bf6648d0b434f211a4571e5694f5cc70b45e9f9844ef7bfa7c
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: c0769c35ccfe2cff70e99aa0c137b825e285e831649945d18e07e5ea72b44efcc1214f3fb3ebc63661d0b0c8371e84c78dcb5fff6b7472471c83176e3dd1b40e
|
|
7
|
+
data.tar.gz: 45b9f905e803023d7932ceb487ade75bbcd5df395754f820cc86626176de272bb422ef5696d3665027b80f7a8f1d34436f266b02c8bb27d9938fbbcce410baf1
|
data/README.rdoc
CHANGED
|
@@ -24,13 +24,13 @@ The simplest possible command to generate a base application would be:
|
|
|
24
24
|
|
|
25
25
|
$ padrino-gen project demo_project
|
|
26
26
|
|
|
27
|
-
This would construct a Padrino application
|
|
27
|
+
This would construct a Padrino application DemoProject::App (which extends from Padrino::Application)
|
|
28
28
|
inside the folder 'demo_project' at our current path. Inside the application there would be configuration and
|
|
29
29
|
setup performed for the default components.
|
|
30
30
|
|
|
31
31
|
You can define specific components to be used:
|
|
32
32
|
|
|
33
|
-
$ padrino-gen project demo_project -t rspec -
|
|
33
|
+
$ padrino-gen project demo_project -t rspec -e haml -m rr -s jquery -d sequel
|
|
34
34
|
|
|
35
35
|
You can also instruct the generator to skip a certain component to avoid using one at all (or to use your own):
|
|
36
36
|
|
|
@@ -39,11 +39,11 @@ You can also instruct the generator to skip a certain component to avoid using o
|
|
|
39
39
|
The available components and their default options are listed below:
|
|
40
40
|
|
|
41
41
|
test:: none (default), bacon, shoulda, cucumber, rspec, minitest, testunit
|
|
42
|
-
renderer:: none (default), slim, erb,
|
|
43
|
-
stylesheet:: none (default), tailwind,
|
|
42
|
+
renderer:: none (default), slim, erb, liquid, haml
|
|
43
|
+
stylesheet:: none (default), tailwind, sass, scss
|
|
44
44
|
mock:: none (default), mocha, rr
|
|
45
45
|
script:: none (default), jquery, prototype, mootools, dojo, vite
|
|
46
|
-
orm:: none (default), mongoid, activerecord,
|
|
46
|
+
orm:: none (default), mongoid, activerecord, sequel
|
|
47
47
|
|
|
48
48
|
In addition, you can generate projects based on existing templates:
|
|
49
49
|
|
|
@@ -10,6 +10,12 @@ module Padrino
|
|
|
10
10
|
#
|
|
11
11
|
DEFAULT_HELPER_NAME = 'Helper'.freeze
|
|
12
12
|
|
|
13
|
+
##
|
|
14
|
+
# The SQL adapters for --adapter, and the ORM components that set up their database with one.
|
|
15
|
+
#
|
|
16
|
+
SQL_ADAPTERS = %w[mysql mysql2 mysql-gem postgres sqlite].freeze
|
|
17
|
+
SQL_ADAPTER_ORMS = %w[activerecord sequel].freeze
|
|
18
|
+
|
|
13
19
|
##
|
|
14
20
|
# Common actions needed to support project and component generation.
|
|
15
21
|
#
|
|
@@ -59,6 +65,16 @@ module Padrino
|
|
|
59
65
|
def apply_component_for(choice, component)
|
|
60
66
|
# I need to override Thor#apply because for unknown reason verbose: false break tasks.
|
|
61
67
|
path = File.expand_path(__dir__ + "/components/#{component.to_s.pluralize}/#{choice}.rb")
|
|
68
|
+
unless File.exist?(path)
|
|
69
|
+
# An existing project's .components can name a component that has since been removed.
|
|
70
|
+
available = Dir["#{__dir__}/components/#{component.to_s.pluralize}/*.rb"].map { |lib| File.basename(lib, '.rb') }.sort
|
|
71
|
+
if choice.to_s.empty?
|
|
72
|
+
say "The project's .components sets no #{component} component (available: #{available.join(', ')})."
|
|
73
|
+
else
|
|
74
|
+
say "The #{choice} #{component} component is no longer available in Padrino (available: #{available.join(', ')})."
|
|
75
|
+
end
|
|
76
|
+
exit 1
|
|
77
|
+
end
|
|
62
78
|
say_status :apply, "#{component.to_s.pluralize}/#{choice}"
|
|
63
79
|
shell.padding += 1
|
|
64
80
|
instance_eval(File.read(path, encoding: 'UTF-8'))
|
|
@@ -157,10 +173,105 @@ module Padrino
|
|
|
157
173
|
choice
|
|
158
174
|
end
|
|
159
175
|
|
|
176
|
+
##
|
|
177
|
+
# Prompts the user if necessary until options[:adapter] or the answer is a valid SQL adapter, and keeps it.
|
|
178
|
+
#
|
|
179
|
+
# @return [String] Name of the SQL adapter.
|
|
180
|
+
#
|
|
181
|
+
# @example
|
|
182
|
+
# resolve_valid_adapter # => 'sqlite'
|
|
183
|
+
#
|
|
184
|
+
def resolve_valid_adapter
|
|
185
|
+
@_adapter ||= begin
|
|
186
|
+
adapter = options[:adapter] || stored_adapter || 'sqlite'
|
|
187
|
+
until SQL_ADAPTERS.include?(adapter)
|
|
188
|
+
say("Option for --adapter '#{adapter}' is not available.", :red)
|
|
189
|
+
adapter = ask_valid_option(:adapter, SQL_ADAPTERS, 'Please enter a valid option for adapter:')
|
|
190
|
+
end
|
|
191
|
+
adapter
|
|
192
|
+
end
|
|
193
|
+
end
|
|
194
|
+
|
|
195
|
+
##
|
|
196
|
+
# Returns whether `padrino g component` is applying a component the project already has,
|
|
197
|
+
# rather than adding it or switching to it. Files the project may have edited stay as they are then.
|
|
198
|
+
#
|
|
199
|
+
# @param [Symbol] component
|
|
200
|
+
# The type of component module.
|
|
201
|
+
#
|
|
202
|
+
# @return [Boolean] Whether the chosen component is the one the project has.
|
|
203
|
+
#
|
|
204
|
+
def reapplying_component?(component)
|
|
205
|
+
!@_stored_components.nil? && @_stored_components[component].to_s == @_components[component].to_s
|
|
206
|
+
end
|
|
207
|
+
|
|
208
|
+
##
|
|
209
|
+
# Removes the lines the project's mock component added to the test helper, when `padrino g component`
|
|
210
|
+
# switches to another mock. Each mock file defines mock_setup_lines(test), the lines its setup_mock adds;
|
|
211
|
+
# the previous mock's file is evaluated on a separate object, so its methods don't replace the new mock's.
|
|
212
|
+
# Only lines that are still exactly as written (indentation aside) are removed; other edits stay.
|
|
213
|
+
#
|
|
214
|
+
# @param [String] test
|
|
215
|
+
# The test component the new mock is set up for.
|
|
216
|
+
#
|
|
217
|
+
def remove_previous_mock_setup(test)
|
|
218
|
+
previous = @_stored_components && @_stored_components[:mock].to_s
|
|
219
|
+
return if previous.nil? || previous.empty? || previous == 'none' || reapplying_component?(:mock)
|
|
220
|
+
source = File.expand_path("components/mocks/#{previous}.rb", __dir__)
|
|
221
|
+
return unless File.exist?(source)
|
|
222
|
+
|
|
223
|
+
probe = Object.new
|
|
224
|
+
probe.instance_eval(File.read(source, encoding: 'UTF-8'), source)
|
|
225
|
+
setup = probe.respond_to?(:mock_setup_lines) && probe.mock_setup_lines(test)
|
|
226
|
+
return unless setup && File.exist?(destination_root(setup[:path]))
|
|
227
|
+
|
|
228
|
+
content = File.binread(destination_root(setup[:path]))
|
|
229
|
+
# The mocks insert after the same line; if the project edited it, the new mock can't be added, so keep the old one.
|
|
230
|
+
return unless content.include?(setup[:after].b)
|
|
231
|
+
|
|
232
|
+
lines = setup[:text].each_line.map(&:strip).reject(&:empty?).uniq
|
|
233
|
+
pattern = /^[ \t]*(?:#{lines.map { |line| Regexp.escape(line) }.join('|')})[ \t]*(?:\n|\z)/
|
|
234
|
+
gsub_file(setup[:path], pattern, '') if content.match?(pattern)
|
|
235
|
+
end
|
|
236
|
+
|
|
237
|
+
##
|
|
238
|
+
# Returns whether config/database.rb stays: the orm is the project's, and so is any --adapter given.
|
|
239
|
+
#
|
|
240
|
+
# @return [Boolean] Whether to keep an existing database config.
|
|
241
|
+
#
|
|
242
|
+
def keep_database_config?
|
|
243
|
+
# An --adapter corrected at the prompt counts as the corrected one, which resolve_valid_adapter keeps in @_adapter.
|
|
244
|
+
given = options[:adapter] && (@_adapter || options[:adapter])
|
|
245
|
+
reapplying_component?(:orm) && [nil, stored_adapter].include?(given)
|
|
246
|
+
end
|
|
247
|
+
|
|
248
|
+
##
|
|
249
|
+
# Returns whether the SQL adapter of the project is unknown: it is applying the orm the project
|
|
250
|
+
# has, without --adapter, in a project that has a database config but no stored adapter
|
|
251
|
+
# (generated by an older version). The config stays and no driver is added then.
|
|
252
|
+
#
|
|
253
|
+
# @return [Boolean] Whether the adapter is unknown.
|
|
254
|
+
#
|
|
255
|
+
def adapter_unknown?
|
|
256
|
+
return @_adapter_unknown if defined?(@_adapter_unknown)
|
|
257
|
+
|
|
258
|
+
@_adapter_unknown = reapplying_component?(:orm) && options[:adapter].nil? && stored_adapter.nil? &&
|
|
259
|
+
File.exist?(destination_root('config/database.rb'))
|
|
260
|
+
end
|
|
261
|
+
|
|
262
|
+
##
|
|
263
|
+
# Returns the SQL adapter stored for the project, if any. Projects generated by older versions have none.
|
|
264
|
+
#
|
|
265
|
+
# @return [String, nil] Name of the SQL adapter.
|
|
266
|
+
#
|
|
267
|
+
def stored_adapter
|
|
268
|
+
@_stored_components && @_stored_components[:adapter]
|
|
269
|
+
end
|
|
270
|
+
|
|
160
271
|
##
|
|
161
272
|
# Asks once for one of the valid choices of an option; callers re-ask while the answer is invalid.
|
|
162
273
|
# Thor's limited_to: would repeat the prompt forever at the end of input (no terminal, empty pipe),
|
|
163
|
-
# so that case raises instead.
|
|
274
|
+
# so that case raises a Thor::Error instead: the generator's start prints its message and exits with status 1.
|
|
164
275
|
#
|
|
165
276
|
# @param [Symbol] option
|
|
166
277
|
# The name of the option.
|
|
@@ -176,7 +287,7 @@ module Padrino
|
|
|
176
287
|
#
|
|
177
288
|
def ask_valid_option(option, choices, statement)
|
|
178
289
|
answer = ask("#{statement} [#{choices.join(', ')}]")
|
|
179
|
-
raise "No input left to choose a valid --#{option}; pass one of: #{choices.join(', ')}" if answer.nil?
|
|
290
|
+
raise Thor::Error, "No input left to choose a valid --#{option}; pass one of: #{choices.join(', ')}" if answer.nil?
|
|
180
291
|
|
|
181
292
|
answer
|
|
182
293
|
end
|
|
@@ -253,6 +364,10 @@ module Padrino
|
|
|
253
364
|
def already_exists?(name, project_name = nil)
|
|
254
365
|
project_name = project_name ? (Object.const_get(project_name) rescue nil) : nil
|
|
255
366
|
Object.const_defined?(name) || project_name&.const_defined?(name)
|
|
367
|
+
rescue ::NameError => e
|
|
368
|
+
# Only Ruby's rejection of the name itself; a NameError from code that autoloading runs is a real error.
|
|
369
|
+
raise unless e.instance_of?(::NameError) && e.message.start_with?('wrong constant name')
|
|
370
|
+
raise Thor::Error, "#{name} is not a valid constant name"
|
|
256
371
|
end
|
|
257
372
|
|
|
258
373
|
##
|
|
@@ -373,9 +488,18 @@ module Padrino
|
|
|
373
488
|
# insert_into_gemfile(name, group: 'test', version: ">1.2.3")
|
|
374
489
|
#
|
|
375
490
|
def insert_into_gemfile(name, options = {})
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
491
|
+
# Generated Gemfiles only have a "Test requirements" section; other groups go with the components.
|
|
492
|
+
group_pattern = "#{options[:group].to_s.capitalize} requirements\n" if options[:group]
|
|
493
|
+
gemfile = destination_root('Gemfile')
|
|
494
|
+
after_pattern = group_pattern if group_pattern && File.exist?(gemfile) && File.read(gemfile).include?(group_pattern)
|
|
495
|
+
after_pattern ||= "Component requirements\n"
|
|
496
|
+
version = options[:version]
|
|
497
|
+
gem_options = options.reject { |k, _| k == :version }.map do |k, v|
|
|
498
|
+
if v.is_a?(Array) then "#{k}: #{v.inspect}"
|
|
499
|
+
elsif k.to_s == 'require' && [true, false].include?(v) then "#{k}: #{v}"
|
|
500
|
+
else "#{k}: '#{v}'"
|
|
501
|
+
end
|
|
502
|
+
end.join(', ')
|
|
379
503
|
write_option = gem_options.empty? ? '' : ", #{gem_options}"
|
|
380
504
|
write_version = version ? ", '#{version}'" : ''
|
|
381
505
|
include_text = "gem '#{name}'" << write_version << write_option << "\n"
|
|
@@ -418,16 +542,18 @@ module Padrino
|
|
|
418
542
|
# Name of the initializer.
|
|
419
543
|
# @param [String] data
|
|
420
544
|
# Text to generate into the initializer file.
|
|
545
|
+
# @param [Boolean] skip
|
|
546
|
+
# Whether to keep an existing initializer file.
|
|
421
547
|
#
|
|
422
548
|
# @example
|
|
423
549
|
# initializer(:test, "some stuff here")
|
|
424
550
|
# #=> generates 'lib/test_init.rb'
|
|
425
551
|
#
|
|
426
|
-
def initializer(name, data = nil)
|
|
552
|
+
def initializer(name, data = nil, skip: false)
|
|
427
553
|
@_init_name, @_init_data = name, data
|
|
428
554
|
register = data ? " register #{name.to_s.underscore.camelize}Initializer\n" : " register #{name}\n"
|
|
429
555
|
inject_into_file destination_root('/app/app.rb'), register, after: "Padrino::Application\n"
|
|
430
|
-
template 'templates/initializer.rb.tt', destination_root("/config/initializers/#{name}.rb") if data
|
|
556
|
+
template 'templates/initializer.rb.tt', destination_root("/config/initializers/#{name}.rb"), skip: skip if data
|
|
431
557
|
end
|
|
432
558
|
|
|
433
559
|
##
|
|
@@ -436,13 +562,15 @@ module Padrino
|
|
|
436
562
|
# Name of the middleware.
|
|
437
563
|
# @param [String] source
|
|
438
564
|
# Text to generate into the middleware file.
|
|
565
|
+
# @param [Boolean] skip
|
|
566
|
+
# Whether to keep an existing middleware file.
|
|
439
567
|
#
|
|
440
568
|
# @example
|
|
441
569
|
# middleware(:hello, "class Hello\nend")
|
|
442
570
|
# #=> generates 'lib/hello_middleware.rb'
|
|
443
571
|
#
|
|
444
|
-
def middleware(name, source)
|
|
445
|
-
create_file destination_root("lib/#{name}_middleware.rb"), source
|
|
572
|
+
def middleware(name, source, skip: false)
|
|
573
|
+
create_file destination_root("lib/#{name}_middleware.rb"), source, skip: skip
|
|
446
574
|
insert_middleware name.to_s.underscore.camelize
|
|
447
575
|
end
|
|
448
576
|
|
|
@@ -505,6 +633,8 @@ module Padrino
|
|
|
505
633
|
saved = ENV.to_h
|
|
506
634
|
if defined?(Bundler) && Bundler.respond_to?(:original_env)
|
|
507
635
|
original = Bundler.original_env
|
|
636
|
+
# ceiling: a template's own changes to Bundler's keys (PATH, RUBYOPT, GEM_PATH...) made after Bundler loaded
|
|
637
|
+
# don't reach its steps; upgrade: re-apply the diff from a snapshot taken when the runner starts.
|
|
508
638
|
BUNDLER_ENV_KEYS.each { |key| original.key?(key) ? ENV[key] = original[key] : ENV.delete(key) }
|
|
509
639
|
ENV.keys.grep(/\ABUNDLER_ORIG_/).each { |key| ENV.delete(key) }
|
|
510
640
|
end
|
|
@@ -565,7 +695,8 @@ module Padrino
|
|
|
565
695
|
end
|
|
566
696
|
|
|
567
697
|
##
|
|
568
|
-
# Ensures that project name is valid, else
|
|
698
|
+
# Ensures that project name is valid, else raises a Thor::Error: the generator's start prints its
|
|
699
|
+
# message and exits with status 1.
|
|
569
700
|
#
|
|
570
701
|
# @param [String] name
|
|
571
702
|
# Name of project.
|
|
@@ -578,9 +709,9 @@ module Padrino
|
|
|
578
709
|
#
|
|
579
710
|
def valid_constant?(name)
|
|
580
711
|
if name =~ /^\d/
|
|
581
|
-
raise ::
|
|
712
|
+
raise Thor::Error, "Constant name #{name} cannot start with numbers"
|
|
582
713
|
elsif name =~ /^\W/
|
|
583
|
-
raise ::
|
|
714
|
+
raise Thor::Error, "Constant name #{name} cannot start with non-word character"
|
|
584
715
|
end
|
|
585
716
|
end
|
|
586
717
|
|
|
@@ -596,7 +727,7 @@ module Padrino
|
|
|
596
727
|
#
|
|
597
728
|
def validate_namespace(name)
|
|
598
729
|
valid_constant? name
|
|
599
|
-
name.match(/^[[:alnum:]_]+$/) || raise(::
|
|
730
|
+
name.match(/^[[:alnum:]_]+$/) || raise(Thor::Error, "Namespace '#{name}' must consist only of alphanumeric characters or '_'")
|
|
600
731
|
end
|
|
601
732
|
|
|
602
733
|
##
|
|
@@ -657,7 +788,7 @@ module Padrino
|
|
|
657
788
|
[:stylesheet, 'stylesheet engine', { aliases: '-c', default: :none }]
|
|
658
789
|
].each do |name, caption, opts|
|
|
659
790
|
opts[:default] = '' if options[:default] == false
|
|
660
|
-
component_option name, caption, opts.merge(choices: Dir["#{__dir__}/components/#{name.to_s.pluralize}/*.rb"].map { |lib| File.basename(lib, '.rb').to_sym
|
|
791
|
+
component_option name, caption, opts.merge(choices: Dir["#{__dir__}/components/#{name.to_s.pluralize}/*.rb"].map { |lib| File.basename(lib, '.rb') }.sort.map(&:to_sym))
|
|
661
792
|
end
|
|
662
793
|
end
|
|
663
794
|
|
|
@@ -4,8 +4,10 @@ module <%= @project_name %>
|
|
|
4
4
|
register Padrino::Mailer
|
|
5
5
|
register Padrino::Helpers
|
|
6
6
|
<% end -%>
|
|
7
|
-
|
|
8
|
-
#
|
|
7
|
+
# The session cookie is Secure (sent over HTTPS only) in production. Over plain HTTP
|
|
8
|
+
# the app then sends no session cookie, so logins and CSRF-protected forms fail. Behind
|
|
9
|
+
# a proxy that terminates TLS, have it send X-Forwarded-Proto: https, or set secure: false.
|
|
10
|
+
set :sessions, same_site: :lax, secure: Padrino.env == :production
|
|
9
11
|
|
|
10
12
|
##
|
|
11
13
|
# Caching support.
|
|
@@ -19,10 +19,16 @@ module Padrino
|
|
|
19
19
|
def load_boot
|
|
20
20
|
saved_env = { 'BUNDLE_GEMFILE' => ENV['BUNDLE_GEMFILE'], 'PADRINO_LOG_LEVEL' => ENV['PADRINO_LOG_LEVEL'] }
|
|
21
21
|
ENV['PADRINO_LOG_LEVEL'] ||= 'test'
|
|
22
|
-
|
|
23
|
-
|
|
22
|
+
root = File.expand_path(options[:root] || '.')
|
|
23
|
+
ENV['BUNDLE_GEMFILE'] = File.join(root, 'Gemfile') if options[:root]
|
|
24
|
+
boot = File.join(root, 'config/boot.rb')
|
|
24
25
|
if File.exist?(boot)
|
|
25
|
-
|
|
26
|
+
# Boot from the project root, as `padrino start`, rake and the binstubs do, so that its relative paths
|
|
27
|
+
# (such as a sqlite database in config/database.rb) don't resolve against the caller's directory.
|
|
28
|
+
# ceiling: only the boot runs there; a path the project opens lazily after boot (an ActiveRecord connection
|
|
29
|
+
# to a relative path) still resolves against the caller's directory. Running the whole generator from the
|
|
30
|
+
# root would cover it.
|
|
31
|
+
Dir.chdir(root) { require boot }
|
|
26
32
|
else
|
|
27
33
|
require 'padrino-support'
|
|
28
34
|
end
|
|
@@ -17,7 +17,7 @@ module Padrino
|
|
|
17
17
|
|
|
18
18
|
class_option :app, desc: 'The application name', aliases: '-n', default: nil, type: :string
|
|
19
19
|
class_option :root, desc: 'The root destination', aliases: '-r', default: '.', type: :string
|
|
20
|
-
class_option :adapter, desc: 'SQL adapter for ORM (sqlite, mysql, mysql2, mysql-gem, postgres)', aliases: '-a', default:
|
|
20
|
+
class_option :adapter, desc: 'SQL adapter for ORM (sqlite, mysql, mysql2, mysql-gem, postgres)', aliases: '-a', default: nil, type: :string
|
|
21
21
|
|
|
22
22
|
defines_component_options default: false
|
|
23
23
|
|
|
@@ -28,6 +28,7 @@ module Padrino
|
|
|
28
28
|
self.destination_root = options[:root]
|
|
29
29
|
if in_app_root?
|
|
30
30
|
@_components = options.class.new(options.slice(*self.class.component_types))
|
|
31
|
+
@_stored_components = retrieve_component_config(destination_root('.components'))
|
|
31
32
|
@app_name = (options[:app] || 'App').gsub(/\W/, '_').camelize
|
|
32
33
|
if @_components.values.delete_if(&:empty?).empty?
|
|
33
34
|
self.class.start(['-h'])
|
|
@@ -41,31 +42,34 @@ module Padrino
|
|
|
41
42
|
exit
|
|
42
43
|
end
|
|
43
44
|
|
|
44
|
-
|
|
45
|
-
self.class.component_types.
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
choice = @_components[comp]
|
|
45
|
+
# Settle every choice, switch and check before applying any, so a failure leaves the project unchanged.
|
|
46
|
+
chosen = self.class.component_types.reject { |comp| @_components[comp].empty? }
|
|
47
|
+
chosen.each { |comp| @_components[comp] = resolve_valid_choice(comp) }
|
|
48
|
+
chosen.select! do |comp|
|
|
49
|
+
choice = @_components[comp]
|
|
49
50
|
existing = fetch_component_choice(comp)
|
|
50
51
|
ask = existing != 'none' && existing != choice
|
|
51
|
-
|
|
52
|
+
!ask || yes?("Switch #{comp} to '#{choice}' from '#{existing}' ?[yes/no]:")
|
|
53
|
+
end
|
|
52
54
|
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
55
|
+
@project_name = fetch_component_choice(:namespace)
|
|
56
|
+
if chosen.include?(:test) && !already_exists?(@app_name, @project_name)
|
|
57
|
+
say "#{@project_name}::#{@app_name} does not exist."
|
|
58
|
+
say 'Please, change app name.'
|
|
59
|
+
exit 1
|
|
60
|
+
end
|
|
61
|
+
resolve_valid_adapter if chosen.include?(:orm) && SQL_ADAPTER_ORMS.include?(@_components[:orm])
|
|
60
62
|
|
|
63
|
+
chosen.each do |comp|
|
|
64
|
+
choice = @_components[comp]
|
|
61
65
|
execute_component_setup(comp, choice)
|
|
62
66
|
store_component_choice(comp, choice)
|
|
67
|
+
store_component_choice(:adapter, resolve_valid_adapter) if comp.to_s == 'orm' && SQL_ADAPTER_ORMS.include?(choice) && !adapter_unknown?
|
|
63
68
|
if comp.to_s == 'orm' && choice.to_s != 'none'
|
|
64
69
|
inject_into_file destination_root('Rakefile'), "PadrinoTasks.use(:database)\n", before: 'PadrinoTasks.init'
|
|
65
70
|
inject_into_file destination_root('Rakefile'), "PadrinoTasks.use(#{choice.to_sym.inspect})\n", before: 'PadrinoTasks.init'
|
|
66
71
|
end
|
|
67
72
|
end
|
|
68
|
-
exit 1 if missing_app
|
|
69
73
|
else
|
|
70
74
|
exit_outside_app_root!
|
|
71
75
|
end
|
|
@@ -28,7 +28,7 @@ module Padrino
|
|
|
28
28
|
return if migration_exist?(filename)
|
|
29
29
|
model_name = name.to_s.pluralize
|
|
30
30
|
field_tuples = columns.map { |value| value.split(':') }
|
|
31
|
-
field_tuples.map! { |field, kind,
|
|
31
|
+
field_tuples.map! { |field, kind, *modifiers| [field, kind =~ /datetime/i ? 'DateTime' : kind, *modifiers] }
|
|
32
32
|
column_declarations = field_tuples.flat_map(&options[:column_format]).join("\n ")
|
|
33
33
|
contents = options[:base].dup.gsub(/\s{4}!UP!\n/m, options[:up]).gsub(/!DOWN!\n/m, options[:down])
|
|
34
34
|
contents = contents.gsub(/!NAME!/, model_name.underscore.camelize).gsub(/!TABLE!/, model_name.underscore)
|
|
@@ -70,7 +70,7 @@ module Padrino
|
|
|
70
70
|
migration_scan = filename.underscore.camelize.scan(/(Add|Remove).*?(?:(?:To|From).+?)*(?:To|From)((?:To|From)?.*?)$/).flatten
|
|
71
71
|
direction, table_name = migration_scan[0].downcase, migration_scan[1].downcase.pluralize if migration_scan.any?
|
|
72
72
|
tuples = direction ? columns.map { |value| value.split(':') } : []
|
|
73
|
-
tuples.map! { |field, kind,
|
|
73
|
+
tuples.map! { |field, kind, *modifiers| [field, kind =~ /datetime/i ? 'DateTime' : kind, *modifiers] }
|
|
74
74
|
add_columns = tuples.flat_map(&options[:add]).join("\n ")
|
|
75
75
|
remove_columns = tuples.flat_map(&options[:remove]).join("\n ")
|
|
76
76
|
forward_text = change_format.gsub(/!TABLE!/, table_name).gsub(/!COLUMNS!/, add_columns) if tuples.any?
|
|
@@ -145,7 +145,8 @@ module Padrino
|
|
|
145
145
|
#
|
|
146
146
|
def insert_test_suite_setup(suite_text, options = {})
|
|
147
147
|
options = { path: 'test/test_config.rb' }.update(options)
|
|
148
|
-
|
|
148
|
+
# --app points the setup at another app, so that run rewrites it.
|
|
149
|
+
create_file(options[:path], suite_text.gsub(/CLASS_NAME/, "#{@project_name}::#{@app_name}"), skip: reapplying_component?(:test) && !self.options[:app])
|
|
149
150
|
end
|
|
150
151
|
|
|
151
152
|
##
|
|
@@ -1,17 +1,23 @@
|
|
|
1
|
+
# The lines setup_mock adds for a test component: the helper file, the line they follow, and the lines.
|
|
2
|
+
# A switch to another mock removes them through remove_previous_mock_setup.
|
|
3
|
+
def mock_setup_lines(test)
|
|
4
|
+
{
|
|
5
|
+
'rspec' => { path: 'spec/spec_helper.rb', after: "RSpec.configure do |conf|\n", text: " conf.mock_with :mocha\n" },
|
|
6
|
+
'minitest' => { path: 'test/test_config.rb', after: "require_relative '../config/boot'\n", text: "require 'mocha/minitest'\n" },
|
|
7
|
+
'testunit' => { path: 'test/test_config.rb', after: "require_relative '../config/boot'\n", text: "require 'mocha/test_unit'\n" },
|
|
8
|
+
'shoulda' => { path: 'test/test_config.rb', after: "require \"test/unit\"\n", text: "require 'mocha/test_unit'\n" }
|
|
9
|
+
}[test.to_s]
|
|
10
|
+
end
|
|
11
|
+
|
|
1
12
|
def setup_mock
|
|
2
13
|
require_dependencies 'mocha', group: 'test', require: false
|
|
3
14
|
|
|
4
15
|
test = options[:test].to_s
|
|
5
16
|
test = fetch_component_choice(:test).to_s if test.empty?
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
inject_into_file 'test/test_config.rb', "require 'mocha/minitest'\n", after: "require_relative '../config/boot'\n"
|
|
11
|
-
when 'testunit'
|
|
12
|
-
inject_into_file 'test/test_config.rb', "require 'mocha/test_unit'\n", after: "require_relative '../config/boot'\n"
|
|
13
|
-
when 'shoulda'
|
|
14
|
-
inject_into_file 'test/test_config.rb', "require 'mocha/test_unit'\n", after: "require \"test/unit\"\n"
|
|
17
|
+
if (setup = mock_setup_lines(test))
|
|
18
|
+
inject_into_file setup[:path], setup[:text], after: setup[:after]
|
|
19
|
+
# After the insert, so a helper Thor fails to read keeps the previous mock.
|
|
20
|
+
remove_previous_mock_setup(test)
|
|
15
21
|
else
|
|
16
22
|
say "Mocha is in the Gemfile but not set up for the '#{test}' test component. To use it, require mocha/api " \
|
|
17
23
|
'and call mocha_setup, mocha_verify and mocha_teardown around each test: https://github.com/freerange/mocha#readme', :yellow
|
|
@@ -1,11 +1,29 @@
|
|
|
1
|
+
# The lines setup_mock adds for a test component: the helper file, the line they follow, and the lines.
|
|
2
|
+
# A switch to another mock removes them through remove_previous_mock_setup.
|
|
3
|
+
def mock_setup_lines(test)
|
|
4
|
+
rspec = " conf.mock_with :rr\n" \
|
|
5
|
+
" # On ExampleGroup, not conf.include, so RSpec's own matchers (satisfy) stay ahead of rr's DSL.\n" \
|
|
6
|
+
" RSpec::Core::ExampleGroup.include RR::DSL\n"
|
|
7
|
+
{
|
|
8
|
+
'rspec' => { path: 'spec/spec_helper.rb', after: "RSpec.configure do |conf|\n", text: rspec },
|
|
9
|
+
'minitest' => { path: 'test/test_config.rb', after: "require_relative '../config/boot'\n", text: "require 'rr'\n" },
|
|
10
|
+
'testunit' => { path: 'test/test_config.rb', after: "require_relative '../config/boot'\n", text: "require 'test/unit/rr'\n" },
|
|
11
|
+
'shoulda' => { path: 'test/test_config.rb', after: "require \"test/unit\"\n", text: "require 'test/unit/rr'\n" }
|
|
12
|
+
}[test.to_s]
|
|
13
|
+
end
|
|
14
|
+
|
|
1
15
|
def setup_mock
|
|
2
16
|
require_dependencies 'rr', require: false, group: 'test'
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
17
|
+
|
|
18
|
+
test = options[:test].to_s
|
|
19
|
+
test = fetch_component_choice(:test).to_s if test.empty?
|
|
20
|
+
require_dependencies 'test-unit-rr', require: false, group: 'test' if %w[testunit shoulda].include?(test)
|
|
21
|
+
if (setup = mock_setup_lines(test))
|
|
22
|
+
inject_into_file setup[:path], setup[:text], after: setup[:after]
|
|
23
|
+
# After the insert, so a helper Thor fails to read keeps the previous mock.
|
|
24
|
+
remove_previous_mock_setup(test)
|
|
8
25
|
else
|
|
9
|
-
|
|
26
|
+
say "RR is in the Gemfile but not set up for the '#{test}' test component. To use it, require 'rr', include RR::DSL " \
|
|
27
|
+
'where your tests run, and call RR.reset before and RR.verify after each test: https://github.com/rr/rr#readme', :yellow
|
|
10
28
|
end
|
|
11
29
|
end
|
|
@@ -115,35 +115,27 @@ def setup_orm
|
|
|
115
115
|
ar = AR.dup
|
|
116
116
|
db = @project_name.underscore
|
|
117
117
|
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
require_dependencies 'sqlite3'
|
|
140
|
-
else
|
|
141
|
-
say "Failed to generate `config/database.rb` for ORM adapter `#{adapter}`", :red
|
|
142
|
-
raise ArgumentError
|
|
143
|
-
end
|
|
144
|
-
rescue ArgumentError
|
|
145
|
-
adapter = ask_valid_option(:adapter, %w[mysql mysql2 mysql-gem postgres sqlite], 'Please, choose a proper adapter:')
|
|
146
|
-
retry
|
|
118
|
+
case (resolve_valid_adapter unless adapter_unknown?)
|
|
119
|
+
when 'mysql-gem'
|
|
120
|
+
ar.sub!(/!DB_DEVELOPMENT!/, MYSQL.sub(/!DB_NAME!/, "'#{db}_development'"))
|
|
121
|
+
ar.sub!(/!DB_PRODUCTION!/, MYSQL.sub(/!DB_NAME!/, "'#{db}_production'"))
|
|
122
|
+
ar.sub!(/!DB_TEST!/, MYSQL.sub(/!DB_NAME!/, "'#{db}_test'"))
|
|
123
|
+
require_dependencies 'mysql', version: '~> 2.8.1'
|
|
124
|
+
when 'mysql', 'mysql2'
|
|
125
|
+
ar.sub!(/!DB_DEVELOPMENT!/, MYSQL2.sub(/!DB_NAME!/, "'#{db}_development'"))
|
|
126
|
+
ar.sub!(/!DB_PRODUCTION!/, MYSQL2.sub(/!DB_NAME!/, "'#{db}_production'"))
|
|
127
|
+
ar.sub!(/!DB_TEST!/, MYSQL2.sub(/!DB_NAME!/, "'#{db}_test'"))
|
|
128
|
+
require_dependencies 'mysql2'
|
|
129
|
+
when 'postgres'
|
|
130
|
+
ar.sub!(/!DB_DEVELOPMENT!/, POSTGRES.sub(/!DB_NAME!/, "'#{db}_development'"))
|
|
131
|
+
ar.sub!(/!DB_PRODUCTION!/, POSTGRES.sub(/!DB_NAME!/, "'#{db}_production'"))
|
|
132
|
+
ar.sub!(/!DB_TEST!/, POSTGRES.sub(/!DB_NAME!/, "'#{db}_test'"))
|
|
133
|
+
require_dependencies 'pg'
|
|
134
|
+
when 'sqlite'
|
|
135
|
+
ar.sub!(/!DB_DEVELOPMENT!/, SQLITE.sub(/!DB_NAME!/, "Padrino.root('db', '#{db}_development.db')"))
|
|
136
|
+
ar.sub!(/!DB_PRODUCTION!/, SQLITE.sub(/!DB_NAME!/, "Padrino.root('db', '#{db}_production.db')"))
|
|
137
|
+
ar.sub!(/!DB_TEST!/, SQLITE.sub(/!DB_NAME!/, "Padrino.root('db', '#{db}_test.db')"))
|
|
138
|
+
require_dependencies 'sqlite3'
|
|
147
139
|
end
|
|
148
140
|
|
|
149
141
|
require_dependencies 'activerecord', require: 'active_record', version: '>= 3.1'
|
|
@@ -156,8 +148,8 @@ def setup_orm
|
|
|
156
148
|
FIBER
|
|
157
149
|
end
|
|
158
150
|
|
|
159
|
-
create_file('config/database.rb', ar)
|
|
160
|
-
middleware :connection_pool_management, CONNECTION_POOL_MIDDLEWARE
|
|
151
|
+
create_file('config/database.rb', ar, skip: keep_database_config?)
|
|
152
|
+
middleware :connection_pool_management, CONNECTION_POOL_MIDDLEWARE, skip: reapplying_component?(:orm)
|
|
161
153
|
end
|
|
162
154
|
|
|
163
155
|
AR_MODEL = <<~MODEL unless defined?(AR_MODEL)
|
|
@@ -214,11 +206,24 @@ def ar_reference?(kind)
|
|
|
214
206
|
%w[references belongs_to].include?(kind.underscore)
|
|
215
207
|
end
|
|
216
208
|
|
|
217
|
-
#
|
|
218
|
-
|
|
209
|
+
# Segments after the kind are modifiers, in any order: index or uniq adds one index (uniq wins),
|
|
210
|
+
# as in user_id:integer:index or email:string:uniq. On a reference polymorphic adds a type column,
|
|
211
|
+
# as in commentable:references:polymorphic, and combines with an index: owner:references:polymorphic:uniq.
|
|
212
|
+
def ar_index_modifier(modifiers)
|
|
213
|
+
if modifiers.include?('uniq') then 'uniq'
|
|
214
|
+
elsif modifiers.include?('index') then 'index'
|
|
215
|
+
end
|
|
216
|
+
end
|
|
217
|
+
|
|
218
|
+
def ar_polymorphic_option(modifiers)
|
|
219
|
+
', polymorphic: true' if modifiers.include?('polymorphic')
|
|
220
|
+
end
|
|
221
|
+
|
|
222
|
+
def ar_add_column(field, kind, modifiers)
|
|
223
|
+
index = ar_index_modifier(modifiers)
|
|
219
224
|
if ar_reference?(kind)
|
|
220
225
|
index_option = { 'index' => ', index: true', 'uniq' => ', index: { unique: true }' }[index]
|
|
221
|
-
return "t.#{kind.underscore} :#{field}#{index_option}"
|
|
226
|
+
return "t.#{kind.underscore} :#{field}#{ar_polymorphic_option(modifiers)}#{index_option}"
|
|
222
227
|
end
|
|
223
228
|
|
|
224
229
|
columns = ["t.#{kind.underscore.gsub(/_/, '')} :#{field}"]
|
|
@@ -227,16 +232,18 @@ def ar_add_column(field, kind, index)
|
|
|
227
232
|
columns
|
|
228
233
|
end
|
|
229
234
|
|
|
230
|
-
# Removing a column also removes its indexes.
|
|
231
|
-
def ar_remove_column(field, kind)
|
|
232
|
-
|
|
235
|
+
# Removing a column also removes its indexes. A polymorphic reference also drops its type column.
|
|
236
|
+
def ar_remove_column(field, kind, modifiers)
|
|
237
|
+
return "t.remove :#{field}" unless ar_reference?(kind)
|
|
238
|
+
|
|
239
|
+
"t.remove_references :#{field}#{ar_polymorphic_option(modifiers)}"
|
|
233
240
|
end
|
|
234
241
|
|
|
235
242
|
def create_model_migration(migration_name, name, columns)
|
|
236
243
|
output_model_migration(
|
|
237
244
|
migration_name, name, columns,
|
|
238
245
|
base: AR_MIGRATION,
|
|
239
|
-
column_format: proc { |field, kind,
|
|
246
|
+
column_format: proc { |field, kind, *modifiers| ar_add_column(field, kind, modifiers) },
|
|
240
247
|
up: AR_MODEL_UP_MG,
|
|
241
248
|
down: AR_MODEL_DOWN_MG
|
|
242
249
|
)
|
|
@@ -253,7 +260,7 @@ def create_migration_file(migration_name, name, columns)
|
|
|
253
260
|
migration_name, name, columns,
|
|
254
261
|
base: AR_MIGRATION,
|
|
255
262
|
change_format: AR_CHANGE_MG,
|
|
256
|
-
add: proc { |field, kind,
|
|
257
|
-
remove: proc { |field, kind| ar_remove_column(field, kind) }
|
|
263
|
+
add: proc { |field, kind, *modifiers| ar_add_column(field, kind, modifiers) },
|
|
264
|
+
remove: proc { |field, kind, *modifiers| ar_remove_column(field, kind, modifiers) }
|
|
258
265
|
)
|
|
259
266
|
end
|