planter-cli 0.0.3
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 +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
|