planter-cli 0.0.3 → 3.0.1
Sign up to get free protection for your applications and to get access to all the features.
- 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