planter-cli 0.0.3
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.editorconfig +9 -0
- data/.gitignore +44 -0
- data/.irbrc +12 -0
- data/.rspec +2 -0
- data/.rubocop.yml +78 -0
- data/.travis.yml +7 -0
- data/.yardopts +8 -0
- data/CHANGELOG.md +45 -0
- data/Gemfile +6 -0
- data/Guardfile +25 -0
- data/LICENSE.txt +20 -0
- data/README.md +208 -0
- data/Rakefile +132 -0
- data/bin/plant +106 -0
- data/debug.log +0 -0
- data/docker/Dockerfile +12 -0
- data/docker/Dockerfile-2.6 +12 -0
- data/docker/Dockerfile-2.7 +12 -0
- data/docker/Dockerfile-3.0 +11 -0
- data/docker/bash_profile +15 -0
- data/docker/inputrc +57 -0
- data/lib/.rubocop.yml +1 -0
- data/lib/planter/array.rb +28 -0
- data/lib/planter/color.rb +370 -0
- data/lib/planter/errors.rb +59 -0
- data/lib/planter/file.rb +11 -0
- data/lib/planter/fileentry.rb +87 -0
- data/lib/planter/filelist.rb +144 -0
- data/lib/planter/hash.rb +103 -0
- data/lib/planter/plant.rb +228 -0
- data/lib/planter/prompt.rb +352 -0
- data/lib/planter/script.rb +59 -0
- data/lib/planter/string.rb +383 -0
- data/lib/planter/symbol.rb +28 -0
- data/lib/planter/version.rb +7 -0
- data/lib/planter.rb +222 -0
- data/planter-cli.gemspec +48 -0
- data/scripts/deploy.rb +97 -0
- data/scripts/runtests.sh +5 -0
- data/spec/.rubocop.yml +4 -0
- data/spec/planter/plant_spec.rb +14 -0
- data/spec/planter/string_spec.rb +20 -0
- data/spec/spec_helper.rb +20 -0
- data/src/_README.md +214 -0
- metadata +400 -0
@@ -0,0 +1,383 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Planter
|
4
|
+
## String helpers
|
5
|
+
class ::String
|
6
|
+
##
|
7
|
+
## Convert string to snake-cased variable name
|
8
|
+
##
|
9
|
+
## @example "Planter String" #=> planter_string
|
10
|
+
## @example "Planter-String" #=> planter_string
|
11
|
+
##
|
12
|
+
## @return [Symbol] string as variable key
|
13
|
+
##
|
14
|
+
def to_var
|
15
|
+
snake_case.to_sym
|
16
|
+
end
|
17
|
+
|
18
|
+
##
|
19
|
+
## Convert a slug into a class name
|
20
|
+
##
|
21
|
+
## @example "planter-string".to_class_name #=> PlanterString
|
22
|
+
##
|
23
|
+
## @return Class name representation of the object.
|
24
|
+
##
|
25
|
+
def to_class_name
|
26
|
+
strip.no_ext.split(/[-_ ]/).map(&:capitalize).join('').gsub(/[^a-z0-9]/i, '')
|
27
|
+
end
|
28
|
+
|
29
|
+
##
|
30
|
+
## Convert a class name to a file slug
|
31
|
+
##
|
32
|
+
## @example "PlanterString".to_class_name #=> planter-string
|
33
|
+
##
|
34
|
+
## @return Filename representation of the object.
|
35
|
+
##
|
36
|
+
def to_slug
|
37
|
+
strip.split(/(?=[A-Z ])/).map(&:downcase).join('-')
|
38
|
+
.gsub(/[^a-z0-9_-]/i, &:slugify)
|
39
|
+
.gsub(/-+/, '-')
|
40
|
+
.gsub(/(^-|-$)/, '')
|
41
|
+
end
|
42
|
+
|
43
|
+
## Convert some characters to text
|
44
|
+
##
|
45
|
+
## @return [String] slugified character or empty string
|
46
|
+
##
|
47
|
+
def slugify
|
48
|
+
char = to_s
|
49
|
+
slug_version = {
|
50
|
+
'.' => 'dot',
|
51
|
+
'/' => 'slash',
|
52
|
+
':' => 'colon',
|
53
|
+
',' => 'comma',
|
54
|
+
'!' => 'bang',
|
55
|
+
'#' => 'hash'
|
56
|
+
}
|
57
|
+
slug_version[char] ? "-#{slug_version[char]}-" : ''
|
58
|
+
end
|
59
|
+
|
60
|
+
##
|
61
|
+
## Convert a string to snake case, handling spaces or CamelCasing
|
62
|
+
##
|
63
|
+
## @example "ClassName".snake_case #=> class-name
|
64
|
+
## @example "A title string".snake_case #=> a-title-string
|
65
|
+
##
|
66
|
+
## @return [String] Snake-cased version of string
|
67
|
+
##
|
68
|
+
def snake_case
|
69
|
+
strip.gsub(/\S[A-Z]/) { |pair| pair.split('').join('_') }
|
70
|
+
.gsub(/[ -]+/, '_')
|
71
|
+
.gsub(/[^a-z0-9_]+/i, '')
|
72
|
+
.gsub(/_+/, '_')
|
73
|
+
.gsub(/(^_|_$)/, '').downcase
|
74
|
+
end
|
75
|
+
|
76
|
+
##
|
77
|
+
## Convert a string to camel case, handling spaces or snake_casing
|
78
|
+
##
|
79
|
+
## @example "class_name".camel_case #=> className
|
80
|
+
## @example "A title string".camel_case #=> aTitleString
|
81
|
+
##
|
82
|
+
## @return [String] Snake-cased version of string
|
83
|
+
##
|
84
|
+
def camel_case
|
85
|
+
strip.gsub(/[ _]+(\S)/) { Regexp.last_match(1).upcase }
|
86
|
+
.gsub(/[^a-z0-9]+/i, '')
|
87
|
+
.sub(/^(\w)/) { Regexp.last_match(1).downcase }
|
88
|
+
end
|
89
|
+
|
90
|
+
##
|
91
|
+
## Capitalize the first character after a word border. Prevents downcasing
|
92
|
+
## intercaps.
|
93
|
+
##
|
94
|
+
## @example "a title string".title_case #=> A Title String
|
95
|
+
##
|
96
|
+
## @return [String] title cased string
|
97
|
+
##
|
98
|
+
def title_case
|
99
|
+
gsub(/\b(\w)/) { Regexp.last_match(1).upcase }
|
100
|
+
end
|
101
|
+
|
102
|
+
##
|
103
|
+
## Apply key/value substitutions to a string. Variables are represented as
|
104
|
+
## %%key%%, and the hash passed to the function is { key: value }
|
105
|
+
##
|
106
|
+
## @param last_only [Boolean] Only replace the last instance of %%key%%
|
107
|
+
##
|
108
|
+
## @return [String] string with variables substituted
|
109
|
+
##
|
110
|
+
def apply_variables(last_only: false)
|
111
|
+
content = dup.clean_encode
|
112
|
+
mod_rx = '(?<mod>
|
113
|
+
(?::
|
114
|
+
(
|
115
|
+
l(?:ow(?:er)?)?)?|
|
116
|
+
u(?:p(?:per)?)?|
|
117
|
+
c(?:ap(?:ital(?:ize)?)?)?|
|
118
|
+
t(?:itle)?|
|
119
|
+
snake|camel|slug|
|
120
|
+
f(?:ile(?:name)?
|
121
|
+
)?
|
122
|
+
)*
|
123
|
+
)'
|
124
|
+
|
125
|
+
Planter.variables.each do |k, v|
|
126
|
+
if last_only
|
127
|
+
pattern = "%%#{k.to_var}"
|
128
|
+
content = content.reverse.sub(/(?mix)%%(?:(?<mod>.*?):)*(?<key>#{pattern.reverse})/) do
|
129
|
+
m = Regexp.last_match
|
130
|
+
if m['mod']
|
131
|
+
m['mod'].reverse.split(/:/).each do |mod|
|
132
|
+
v = v.apply_mod(mod.normalize_mod)
|
133
|
+
end
|
134
|
+
end
|
135
|
+
|
136
|
+
v.reverse
|
137
|
+
end.reverse
|
138
|
+
else
|
139
|
+
rx = /(?mix)%%(?<key>#{k.to_var})#{mod_rx}%%/
|
140
|
+
|
141
|
+
content.gsub!(rx) do
|
142
|
+
m = Regexp.last_match
|
143
|
+
|
144
|
+
mods = m['mod']&.split(/:/)
|
145
|
+
mods&.each do |mod|
|
146
|
+
v = v.apply_mod(mod.normalize_mod)
|
147
|
+
end
|
148
|
+
v
|
149
|
+
end
|
150
|
+
end
|
151
|
+
end
|
152
|
+
|
153
|
+
content
|
154
|
+
end
|
155
|
+
|
156
|
+
##
|
157
|
+
## Apply regex replacements from @config[:replacements]
|
158
|
+
##
|
159
|
+
## @return [String] string with regexes applied
|
160
|
+
##
|
161
|
+
def apply_regexes
|
162
|
+
content = dup.clean_encode
|
163
|
+
return self unless Planter.config.key?(:replacements)
|
164
|
+
|
165
|
+
Planter.config[:replacements].stringify_keys.each do |pattern, replacement|
|
166
|
+
pattern = Regexp.new(pattern) unless pattern.is_a?(Regexp)
|
167
|
+
replacement = replacement.gsub(/\$(\d)/, '\\\1').apply_variables
|
168
|
+
content.gsub!(pattern, replacement)
|
169
|
+
end
|
170
|
+
content
|
171
|
+
end
|
172
|
+
|
173
|
+
##
|
174
|
+
## Destructive version of #apply_variables
|
175
|
+
##
|
176
|
+
## @param last_only [Boolean] Only replace the last instance of %%key%%
|
177
|
+
##
|
178
|
+
## @return [String] string with variables substituted
|
179
|
+
##
|
180
|
+
def apply_variables!(last_only: false)
|
181
|
+
replace apply_variables(last_only: last_only)
|
182
|
+
end
|
183
|
+
|
184
|
+
##
|
185
|
+
## Destructive version of #apply_regexes
|
186
|
+
##
|
187
|
+
## @return [String] string with variables substituted
|
188
|
+
##
|
189
|
+
def apply_regexes!
|
190
|
+
replace apply_regexes
|
191
|
+
end
|
192
|
+
|
193
|
+
##
|
194
|
+
## Remove any file extension
|
195
|
+
##
|
196
|
+
## @example "planter-string.rb".no_ext #=> planter-string
|
197
|
+
##
|
198
|
+
## @return [String] string with no extension
|
199
|
+
##
|
200
|
+
def no_ext
|
201
|
+
sub(/\.\w{2,4}$/, '')
|
202
|
+
end
|
203
|
+
|
204
|
+
##
|
205
|
+
## Add an extension to the string, replacing existing extension if needed
|
206
|
+
##
|
207
|
+
## @example "planter-string".ext('rb') #=> planter-string.rb
|
208
|
+
##
|
209
|
+
## @example "planter-string.rb".ext('erb') #=> planter-string.erb
|
210
|
+
##
|
211
|
+
## @param extension [String] The extension to add
|
212
|
+
##
|
213
|
+
## @return [String] string with new extension
|
214
|
+
##
|
215
|
+
def ext(extension)
|
216
|
+
extension = extension.sub(/^\./, '')
|
217
|
+
sub(/(\.\w+)?$/, ".#{extension}")
|
218
|
+
end
|
219
|
+
|
220
|
+
##
|
221
|
+
## Apply a modification to string
|
222
|
+
##
|
223
|
+
## @param mod [Symbol] The modifier to apply
|
224
|
+
##
|
225
|
+
def apply_mod(mod)
|
226
|
+
case mod
|
227
|
+
when :slug
|
228
|
+
to_slug
|
229
|
+
when :title_case
|
230
|
+
title_case
|
231
|
+
when :lowercase
|
232
|
+
downcase
|
233
|
+
when :uppercase
|
234
|
+
upcase
|
235
|
+
when :snake_case
|
236
|
+
snake_case
|
237
|
+
when :camel_case
|
238
|
+
camel_case
|
239
|
+
else
|
240
|
+
self
|
241
|
+
end
|
242
|
+
end
|
243
|
+
|
244
|
+
##
|
245
|
+
## Convert mod string to symbol
|
246
|
+
##
|
247
|
+
## @example "snake" => :snake_case
|
248
|
+
## @example "cap" => :title_case
|
249
|
+
##
|
250
|
+
## @return [Symbol] symbolized modifier
|
251
|
+
##
|
252
|
+
def normalize_mod
|
253
|
+
case self
|
254
|
+
when /^(f|slug)/
|
255
|
+
:slug
|
256
|
+
when /^cam/
|
257
|
+
:camel_case
|
258
|
+
when /^s/
|
259
|
+
:snake_case
|
260
|
+
when /^u/
|
261
|
+
:uppercase
|
262
|
+
when /^l/
|
263
|
+
:lowercase
|
264
|
+
when /^[ct]/
|
265
|
+
:title_case
|
266
|
+
end
|
267
|
+
end
|
268
|
+
|
269
|
+
##
|
270
|
+
## Convert operator string to symbol
|
271
|
+
##
|
272
|
+
## @example "ignore" => :ignore
|
273
|
+
## @example "m" => :merge
|
274
|
+
##
|
275
|
+
## @return [Symbol] symbolized operator
|
276
|
+
##
|
277
|
+
def normalize_operator
|
278
|
+
case self
|
279
|
+
# merge or append
|
280
|
+
when /^i/
|
281
|
+
:ignore
|
282
|
+
when /^(m|ap)/
|
283
|
+
:merge
|
284
|
+
# ask or optional
|
285
|
+
when /^(a|op)/
|
286
|
+
:ask
|
287
|
+
# overwrite
|
288
|
+
when /^o/
|
289
|
+
:overwrite
|
290
|
+
else
|
291
|
+
:copy
|
292
|
+
end
|
293
|
+
end
|
294
|
+
|
295
|
+
##
|
296
|
+
## Convert type string to symbol
|
297
|
+
##
|
298
|
+
## @example "string".coerce #=> :string
|
299
|
+
## @example "date".coerce #=> :date
|
300
|
+
## @example "num".coerce #=> :number
|
301
|
+
##
|
302
|
+
## @return [Symbol] type symbol
|
303
|
+
##
|
304
|
+
def normalize_type
|
305
|
+
case self
|
306
|
+
# date
|
307
|
+
when /^da/
|
308
|
+
:date
|
309
|
+
# integer
|
310
|
+
when /^i/
|
311
|
+
:integer
|
312
|
+
# number or float
|
313
|
+
when /^[nf]/
|
314
|
+
:float
|
315
|
+
# multiline or paragraph
|
316
|
+
when /^(mu|p)/
|
317
|
+
:multiline
|
318
|
+
# class
|
319
|
+
when /^c/
|
320
|
+
:class
|
321
|
+
# module
|
322
|
+
when /^m/
|
323
|
+
:module
|
324
|
+
# string
|
325
|
+
else
|
326
|
+
:string
|
327
|
+
end
|
328
|
+
end
|
329
|
+
|
330
|
+
##
|
331
|
+
## Coerce a variable to a type
|
332
|
+
##
|
333
|
+
## @param type [Symbol] The type
|
334
|
+
##
|
335
|
+
##
|
336
|
+
def coerce(type)
|
337
|
+
case type
|
338
|
+
when :date
|
339
|
+
Chronic.parse(self)
|
340
|
+
when :integer || :number
|
341
|
+
to_i
|
342
|
+
when :float
|
343
|
+
to_f
|
344
|
+
when :class || :module
|
345
|
+
to_class_name
|
346
|
+
else
|
347
|
+
to_s
|
348
|
+
end
|
349
|
+
end
|
350
|
+
|
351
|
+
##
|
352
|
+
## Get a clean UTF-8 string by forcing an ISO encoding and then re-encoding
|
353
|
+
##
|
354
|
+
## @return [String] UTF-8 string
|
355
|
+
##
|
356
|
+
def clean_encode
|
357
|
+
force_encoding('ISO-8859-1').encode('utf-8', replace: nil)
|
358
|
+
end
|
359
|
+
|
360
|
+
##
|
361
|
+
## Destructive version of #clean_encode
|
362
|
+
##
|
363
|
+
## @return [String] UTF-8 string, in place
|
364
|
+
##
|
365
|
+
def clean_encode!
|
366
|
+
replace clean_encode
|
367
|
+
end
|
368
|
+
|
369
|
+
##
|
370
|
+
## Highlight characters in parenthesis, with special color for default if
|
371
|
+
## provided. Output is color templated string, unprocessed.
|
372
|
+
##
|
373
|
+
## @param default [String] The default
|
374
|
+
##
|
375
|
+
def highlight_character(default: nil)
|
376
|
+
if default
|
377
|
+
gsub(/\((#{default})\)/, '{dw}({xbc}\1{dw}){xw}').gsub(/\((.)\)/, '{dw}({xbw}\1{dw}){xw}')
|
378
|
+
else
|
379
|
+
gsub(/\((.)\)/, '{dw}({xbw}\1{dw}){xw}')
|
380
|
+
end
|
381
|
+
end
|
382
|
+
end
|
383
|
+
end
|
@@ -0,0 +1,28 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# Symbol helpers
|
4
|
+
class ::Symbol
|
5
|
+
# Handle calling to_var on a Symbol
|
6
|
+
#
|
7
|
+
# @return [Symbol] same symbol, normalized if needed
|
8
|
+
#
|
9
|
+
def to_var
|
10
|
+
to_s.to_var
|
11
|
+
end
|
12
|
+
|
13
|
+
# Handle calling normalize_type on a Symbol
|
14
|
+
#
|
15
|
+
# @return [Symbol] same symbol, normalized if needed
|
16
|
+
#
|
17
|
+
def normalize_type
|
18
|
+
to_s.normalize_type
|
19
|
+
end
|
20
|
+
|
21
|
+
# Handle calling normalize_operator on a Symbol
|
22
|
+
#
|
23
|
+
# @return [Symbol] same symbol, normalized if needed
|
24
|
+
#
|
25
|
+
def normalize_operator
|
26
|
+
to_s.normalize_operator
|
27
|
+
end
|
28
|
+
end
|
data/lib/planter.rb
ADDED
@@ -0,0 +1,222 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'time'
|
4
|
+
require 'shellwords'
|
5
|
+
require 'json'
|
6
|
+
require 'yaml'
|
7
|
+
require 'fileutils'
|
8
|
+
require 'open3'
|
9
|
+
|
10
|
+
require 'chronic'
|
11
|
+
require 'tty-reader'
|
12
|
+
require 'tty-screen'
|
13
|
+
require 'tty-spinner'
|
14
|
+
require 'tty-which'
|
15
|
+
|
16
|
+
require_relative 'planter/version'
|
17
|
+
require_relative 'planter/hash'
|
18
|
+
require_relative 'planter/array'
|
19
|
+
require_relative 'planter/symbol'
|
20
|
+
require_relative 'planter/file'
|
21
|
+
require_relative 'planter/color'
|
22
|
+
require_relative 'planter/errors'
|
23
|
+
require_relative 'planter/prompt'
|
24
|
+
require_relative 'planter/string'
|
25
|
+
require_relative 'planter/filelist'
|
26
|
+
require_relative 'planter/fileentry'
|
27
|
+
require_relative 'planter/plant'
|
28
|
+
|
29
|
+
# Main Journal module
|
30
|
+
module Planter
|
31
|
+
# Base directory for templates
|
32
|
+
BASE_DIR = File.expand_path('~/.config/planter/')
|
33
|
+
|
34
|
+
class << self
|
35
|
+
include Color
|
36
|
+
include Prompt
|
37
|
+
|
38
|
+
## Debug mode
|
39
|
+
attr_accessor :debug
|
40
|
+
|
41
|
+
## Target
|
42
|
+
attr_accessor :target
|
43
|
+
|
44
|
+
## Overwrite files
|
45
|
+
attr_accessor :overwrite
|
46
|
+
|
47
|
+
## Current date
|
48
|
+
attr_accessor :date
|
49
|
+
|
50
|
+
## Template name
|
51
|
+
attr_accessor :template
|
52
|
+
|
53
|
+
## Config Hash
|
54
|
+
attr_reader :config
|
55
|
+
|
56
|
+
## Variable key/values
|
57
|
+
attr_accessor :variables
|
58
|
+
|
59
|
+
## Filter patterns
|
60
|
+
attr_writer :patterns
|
61
|
+
|
62
|
+
## Accept all defaults
|
63
|
+
attr_accessor :accept_defaults
|
64
|
+
|
65
|
+
##
|
66
|
+
## Print a message on the command line
|
67
|
+
##
|
68
|
+
## @param string [String] The message string
|
69
|
+
## @param notification_type [Symbol] The notification type (:debug, :error, :warn, :info)
|
70
|
+
## @param exit_code [Integer] If provided, exit with code after delivering message
|
71
|
+
##
|
72
|
+
def notify(string, notification_type = :info, exit_code: nil)
|
73
|
+
case notification_type
|
74
|
+
when :debug
|
75
|
+
warn "\n{dw}#{string}{x}".x if @debug
|
76
|
+
when :error
|
77
|
+
warn "{br}#{string}{x}".x
|
78
|
+
when :warn
|
79
|
+
warn "{by}#{string}{x}".x
|
80
|
+
else
|
81
|
+
warn "{bw}#{string}{x}".x
|
82
|
+
end
|
83
|
+
|
84
|
+
Process.exit exit_code unless exit_code.nil?
|
85
|
+
end
|
86
|
+
|
87
|
+
##
|
88
|
+
## Global progress indicator reader, will init if nil
|
89
|
+
##
|
90
|
+
## @return [TTY::Spinner] Spinner object
|
91
|
+
##
|
92
|
+
def spinner
|
93
|
+
@spinner ||= TTY::Spinner.new('{bw}[{by}:spinner{bw}] {w}:title'.x,
|
94
|
+
hide_cursor: true,
|
95
|
+
format: :dots,
|
96
|
+
success_mark: '{bg}✔{x}'.x,
|
97
|
+
error_mark: '{br}✖{x}'.x)
|
98
|
+
end
|
99
|
+
|
100
|
+
##
|
101
|
+
## Build a configuration from template name
|
102
|
+
##
|
103
|
+
## @param template [String] The template name
|
104
|
+
##
|
105
|
+
## @return [Hash] Configuration object
|
106
|
+
##
|
107
|
+
def config=(template)
|
108
|
+
Planter.spinner.update(title: 'Initializing configuration')
|
109
|
+
@template = template
|
110
|
+
Planter.variables ||= {}
|
111
|
+
FileUtils.mkdir_p(BASE_DIR) unless File.directory?(BASE_DIR)
|
112
|
+
base_config = File.join(BASE_DIR, 'config.yml')
|
113
|
+
|
114
|
+
unless File.exist?(base_config)
|
115
|
+
default_base_config = {
|
116
|
+
defaults: false,
|
117
|
+
git_init: false,
|
118
|
+
files: { '_planter.yml' => 'ignore' },
|
119
|
+
color: true
|
120
|
+
}
|
121
|
+
File.open(base_config, 'w') { |f| f.puts(YAML.dump(default_base_config.stringify_keys)) }
|
122
|
+
Planter.notify("New configuration written to #{config}, edit as needed.", :warn)
|
123
|
+
end
|
124
|
+
|
125
|
+
@config = YAML.load(IO.read(base_config)).symbolize_keys
|
126
|
+
|
127
|
+
base_dir = File.join(BASE_DIR, 'templates', @template)
|
128
|
+
unless File.directory?(base_dir)
|
129
|
+
notify("Template #{@template} does not exist", :error)
|
130
|
+
res = Prompt.yn('Create template directory', default_response: false)
|
131
|
+
|
132
|
+
raise Errors::InputError.new('Canceled') unless res
|
133
|
+
|
134
|
+
FileUtils.mkdir_p(base_dir)
|
135
|
+
end
|
136
|
+
|
137
|
+
load_template_config
|
138
|
+
|
139
|
+
config_array_to_hash(:files) if @config[:files].is_a?(Array)
|
140
|
+
config_array_to_hash(:replacements) if @config[:replacements].is_a?(Array)
|
141
|
+
rescue Psych::SyntaxError => e
|
142
|
+
raise Errors::ConfigError.new "Parse error in configuration file:\n#{e.message}"
|
143
|
+
end
|
144
|
+
|
145
|
+
##
|
146
|
+
## Load a template-specific configuration
|
147
|
+
##
|
148
|
+
## @return [Hash] updated config object
|
149
|
+
##
|
150
|
+
def load_template_config
|
151
|
+
base_dir = File.join(BASE_DIR, 'templates', @template)
|
152
|
+
config = File.join(base_dir, '_planter.yml')
|
153
|
+
|
154
|
+
unless File.exist?(config)
|
155
|
+
default_config = {
|
156
|
+
variables: [
|
157
|
+
key: 'var_key',
|
158
|
+
prompt: 'CLI Prompt',
|
159
|
+
type: '[string, float, integer, number, date]',
|
160
|
+
value: '(optional, for date type can be today, time, now, etc., empty to prompt)',
|
161
|
+
default: '(optional default value, leave empty or remove key for no default)',
|
162
|
+
min: '(optional, for number type set a minimum value)',
|
163
|
+
max: '(optional, for number type set a maximum value)'
|
164
|
+
],
|
165
|
+
git_init: false,
|
166
|
+
files: { '*.tmp' => 'ignore' }
|
167
|
+
}
|
168
|
+
File.open(config, 'w') { |f| f.puts(YAML.dump(default_config.stringify_keys)) }
|
169
|
+
puts "New configuration written to #{config}, please edit."
|
170
|
+
Process.exit 0
|
171
|
+
end
|
172
|
+
@config = @config.deep_merge(YAML.load(IO.read(config)).symbolize_keys)
|
173
|
+
end
|
174
|
+
|
175
|
+
##
|
176
|
+
## Convert an errant array to a hash
|
177
|
+
##
|
178
|
+
## @param key [Symbol] The key in @config to convert
|
179
|
+
##
|
180
|
+
def config_array_to_hash(key)
|
181
|
+
files = {}
|
182
|
+
@config[key].each do |k, v|
|
183
|
+
files[k] = v
|
184
|
+
end
|
185
|
+
@config[key] = files
|
186
|
+
end
|
187
|
+
|
188
|
+
##
|
189
|
+
## Patterns reader, file handling config
|
190
|
+
##
|
191
|
+
## @return [Hash] hash of file patterns
|
192
|
+
##
|
193
|
+
def patterns
|
194
|
+
@patterns ||= process_patterns
|
195
|
+
end
|
196
|
+
|
197
|
+
##
|
198
|
+
## Process :files in config into regex pattern/operator pairs
|
199
|
+
##
|
200
|
+
## @return [Hash] { regex => operator } hash
|
201
|
+
##
|
202
|
+
def process_patterns
|
203
|
+
patterns = {}
|
204
|
+
@config[:files].each do |file, oper|
|
205
|
+
pattern = Regexp.new(".*?/#{file.to_s.sub(%r{^/}, '').gsub(/\./, '\.').gsub(/\*/, '.*?').gsub(/\?/, '.')}$")
|
206
|
+
operator = oper.normalize_operator
|
207
|
+
patterns[pattern] = operator
|
208
|
+
end
|
209
|
+
patterns
|
210
|
+
end
|
211
|
+
|
212
|
+
##
|
213
|
+
## Execute a shell command and return a Boolean success response
|
214
|
+
##
|
215
|
+
## @param cmd [String] The shell command
|
216
|
+
##
|
217
|
+
def pass_fail(cmd)
|
218
|
+
_, status = Open3.capture2("#{cmd} &> /dev/null")
|
219
|
+
status.exitstatus.zero?
|
220
|
+
end
|
221
|
+
end
|
222
|
+
end
|
data/planter-cli.gemspec
ADDED
@@ -0,0 +1,48 @@
|
|
1
|
+
lib = File.expand_path(File.join('..', 'lib'), __FILE__)
|
2
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
3
|
+
require 'planter/version'
|
4
|
+
|
5
|
+
Gem::Specification.new do |spec|
|
6
|
+
spec.name = 'planter-cli'
|
7
|
+
spec.version = Planter::VERSION
|
8
|
+
spec.authors = ['Brett Terpstra']
|
9
|
+
spec.email = ['me@brettterpstra.com']
|
10
|
+
spec.description = 'Plant a file and directory structure'
|
11
|
+
spec.summary = 'Plant files and directories using templates'
|
12
|
+
spec.homepage = 'https://github.com/ttscoff/planter-cli'
|
13
|
+
spec.license = 'MIT'
|
14
|
+
|
15
|
+
spec.files = `git ls-files`.split($INPUT_RECORD_SEPARATOR)
|
16
|
+
spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
|
17
|
+
spec.test_files = spec.files.grep(%r{^(features|spec|test)/})
|
18
|
+
spec.require_paths = ['lib']
|
19
|
+
|
20
|
+
spec.required_ruby_version = '>= 2.6.0'
|
21
|
+
|
22
|
+
spec.add_development_dependency 'bump', '~> 0.10'
|
23
|
+
spec.add_development_dependency 'bundler', '~> 2.2'
|
24
|
+
spec.add_development_dependency 'rake', '~> 13.0'
|
25
|
+
|
26
|
+
spec.add_development_dependency 'guard', '~> 2.11'
|
27
|
+
spec.add_development_dependency 'guard-rspec', '~> 4.5'
|
28
|
+
spec.add_development_dependency 'guard-rubocop', '~> 1.2'
|
29
|
+
spec.add_development_dependency 'guard-yard', '~> 2.1'
|
30
|
+
|
31
|
+
spec.add_development_dependency 'cli-test', '~> 1.0'
|
32
|
+
spec.add_development_dependency 'fuubar', '~> 2.0'
|
33
|
+
spec.add_development_dependency 'rspec', '~> 3.13'
|
34
|
+
spec.add_development_dependency 'rubocop', '>= 1.50'
|
35
|
+
spec.add_development_dependency 'rubocop-rake', '>= 0.6.0'
|
36
|
+
spec.add_development_dependency 'rubocop-rspec', '>= 2.20.0'
|
37
|
+
spec.add_development_dependency 'simplecov', '~> 0.9'
|
38
|
+
|
39
|
+
spec.add_development_dependency 'github-markup', '~> 1.3'
|
40
|
+
spec.add_development_dependency 'redcarpet', '~> 3.2'
|
41
|
+
spec.add_development_dependency 'yard', '~> 0.9.5'
|
42
|
+
|
43
|
+
spec.add_runtime_dependency 'chronic', '~> 0.10'
|
44
|
+
spec.add_runtime_dependency 'tty-reader', '~> 0.9'
|
45
|
+
spec.add_runtime_dependency 'tty-screen', '~> 0.8'
|
46
|
+
spec.add_runtime_dependency 'tty-spinner', '~> 0.9'
|
47
|
+
spec.add_runtime_dependency 'tty-which', '~> 0.5'
|
48
|
+
end
|