pineapples 0.1.0 → 0.3.34
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/.gitignore +1 -0
- data/.rspec +1 -0
- data/.ruby-gemset +1 -0
- data/bin/pineapples +4 -9
- data/lib/pineapples/actions/apply.rb +34 -0
- data/lib/pineapples/actions/base/action.rb +72 -0
- data/lib/pineapples/actions/base/target.rb +136 -0
- data/lib/pineapples/actions/chmod.rb +28 -0
- data/lib/pineapples/actions/copy_file.rb +48 -0
- data/lib/pineapples/actions/create_file.rb +114 -0
- data/lib/pineapples/actions/directory.rb +115 -0
- data/lib/pineapples/actions/empty_directory.rb +71 -0
- data/lib/pineapples/actions/get.rb +38 -0
- data/lib/pineapples/actions/git.rb +17 -0
- data/lib/pineapples/actions/gsub_file.rb +36 -0
- data/lib/pineapples/actions/insert_into_file.rb +131 -0
- data/lib/pineapples/actions/inside.rb +69 -0
- data/lib/pineapples/actions/keep_file.rb +15 -0
- data/lib/pineapples/actions/prepend_to_class.rb +26 -0
- data/lib/pineapples/actions/rails/copy_migration.rb +67 -0
- data/lib/pineapples/actions/rails/erb_converters.rb +37 -0
- data/lib/pineapples/actions/rails/rails.rb +186 -0
- data/lib/pineapples/actions/remove_file.rb +28 -0
- data/lib/pineapples/actions/ruby.rb +49 -0
- data/lib/pineapples/actions/shell.rb +40 -0
- data/lib/pineapples/actions/template.rb +35 -0
- data/lib/pineapples/actions.rb +124 -39
- data/lib/pineapples/app_builder.rb +70 -0
- data/lib/pineapples/app_generator.rb +187 -0
- data/lib/pineapples/build_tasks/root_files.rb +23 -0
- data/lib/pineapples/error.rb +7 -0
- data/lib/pineapples/helpers.rb +34 -0
- data/lib/pineapples/parser.rb +37 -0
- data/lib/pineapples/setting.rb +155 -0
- data/lib/pineapples/settings.rb +31 -0
- data/lib/pineapples/templates/.buildpacks +2 -0
- data/lib/pineapples/templates/.editor-config +11 -0
- data/lib/pineapples/templates/.example.env.tt +11 -0
- data/lib/pineapples/templates/.example.rspec +3 -0
- data/lib/pineapples/templates/.gitignore +23 -0
- data/lib/pineapples/templates/Aptfile +0 -0
- data/lib/pineapples/templates/Gemfile.tt +89 -0
- data/lib/pineapples/templates/Guardfile +27 -0
- data/lib/pineapples/templates/Procfile +1 -0
- data/lib/pineapples/templates/README.md.tt +32 -0
- data/lib/pineapples/templates/Rakefile +6 -0
- data/lib/pineapples/templates/app/assets/javascripts/application.js +1 -0
- data/lib/pineapples/templates/app/assets/javascripts/libs.js +2 -0
- data/lib/pineapples/templates/app/assets/stylesheets/application.scss +1 -0
- data/lib/pineapples/templates/app/controllers/application_controller.rb.tt +48 -0
- data/lib/pineapples/templates/app/controllers/auth/confirmations_controller.rb +2 -0
- data/lib/pineapples/templates/app/controllers/pages_controller.rb +6 -0
- data/lib/pineapples/templates/app/helpers/application_helper.rb +59 -0
- data/lib/pineapples/templates/app/helpers/title_helper.rb +77 -0
- data/lib/pineapples/templates/app/models/user!=needs_user_model!.rb.tt +28 -0
- data/lib/pineapples/templates/app/views/common/_flashes.html.erb +16 -0
- data/lib/pineapples/templates/app/views/common/_footer.html.erb +3 -0
- data/lib/pineapples/templates/app/views/common/_header.html.erb +5 -0
- data/lib/pineapples/templates/app/views/layouts/application.html.erb.tt +33 -0
- data/lib/pineapples/templates/app/views/pages/home.html.erb +4 -0
- data/lib/pineapples/templates/bin/bundle +3 -0
- data/lib/pineapples/templates/bin/rails +8 -0
- data/lib/pineapples/templates/bin/rake +8 -0
- data/lib/pineapples/templates/bin/setup +33 -0
- data/lib/pineapples/templates/bin/spring +15 -0
- data/lib/pineapples/templates/browserlist +4 -0
- data/lib/pineapples/templates/config/application.rb.tt +43 -0
- data/lib/pineapples/templates/config/boot.rb +5 -0
- data/lib/pineapples/templates/config/database.yml.tt +20 -0
- data/lib/pineapples/templates/config/environment.rb +5 -0
- data/lib/pineapples/templates/config/environments/development.rb +64 -0
- data/lib/pineapples/templates/config/environments/production.rb +77 -0
- data/lib/pineapples/templates/config/environments/test.rb +34 -0
- data/lib/pineapples/templates/config/i18n-tasks.yml +13 -0
- data/lib/pineapples/templates/config/initializers/assets.rb +11 -0
- data/lib/pineapples/templates/config/initializers/backtrace_silencers.rb +7 -0
- data/lib/pineapples/templates/config/initializers/carrierwave.rb +22 -0
- data/lib/pineapples/templates/config/initializers/cookies_serializer.rb +3 -0
- data/lib/pineapples/templates/config/initializers/filter_parameters_logging.rb +4 -0
- data/lib/pineapples/templates/config/initializers/inflections.rb +16 -0
- data/lib/pineapples/templates/config/initializers/mime_types.rb +4 -0
- data/lib/pineapples/templates/config/initializers/pry.rb +10 -0
- data/lib/pineapples/templates/config/initializers/redis.rb +3 -0
- data/lib/pineapples/templates/config/initializers/session_store.rb.tt +3 -0
- data/lib/pineapples/templates/config/initializers/wrap_parameters.rb +14 -0
- data/lib/pineapples/templates/config/locales/en.yml +19 -0
- data/lib/pineapples/templates/config/puma.rb +21 -0
- data/lib/pineapples/templates/config/routes.rb.tt +10 -0
- data/lib/pineapples/templates/config/secrets.yml +11 -0
- data/lib/pineapples/templates/config/smtp.rb +11 -0
- data/lib/pineapples/templates/config.ru.tt +7 -0
- data/lib/pineapples/templates/db/migrate/01_enable_postgres_extensions.rb +15 -0
- data/lib/pineapples/templates/db/migrate/02_create_data_migrations.rb +14 -0
- data/lib/pineapples/templates/db/migrate/03_devise_create_users.rb +41 -0
- data/lib/pineapples/templates/db/migrate/04_add_role_field_to_users.rb +7 -0
- data/lib/pineapples/templates/lib/devise!=devise!/ajax_failure.rb +11 -0
- data/lib/pineapples/templates/lib/extensions/form_builder.rb +24 -0
- data/lib/pineapples/templates/lib/extensions/http_errors.rb +34 -0
- data/lib/pineapples/templates/lib/extensions/to_boolean.rb +45 -0
- data/lib/pineapples/templates/lib/logging/custom_rack_logger.rb +11 -0
- data/lib/pineapples/templates/lib/logging/custom_request_logger.rb +65 -0
- data/lib/pineapples/templates/lib/simple_form/components/append.rb +15 -0
- data/lib/pineapples/templates/lib/simple_form/components/prepend.rb +15 -0
- data/lib/pineapples/templates/lib/tasks/admin!=user_role_field!.rake +52 -0
- data/lib/pineapples/templates/lib/tasks/auto_annotate_models.rake +37 -0
- data/lib/pineapples/templates/lib/templates/erb/scaffold/_form.html.erb +14 -0
- data/lib/pineapples/templates/lib/virtus/virtus.rb +23 -0
- data/lib/pineapples/templates/public/404.html +67 -0
- data/lib/pineapples/templates/public/422.html +67 -0
- data/lib/pineapples/templates/public/500.html +66 -0
- data/lib/pineapples/templates/public/favicon.ico +0 -0
- data/lib/pineapples/templates/public/robots.txt +5 -0
- data/lib/pineapples/version.rb +1 -1
- data/lib/pineapples.rb +17 -2
- data/pineapples.gemspec +16 -15
- metadata +208 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 4168dcbfa92187c3d8bcf7b67f594cb7a3308067
|
4
|
+
data.tar.gz: 9fd839446bd1602e213722deb11049aa9cd9c642
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 92f8f1dfe6d724cc76085cd4cda7f55c0ba0bb03557292b156310b3613c5afbd70d75537abffc289c513a7280068ba4b2d1f9c3d152f276e8e84867be3279fb5
|
7
|
+
data.tar.gz: fedbe56b5c2475020d96dabe94a904cf4b11ff6c41bf3994a98e3a7724b32ad0b9581d5b3a61613da04d4887f5976452ccf0ba41dba520d8297fba1a11b551dd
|
data/.gitignore
CHANGED
data/.rspec
CHANGED
data/.ruby-gemset
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
pineapples
|
data/bin/pineapples
CHANGED
@@ -6,13 +6,8 @@ $LOAD_PATH << source_path
|
|
6
6
|
|
7
7
|
require 'pineapples'
|
8
8
|
|
9
|
-
|
10
|
-
ARGV.shift
|
11
|
-
puts "[WARNING] the suspenders create argument is deprecated. Just use `suspenders #{ARGV.join}` instead"
|
12
|
-
end
|
9
|
+
$terminal.indent_size = 2
|
13
10
|
|
14
|
-
|
15
|
-
Pineapples::AppGenerator.
|
16
|
-
|
17
|
-
|
18
|
-
Pineapples::AppGenerator.start
|
11
|
+
options = Pineapples::Parser.parse(ARGV)
|
12
|
+
generator = Pineapples::AppGenerator.new(options)
|
13
|
+
generator.start!
|
@@ -0,0 +1,34 @@
|
|
1
|
+
module Pineapples
|
2
|
+
module Actions
|
3
|
+
# Loads an external file and execute it in the instance binding.
|
4
|
+
#
|
5
|
+
# ==== Parameters
|
6
|
+
# path<String>:: The path to the file to execute. Can be a web address or
|
7
|
+
# a relative path from the source root.
|
8
|
+
#
|
9
|
+
# ==== Examples
|
10
|
+
#
|
11
|
+
# apply "http://gist.github.com/103208"
|
12
|
+
#
|
13
|
+
# apply "recipes/jquery.rb"
|
14
|
+
#
|
15
|
+
def apply(path, options = {})
|
16
|
+
verbose = options.fetch(:verbose, verbose?)
|
17
|
+
is_uri = path =~ %r{^https?\://}
|
18
|
+
path = find_in_source_paths(path) if !is_uri
|
19
|
+
color = options.fetch(:color, DEFAULT_COLOR)
|
20
|
+
|
21
|
+
say_status(:apply, path, color, verbose)
|
22
|
+
|
23
|
+
indent(verbose) do
|
24
|
+
if is_uri
|
25
|
+
contents = open(path, 'Accept' => 'application/x-thor-template') { |io| io.read }
|
26
|
+
else
|
27
|
+
contents = open(path) { |io| io.read }
|
28
|
+
end
|
29
|
+
|
30
|
+
instance_eval(contents, path)
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
@@ -0,0 +1,72 @@
|
|
1
|
+
module Pineapples
|
2
|
+
module Actions
|
3
|
+
class Action
|
4
|
+
STATUS_COLORS = {creative: :light_green,
|
5
|
+
destructive: :light_red,
|
6
|
+
neutral: :light_blue,
|
7
|
+
warning: :light_yellow}
|
8
|
+
|
9
|
+
# Reference to AppGenerator instance
|
10
|
+
attr_reader :generator
|
11
|
+
|
12
|
+
attr_reader :colors
|
13
|
+
|
14
|
+
# Options hash
|
15
|
+
attr_accessor :options
|
16
|
+
|
17
|
+
# Let status colors set by .status_color macro be inherited
|
18
|
+
def self.inherited(child_class)
|
19
|
+
super
|
20
|
+
child_class.instance_variable_set('@colors', self.colors)
|
21
|
+
end
|
22
|
+
|
23
|
+
def self.status_color(status, color)
|
24
|
+
colors[status] = color
|
25
|
+
end
|
26
|
+
|
27
|
+
def self.colors
|
28
|
+
@colors ||= {}
|
29
|
+
end
|
30
|
+
|
31
|
+
def initialize(generator, options = {})
|
32
|
+
@generator = generator
|
33
|
+
@options = options
|
34
|
+
end
|
35
|
+
|
36
|
+
def invoke!
|
37
|
+
raise NotImplementedError, "You should really implement invoke! method on Action subclasses"
|
38
|
+
end
|
39
|
+
|
40
|
+
def skip?
|
41
|
+
@skip = false if @skip.nil?
|
42
|
+
@skip
|
43
|
+
end
|
44
|
+
|
45
|
+
protected
|
46
|
+
|
47
|
+
def pretend?
|
48
|
+
generator.pretend?
|
49
|
+
end
|
50
|
+
|
51
|
+
def execute?
|
52
|
+
!pretend?
|
53
|
+
end
|
54
|
+
|
55
|
+
def verbose?
|
56
|
+
options[:verbose] || generator.verbose?
|
57
|
+
end
|
58
|
+
|
59
|
+
# used to log action actions (no pun intended)
|
60
|
+
def say_status(status, message = self.message, color = nil)
|
61
|
+
color = color(status) if color.nil?
|
62
|
+
generator.say_status(status, message, color) if verbose?
|
63
|
+
end
|
64
|
+
|
65
|
+
def color(status)
|
66
|
+
color = self.class.colors[status]
|
67
|
+
color = STATUS_COLORS[color] if STATUS_COLORS.keys.include?(color)
|
68
|
+
end
|
69
|
+
|
70
|
+
end
|
71
|
+
end
|
72
|
+
end
|
@@ -0,0 +1,136 @@
|
|
1
|
+
module Pineapples
|
2
|
+
module Actions
|
3
|
+
class Target
|
4
|
+
PASS_MATCH = /!=(.*?)!/
|
5
|
+
GUARD_MATCH = /!(.*?)!/
|
6
|
+
EVAL_MATCH = /%(.*?)%/
|
7
|
+
|
8
|
+
attr_reader :given,
|
9
|
+
:fullpath,
|
10
|
+
:relative,
|
11
|
+
:action
|
12
|
+
|
13
|
+
def initialize(target, action)
|
14
|
+
raise Error, 'Target should not be falsy' if !target
|
15
|
+
|
16
|
+
@action = action
|
17
|
+
@given = target.to_s
|
18
|
+
|
19
|
+
evaluate_pass_method!
|
20
|
+
evaluate_guard_methods!
|
21
|
+
evaluate_filename_method!
|
22
|
+
|
23
|
+
@fullpath = File.expand_path(@given, generator.current_app_dir)
|
24
|
+
@relative = generator.relative_to_current_app_dir(@fullpath)
|
25
|
+
end
|
26
|
+
|
27
|
+
def generator
|
28
|
+
action.generator
|
29
|
+
end
|
30
|
+
|
31
|
+
def skip?
|
32
|
+
@skip = false if @skip.nil?
|
33
|
+
@skip
|
34
|
+
end
|
35
|
+
|
36
|
+
private
|
37
|
+
|
38
|
+
def evaluate_pass_method!
|
39
|
+
if pass_match
|
40
|
+
raise Error, methods_missing_error_message(pass_methods) if !generator_pass_method
|
41
|
+
|
42
|
+
@skip = !generator.send(generator_pass_method)
|
43
|
+
@given.gsub!(pass_match[0], '')
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
def evaluate_guard_methods!
|
48
|
+
if guard_match
|
49
|
+
raise Error, methods_missing_error_message(guard_methods) if !generator_guard_method
|
50
|
+
|
51
|
+
@skip = generator.send(generator_guard_method)
|
52
|
+
@given.gsub!(guard_match[0], '')
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
def evaluate_filename_method!
|
57
|
+
if eval_match
|
58
|
+
raise Error, methods_missing_error_message(filename_method) if !generator_filename_method
|
59
|
+
|
60
|
+
filename = generator.send(filename_method)
|
61
|
+
@given.gsub!(eval_match[0], filename)
|
62
|
+
end
|
63
|
+
end
|
64
|
+
|
65
|
+
def pass_match
|
66
|
+
PASS_MATCH.match(@given)
|
67
|
+
end
|
68
|
+
|
69
|
+
def guard_match
|
70
|
+
GUARD_MATCH.match(@given)
|
71
|
+
end
|
72
|
+
|
73
|
+
def eval_match
|
74
|
+
EVAL_MATCH.match(@given)
|
75
|
+
end
|
76
|
+
|
77
|
+
def generator_pass_method
|
78
|
+
if @pass_method.nil?
|
79
|
+
pass_methods.each do |method|
|
80
|
+
if generator.respond_to?(method, true)
|
81
|
+
@pass_method = method
|
82
|
+
break
|
83
|
+
end
|
84
|
+
end
|
85
|
+
end
|
86
|
+
@pass_method = false if @pass_method.nil?
|
87
|
+
@pass_method
|
88
|
+
end
|
89
|
+
|
90
|
+
def generator_guard_method
|
91
|
+
if @guard_method.nil?
|
92
|
+
guard_methods.each do |method|
|
93
|
+
if generator.respond_to?(method, true)
|
94
|
+
@guard_method = method
|
95
|
+
break
|
96
|
+
end
|
97
|
+
end
|
98
|
+
end
|
99
|
+
@guard_method = false if @guard_method.nil?
|
100
|
+
@guard_method
|
101
|
+
end
|
102
|
+
|
103
|
+
def generator_filename_method
|
104
|
+
if @filename_method.nil?
|
105
|
+
@filename_method = generator.respond_to?(filename_method, true) ? filename_method : false
|
106
|
+
end
|
107
|
+
@filename_method
|
108
|
+
end
|
109
|
+
|
110
|
+
def guard_methods
|
111
|
+
guard_method = guard_match[1].strip
|
112
|
+
predicate_method = guard_method + '?'
|
113
|
+
[guard_method, predicate_method]
|
114
|
+
end
|
115
|
+
|
116
|
+
def pass_methods
|
117
|
+
pass_method = pass_match[1].strip
|
118
|
+
predicate_method = pass_method + '?'
|
119
|
+
[pass_method, predicate_method]
|
120
|
+
end
|
121
|
+
|
122
|
+
def filename_method
|
123
|
+
eval_match[1].strip
|
124
|
+
end
|
125
|
+
|
126
|
+
def methods_missing_error_message(methods)
|
127
|
+
plural = methods.is_a?(Array)
|
128
|
+
methods = methods.join(', ') if plural
|
129
|
+
methods_message = plural ? 'methods' : 'method'
|
130
|
+
|
131
|
+
"No instance #{methods_message} #{methods} for AppGenerator, can't evaluate filepath #{given}"
|
132
|
+
end
|
133
|
+
end
|
134
|
+
|
135
|
+
end
|
136
|
+
end
|
@@ -0,0 +1,28 @@
|
|
1
|
+
module Pineapples
|
2
|
+
module Actions
|
3
|
+
# Changes the mode of the given file or directory.
|
4
|
+
#
|
5
|
+
# ==== Parameters
|
6
|
+
# mode<Integer>:: the file mode
|
7
|
+
# path<String>:: the name of the file to change mode
|
8
|
+
# options<Hash>:: give :verbose => false to not log the status.
|
9
|
+
#
|
10
|
+
# ==== Example
|
11
|
+
#
|
12
|
+
# chmod "script/server", 0755
|
13
|
+
#
|
14
|
+
def chmod(path, mode, options = {})
|
15
|
+
return unless behaviour == :invoke
|
16
|
+
|
17
|
+
verbose = options.fetch(:verbose, verbose?)
|
18
|
+
execute = !options.fetch(:pretend, pretend?)
|
19
|
+
color = options.fetch(:color, DEFAULT_COLOR)
|
20
|
+
|
21
|
+
relative_path = relative_to_app_root(File.join(app_root, path))
|
22
|
+
|
23
|
+
say_status(:chmod, relative_path, color, verbose)
|
24
|
+
|
25
|
+
FileUtils.chmod_R(mode, path) if execute
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
@@ -0,0 +1,48 @@
|
|
1
|
+
require_relative 'create_file'
|
2
|
+
|
3
|
+
module Pineapples
|
4
|
+
module Actions
|
5
|
+
# Copies the file from the relative source to the relative destination. If
|
6
|
+
# the destination is not given it's assumed to be equal to the source.
|
7
|
+
#
|
8
|
+
# ==== Parameters
|
9
|
+
# source<String>:: the relative path to the source root.
|
10
|
+
# target<String>:: the relative path to the destination root.
|
11
|
+
# options<Hash>:: give :verbose => false to not log the status, and
|
12
|
+
# :mode => :preserve, to preserve the file mode from the source.
|
13
|
+
|
14
|
+
#
|
15
|
+
# ==== Examples
|
16
|
+
#
|
17
|
+
# copy_file "README", "doc/README"
|
18
|
+
#
|
19
|
+
# copy_file "doc/README"
|
20
|
+
#
|
21
|
+
def copy_file(source, *args, &block)
|
22
|
+
options = args.last.is_a?(Hash) ? args.pop : {}
|
23
|
+
target = args.first || source
|
24
|
+
|
25
|
+
action CopyFile.new(self, source, target, options, &block)
|
26
|
+
end
|
27
|
+
|
28
|
+
class CopyFile < CreateFile
|
29
|
+
attr_reader :source
|
30
|
+
|
31
|
+
def initialize(generator, source, target, options, &block)
|
32
|
+
@source = File.expand_path(generator.find_in_source_paths(source.to_s))
|
33
|
+
@content = File.binread(@source)
|
34
|
+
@content = block.call(@content) if block
|
35
|
+
super(generator, target, @content, options)
|
36
|
+
end
|
37
|
+
|
38
|
+
def invoke!
|
39
|
+
super
|
40
|
+
if options[:mode] == :preserve
|
41
|
+
mode = File.stat(source).mode
|
42
|
+
generator.chmod(target, mode, options)
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
@@ -0,0 +1,114 @@
|
|
1
|
+
require_relative 'empty_directory'
|
2
|
+
|
3
|
+
module Pineapples
|
4
|
+
module Actions
|
5
|
+
# Create a new file relative to the destination root with the given data,
|
6
|
+
# which is the return value of a block or a data string.
|
7
|
+
#
|
8
|
+
# ==== Parameters
|
9
|
+
# target<String>:: the relative path to the destination root.
|
10
|
+
# content<String|NilClass>:: the data to append to the file.
|
11
|
+
# options<Hash>:: give :verbose => false to not log the status.
|
12
|
+
#
|
13
|
+
# ==== Examples
|
14
|
+
#
|
15
|
+
# create_file "lib/fun_party.rb" do
|
16
|
+
# hostname = ask("What is the virtual hostname I should use?")
|
17
|
+
# "vhost.name = #{hostname}"
|
18
|
+
# end
|
19
|
+
#
|
20
|
+
# create_file "config/apache.conf", "your apache config"
|
21
|
+
#
|
22
|
+
def create_file(target, *args, &block)
|
23
|
+
options = args.last.is_a?(Hash) ? args.pop : {}
|
24
|
+
content = args.first
|
25
|
+
action CreateFile.new(self, target, block || content, options)
|
26
|
+
end
|
27
|
+
|
28
|
+
def ask_file_collision(target) # rubocop:disable MethodLength
|
29
|
+
return true if @always_force
|
30
|
+
options = '[Ynaq]'
|
31
|
+
|
32
|
+
question_string = "Overwrite #{target}?"
|
33
|
+
options = ['Yes', 'No', 'Always', 'Quit']
|
34
|
+
colors = [:light_green, :light_red, :light_yellow, :light_red]
|
35
|
+
options_with_color = options.map.with_index { |option, index| option.send(colors[index])}
|
36
|
+
|
37
|
+
loop do
|
38
|
+
answer_index = Ask.list(question_string, options_with_color, {clear: false, response: false})
|
39
|
+
answer = options[answer_index]
|
40
|
+
|
41
|
+
case answer
|
42
|
+
when 'Yes'
|
43
|
+
return true
|
44
|
+
when 'No'
|
45
|
+
return false
|
46
|
+
when 'Always'
|
47
|
+
return @always_force = true
|
48
|
+
when 'Quit'
|
49
|
+
say 'Aborting'
|
50
|
+
raise SystemExit
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
class CreateFile < EmptyDirectory
|
56
|
+
attr_reader :content
|
57
|
+
|
58
|
+
status_color :identical, :neutral
|
59
|
+
status_color :force, :warning
|
60
|
+
status_color :skip, :warning
|
61
|
+
status_color :conflict, :destructive
|
62
|
+
|
63
|
+
def initialize(generator, target, content, options = {})
|
64
|
+
super(generator, target, options)
|
65
|
+
@content = content
|
66
|
+
end
|
67
|
+
|
68
|
+
def identical?
|
69
|
+
exists? && File.binread(target.fullpath) == render
|
70
|
+
end
|
71
|
+
|
72
|
+
def render
|
73
|
+
@render ||= content.is_a?(Proc) ? content.call : content.to_s
|
74
|
+
end
|
75
|
+
|
76
|
+
def invoke!
|
77
|
+
invoke_with_conflict_check do
|
78
|
+
FileUtils.mkdir_p(File.dirname(target.fullpath))
|
79
|
+
File.open(target.fullpath, 'wb') { |file| file.write render }
|
80
|
+
end
|
81
|
+
target.given
|
82
|
+
end
|
83
|
+
|
84
|
+
protected
|
85
|
+
|
86
|
+
def on_conflict_behaviour(&block)
|
87
|
+
if identical?
|
88
|
+
say_status :identical
|
89
|
+
else
|
90
|
+
force_or_skip_or_conflict(options[:force], options[:skip], &block)
|
91
|
+
end
|
92
|
+
end
|
93
|
+
|
94
|
+
def force_or_skip_or_conflict(force, skip, &block)
|
95
|
+
if force
|
96
|
+
say_status :force
|
97
|
+
block.call if execute?
|
98
|
+
elsif skip
|
99
|
+
say_status :skip
|
100
|
+
else
|
101
|
+
say_status :conflict
|
102
|
+
force_or_skip_or_conflict(force_on_collision?, true, &block)
|
103
|
+
end
|
104
|
+
end
|
105
|
+
|
106
|
+
# Shows the file collision menu to the user and gets the result.
|
107
|
+
#
|
108
|
+
def force_on_collision?
|
109
|
+
generator.ask_file_collision(target)
|
110
|
+
end
|
111
|
+
|
112
|
+
end
|
113
|
+
end
|
114
|
+
end
|
@@ -0,0 +1,115 @@
|
|
1
|
+
module Pineapples
|
2
|
+
module Actions
|
3
|
+
# Copies recursively the files from source directory to root directory.
|
4
|
+
# If any of the files finishes with .tt, it's considered to be a template
|
5
|
+
# and is placed in the destination without the extension .tt. If any
|
6
|
+
# empty directory is found, it's copied and all .empty_directory files are
|
7
|
+
# ignored. If any file name is wrapped within % signs, the text within
|
8
|
+
# the % signs will be executed as a method and replaced with the returned
|
9
|
+
# value. Let's suppose a doc directory with the following files:
|
10
|
+
#
|
11
|
+
# doc/
|
12
|
+
# components/.empty_directory
|
13
|
+
# README
|
14
|
+
# rdoc.rb.tt
|
15
|
+
# %app_name%.rb
|
16
|
+
#
|
17
|
+
# When invoked as:
|
18
|
+
#
|
19
|
+
# directory "doc"
|
20
|
+
#
|
21
|
+
# It will create a doc directory in the destination with the following
|
22
|
+
# files (assuming that the `app_name` method returns the value "blog"):
|
23
|
+
#
|
24
|
+
# doc/
|
25
|
+
# components/
|
26
|
+
# README
|
27
|
+
# rdoc.rb
|
28
|
+
# blog.rb
|
29
|
+
#
|
30
|
+
# ==== Parameters
|
31
|
+
# source<String>:: the relative path to the source root.
|
32
|
+
# target<String>:: the relative path to the destination root.
|
33
|
+
# options<Hash>:: give :verbose => false to not log the status.
|
34
|
+
# If :recursive => false, does not look for paths recursively.
|
35
|
+
# If :mode => :preserve, preserve the file mode from the source.
|
36
|
+
# If :exclude_pattern => /regexp/, prevents copying files that match that regexp.
|
37
|
+
#
|
38
|
+
# ==== Examples
|
39
|
+
#
|
40
|
+
# directory "doc"
|
41
|
+
# directory "doc", "docs", :recursive => false
|
42
|
+
#
|
43
|
+
def directory(source, *args, &block)
|
44
|
+
options = args.last.is_a?(Hash) ? args.pop : {}
|
45
|
+
target = args.first || source
|
46
|
+
action Directory.new(self, source, target, options, &block)
|
47
|
+
end
|
48
|
+
|
49
|
+
class Directory < EmptyDirectory
|
50
|
+
attr_reader :source
|
51
|
+
|
52
|
+
def initialize(generator, source, target, options, &block)
|
53
|
+
super(generator, target, {recursive: true}.merge(options))
|
54
|
+
|
55
|
+
@source = File.expand_path(generator.find_in_source_paths(source.to_s))
|
56
|
+
@block = block
|
57
|
+
end
|
58
|
+
|
59
|
+
def invoke!
|
60
|
+
generator.empty_directory(target.given, options)
|
61
|
+
execute!
|
62
|
+
end
|
63
|
+
|
64
|
+
def revoke!
|
65
|
+
execute!
|
66
|
+
end
|
67
|
+
|
68
|
+
protected
|
69
|
+
|
70
|
+
def execute!
|
71
|
+
lookup = escape_globs(source)
|
72
|
+
lookup = options[:recursive] ? File.join(lookup, '**') : lookup
|
73
|
+
lookup = file_level_lookup(lookup)
|
74
|
+
|
75
|
+
excluded_files = Array(options[:exclude])
|
76
|
+
exclude_pattern = options[:exclude_pattern]
|
77
|
+
|
78
|
+
files(lookup).sort.each do |file_source|
|
79
|
+
next if File.directory?(file_source)
|
80
|
+
next if exclude_pattern && file_source.match(exclude_pattern)
|
81
|
+
next if excluded_files.any? do |excluded_file|
|
82
|
+
File.basename(excluded_file) == File.basename(file_source)
|
83
|
+
end
|
84
|
+
|
85
|
+
file_target = File.join(target.given, file_source.gsub(source, '.'))
|
86
|
+
file_target.gsub!('/./', '/')
|
87
|
+
|
88
|
+
case file_source
|
89
|
+
when /\.empty_directory$/
|
90
|
+
dirname = File.dirname(file_target).gsub(/\/\.$/, '')
|
91
|
+
next if dirname == target.given
|
92
|
+
generator.empty_directory(dirname, options)
|
93
|
+
when /#{TEMPLATE_EXTNAME}$/
|
94
|
+
generator.template(file_source, file_target[0..-4], options, &@block)
|
95
|
+
else
|
96
|
+
generator.copy_file(file_source, file_target, options, &@block)
|
97
|
+
end
|
98
|
+
end
|
99
|
+
end
|
100
|
+
|
101
|
+
def file_level_lookup(previous_lookup)
|
102
|
+
File.join(previous_lookup, '*')
|
103
|
+
end
|
104
|
+
|
105
|
+
def files(lookup)
|
106
|
+
Dir.glob(lookup, File::FNM_DOTMATCH)
|
107
|
+
end
|
108
|
+
|
109
|
+
def escape_globs(path)
|
110
|
+
path.to_s.gsub(/[*?{}\[\]]/, '\\\\\\&')
|
111
|
+
end
|
112
|
+
end
|
113
|
+
|
114
|
+
end
|
115
|
+
end
|
@@ -0,0 +1,71 @@
|
|
1
|
+
module Pineapples
|
2
|
+
module Actions
|
3
|
+
# Creates an empty directory.
|
4
|
+
#
|
5
|
+
# ==== Parameters
|
6
|
+
# target<String>:: the relative path to the app root.
|
7
|
+
# options<Hash>:: give :verbose => false to not log the status.
|
8
|
+
#
|
9
|
+
# ==== Examples
|
10
|
+
#
|
11
|
+
# empty_directory "doc"
|
12
|
+
#
|
13
|
+
def empty_directory(target, options = {})
|
14
|
+
action EmptyDirectory.new(self, target, options)
|
15
|
+
end
|
16
|
+
|
17
|
+
class EmptyDirectory < Action
|
18
|
+
attr_reader :target,
|
19
|
+
:skip
|
20
|
+
|
21
|
+
status_color :create, :creative
|
22
|
+
status_color :remove, :destructive
|
23
|
+
status_color :exist, :neutral
|
24
|
+
|
25
|
+
def initialize(generator, target, options = {})
|
26
|
+
super(generator, options)
|
27
|
+
|
28
|
+
@target = Target.new(target, self)
|
29
|
+
@skip = @target.skip?
|
30
|
+
end
|
31
|
+
|
32
|
+
def invoke!
|
33
|
+
invoke_with_conflict_check do
|
34
|
+
::FileUtils.mkdir_p(target.fullpath)
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
def revoke!
|
39
|
+
say_status :remove
|
40
|
+
::FileUtils.rm_rf(target.fullpath) if execute? && exists?
|
41
|
+
given_target
|
42
|
+
end
|
43
|
+
|
44
|
+
def exists?
|
45
|
+
::File.exist?(target.fullpath)
|
46
|
+
end
|
47
|
+
|
48
|
+
protected
|
49
|
+
|
50
|
+
def invoke_with_conflict_check(&block)
|
51
|
+
if exists?
|
52
|
+
on_conflict_behaviour(&block)
|
53
|
+
else
|
54
|
+
say_status :create
|
55
|
+
block.call if execute?
|
56
|
+
end
|
57
|
+
|
58
|
+
target.fullpath
|
59
|
+
end
|
60
|
+
|
61
|
+
def on_conflict_behaviour(&block)
|
62
|
+
say_status :exist
|
63
|
+
end
|
64
|
+
|
65
|
+
def message
|
66
|
+
target.relative
|
67
|
+
end
|
68
|
+
|
69
|
+
end
|
70
|
+
end
|
71
|
+
end
|