planter-cli 3.0.4 → 3.0.5
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/.irbrc +2 -1
- data/.yardopts +1 -0
- data/CHANGELOG.md +20 -0
- data/README.md +131 -45
- data/bin/plant +5 -4
- data/lib/planter/array.rb +15 -1
- data/lib/planter/color.rb +7 -1
- data/lib/planter/config.rb +172 -0
- data/lib/planter/filelist.rb +12 -2
- data/lib/planter/numeric.rb +31 -0
- data/lib/planter/plant.rb +24 -17
- data/lib/planter/prompt.rb +23 -11
- data/lib/planter/script.rb +3 -3
- data/lib/planter/string.rb +150 -74
- data/lib/planter/tag.rb +38 -0
- data/lib/planter/version.rb +1 -1
- data/lib/planter.rb +39 -108
- data/lib/tty-spinner/lib/tty/spinner/multi.rb +3 -3
- data/planter-cli.gemspec +2 -0
- data/spec/cli_spec.rb +12 -2
- data/spec/planter/filelist_spec.rb +2 -2
- data/spec/planter/prompt_spec.rb +71 -0
- data/spec/planter/script_spec.rb +5 -4
- data/spec/planter/string_spec.rb +26 -0
- data/spec/spec_helper.rb +3 -2
- data/spec/templates/test/_planter.yml +2 -0
- data/spec/templates/test/_scripts/plant.sh +3 -0
- data/src/_README.md +131 -45
- metadata +8 -2
data/lib/planter.rb
CHANGED
@@ -15,14 +15,15 @@ require 'tty-screen'
|
|
15
15
|
require 'tty-which'
|
16
16
|
|
17
17
|
require_relative 'tty-spinner/lib/tty-spinner'
|
18
|
+
require_relative 'planter/config'
|
18
19
|
require_relative 'planter/version'
|
19
20
|
require_relative 'planter/hash'
|
20
21
|
require_relative 'planter/array'
|
21
22
|
require_relative 'planter/symbol'
|
23
|
+
require_relative 'planter/numeric'
|
22
24
|
require_relative 'planter/file'
|
23
25
|
require_relative 'planter/tag'
|
24
26
|
require_relative 'planter/color'
|
25
|
-
require_relative 'planter/errors'
|
26
27
|
require_relative 'planter/prompt'
|
27
28
|
require_relative 'planter/string'
|
28
29
|
require_relative 'planter/filelist'
|
@@ -30,6 +31,28 @@ require_relative 'planter/fileentry'
|
|
30
31
|
require_relative 'planter/script'
|
31
32
|
require_relative 'planter/plant'
|
32
33
|
|
34
|
+
# @return [Integer] Exit codes
|
35
|
+
EXIT_CODES = {
|
36
|
+
argument: 12,
|
37
|
+
input: 13,
|
38
|
+
canceled: 1,
|
39
|
+
script: 10,
|
40
|
+
config: 127,
|
41
|
+
git: 129
|
42
|
+
}.deep_freeze
|
43
|
+
|
44
|
+
#
|
45
|
+
# Exit the program with a message
|
46
|
+
#
|
47
|
+
# @param msg [String] error message
|
48
|
+
# @param level [Symbol] notification level
|
49
|
+
# @param code [Integer] Exit code
|
50
|
+
#
|
51
|
+
def die(msg = 'Exited', code = :canceled)
|
52
|
+
code = EXIT_CODES.key?(code) ? code : :canceled
|
53
|
+
Planter.notify(msg, :error, above_spinner: false, exit_code: EXIT_CODES[code])
|
54
|
+
end
|
55
|
+
|
33
56
|
# Main Journal module
|
34
57
|
module Planter
|
35
58
|
# Base directory for templates
|
@@ -56,7 +79,7 @@ module Planter
|
|
56
79
|
attr_accessor :template
|
57
80
|
|
58
81
|
## Config Hash
|
59
|
-
attr_reader :config
|
82
|
+
# attr_reader :config
|
60
83
|
|
61
84
|
## Variable key/values
|
62
85
|
attr_accessor :variables
|
@@ -67,6 +90,10 @@ module Planter
|
|
67
90
|
## Accept all defaults
|
68
91
|
attr_accessor :accept_defaults
|
69
92
|
|
93
|
+
def config
|
94
|
+
@config ||= Config.new
|
95
|
+
end
|
96
|
+
|
70
97
|
##
|
71
98
|
## Print a message on the command line
|
72
99
|
##
|
@@ -92,11 +119,17 @@ module Planter
|
|
92
119
|
'{bw}'
|
93
120
|
end
|
94
121
|
out = "#{color}#{string}{x}"
|
95
|
-
out = out.gsub(/\[(.*?)\]/, "{by}\\1{x}#{color}")
|
122
|
+
# out = out.gsub(/\[(.*?)\]/, "{by}\\1{x}#{color}")
|
96
123
|
out = "\n#{out}" if newline
|
124
|
+
|
125
|
+
spinner.update(title: 'ERROR') if exit_code
|
126
|
+
spinner.error if notification_type == :error
|
127
|
+
|
97
128
|
above_spinner ? spinner.log(out.x) : warn(out.x)
|
98
129
|
|
99
|
-
|
130
|
+
if exit_code && $stdout.isatty && (ENV['PLANTER_RSPEC'] == 'true' || ENV['PLANTER_DEBUG'] != 'true')
|
131
|
+
exit(exit_code)
|
132
|
+
end
|
100
133
|
|
101
134
|
true
|
102
135
|
end
|
@@ -118,56 +151,6 @@ module Planter
|
|
118
151
|
@base_dir ||= ENV['PLANTER_BASE_DIR'] || File.join(Dir.home, '.config', 'planter')
|
119
152
|
end
|
120
153
|
|
121
|
-
##
|
122
|
-
## Build a configuration from template name
|
123
|
-
##
|
124
|
-
## @param template [String] The template name
|
125
|
-
##
|
126
|
-
## @return [Hash] Configuration object
|
127
|
-
##
|
128
|
-
def config=(template)
|
129
|
-
@template = template
|
130
|
-
Planter.variables ||= {}
|
131
|
-
FileUtils.mkdir_p(Planter.base_dir) unless File.directory?(Planter.base_dir)
|
132
|
-
base_config = File.join(Planter.base_dir, 'planter.yml')
|
133
|
-
|
134
|
-
if File.exist?(base_config)
|
135
|
-
@config = YAML.load(IO.read(base_config)).symbolize_keys
|
136
|
-
else
|
137
|
-
default_base_config = {
|
138
|
-
defaults: false,
|
139
|
-
git_init: false,
|
140
|
-
files: { '_planter.yml' => 'ignore' },
|
141
|
-
color: true,
|
142
|
-
preserve_tags: true
|
143
|
-
}
|
144
|
-
begin
|
145
|
-
File.open(base_config, 'w') { |f| f.puts(YAML.dump(default_base_config.stringify_keys)) }
|
146
|
-
rescue Errno::ENOENT
|
147
|
-
Planter.notify("Unable to create #{base_config}", :error)
|
148
|
-
end
|
149
|
-
@config = default_base_config.symbolize_keys
|
150
|
-
Planter.notify("New configuration written to #{base_config}, edit as needed.", :warn)
|
151
|
-
end
|
152
|
-
|
153
|
-
base_dir = File.join(Planter.base_dir, 'templates', @template)
|
154
|
-
unless File.directory?(base_dir)
|
155
|
-
notify("Template #{@template} does not exist", :error)
|
156
|
-
res = Prompt.yn('Create template directory', default_response: false)
|
157
|
-
|
158
|
-
raise Errors::InputError.new('Canceled') unless res
|
159
|
-
|
160
|
-
FileUtils.mkdir_p(base_dir)
|
161
|
-
end
|
162
|
-
|
163
|
-
load_template_config
|
164
|
-
|
165
|
-
config_array_to_hash(:files) if @config[:files].is_a?(Array)
|
166
|
-
config_array_to_hash(:replacements) if @config[:replacements].is_a?(Array)
|
167
|
-
rescue Psych::SyntaxError => e
|
168
|
-
raise Errors::ConfigError.new "Parse error in configuration file:\n#{e.message}"
|
169
|
-
end
|
170
|
-
|
171
154
|
##
|
172
155
|
## Execute a shell command and return a Boolean success response
|
173
156
|
##
|
@@ -189,58 +172,6 @@ module Planter
|
|
189
172
|
|
190
173
|
private
|
191
174
|
|
192
|
-
##
|
193
|
-
## Load a template-specific configuration
|
194
|
-
##
|
195
|
-
## @return [Hash] updated config object
|
196
|
-
##
|
197
|
-
## @api private
|
198
|
-
##
|
199
|
-
def load_template_config
|
200
|
-
base_dir = File.join(Planter.base_dir, 'templates', @template)
|
201
|
-
config = File.join(base_dir, '_planter.yml')
|
202
|
-
|
203
|
-
unless File.exist?(config)
|
204
|
-
default_config = {
|
205
|
-
variables: [
|
206
|
-
key: 'var_key',
|
207
|
-
prompt: 'CLI Prompt',
|
208
|
-
type: '[string, float, integer, number, date]',
|
209
|
-
value: '(optional, force value, can include variables. Empty to prompt. For date type: today, now, etc.)',
|
210
|
-
default: '(optional default value, leave empty or remove key for no default)',
|
211
|
-
min: '(optional, for number type set a minimum value)',
|
212
|
-
max: '(optional, for number type set a maximum value)'
|
213
|
-
],
|
214
|
-
git_init: false,
|
215
|
-
files: {
|
216
|
-
'*.tmp' => 'ignore',
|
217
|
-
'*.bak' => 'ignore',
|
218
|
-
'.DS_Store' => 'ignore'
|
219
|
-
}
|
220
|
-
}
|
221
|
-
FileUtils.mkdir_p(base_dir)
|
222
|
-
File.open(config, 'w') { |f| f.puts(YAML.dump(default_config.stringify_keys)) }
|
223
|
-
notify("New configuration written to #{config}, please edit.", :warn)
|
224
|
-
Process.exit 0
|
225
|
-
end
|
226
|
-
@config = @config.deep_merge(YAML.load(IO.read(config)).symbolize_keys)
|
227
|
-
end
|
228
|
-
|
229
|
-
##
|
230
|
-
## Convert an errant array to a hash
|
231
|
-
##
|
232
|
-
## @param key [Symbol] The key in @config to convert
|
233
|
-
##
|
234
|
-
## @api private
|
235
|
-
##
|
236
|
-
def config_array_to_hash(key)
|
237
|
-
files = {}
|
238
|
-
@config[key].each do |k, v|
|
239
|
-
files[k] = v
|
240
|
-
end
|
241
|
-
@config[key] = files
|
242
|
-
end
|
243
|
-
|
244
175
|
##
|
245
176
|
## Process :files in config into regex pattern/operator pairs
|
246
177
|
##
|
@@ -250,9 +181,9 @@ module Planter
|
|
250
181
|
##
|
251
182
|
def process_patterns
|
252
183
|
patterns = {}
|
253
|
-
@config
|
184
|
+
@config.files.each do |file, oper|
|
254
185
|
pattern = Regexp.new(".*?/#{file.to_s.sub(%r{^/}, '').to_rx}$")
|
255
|
-
operator = oper.
|
186
|
+
operator = oper.apply_operator_logic(Planter.variables)
|
256
187
|
patterns[pattern] = operator
|
257
188
|
end
|
258
189
|
patterns
|
@@ -112,7 +112,7 @@ module TTY
|
|
112
112
|
pattern_or_spinner
|
113
113
|
else
|
114
114
|
raise ArgumentError, "Expected a pattern or spinner, " \
|
115
|
-
|
115
|
+
"got: #{pattern_or_spinner.class}"
|
116
116
|
end
|
117
117
|
end
|
118
118
|
|
@@ -259,8 +259,8 @@ module TTY
|
|
259
259
|
# @api public
|
260
260
|
def on(key, &callback)
|
261
261
|
unless @callbacks.key?(key)
|
262
|
-
raise ArgumentError, "The event #{key} does not exist.
|
263
|
-
"
|
262
|
+
raise ArgumentError, "The event #{key} does not exist. " \
|
263
|
+
"Use :spin, :success, :error, or :done instead"
|
264
264
|
end
|
265
265
|
@callbacks[key] << callback
|
266
266
|
self
|
data/planter-cli.gemspec
CHANGED
data/spec/cli_spec.rb
CHANGED
@@ -17,11 +17,21 @@ describe 'CLI' do
|
|
17
17
|
|
18
18
|
it 'displays help message' do
|
19
19
|
output, stderr, status = planter('--help')
|
20
|
-
expect(output).
|
20
|
+
expect(output).to match(/Usage: plant \[options\] TEMPLATE/)
|
21
|
+
end
|
22
|
+
|
23
|
+
it 'displays variables for a template' do
|
24
|
+
output, stderr, status = planter('--help', 'test')
|
25
|
+
expect(output).to match(/CLI Prompt/)
|
21
26
|
end
|
22
27
|
|
23
28
|
it 'plants a new project' do
|
24
|
-
output, stderr, status = planter(
|
29
|
+
output, stderr, status = planter("--in=#{TEST_DIR}", 'test')
|
25
30
|
expect(File.exist?(File.join(TEST_DIR, 'bollocks_and_beans.rtf'))).to be true
|
26
31
|
end
|
32
|
+
|
33
|
+
it 'plants a new file with a script' do
|
34
|
+
output, stderr, status = planter("--in=#{TEST_DIR}", 'test')
|
35
|
+
expect(File.exist?(File.join(TEST_DIR, 'planted_by_script.txt'))).to be true
|
36
|
+
end
|
27
37
|
end
|
@@ -7,8 +7,8 @@ describe Planter::FileList do
|
|
7
7
|
it 'initializes with an empty list' do
|
8
8
|
Planter.base_dir = File.expand_path('spec')
|
9
9
|
Planter.variables = { project: 'Untitled', script: 'Script', title: 'Title' }
|
10
|
-
Planter.
|
11
|
-
filelist =
|
10
|
+
Planter.template = 'test'
|
11
|
+
filelist = described_class.new
|
12
12
|
expect(filelist.files).not_to eq([])
|
13
13
|
end
|
14
14
|
end
|
@@ -0,0 +1,71 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'spec_helper'
|
4
|
+
|
5
|
+
describe Planter::Prompt::Question do
|
6
|
+
let(:question) do
|
7
|
+
question = {
|
8
|
+
key: 'test',
|
9
|
+
prompt: 'CLI Prompt',
|
10
|
+
type: :string,
|
11
|
+
default: 'default',
|
12
|
+
value: nil
|
13
|
+
}
|
14
|
+
end
|
15
|
+
|
16
|
+
before do
|
17
|
+
Planter.accept_defaults = true
|
18
|
+
end
|
19
|
+
|
20
|
+
describe '#initialize' do
|
21
|
+
it 'initializes a new question object' do
|
22
|
+
q = described_class.new(question)
|
23
|
+
expect(q).to be_a described_class
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
describe '#ask' do
|
28
|
+
it 'asks a question' do
|
29
|
+
q = described_class.new(question)
|
30
|
+
expect(q.ask).to eq('default')
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
describe "#ask with date type" do
|
35
|
+
it 'asks a date question' do
|
36
|
+
question[:type] = :date
|
37
|
+
question[:value] = 'today'
|
38
|
+
q = described_class.new(question)
|
39
|
+
expect(q.ask).to eq(Date.today.strftime('%Y-%m-%d'))
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
describe "#ask with date type and inline date format" do
|
44
|
+
it 'asks a date question' do
|
45
|
+
question[:type] = :date
|
46
|
+
question[:value] = "today '%Y'"
|
47
|
+
q = described_class.new(question)
|
48
|
+
expect(q.ask).to eq(Date.today.strftime('%Y'))
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
describe "#ask with date type and date format config" do
|
53
|
+
it 'asks a date question' do
|
54
|
+
question[:type] = :date
|
55
|
+
question[:date_format] = '%Y'
|
56
|
+
question[:value] = "today"
|
57
|
+
q = described_class.new(question)
|
58
|
+
expect(q.ask).to eq(Date.today.strftime('%Y'))
|
59
|
+
end
|
60
|
+
end
|
61
|
+
|
62
|
+
describe "#ask with choices" do
|
63
|
+
it 'asks a question with choices' do
|
64
|
+
question[:type] = :string
|
65
|
+
question[:choices] = %w[(o)ne (t)wo t(h)ree]
|
66
|
+
question[:default] = 'one'
|
67
|
+
q = described_class.new(question)
|
68
|
+
expect(q.ask).to eq('one')
|
69
|
+
end
|
70
|
+
end
|
71
|
+
end
|
data/spec/planter/script_spec.rb
CHANGED
@@ -12,6 +12,7 @@ describe Planter::Script do
|
|
12
12
|
let(:base_script_path) { File.join(Planter.base_dir, 'scripts', script_name) }
|
13
13
|
|
14
14
|
before do
|
15
|
+
ENV['PLANTER_RSPEC'] = 'true'
|
15
16
|
Planter.base_dir = File.expand_path('spec')
|
16
17
|
allow(File).to receive(:exist?).and_call_original
|
17
18
|
allow(File).to receive(:directory?).and_call_original
|
@@ -30,14 +31,14 @@ describe Planter::Script do
|
|
30
31
|
allow(File).to receive(:exist?).with(script_path).and_return(false)
|
31
32
|
expect do
|
32
33
|
Planter::Script.new(template_dir, output_dir, script_name)
|
33
|
-
end.to raise_error(
|
34
|
+
end.to raise_error(SystemExit)
|
34
35
|
end
|
35
36
|
|
36
37
|
it 'raises an error if output directory is not found' do
|
37
38
|
allow(File).to receive(:directory?).with(output_dir).and_return(false)
|
38
39
|
expect do
|
39
40
|
Planter::Script.new(template_dir, output_dir, script_name)
|
40
|
-
end.to raise_error(
|
41
|
+
end.to raise_error(SystemExit)
|
41
42
|
end
|
42
43
|
end
|
43
44
|
|
@@ -60,7 +61,7 @@ describe Planter::Script do
|
|
60
61
|
expect do
|
61
62
|
script = Planter::Script.new(template_dir, output_dir, script_name)
|
62
63
|
script.find_script(template_dir, script_name)
|
63
|
-
end.to raise_error(
|
64
|
+
end.to raise_error(SystemExit)
|
64
65
|
end
|
65
66
|
end
|
66
67
|
|
@@ -74,7 +75,7 @@ describe Planter::Script do
|
|
74
75
|
script = Planter::Script.new(template_dir, output_dir, script_name_fail)
|
75
76
|
expect do
|
76
77
|
script.run
|
77
|
-
end.to raise_error(
|
78
|
+
end.to raise_error(SystemExit)
|
78
79
|
end
|
79
80
|
end
|
80
81
|
end
|
data/spec/planter/string_spec.rb
CHANGED
@@ -141,6 +141,32 @@ describe ::String do
|
|
141
141
|
end
|
142
142
|
end
|
143
143
|
|
144
|
+
describe '.apply_operator_logic' do
|
145
|
+
it 'applies logic' do
|
146
|
+
template = 'copy if language == ruby'
|
147
|
+
operators = { language: 'ruby' }
|
148
|
+
expect(template.apply_operator_logic(operators)).to eq :copy
|
149
|
+
end
|
150
|
+
|
151
|
+
it 'applies default' do
|
152
|
+
template = 'copy if language == perl'
|
153
|
+
operators = { language: 'ruby' }
|
154
|
+
expect(template.apply_operator_logic(operators)).to eq :ignore
|
155
|
+
end
|
156
|
+
|
157
|
+
it 'handles no logic' do
|
158
|
+
template = 'copy'
|
159
|
+
operators = {}
|
160
|
+
expect(template.apply_operator_logic(operators)).to eq :copy
|
161
|
+
end
|
162
|
+
|
163
|
+
it 'handles if then' do
|
164
|
+
template = 'if language == ruby: copy; else: ignore'
|
165
|
+
operators = { language: 'perl' }
|
166
|
+
expect(template.apply_operator_logic(operators)).to eq :ignore
|
167
|
+
end
|
168
|
+
end
|
169
|
+
|
144
170
|
describe '.apply_regexes' do
|
145
171
|
it 'applies a single regex replacement' do
|
146
172
|
template = 'Hello, World!'
|
data/spec/spec_helper.rb
CHANGED
@@ -29,10 +29,11 @@ require 'open3'
|
|
29
29
|
require 'time'
|
30
30
|
|
31
31
|
module PlanterHelpers
|
32
|
-
PLANTER_EXEC = File.join(File.dirname(__FILE__), '..', '
|
32
|
+
PLANTER_EXEC = File.join(File.dirname(__FILE__), '..', 'bin', 'plant')
|
33
33
|
|
34
34
|
def planter_with_env(env, *args, stdin: nil)
|
35
|
-
pread(env, 'bundle', 'exec', PLANTER_EXEC, "--base-dir=#{File.dirname(__FILE__)}", *args,
|
35
|
+
pread(env, 'bundle', 'exec', PLANTER_EXEC, "--base-dir=#{File.dirname(__FILE__)}", "--defaults", *args,
|
36
|
+
stdin: stdin)
|
36
37
|
end
|
37
38
|
|
38
39
|
def pread(env, *cmd, stdin: nil)
|