planter-cli 0.0.3 → 3.0.1
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 +4 -1
- data/.rubocop.yml +5 -7
- data/CHANGELOG.md +21 -0
- data/README.md +28 -1
- data/Rakefile +54 -18
- data/bin/plant +6 -0
- data/docker/Dockerfile-2.6 +5 -5
- data/docker/Dockerfile-2.7 +3 -3
- data/docker/Dockerfile-3.0 +3 -3
- data/lib/planter/array.rb +51 -0
- data/lib/planter/color.rb +1 -1
- data/lib/planter/errors.rb +14 -0
- data/lib/planter/file.rb +87 -4
- data/lib/planter/fileentry.rb +5 -1
- data/lib/planter/filelist.rb +43 -7
- data/lib/planter/hash.rb +81 -84
- data/lib/planter/plant.rb +4 -10
- data/lib/planter/prompt.rb +6 -3
- data/lib/planter/script.rb +24 -12
- data/lib/planter/string.rb +134 -29
- data/lib/planter/tag.rb +54 -0
- data/lib/planter/version.rb +1 -1
- data/lib/planter.rb +60 -34
- data/planter-cli.gemspec +1 -0
- data/spec/config.yml +2 -0
- data/spec/planter/array_spec.rb +28 -0
- data/spec/planter/file_entry_spec.rb +40 -0
- data/spec/planter/file_spec.rb +19 -0
- data/spec/planter/filelist_spec.rb +15 -0
- data/spec/planter/hash_spec.rb +110 -0
- data/spec/planter/plant_spec.rb +1 -0
- data/spec/planter/script_spec.rb +80 -0
- data/spec/planter/string_spec.rb +215 -2
- data/spec/planter/symbol_spec.rb +23 -0
- data/spec/planter.yml +6 -0
- data/spec/planter_spec.rb +82 -0
- data/spec/scripts/test.sh +3 -0
- data/spec/scripts/test_fail.sh +3 -0
- data/spec/spec_helper.rb +8 -2
- data/spec/templates/test/%%project:snake%%.rtf +10 -0
- data/spec/templates/test/Rakefile +6 -0
- data/spec/templates/test/_planter.yml +12 -0
- data/spec/templates/test/_scripts/test.sh +3 -0
- data/spec/templates/test/_scripts/test_fail.sh +3 -0
- data/spec/templates/test/test.rb +5 -0
- data/spec/test_out/image.png +0 -0
- data/spec/test_out/test2.rb +5 -0
- data/src/_README.md +28 -1
- metadata +57 -2
data/lib/planter/hash.rb
CHANGED
@@ -1,103 +1,100 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
v.map(&:symbolize_keys)
|
15
|
-
else
|
16
|
-
v
|
17
|
-
end
|
18
|
-
end
|
19
|
-
end
|
20
|
-
|
21
|
-
##
|
22
|
-
## Turn all keys into symbols
|
23
|
-
##
|
24
|
-
## @return [Hash] hash with symbolized keys
|
25
|
-
##
|
26
|
-
def symbolize_keys
|
27
|
-
each_with_object({}) do |(k, v), hsh|
|
28
|
-
hsh[k.to_sym] = if v.is_a?(Hash)
|
29
|
-
v.symbolize_keys
|
30
|
-
elsif v.is_a?(Array)
|
31
|
-
v.map(&:symbolize_keys)
|
3
|
+
module Planter
|
4
|
+
## Hash helpers
|
5
|
+
class ::Hash
|
6
|
+
## Turn all keys into string
|
7
|
+
##
|
8
|
+
## @return [Hash] copy of the hash where all its keys are strings
|
9
|
+
##
|
10
|
+
def stringify_keys
|
11
|
+
each_with_object({}) do |(k, v), hsh|
|
12
|
+
hsh[k.to_s] = if v.is_a?(Hash) || v.is_a?(Array)
|
13
|
+
v.stringify_keys
|
32
14
|
else
|
33
15
|
v
|
34
16
|
end
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
## Destructive version of #stringify_keys
|
21
|
+
##
|
22
|
+
## @return [Hash] Hash with stringified keys
|
23
|
+
##
|
24
|
+
def stringify_keys!
|
25
|
+
replace stringify_keys
|
35
26
|
end
|
36
|
-
end
|
37
27
|
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
v1
|
51
|
-
else
|
52
|
-
v2
|
28
|
+
##
|
29
|
+
## Turn all keys into symbols
|
30
|
+
##
|
31
|
+
## @return [Hash] hash with symbolized keys
|
32
|
+
##
|
33
|
+
def symbolize_keys
|
34
|
+
each_with_object({}) do |(k, v), hsh|
|
35
|
+
hsh[k.to_sym] = if v.is_a?(Hash) || v.is_a?(Array)
|
36
|
+
v.symbolize_keys
|
37
|
+
else
|
38
|
+
v
|
39
|
+
end
|
53
40
|
end
|
54
41
|
end
|
55
|
-
merge(second.to_h, &merger)
|
56
|
-
end
|
57
42
|
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
each do |k, v|
|
66
|
-
chilled[k] = v.is_a?(Hash) ? v.deep_freeze : v.freeze
|
43
|
+
##
|
44
|
+
## Destructive version of #symbolize_keys
|
45
|
+
##
|
46
|
+
## @return [Hash] Hash with symbolized keys
|
47
|
+
##
|
48
|
+
def symbolize_keys!
|
49
|
+
replace symbolize_keys
|
67
50
|
end
|
68
51
|
|
69
|
-
|
70
|
-
|
52
|
+
##
|
53
|
+
## Deep merge a hash
|
54
|
+
##
|
55
|
+
## @param second [Hash] The hash to merge into self
|
56
|
+
##
|
57
|
+
def deep_merge(second)
|
58
|
+
merger = proc do |_, v1, v2|
|
59
|
+
if v1.is_a?(Hash) && v2.is_a?(Hash)
|
60
|
+
v1.merge(v2, &merger)
|
61
|
+
elsif v1.is_a?(Array) && v2.is_a?(Array)
|
62
|
+
v1 | v2
|
63
|
+
elsif [:undefined, nil, :nil].include?(v2)
|
64
|
+
v1
|
65
|
+
else
|
66
|
+
v2
|
67
|
+
end
|
68
|
+
end
|
69
|
+
merge(second.to_h, &merger)
|
70
|
+
end
|
71
71
|
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
72
|
+
##
|
73
|
+
## Freeze all values in a hash
|
74
|
+
##
|
75
|
+
## @return [Hash] Hash with all values frozen
|
76
|
+
##
|
77
|
+
def deep_freeze
|
78
|
+
chilled = {}
|
79
|
+
each do |k, v|
|
80
|
+
chilled[k] = v.is_a?(Hash) ? v.deep_freeze : v.freeze
|
81
|
+
end
|
80
82
|
|
81
|
-
|
82
|
-
## Unfreeze a hash and all nested values
|
83
|
-
##
|
84
|
-
## @return [Hash] unfrozen hash
|
85
|
-
##
|
86
|
-
def deep_thaw
|
87
|
-
chilled = {}
|
88
|
-
each do |k, v|
|
89
|
-
chilled[k] = v.is_a?(Hash) ? v.deep_thaw : v.dup
|
83
|
+
chilled.freeze
|
90
84
|
end
|
91
85
|
|
92
|
-
|
93
|
-
|
86
|
+
##
|
87
|
+
## Unfreeze a hash and all nested values
|
88
|
+
##
|
89
|
+
## @return [Hash] unfrozen hash
|
90
|
+
##
|
91
|
+
def deep_thaw
|
92
|
+
chilled = {}
|
93
|
+
each do |k, v|
|
94
|
+
chilled[k] = v.is_a?(Hash) ? v.deep_thaw : v.dup
|
95
|
+
end
|
94
96
|
|
95
|
-
|
96
|
-
|
97
|
-
##
|
98
|
-
## @return [Hash] unfrozen hash
|
99
|
-
##
|
100
|
-
def deep_thaw!
|
101
|
-
replace deep_thaw
|
97
|
+
chilled.dup
|
98
|
+
end
|
102
99
|
end
|
103
100
|
end
|
data/lib/planter/plant.rb
CHANGED
@@ -13,7 +13,7 @@ module Planter
|
|
13
13
|
Planter.variables = variables if variables.is_a?(Hash)
|
14
14
|
Planter.config = template if template
|
15
15
|
|
16
|
-
@basedir = File.join(Planter
|
16
|
+
@basedir = File.join(Planter.base_dir, 'templates', Planter.template)
|
17
17
|
@target = Planter.target || Dir.pwd
|
18
18
|
|
19
19
|
@git = Planter.config[:git_init] || false
|
@@ -68,6 +68,8 @@ module Planter
|
|
68
68
|
## @example Pass a GitHub-style repo path and get full url
|
69
69
|
## expand_repo("ttscoff/planter-cli") #=> https://github.com/ttscoff/planter-cli.git
|
70
70
|
##
|
71
|
+
## @param repo [String] The repo
|
72
|
+
##
|
71
73
|
## @return { description_of_the_return_value }
|
72
74
|
##
|
73
75
|
def expand_repo(repo)
|
@@ -177,15 +179,7 @@ module Planter
|
|
177
179
|
files = Dir.glob('**/*', File::FNM_DOTMATCH).reject { |f| File.directory?(f) || f =~ /^(\.git|config\.yml)/ }
|
178
180
|
|
179
181
|
files.each do |file|
|
180
|
-
|
181
|
-
case type.sub(/^#{Regexp.escape(file)}: /, '').split(/:/).first
|
182
|
-
when /Apple binary property list/
|
183
|
-
`plutil -convert xml1 #{file}`
|
184
|
-
when /data/
|
185
|
-
next
|
186
|
-
else
|
187
|
-
next if File.binary?(file)
|
188
|
-
end
|
182
|
+
next if File.binary?(file)
|
189
183
|
|
190
184
|
content = IO.read(file)
|
191
185
|
new_content = content.apply_variables.apply_regexes
|
data/lib/planter/prompt.rb
CHANGED
@@ -34,7 +34,7 @@ module Planter
|
|
34
34
|
def ask
|
35
35
|
return nil if @prompt.nil?
|
36
36
|
|
37
|
-
return @value.to_s.apply_variables.apply_regexes.coerce(@type) if @value
|
37
|
+
return @value.to_s.apply_variables.apply_regexes.coerce(@type) if @value && @type != :date
|
38
38
|
|
39
39
|
res = case @type
|
40
40
|
when :integer
|
@@ -133,7 +133,7 @@ module Planter
|
|
133
133
|
## @return [String] the single-line response
|
134
134
|
##
|
135
135
|
def read_line(prompt: nil)
|
136
|
-
prompt ||=
|
136
|
+
prompt ||= @prompt
|
137
137
|
default = @default ? " {bw}[#{@default}]" : ''
|
138
138
|
Planter.notify("{by}#{prompt}#{default}")
|
139
139
|
|
@@ -249,7 +249,7 @@ module Planter
|
|
249
249
|
return default unless $stdout.isatty
|
250
250
|
|
251
251
|
# If --defaults is set, return default
|
252
|
-
return default if Planter.accept_defaults
|
252
|
+
return default if Planter.accept_defaults || ENV['PLANTER_DEBUG']
|
253
253
|
|
254
254
|
# clear the buffer
|
255
255
|
if ARGV&.length
|
@@ -316,6 +316,9 @@ module Planter
|
|
316
316
|
default_response
|
317
317
|
end
|
318
318
|
|
319
|
+
# if PLANTER_DEBUG is set, answer default
|
320
|
+
return true if ENV['PLANTER_DEBUG']
|
321
|
+
|
319
322
|
# if this isn't an interactive shell, answer default
|
320
323
|
return default unless $stdout.isatty
|
321
324
|
|
data/lib/planter/script.rb
CHANGED
@@ -13,15 +13,24 @@ module Planter
|
|
13
13
|
## @param script [String] The script name
|
14
14
|
##
|
15
15
|
def initialize(template_dir, output_dir, script)
|
16
|
-
found = find_script(template_dir,
|
17
|
-
|
16
|
+
found = find_script(template_dir, script)
|
17
|
+
raise ScriptError.new("Script #{script} not found") unless found
|
18
|
+
|
18
19
|
@script = found
|
20
|
+
make_executable
|
21
|
+
|
22
|
+
raise ScriptError.new("Output directory #{output_dir} not found") unless File.directory?(output_dir)
|
19
23
|
|
20
|
-
Planter.notify("Directory #{output_dir} not found", :error, exit_code: 10) unless File.directory?(output_dir)
|
21
24
|
@template_directory = template_dir
|
22
25
|
@directory = output_dir
|
23
26
|
end
|
24
27
|
|
28
|
+
## Make a script executable if it's not already
|
29
|
+
def make_executable
|
30
|
+
File.chmod(0o755, @script) unless File.executable?(@script)
|
31
|
+
File.executable?(@script)
|
32
|
+
end
|
33
|
+
|
25
34
|
##
|
26
35
|
## Locate a script in either the base directory or template directory
|
27
36
|
##
|
@@ -31,13 +40,15 @@ module Planter
|
|
31
40
|
## @return [String] Path to script
|
32
41
|
##
|
33
42
|
def find_script(template_dir, script)
|
34
|
-
parts = Shellwords.split(script)
|
35
|
-
|
43
|
+
parts = Shellwords.split(script)
|
44
|
+
script_name = parts[0]
|
45
|
+
args = parts[1..-1].join(' ')
|
46
|
+
return script if File.exist?(script_name)
|
36
47
|
|
37
|
-
if File.exist?(File.join(template_dir, '_scripts',
|
38
|
-
return "#{File.join(template_dir, '_scripts',
|
39
|
-
elsif File.exist?(File.join(
|
40
|
-
return "#{File.join(
|
48
|
+
if File.exist?(File.join(template_dir, '_scripts', script_name))
|
49
|
+
return "#{File.join(template_dir, '_scripts', script_name)} #{args}".strip
|
50
|
+
elsif File.exist?(File.join(Planter.base_dir, 'scripts', script_name))
|
51
|
+
return "#{File.join(Planter.base_dir, 'scripts', script_name)} #{args}".strip
|
41
52
|
end
|
42
53
|
|
43
54
|
nil
|
@@ -49,9 +60,10 @@ module Planter
|
|
49
60
|
## @return [Boolean] true if success?
|
50
61
|
##
|
51
62
|
def run
|
52
|
-
|
53
|
-
|
54
|
-
Planter.notify("
|
63
|
+
stdout, stderr, status = Open3.capture3(@script, @template_directory, @directory)
|
64
|
+
Planter.notify("STDOUT:\n#{stdout}", :debug) unless stdout.empty?
|
65
|
+
Planter.notify("STDERR:\n#{stderr}", :debug) unless stderr.empty?
|
66
|
+
raise ScriptError.new("Error running #{@script}") unless status.success?
|
55
67
|
|
56
68
|
true
|
57
69
|
end
|
data/lib/planter/string.rb
CHANGED
@@ -23,7 +23,7 @@ module Planter
|
|
23
23
|
## @return Class name representation of the object.
|
24
24
|
##
|
25
25
|
def to_class_name
|
26
|
-
strip.no_ext.
|
26
|
+
strip.no_ext.title_case.gsub(/[^a-z0-9]/i, '').sub(/^\S/, &:upcase)
|
27
27
|
end
|
28
28
|
|
29
29
|
##
|
@@ -66,7 +66,7 @@ module Planter
|
|
66
66
|
## @return [String] Snake-cased version of string
|
67
67
|
##
|
68
68
|
def snake_case
|
69
|
-
strip.gsub(/\S[A-Z]
|
69
|
+
strip.gsub(/\S(?=[A-Z])/, '\0_')
|
70
70
|
.gsub(/[ -]+/, '_')
|
71
71
|
.gsub(/[^a-z0-9_]+/i, '')
|
72
72
|
.gsub(/_+/, '_')
|
@@ -82,7 +82,7 @@ module Planter
|
|
82
82
|
## @return [String] Snake-cased version of string
|
83
83
|
##
|
84
84
|
def camel_case
|
85
|
-
strip.gsub(/[
|
85
|
+
strip.gsub(/(?<=[^a-z0-9])(\S)/) { Regexp.last_match(1).upcase }
|
86
86
|
.gsub(/[^a-z0-9]+/i, '')
|
87
87
|
.sub(/^(\w)/) { Regexp.last_match(1).downcase }
|
88
88
|
end
|
@@ -96,20 +96,11 @@ module Planter
|
|
96
96
|
## @return [String] title cased string
|
97
97
|
##
|
98
98
|
def title_case
|
99
|
-
|
99
|
+
split(/\b(\w+)/).map(&:capitalize).join('')
|
100
100
|
end
|
101
101
|
|
102
|
-
|
103
|
-
|
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>
|
102
|
+
# @return [String] Regular expression for matching variable modifiers
|
103
|
+
MOD_RX = '(?<mod>
|
113
104
|
(?::
|
114
105
|
(
|
115
106
|
l(?:ow(?:er)?)?)?|
|
@@ -121,11 +112,75 @@ module Planter
|
|
121
112
|
)?
|
122
113
|
)*
|
123
114
|
)'
|
115
|
+
# @return [String] regular expression string for default values
|
116
|
+
DEFAULT_RX = '(?:%(?<default>[^%]+))?'
|
117
|
+
|
118
|
+
#
|
119
|
+
# Apply default values to a string
|
120
|
+
#
|
121
|
+
# Default values are applied to variables that are not present in the variables hash,
|
122
|
+
# or whose value matches the default value
|
123
|
+
#
|
124
|
+
# @param variables [Hash] Hash of variable values
|
125
|
+
#
|
126
|
+
# @return [String] string with default values applied
|
127
|
+
#
|
128
|
+
def apply_defaults(variables)
|
129
|
+
# Perform an in-place substitution on the content string for default values
|
130
|
+
gsub(/%%(?<varname>[^%:]+)(?<mods>(?::[^%]+)*)%(?<default>[^%]+)%%/) do
|
131
|
+
# Capture the last match object
|
132
|
+
m = Regexp.last_match
|
133
|
+
|
134
|
+
# Check if the variable is not present in the variables hash
|
135
|
+
if !variables.key?(m['varname'].to_var)
|
136
|
+
# If the variable is not present, use the default value from the match
|
137
|
+
m['default'].apply_var_names
|
138
|
+
else
|
139
|
+
# Retrieve the default value for the variable from the configuration
|
140
|
+
vars = Planter.config[:variables].filter { |v| v[:key] == m['varname'] }
|
141
|
+
default = vars.first[:default] if vars.count.positive?
|
142
|
+
if default.nil?
|
143
|
+
m[0]
|
144
|
+
elsif variables[m['varname'].to_var] == default
|
145
|
+
# If the variable's value matches the default value, use the default value from the match
|
146
|
+
m['default'].apply_var_names
|
147
|
+
else
|
148
|
+
m[0]
|
149
|
+
end
|
150
|
+
end
|
151
|
+
end
|
152
|
+
end
|
153
|
+
|
154
|
+
#
|
155
|
+
# Destructive version of #apply_defaults
|
156
|
+
#
|
157
|
+
# @param variables [Hash] hash of variables to apply
|
158
|
+
#
|
159
|
+
# @return [String] string with defaults applied
|
160
|
+
#
|
161
|
+
def apply_defaults!(variables)
|
162
|
+
replace apply_defaults(variables)
|
163
|
+
end
|
124
164
|
|
125
|
-
|
165
|
+
##
|
166
|
+
## Apply key/value substitutions to a string. Variables are represented as
|
167
|
+
## %%key%%, and the hash passed to the function is { key: value }
|
168
|
+
##
|
169
|
+
## @param last_only [Boolean] Only replace the last instance of %%key%%
|
170
|
+
##
|
171
|
+
## @return [String] string with variables substituted
|
172
|
+
##
|
173
|
+
def apply_variables(variables: nil, last_only: false)
|
174
|
+
variables = variables.nil? ? Planter.variables : variables
|
175
|
+
|
176
|
+
content = dup.clean_encode
|
177
|
+
|
178
|
+
content = content.apply_defaults(variables)
|
179
|
+
|
180
|
+
variables.each do |k, v|
|
126
181
|
if last_only
|
127
182
|
pattern = "%%#{k.to_var}"
|
128
|
-
content = content.reverse.sub(/(?mix)%%(?:(?<mod>.*?):)*(?<key>#{pattern.reverse})/) do
|
183
|
+
content = content.reverse.sub(/(?mix)%%(?:(?<mod>.*?):)*(?<key>#{pattern.reverse})/i) do
|
129
184
|
m = Regexp.last_match
|
130
185
|
if m['mod']
|
131
186
|
m['mod'].reverse.split(/:/).each do |mod|
|
@@ -136,14 +191,16 @@ module Planter
|
|
136
191
|
v.reverse
|
137
192
|
end.reverse
|
138
193
|
else
|
139
|
-
rx = /(?mix)%%(?<key>#{k.to_var})#{
|
194
|
+
rx = /(?mix)%%(?<key>#{k.to_var})#{MOD_RX}#{DEFAULT_RX}%%/
|
140
195
|
|
141
196
|
content.gsub!(rx) do
|
142
197
|
m = Regexp.last_match
|
143
198
|
|
144
|
-
|
145
|
-
|
146
|
-
|
199
|
+
if m['mod']
|
200
|
+
mods = m['mod']&.split(/:/)
|
201
|
+
mods&.each do |mod|
|
202
|
+
v = v.apply_mod(mod.normalize_mod)
|
203
|
+
end
|
147
204
|
end
|
148
205
|
v
|
149
206
|
end
|
@@ -153,16 +210,49 @@ module Planter
|
|
153
210
|
content
|
154
211
|
end
|
155
212
|
|
213
|
+
#
|
214
|
+
# Handle $varname and ${varname} variable substitutions
|
215
|
+
#
|
216
|
+
# @return [String] String with variables substituted
|
217
|
+
#
|
218
|
+
def apply_var_names
|
219
|
+
sub(/\$\{?(?<varname>\w+)(?<mods>(?::\w+)+)?\}?/) do
|
220
|
+
m = Regexp.last_match
|
221
|
+
if Planter.variables.key?(m['varname'].to_var)
|
222
|
+
Planter.variables[m['varname'].to_var].apply_mods(m['mods'])
|
223
|
+
else
|
224
|
+
m
|
225
|
+
end
|
226
|
+
end
|
227
|
+
end
|
228
|
+
|
229
|
+
#
|
230
|
+
# Apply modifiers to a string
|
231
|
+
#
|
232
|
+
# @param mods [String] Colon separated list of modifiers to apply
|
233
|
+
#
|
234
|
+
# @return [String] string with modifiers applied
|
235
|
+
#
|
236
|
+
def apply_mods(mods)
|
237
|
+
content = dup
|
238
|
+
mods.split(/:/).each do |mod|
|
239
|
+
content.apply_mod!(mod.normalize_mod)
|
240
|
+
end
|
241
|
+
content
|
242
|
+
end
|
243
|
+
|
156
244
|
##
|
157
245
|
## Apply regex replacements from @config[:replacements]
|
158
246
|
##
|
159
247
|
## @return [String] string with regexes applied
|
160
248
|
##
|
161
|
-
def apply_regexes
|
249
|
+
def apply_regexes(regexes = nil)
|
162
250
|
content = dup.clean_encode
|
163
|
-
|
251
|
+
regexes = regexes.nil? && Planter.config.key?(:replacements) ? Planter.config[:replacements] : regexes
|
164
252
|
|
165
|
-
|
253
|
+
return self unless regexes
|
254
|
+
|
255
|
+
regexes.stringify_keys.each do |pattern, replacement|
|
166
256
|
pattern = Regexp.new(pattern) unless pattern.is_a?(Regexp)
|
167
257
|
replacement = replacement.gsub(/\$(\d)/, '\\\1').apply_variables
|
168
258
|
content.gsub!(pattern, replacement)
|
@@ -177,8 +267,8 @@ module Planter
|
|
177
267
|
##
|
178
268
|
## @return [String] string with variables substituted
|
179
269
|
##
|
180
|
-
def apply_variables!(last_only: false)
|
181
|
-
replace apply_variables(last_only: last_only)
|
270
|
+
def apply_variables!(variables: nil, last_only: false)
|
271
|
+
replace apply_variables(variables: variables, last_only: last_only)
|
182
272
|
end
|
183
273
|
|
184
274
|
##
|
@@ -186,8 +276,8 @@ module Planter
|
|
186
276
|
##
|
187
277
|
## @return [String] string with variables substituted
|
188
278
|
##
|
189
|
-
def apply_regexes!
|
190
|
-
replace apply_regexes
|
279
|
+
def apply_regexes!(regexes = nil)
|
280
|
+
replace apply_regexes(regexes)
|
191
281
|
end
|
192
282
|
|
193
283
|
##
|
@@ -222,6 +312,8 @@ module Planter
|
|
222
312
|
##
|
223
313
|
## @param mod [Symbol] The modifier to apply
|
224
314
|
##
|
315
|
+
## @return [String] modified string
|
316
|
+
##
|
225
317
|
def apply_mod(mod)
|
226
318
|
case mod
|
227
319
|
when :slug
|
@@ -241,6 +333,17 @@ module Planter
|
|
241
333
|
end
|
242
334
|
end
|
243
335
|
|
336
|
+
#
|
337
|
+
# Destructive version of #apply_mod
|
338
|
+
#
|
339
|
+
# @param mod [String] modified string
|
340
|
+
#
|
341
|
+
# @return [<Type>] <description>
|
342
|
+
#
|
343
|
+
def apply_mod!(mod)
|
344
|
+
replace apply_mod(mod)
|
345
|
+
end
|
346
|
+
|
244
347
|
##
|
245
348
|
## Convert mod string to symbol
|
246
349
|
##
|
@@ -334,9 +437,11 @@ module Planter
|
|
334
437
|
##
|
335
438
|
##
|
336
439
|
def coerce(type)
|
440
|
+
type = type.normalize_type
|
441
|
+
|
337
442
|
case type
|
338
443
|
when :date
|
339
|
-
Chronic.parse(self)
|
444
|
+
Chronic.parse(self).strftime('%Y-%m-%d %H:%M')
|
340
445
|
when :integer || :number
|
341
446
|
to_i
|
342
447
|
when :float
|
data/lib/planter/tag.rb
ADDED
@@ -0,0 +1,54 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Planter
|
4
|
+
module Tag
|
5
|
+
class << self
|
6
|
+
def set(target, tags)
|
7
|
+
tags = [tags] unless tags.is_a?(Array)
|
8
|
+
|
9
|
+
set_tags(target, tags)
|
10
|
+
$? == 0
|
11
|
+
end
|
12
|
+
|
13
|
+
# Add tags to a directory.
|
14
|
+
#
|
15
|
+
# @param dir [String] The directory to tag.
|
16
|
+
# @param tags [Array<String>] The tags to add.
|
17
|
+
def add(target, tags)
|
18
|
+
tags = [tags] unless tags.is_a?(Array)
|
19
|
+
existing_tags = get(target)
|
20
|
+
tags.concat(existing_tags).uniq!
|
21
|
+
|
22
|
+
set_tags(target, tags)
|
23
|
+
$? == 0
|
24
|
+
end
|
25
|
+
|
26
|
+
def get(target)
|
27
|
+
res = `xattr -p com.apple.metadata:_kMDItemUserTags "#{target}" 2>/dev/null`.clean_encode
|
28
|
+
return [] if res =~ /no such xattr/ || res.empty?
|
29
|
+
|
30
|
+
tags = Plist.parse_xml(res)
|
31
|
+
|
32
|
+
return false if tags.nil?
|
33
|
+
|
34
|
+
tags
|
35
|
+
end
|
36
|
+
|
37
|
+
def copy(source, target)
|
38
|
+
tags = `xattr -px com.apple.metadata:_kMDItemUserTags "#{source}" 2>/dev/null`
|
39
|
+
`xattr -wx com.apple.metadata:_kMDItemUserTags "#{tags}" "#{target}"`
|
40
|
+
$? == 0
|
41
|
+
end
|
42
|
+
|
43
|
+
private
|
44
|
+
|
45
|
+
def set_tags(target, tags)
|
46
|
+
tags.map! { |tag| "<string>#{tag}</string>" }
|
47
|
+
`xattr -w com.apple.metadata:_kMDItemUserTags '<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
48
|
+
<plist version="1.0">
|
49
|
+
<array>#{tags.join}</array>
|
50
|
+
</plist>' "#{target}"`
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
data/lib/planter/version.rb
CHANGED