planter-cli 3.0.2 → 3.0.3
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.gitignore +1 -0
- data/.rubocop.yml +1 -2
- data/CHANGELOG.md +14 -0
- data/README.md +81 -2
- data/docker/Dockerfile-2.6 +2 -1
- data/docker/Dockerfile-2.7 +2 -1
- data/docker/Dockerfile-3.0 +2 -1
- data/docker/Dockerfile-3.3 +2 -1
- data/docker/bash_profile +2 -1
- data/lib/planter/array.rb +56 -1
- data/lib/planter/hash.rb +24 -0
- data/lib/planter/plant.rb +9 -7
- data/lib/planter/prompt.rb +51 -11
- data/lib/planter/string.rb +121 -5
- data/lib/planter/version.rb +1 -1
- data/lib/planter.rb +1 -1
- data/spec/planter/string_spec.rb +31 -4
- data/spec/spec_helper.rb +1 -1
- data/src/_README.md +81 -2
- metadata +2 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: d811f48053b6544dc93bec282620300d673dc5524cc534e0b4fe59d194d09c19
|
4
|
+
data.tar.gz: 8da8c9f74029c96f7286f25c80be21a35d87237c2942db24a9864d881d04f509
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 73daffea9046af25fb1c1d1839eb622ca87ec43ab2d5aab4bbf6f593cb4ea24176f9f443aadf4178610d181eb4cec5276ed92cd7ca880f1d5a72ab364e397ab9
|
7
|
+
data.tar.gz: 104ca18f851112b4142f5ef6e5b31eb564d8e83be9de87f0cb1107a42e35f180adf23858896a599f3aeee7eff2683b90b03fe4ffc6ae8dc3945bc237ffffaf14
|
data/.gitignore
CHANGED
data/.rubocop.yml
CHANGED
@@ -11,7 +11,7 @@ AllCops:
|
|
11
11
|
- Gemfile
|
12
12
|
- Guardfile
|
13
13
|
- Rakefile
|
14
|
-
- bin/
|
14
|
+
- bin/plant
|
15
15
|
- lib/**/*.rb
|
16
16
|
Exclude:
|
17
17
|
- pkg/**/*.rb
|
@@ -41,7 +41,6 @@ Metrics/BlockLength:
|
|
41
41
|
Max: 45
|
42
42
|
Exclude:
|
43
43
|
- Rakefile
|
44
|
-
- bin/untitled
|
45
44
|
- lib/*.rb
|
46
45
|
|
47
46
|
Metrics/ClassLength:
|
data/CHANGELOG.md
CHANGED
@@ -1,3 +1,17 @@
|
|
1
|
+
### 3.0.3
|
2
|
+
|
3
|
+
2024-09-02 08:02
|
4
|
+
|
5
|
+
#### CHANGED
|
6
|
+
|
7
|
+
- Multiline is now "paragraph"
|
8
|
+
- Module type now requires "mod" at minimum
|
9
|
+
|
10
|
+
#### NEW
|
11
|
+
|
12
|
+
- Multiple choice variable type (ch|mu)
|
13
|
+
- If/then logic in templates
|
14
|
+
|
1
15
|
### 3.0.2
|
2
16
|
|
3
17
|
2024-09-01 09:46
|
data/README.md
CHANGED
@@ -47,7 +47,7 @@ First, there's a `variables` section that defines variables used in the template
|
|
47
47
|
variables:
|
48
48
|
- key: var_key
|
49
49
|
prompt: Prompt text
|
50
|
-
type: string # [string,
|
50
|
+
type: string # [string,paragraph,float,integer,number,date,choice] defaults to string
|
51
51
|
# value: (force value, string can include %%variables%% and regexes will be replaced. For date type can be today, time, now, etc.)
|
52
52
|
default: Untitled
|
53
53
|
min: 1
|
@@ -64,6 +64,85 @@ repo: # If a repository URL is provided, it will be pulled and duplicated instea
|
|
64
64
|
|
65
65
|
In a template you can add a default value for a placholder by adding `%default value` to it. For example, `%%project%Default Project%%` will set the placeholder to `Default Project` if the variable value matches the default value in the configuration. This allows you to accept the default on the command line but have a different value inserted in the template. To use another variable in its place, use `$KEY` in the placeholder, e.g. `%%project%$title%%` will replace the `project` key with the value of `title` if the default is selected. Modifiers can be used on either side of the `%`, e.g. `%%project%$title:snake%%`.
|
66
66
|
|
67
|
+
#### Multiple choice type
|
68
|
+
|
69
|
+
If the `type` is set to `choice`, then the key `choices` can contain a hash or array of choices. The key that accepts the choice should be surrounded with parenthesis (required for each choice).
|
70
|
+
|
71
|
+
If a Hash is defined, each choice can have a result string:
|
72
|
+
|
73
|
+
```yaml
|
74
|
+
variables:
|
75
|
+
- key: shebang
|
76
|
+
prompt: Shebang line
|
77
|
+
type: choice
|
78
|
+
default: r
|
79
|
+
choices:
|
80
|
+
(r)uby: "#! /usr/bin/env ruby"
|
81
|
+
(j)avascript: "#! /usr/bin/env node"
|
82
|
+
(p)ython: "#! /usr/bin/env python"
|
83
|
+
(b)ash: "#! /bin/bash"
|
84
|
+
(z)sh: "#! /bin/zsh"
|
85
|
+
```
|
86
|
+
|
87
|
+
If an array is defined, the string of the choice will also be its result:
|
88
|
+
|
89
|
+
```yaml
|
90
|
+
variables:
|
91
|
+
- key: language
|
92
|
+
prompt: Programming language
|
93
|
+
type: choice
|
94
|
+
default: 1
|
95
|
+
choices:
|
96
|
+
- 1. ruby
|
97
|
+
- 1. javascript
|
98
|
+
- 1. python
|
99
|
+
- 1. bash
|
100
|
+
- 1. zsh
|
101
|
+
```
|
102
|
+
|
103
|
+
If the choice starts with a number (as above), then a numeric list will be generated and typing the associated index number will accept that choice. Numeric lists are automatically numbered, so the preceding digit doesn't matter, as long as it's a digit. In this case a default can be defined with an integer for its placement in the list (starting with 1), and parenthesis aren't required.
|
104
|
+
|
105
|
+
#### If/then logic
|
106
|
+
|
107
|
+
A template can use if/then logic, which is useful with multiple choice types. It can be applied to any type, though.
|
108
|
+
|
109
|
+
The format for if/then logic is:
|
110
|
+
|
111
|
+
```
|
112
|
+
%%if KEY OPERATOR VALUE%%
|
113
|
+
content
|
114
|
+
%%else if KEY OPERATOR VALUE2%%
|
115
|
+
content 2
|
116
|
+
%%else%%
|
117
|
+
content 3
|
118
|
+
%%endif%%
|
119
|
+
```
|
120
|
+
|
121
|
+
There should be no spaces around the comparison, e.g. `%% if language == javascript %%` won't work. The block must start with an `if` statement and end with `%%endif%%` or `%%end%%`. The `%%else%%` statement is optional -- if it doesn't exist then the entire block will be removed if no conditions are met.
|
122
|
+
|
123
|
+
The key should be an existing key defined in `variables`. The operator can be any of:
|
124
|
+
|
125
|
+
- `==` or `=` (equals)
|
126
|
+
- `=~` (matches regex)
|
127
|
+
- `*=` (contains)
|
128
|
+
- `^=` (starts with)
|
129
|
+
- `$=` (ends with)
|
130
|
+
- `>` (greater than)
|
131
|
+
- `>=` (greater than or equal)
|
132
|
+
- `<` (less than)
|
133
|
+
- `<=` (less than or equal)
|
134
|
+
|
135
|
+
The value after the operator doesn't need to be quoted, anything after the operator will be compared to the value of the key.
|
136
|
+
|
137
|
+
Logic can be used on multiple lines like the example above, or on a single line (useful for filenames):
|
138
|
+
|
139
|
+
```
|
140
|
+
%%project%%.%%if language == javascript%%js%%else if language == ruby%%rb%%else%%sh%%endif%%
|
141
|
+
```
|
142
|
+
|
143
|
+
Content within if/else blocks can contain variables.
|
144
|
+
|
145
|
+
|
67
146
|
### File-specific handling
|
68
147
|
|
69
148
|
A `files` dictionary can specify how to handle specific files. Options are `copy`, `overwrite`, `merge`, or `ask`. The key for each entry is a filename or glob that matches the source filename (accounting for template variables if applicable):
|
@@ -74,7 +153,7 @@ files:
|
|
74
153
|
"%%title%%.md": overwrite
|
75
154
|
```
|
76
155
|
|
77
|
-
Filenames can include wildcards (`*`, `?`), and Bash-
|
156
|
+
Filenames can include wildcards (`*`, `?`), and Bash-ish globbing (`[0-9]`, `[a-z]`, `{one,two,three}`).
|
78
157
|
|
79
158
|
If `merge` is specified, then the source file is scanned for merge comments and those are merged if they don't exist in the copied/existing file. If no merge comments are defined, then the entire contents of the source file are appended to the destination file (unless the file already matches the source). Merge comments start with `merge` and end with `/merge` and can have any comment syntax preceding them, for example:
|
80
159
|
|
data/docker/Dockerfile-2.6
CHANGED
@@ -4,7 +4,8 @@ WORKDIR /planter
|
|
4
4
|
RUN gem install bundler:2.2.29
|
5
5
|
COPY ./docker/sources.list /etc/apt/sources.list
|
6
6
|
RUN apt-get update -y --allow-insecure-repositories || true
|
7
|
-
RUN apt-get install -y
|
7
|
+
RUN apt-get install -y sudo || true
|
8
|
+
RUN sudo apt-get install -y less vim || true
|
8
9
|
COPY ./docker/inputrc /root/.inputrc
|
9
10
|
COPY ./docker/bash_profile /root/.bash_profile
|
10
11
|
CMD ["/planter/scripts/runtests.sh"]
|
data/docker/Dockerfile-2.7
CHANGED
@@ -4,7 +4,8 @@ WORKDIR /planter
|
|
4
4
|
RUN gem install bundler:2.2.29
|
5
5
|
COPY ./docker/sources.list /etc/apt/sources.list
|
6
6
|
RUN apt-get update -y --allow-insecure-repositories || true
|
7
|
-
RUN apt-get install -y
|
7
|
+
RUN apt-get install -y sudo || true
|
8
|
+
RUN sudo apt-get install -y less vim || true
|
8
9
|
COPY ./docker/inputrc /root/.inputrc
|
9
10
|
COPY ./docker/bash_profile /root/.bash_profile
|
10
11
|
CMD ["/planter/scripts/runtests.sh"]
|
data/docker/Dockerfile-3.0
CHANGED
@@ -4,7 +4,8 @@ WORKDIR /planter
|
|
4
4
|
RUN gem install bundler:2.2.29
|
5
5
|
COPY ./docker/sources.list /etc/apt/sources.list
|
6
6
|
RUN apt-get update -y --allow-insecure-repositories || true
|
7
|
-
RUN apt-get install -y
|
7
|
+
RUN apt-get install -y sudo || true
|
8
|
+
RUN sudo apt-get install -y less vim || true
|
8
9
|
COPY ./docker/inputrc /root/.inputrc
|
9
10
|
COPY ./docker/bash_profile /root/.bash_profile
|
10
11
|
CMD ["/planter/scripts/runtests.sh"]
|
data/docker/Dockerfile-3.3
CHANGED
@@ -5,7 +5,8 @@ WORKDIR /planter
|
|
5
5
|
RUN gem install bundler:2.2.29
|
6
6
|
COPY ./docker/sources.list /etc/apt/sources.list
|
7
7
|
RUN apt-get update -y --allow-insecure-repositories || true
|
8
|
-
RUN apt-get install -y
|
8
|
+
RUN apt-get install -y sudo || true
|
9
|
+
RUN sudo apt-get install -y less vim || true
|
9
10
|
COPY ./docker/inputrc /root/.inputrc
|
10
11
|
COPY ./docker/bash_profile /root/.bash_profile
|
11
12
|
CMD ["/planter/scripts/runtests.sh"]
|
data/docker/bash_profile
CHANGED
@@ -2,6 +2,7 @@
|
|
2
2
|
export GLI_DEBUG=true
|
3
3
|
export EDITOR="/usr/bin/vim"
|
4
4
|
alias b="bundle exec bin/plant"
|
5
|
+
alias be="bundle exec"
|
5
6
|
alias quit="exit"
|
6
7
|
|
7
8
|
shopt -s nocaseglob
|
@@ -11,5 +12,5 @@ shopt -s histverify
|
|
11
12
|
shopt -s cmdhist
|
12
13
|
|
13
14
|
cd /planter
|
14
|
-
bundle
|
15
|
+
bundle update
|
15
16
|
gem install pkg/*.gem
|
data/lib/planter/array.rb
CHANGED
@@ -12,7 +12,7 @@ module Planter
|
|
12
12
|
## @param default [String] The color templated output string
|
13
13
|
##
|
14
14
|
def abbr_choices(default: nil)
|
15
|
-
chars = join(' ').scan(/\((.)\)/).map { |c| c[0] }
|
15
|
+
chars = join(' ').scan(/\((?:(.)\.?)\)/).map { |c| c[0] }
|
16
16
|
out = String.new
|
17
17
|
out << '{xdw}['
|
18
18
|
out << chars.map do |c|
|
@@ -25,6 +25,61 @@ module Planter
|
|
25
25
|
out << '{dw}]{x}'
|
26
26
|
end
|
27
27
|
|
28
|
+
## Convert an array of choices to a string with optional numbering
|
29
|
+
##
|
30
|
+
## @param numeric [Boolean] Include numbering
|
31
|
+
##
|
32
|
+
## @return [Array] Array of choices
|
33
|
+
##
|
34
|
+
def to_options(numeric)
|
35
|
+
map.with_index do |c, i|
|
36
|
+
# v = c.to_s.match(/\(?([a-z]|\d+\.?)\)?/)[1].strip
|
37
|
+
if numeric
|
38
|
+
"(#{i + 1}). #{c.to_s.sub(/^\(?\d+\.?\)? +/, '')}"
|
39
|
+
else
|
40
|
+
c
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
## Find the index of a choice in an array of choices
|
46
|
+
##
|
47
|
+
## @param choice [String] The choice to find
|
48
|
+
##
|
49
|
+
## @return [Integer] Index of the choice
|
50
|
+
##
|
51
|
+
def option_index(choice)
|
52
|
+
index = find_index { |c| c.to_s.match(/\((.+)\)/)[1].strip.sub(/\.$/, '') == choice }
|
53
|
+
index || false
|
54
|
+
end
|
55
|
+
|
56
|
+
## Convert an array of choices to a hash
|
57
|
+
## - If the array contains hashes, they are converted to key/value pairs
|
58
|
+
## - If the array contains strings, they are used as both key and value
|
59
|
+
##
|
60
|
+
## @return [Hash] Hash of choices
|
61
|
+
##
|
62
|
+
def choices_to_hash
|
63
|
+
hash = {}
|
64
|
+
each do |c|
|
65
|
+
if c.is_a?(Hash)
|
66
|
+
hash[c.keys.first.to_s] = c.values.first.to_s
|
67
|
+
else
|
68
|
+
hash[c.to_s] = c.to_s
|
69
|
+
end
|
70
|
+
end
|
71
|
+
|
72
|
+
hash
|
73
|
+
end
|
74
|
+
|
75
|
+
## Clean strings in an array by removing numbers and parentheses
|
76
|
+
##
|
77
|
+
## @return [Array] Array with cleaned strings
|
78
|
+
##
|
79
|
+
def to_values
|
80
|
+
map(&:clean_value)
|
81
|
+
end
|
82
|
+
|
28
83
|
##
|
29
84
|
## Stringify keys in an array of hashes or arrays
|
30
85
|
##
|
data/lib/planter/hash.rb
CHANGED
@@ -3,6 +3,30 @@
|
|
3
3
|
module Planter
|
4
4
|
## Hash helpers
|
5
5
|
class ::Hash
|
6
|
+
## Turn all keys and values into string
|
7
|
+
##
|
8
|
+
## @return [Hash] copy of the hash where all its keys are strings
|
9
|
+
##
|
10
|
+
def stringify
|
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
|
14
|
+
else
|
15
|
+
v.to_s
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
## Destructive version of #stringify
|
21
|
+
##
|
22
|
+
## @return [Hash] Hash with stringified keys and values
|
23
|
+
##
|
24
|
+
## @see #stringify
|
25
|
+
##
|
26
|
+
def stringify!
|
27
|
+
replace stringify
|
28
|
+
end
|
29
|
+
|
6
30
|
## Turn all keys into string
|
7
31
|
##
|
8
32
|
## @return [Hash] copy of the hash where all its keys are strings
|
data/lib/planter/plant.rb
CHANGED
@@ -45,7 +45,8 @@ module Planter
|
|
45
45
|
default: var[:default],
|
46
46
|
value: var[:value],
|
47
47
|
min: var[:min],
|
48
|
-
max: var[:max]
|
48
|
+
max: var[:max],
|
49
|
+
choices: var[:choices] || nil
|
49
50
|
)
|
50
51
|
answer = q.ask
|
51
52
|
if answer.nil?
|
@@ -158,8 +159,8 @@ module Planter
|
|
158
159
|
s.run
|
159
160
|
end
|
160
161
|
end
|
161
|
-
Planter.spinner.update(title: '
|
162
|
-
Planter.spinner.success
|
162
|
+
Planter.spinner.update(title: 'Planting complete!')
|
163
|
+
Planter.spinner.success
|
163
164
|
end
|
164
165
|
|
165
166
|
##
|
@@ -182,19 +183,20 @@ module Planter
|
|
182
183
|
next if File.binary?(file)
|
183
184
|
|
184
185
|
content = IO.read(file)
|
185
|
-
|
186
|
+
|
187
|
+
new_content = content.apply_logic.apply_variables.apply_regexes
|
186
188
|
|
187
189
|
new_content.gsub!(%r{^.{.4}/?merge *.{,4}\n}, '') if new_content =~ /^.{.4}merge *\n/
|
188
190
|
|
189
191
|
unless content == new_content
|
190
|
-
Planter.notify("Applying variables to #{file}", :debug
|
192
|
+
Planter.notify("Applying variables to #{file}", :debug)
|
191
193
|
File.open(file, 'w') { |f| f.puts new_content }
|
192
194
|
end
|
193
195
|
end
|
194
196
|
|
195
197
|
true
|
196
198
|
rescue StandardError => e
|
197
|
-
Planter.notify("#{e}\n#{e.backtrace}", :debug
|
199
|
+
Planter.notify("#{e}\n#{e.backtrace}", :debug)
|
198
200
|
'Error updating files/directories'
|
199
201
|
end
|
200
202
|
|
@@ -215,7 +217,7 @@ module Planter
|
|
215
217
|
|
216
218
|
true
|
217
219
|
rescue StandardError => e
|
218
|
-
Planter.notify("#{e}\n#{e.backtrace}", :debug
|
220
|
+
Planter.notify("#{e}\n#{e.backtrace}", :debug)
|
219
221
|
'Error initializing git'
|
220
222
|
end
|
221
223
|
end
|
data/lib/planter/prompt.rb
CHANGED
@@ -23,6 +23,7 @@ module Planter
|
|
23
23
|
@prompt = question[:prompt] || nil
|
24
24
|
@default = question[:default]
|
25
25
|
@value = question[:value]
|
26
|
+
@choices = question[:choices] || []
|
26
27
|
@gum = false # TTY::Which.exist?('gum')
|
27
28
|
end
|
28
29
|
|
@@ -37,6 +38,8 @@ module Planter
|
|
37
38
|
return @value.to_s.apply_variables.apply_regexes.coerce(@type) if @value && @type != :date
|
38
39
|
|
39
40
|
res = case @type
|
41
|
+
when :choice
|
42
|
+
Prompt.choice(@choices, @prompt, default_response: @default)
|
40
43
|
when :integer
|
41
44
|
read_number(integer: true)
|
42
45
|
when :float
|
@@ -238,18 +241,36 @@ module Planter
|
|
238
241
|
## @param default_response [String] The character of the default
|
239
242
|
## response
|
240
243
|
##
|
241
|
-
## @return [String]
|
244
|
+
## @return [String] string of selected response with parenthesis removed
|
242
245
|
##
|
243
246
|
def self.choice(choices, prompt = 'Make a selection', default_response: nil)
|
244
247
|
$stdin.reopen('/dev/tty')
|
245
248
|
|
246
|
-
|
249
|
+
choices = choices.choices_to_hash if choices.is_a?(Array) && choices.first.is_a?(Hash)
|
247
250
|
|
248
|
-
|
249
|
-
|
251
|
+
if choices.is_a?(Hash)
|
252
|
+
choices.stringify!
|
253
|
+
numeric = choices.keys.first =~ /^\(?\d+\.?\)? /
|
254
|
+
keys = choices.keys.to_options(numeric)
|
255
|
+
values = choices.values.map(&:clean_value)
|
256
|
+
choices = choices.keys
|
257
|
+
else
|
258
|
+
numeric = choices.first =~ /^\(?\d+\.?\)? /
|
259
|
+
keys = choices.to_options(numeric)
|
260
|
+
values = choices.to_values.map(&:clean_value)
|
261
|
+
end
|
250
262
|
|
251
|
-
|
252
|
-
|
263
|
+
default = case default_response.to_s
|
264
|
+
when /^\d+$/
|
265
|
+
values[default.to_i]
|
266
|
+
when /^[a-z]$/i
|
267
|
+
keys.include?(default_response) ? values[keys.index(default_response)] : nil
|
268
|
+
end
|
269
|
+
|
270
|
+
# If --defaults is set or not an interactive shell, return default
|
271
|
+
return default if Planter.accept_defaults || ENV['PLANTER_DEBUG'] || !$stdout.isatty
|
272
|
+
|
273
|
+
default = default_response.to_s if default_response
|
253
274
|
|
254
275
|
# clear the buffer
|
255
276
|
if ARGV&.length
|
@@ -259,9 +280,10 @@ module Planter
|
|
259
280
|
end
|
260
281
|
system 'stty cbreak'
|
261
282
|
|
262
|
-
vertical = choices.join(' ').length + 4 > TTY::Screen.cols
|
263
|
-
|
264
|
-
|
283
|
+
vertical = numeric || choices.join(', ').length + 4 > TTY::Screen.cols
|
284
|
+
|
285
|
+
desc = keys.map { |c| c.highlight_character(default: default) }
|
286
|
+
abbr = keys.abbr_choices(default: default)
|
265
287
|
|
266
288
|
options = if vertical
|
267
289
|
"{x}#{desc.join("\n")}\n{by}#{prompt}{x} #{abbr}{bw}? "
|
@@ -270,16 +292,34 @@ module Planter
|
|
270
292
|
end
|
271
293
|
|
272
294
|
$stdout.syswrite options.x
|
273
|
-
|
295
|
+
|
296
|
+
res = if numeric && choices.length > 9
|
297
|
+
$stdin.sysread choices.length.to_s.length
|
298
|
+
else
|
299
|
+
$stdin.sysread 1
|
300
|
+
end
|
301
|
+
|
274
302
|
puts
|
275
303
|
system 'stty cooked'
|
276
304
|
|
277
305
|
res.chomp!
|
278
306
|
res.downcase!
|
279
307
|
|
280
|
-
res.empty? ? default : res
|
308
|
+
res = res.empty? ? default : res
|
309
|
+
|
310
|
+
if res.to_i.positive?
|
311
|
+
values[res.to_i - 1]
|
312
|
+
elsif res =~ /^[a-z]/ && keys&.option_index(res)
|
313
|
+
values[keys.option_index(res)]
|
314
|
+
end
|
281
315
|
end
|
282
316
|
|
317
|
+
## Determine what to do with a file
|
318
|
+
##
|
319
|
+
## @param entry [FileEntry] The file entry
|
320
|
+
##
|
321
|
+
## @return [Symbol] :merge, :overwrite, :copy, :ignore
|
322
|
+
##
|
283
323
|
def self.file_what?(entry)
|
284
324
|
options = %w[(o)vewrite (m)erge]
|
285
325
|
options << '(c)opy' unless File.exist?(entry.target)
|
data/lib/planter/string.rb
CHANGED
@@ -12,7 +12,20 @@ module Planter
|
|
12
12
|
## @return [Symbol] string as variable key
|
13
13
|
##
|
14
14
|
def to_var
|
15
|
-
snake_case.to_sym
|
15
|
+
strip_quotes.snake_case.to_sym
|
16
|
+
end
|
17
|
+
|
18
|
+
## Strip quotes from a string
|
19
|
+
##
|
20
|
+
## @return [String] string with quotes stripped
|
21
|
+
##
|
22
|
+
def strip_quotes
|
23
|
+
sub(/^(["'])(.*)\1$/, '\2')
|
24
|
+
end
|
25
|
+
|
26
|
+
## Destructive version of #strip_quotes
|
27
|
+
def strip_quotes!
|
28
|
+
replace strip_quotes
|
16
29
|
end
|
17
30
|
|
18
31
|
#
|
@@ -184,6 +197,96 @@ module Planter
|
|
184
197
|
replace apply_defaults(variables)
|
185
198
|
end
|
186
199
|
|
200
|
+
## Apply logic to a string
|
201
|
+
##
|
202
|
+
## @param variables [Hash] Hash of variables to apply
|
203
|
+
##
|
204
|
+
def apply_logic(variables = nil)
|
205
|
+
variables = variables.nil? ? Planter.variables : variables
|
206
|
+
|
207
|
+
gsub(/%%if .*?%%.*?%%end(if)?%%/mi) do |construct|
|
208
|
+
res = false
|
209
|
+
# Get the condition and the content
|
210
|
+
output = construct.match(/%%else%%(.*?)%%end/m) ? Regexp.last_match(1) : ''
|
211
|
+
|
212
|
+
conditions = construct.to_enum(:scan,
|
213
|
+
/%%(?<statement>(?:els(?:e )?)?if) (?<condition>.*?)%%(?<content>.*?)(?=%%)/mi).map do
|
214
|
+
Regexp.last_match
|
215
|
+
end
|
216
|
+
conditions.each do |condition|
|
217
|
+
variable, operator, value = condition['condition'].split(/ +/, 3)
|
218
|
+
value.strip_quotes!
|
219
|
+
variable = variable.to_var
|
220
|
+
negate = false
|
221
|
+
if operator =~ /^!/
|
222
|
+
operator = operator[1..-1]
|
223
|
+
negate = true
|
224
|
+
end
|
225
|
+
operator = case operator
|
226
|
+
when /^={1,2}/
|
227
|
+
:equal
|
228
|
+
when /^=~/
|
229
|
+
:matches_regex
|
230
|
+
when /\*=/
|
231
|
+
:contains
|
232
|
+
when /\^=/
|
233
|
+
:starts_with
|
234
|
+
when /\$=/
|
235
|
+
:ends_with
|
236
|
+
when />/
|
237
|
+
:greater_than
|
238
|
+
when /</
|
239
|
+
:less_than
|
240
|
+
when />=/
|
241
|
+
:greater_than_or_equal
|
242
|
+
when /<=/
|
243
|
+
:less_than_or_equal
|
244
|
+
else
|
245
|
+
:equal
|
246
|
+
end
|
247
|
+
|
248
|
+
comp = variables[variable.to_var].to_s
|
249
|
+
|
250
|
+
res = case operator
|
251
|
+
when :equal
|
252
|
+
comp =~ /^#{value}$/i
|
253
|
+
when :matches_regex
|
254
|
+
comp =~ Regexp.new(value.gsub(%r{^/|/$}, ''))
|
255
|
+
when :contains
|
256
|
+
comp =~ /#{value}/i
|
257
|
+
when :starts_with
|
258
|
+
comp =~ /^#{value}/i
|
259
|
+
when :ends_with
|
260
|
+
comp =~ /#{value}$/i
|
261
|
+
when :greater_than
|
262
|
+
comp > value.to_f
|
263
|
+
when :less_than
|
264
|
+
comp < value.to_f
|
265
|
+
when :greater_than_or_equal
|
266
|
+
comp >= value.to_f
|
267
|
+
when :less_than_or_equal
|
268
|
+
comp <= value.to_f
|
269
|
+
else
|
270
|
+
false
|
271
|
+
end
|
272
|
+
res = !res if negate
|
273
|
+
|
274
|
+
next unless res
|
275
|
+
|
276
|
+
Planter.notify("Condition matched: #{comp} #{negate ? 'not ' : ''}#{operator} #{value}", :debug)
|
277
|
+
output = condition['content']
|
278
|
+
break
|
279
|
+
end
|
280
|
+
|
281
|
+
output
|
282
|
+
end
|
283
|
+
end
|
284
|
+
|
285
|
+
## Destructive version of #apply_logic
|
286
|
+
def apply_logic!(variables)
|
287
|
+
replace apply_logic(variables)
|
288
|
+
end
|
289
|
+
|
187
290
|
##
|
188
291
|
## Apply key/value substitutions to a string. Variables are represented as
|
189
292
|
## %%key%%, and the hash passed to the function is { key: value }
|
@@ -199,6 +302,8 @@ module Planter
|
|
199
302
|
|
200
303
|
content = content.apply_defaults(variables)
|
201
304
|
|
305
|
+
content = content.apply_logic(variables)
|
306
|
+
|
202
307
|
variables.each do |k, v|
|
203
308
|
if last_only
|
204
309
|
pattern = "%%#{k.to_var}"
|
@@ -437,15 +542,18 @@ module Planter
|
|
437
542
|
# number or float
|
438
543
|
when /^[nf]/
|
439
544
|
:float
|
440
|
-
#
|
441
|
-
when /^
|
545
|
+
# paragraph
|
546
|
+
when /^p/
|
442
547
|
:multiline
|
443
548
|
# class
|
444
|
-
when /^
|
549
|
+
when /^cl/
|
445
550
|
:class
|
446
551
|
# module
|
447
|
-
when /^
|
552
|
+
when /^mod/
|
448
553
|
:module
|
554
|
+
# multiple choice
|
555
|
+
when /^(ch|mu)/
|
556
|
+
:choice
|
449
557
|
# string
|
450
558
|
else
|
451
559
|
:string
|
@@ -493,6 +601,14 @@ module Planter
|
|
493
601
|
replace clean_encode
|
494
602
|
end
|
495
603
|
|
604
|
+
## Clean up a string by removing leading numbers and parentheticalse
|
605
|
+
##
|
606
|
+
## @return [String] cleaned string
|
607
|
+
##
|
608
|
+
def clean_value
|
609
|
+
sub(/^\(?\d+\.\)? +/, '').sub(/\((.*?)\)/, '\1')
|
610
|
+
end
|
611
|
+
|
496
612
|
##
|
497
613
|
## Highlight characters in parenthesis, with special color for default if
|
498
614
|
## provided. Output is color templated string, unprocessed.
|
data/lib/planter/version.rb
CHANGED
data/lib/planter.rb
CHANGED
data/spec/planter/string_spec.rb
CHANGED
@@ -114,6 +114,33 @@ describe ::String do
|
|
114
114
|
end
|
115
115
|
end
|
116
116
|
|
117
|
+
describe '.apply_logic' do
|
118
|
+
it 'applies a single logic replacement' do
|
119
|
+
template = 'Hello %%if language == ruby%%World%%else%%There%%end%%!'
|
120
|
+
logic = { language: 'ruby' }
|
121
|
+
expect(template.apply_logic(logic)).to eq 'Hello World!'
|
122
|
+
end
|
123
|
+
|
124
|
+
it 'handles quotes in logic' do
|
125
|
+
template = 'Hello %%if language == "ruby"%%World%%else%%There%%end%%!'
|
126
|
+
logic = { language: 'ruby' }
|
127
|
+
expect(template.apply_logic(logic)).to eq 'Hello World!'
|
128
|
+
end
|
129
|
+
|
130
|
+
it 'handles no logic replacements' do
|
131
|
+
template = 'Hello, World!'
|
132
|
+
logic = {}
|
133
|
+
expect(template.apply_logic(logic)).to eq 'Hello, World!'
|
134
|
+
end
|
135
|
+
|
136
|
+
it 'Operates in place' do
|
137
|
+
template = 'Hello %%if language == "ruby"%%World%%else%%There%%end%%!'
|
138
|
+
logic = { language: 'ruby' }
|
139
|
+
template.apply_logic!(logic)
|
140
|
+
expect(template).to eq 'Hello World!'
|
141
|
+
end
|
142
|
+
end
|
143
|
+
|
117
144
|
describe '.apply_regexes' do
|
118
145
|
it 'applies a single regex replacement' do
|
119
146
|
template = 'Hello, World!'
|
@@ -171,15 +198,15 @@ describe ::String do
|
|
171
198
|
end
|
172
199
|
|
173
200
|
it 'normalizes a multiline type' do
|
174
|
-
expect("
|
201
|
+
expect("para".normalize_type.to_s).to eq "multiline"
|
175
202
|
end
|
176
203
|
|
177
204
|
it 'normalizes a class type' do
|
178
|
-
expect("
|
205
|
+
expect("cl".normalize_type.to_s).to eq "class"
|
179
206
|
end
|
180
207
|
|
181
|
-
it 'normalizes a
|
182
|
-
expect("
|
208
|
+
it 'normalizes a multiple choice type' do
|
209
|
+
expect("choice".normalize_type.to_s).to eq "choice"
|
183
210
|
end
|
184
211
|
end
|
185
212
|
|
data/spec/spec_helper.rb
CHANGED
@@ -29,7 +29,7 @@ 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__), '..', 'exe', 'plant')
|
33
33
|
|
34
34
|
def planter_with_env(env, *args, stdin: nil)
|
35
35
|
pread(env, 'bundle', 'exec', PLANTER_EXEC, "--base-dir=#{File.dirname(__FILE__)}", *args, stdin: stdin)
|
data/src/_README.md
CHANGED
@@ -52,7 +52,7 @@ First, there's a `variables` section that defines variables used in the template
|
|
52
52
|
variables:
|
53
53
|
- key: var_key
|
54
54
|
prompt: Prompt text
|
55
|
-
type: string # [string,
|
55
|
+
type: string # [string,paragraph,float,integer,number,date,choice] defaults to string
|
56
56
|
# value: (force value, string can include %%variables%% and regexes will be replaced. For date type can be today, time, now, etc.)
|
57
57
|
default: Untitled
|
58
58
|
min: 1
|
@@ -69,6 +69,85 @@ repo: # If a repository URL is provided, it will be pulled and duplicated instea
|
|
69
69
|
|
70
70
|
In a template you can add a default value for a placholder by adding `%default value` to it. For example, `%%project%Default Project%%` will set the placeholder to `Default Project` if the variable value matches the default value in the configuration. This allows you to accept the default on the command line but have a different value inserted in the template. To use another variable in its place, use `$KEY` in the placeholder, e.g. `%%project%$title%%` will replace the `project` key with the value of `title` if the default is selected. Modifiers can be used on either side of the `%`, e.g. `%%project%$title:snake%%`.
|
71
71
|
|
72
|
+
#### Multiple choice type
|
73
|
+
|
74
|
+
If the `type` is set to `choice`, then the key `choices` can contain a hash or array of choices. The key that accepts the choice should be surrounded with parenthesis (required for each choice).
|
75
|
+
|
76
|
+
If a Hash is defined, each choice can have a result string:
|
77
|
+
|
78
|
+
```yaml
|
79
|
+
variables:
|
80
|
+
- key: shebang
|
81
|
+
prompt: Shebang line
|
82
|
+
type: choice
|
83
|
+
default: r
|
84
|
+
choices:
|
85
|
+
(r)uby: "#! /usr/bin/env ruby"
|
86
|
+
(j)avascript: "#! /usr/bin/env node"
|
87
|
+
(p)ython: "#! /usr/bin/env python"
|
88
|
+
(b)ash: "#! /bin/bash"
|
89
|
+
(z)sh: "#! /bin/zsh"
|
90
|
+
```
|
91
|
+
|
92
|
+
If an array is defined, the string of the choice will also be its result:
|
93
|
+
|
94
|
+
```yaml
|
95
|
+
variables:
|
96
|
+
- key: language
|
97
|
+
prompt: Programming language
|
98
|
+
type: choice
|
99
|
+
default: 1
|
100
|
+
choices:
|
101
|
+
- 1. ruby
|
102
|
+
- 1. javascript
|
103
|
+
- 1. python
|
104
|
+
- 1. bash
|
105
|
+
- 1. zsh
|
106
|
+
```
|
107
|
+
|
108
|
+
If the choice starts with a number (as above), then a numeric list will be generated and typing the associated index number will accept that choice. Numeric lists are automatically numbered, so the preceding digit doesn't matter, as long as it's a digit. In this case a default can be defined with an integer for its placement in the list (starting with 1), and parenthesis aren't required.
|
109
|
+
|
110
|
+
#### If/then logic
|
111
|
+
|
112
|
+
A template can use if/then logic, which is useful with multiple choice types. It can be applied to any type, though.
|
113
|
+
|
114
|
+
The format for if/then logic is:
|
115
|
+
|
116
|
+
```
|
117
|
+
%%if KEY OPERATOR VALUE%%
|
118
|
+
content
|
119
|
+
%%else if KEY OPERATOR VALUE2%%
|
120
|
+
content 2
|
121
|
+
%%else%%
|
122
|
+
content 3
|
123
|
+
%%endif%%
|
124
|
+
```
|
125
|
+
|
126
|
+
There should be no spaces around the comparison, e.g. `%% if language == javascript %%` won't work. The block must start with an `if` statement and end with `%%endif%%` or `%%end%%`. The `%%else%%` statement is optional -- if it doesn't exist then the entire block will be removed if no conditions are met.
|
127
|
+
|
128
|
+
The key should be an existing key defined in `variables`. The operator can be any of:
|
129
|
+
|
130
|
+
- `==` or `=` (equals)
|
131
|
+
- `=~` (matches regex)
|
132
|
+
- `*=` (contains)
|
133
|
+
- `^=` (starts with)
|
134
|
+
- `$=` (ends with)
|
135
|
+
- `>` (greater than)
|
136
|
+
- `>=` (greater than or equal)
|
137
|
+
- `<` (less than)
|
138
|
+
- `<=` (less than or equal)
|
139
|
+
|
140
|
+
The value after the operator doesn't need to be quoted, anything after the operator will be compared to the value of the key.
|
141
|
+
|
142
|
+
Logic can be used on multiple lines like the example above, or on a single line (useful for filenames):
|
143
|
+
|
144
|
+
```
|
145
|
+
%%project%%.%%if language == javascript%%js%%else if language == ruby%%rb%%else%%sh%%endif%%
|
146
|
+
```
|
147
|
+
|
148
|
+
Content within if/else blocks can contain variables.
|
149
|
+
|
150
|
+
|
72
151
|
### File-specific handling
|
73
152
|
|
74
153
|
A `files` dictionary can specify how to handle specific files. Options are `copy`, `overwrite`, `merge`, or `ask`. The key for each entry is a filename or glob that matches the source filename (accounting for template variables if applicable):
|
@@ -79,7 +158,7 @@ files:
|
|
79
158
|
"%%title%%.md": overwrite
|
80
159
|
```
|
81
160
|
|
82
|
-
Filenames can include wildcards (`*`, `?`), and Bash-
|
161
|
+
Filenames can include wildcards (`*`, `?`), and Bash-ish globbing (`[0-9]`, `[a-z]`, `{one,two,three}`).
|
83
162
|
|
84
163
|
If `merge` is specified, then the source file is scanned for merge comments and those are merged if they don't exist in the copied/existing file. If no merge comments are defined, then the entire contents of the source file are appended to the destination file (unless the file already matches the source). Merge comments start with `merge` and end with `/merge` and can have any comment syntax preceding them, for example:
|
85
164
|
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: planter-cli
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 3.0.
|
4
|
+
version: 3.0.3
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Brett Terpstra
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2024-09-
|
11
|
+
date: 2024-09-02 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: bump
|