pineapples 0.1.0 → 0.3.34
Sign up to get free protection for your applications and to get access to all the features.
- 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
|