planter-cli 3.0.1 → 3.0.3

Sign up to get free protection for your applications and to get access to all the features.
Files changed (109) hide show
  1. checksums.yaml +4 -4
  2. data/.gitignore +2 -0
  3. data/.gitmodules +3 -0
  4. data/.rubocop.yml +1 -2
  5. data/CHANGELOG.md +24 -0
  6. data/README.md +85 -4
  7. data/bin/plant +1 -1
  8. data/docker/Dockerfile +2 -4
  9. data/docker/Dockerfile-2.6 +4 -5
  10. data/docker/Dockerfile-2.7 +4 -5
  11. data/docker/Dockerfile-3.0 +4 -4
  12. data/docker/Dockerfile-3.3 +12 -0
  13. data/docker/bash_profile +2 -1
  14. data/docker/sources.list +11 -0
  15. data/lib/planter/array.rb +56 -1
  16. data/lib/planter/filelist.rb +5 -4
  17. data/lib/planter/hash.rb +24 -0
  18. data/lib/planter/plant.rb +6 -4
  19. data/lib/planter/prompt.rb +56 -16
  20. data/lib/planter/string.rb +143 -5
  21. data/lib/planter/tag.rb +39 -2
  22. data/lib/planter/version.rb +1 -1
  23. data/lib/planter.rb +30 -17
  24. data/lib/tty-spinner/.editorconfig +9 -0
  25. data/lib/tty-spinner/.github/FUNDING.yml +1 -0
  26. data/lib/tty-spinner/.github/ISSUE_TEMPLATE/BUG_REPORT.md +31 -0
  27. data/lib/tty-spinner/.github/ISSUE_TEMPLATE/FEATURE_REQUEST.md +23 -0
  28. data/lib/tty-spinner/.github/ISSUE_TEMPLATE/config.yml +5 -0
  29. data/lib/tty-spinner/.github/PULL_REQUEST_TEMPLATE.md +19 -0
  30. data/lib/tty-spinner/.github/workflows/ci.yml +59 -0
  31. data/lib/tty-spinner/.gitignore +14 -0
  32. data/lib/tty-spinner/.rspec +2 -0
  33. data/lib/tty-spinner/.rubocop.yml +78 -0
  34. data/lib/tty-spinner/CHANGELOG.md +151 -0
  35. data/lib/tty-spinner/CODE_OF_CONDUCT.md +132 -0
  36. data/lib/tty-spinner/Gemfile +17 -0
  37. data/lib/tty-spinner/LICENSE.txt +22 -0
  38. data/lib/tty-spinner/README.md +581 -0
  39. data/lib/tty-spinner/Rakefile +10 -0
  40. data/lib/tty-spinner/appveyor.yml +33 -0
  41. data/lib/tty-spinner/bin/console +14 -0
  42. data/lib/tty-spinner/bin/setup +8 -0
  43. data/lib/tty-spinner/demo.gif +0 -0
  44. data/lib/tty-spinner/examples/auto_spin.rb +10 -0
  45. data/lib/tty-spinner/examples/basic.rb +10 -0
  46. data/lib/tty-spinner/examples/clear.rb +11 -0
  47. data/lib/tty-spinner/examples/color.rb +14 -0
  48. data/lib/tty-spinner/examples/error.rb +11 -0
  49. data/lib/tty-spinner/examples/formats.rb +13 -0
  50. data/lib/tty-spinner/examples/hide_cursor.rb +14 -0
  51. data/lib/tty-spinner/examples/log.rb +13 -0
  52. data/lib/tty-spinner/examples/multi/basic.rb +15 -0
  53. data/lib/tty-spinner/examples/multi/basic_top_level.rb +15 -0
  54. data/lib/tty-spinner/examples/multi/custom_style.rb +28 -0
  55. data/lib/tty-spinner/examples/multi/files.rb +16 -0
  56. data/lib/tty-spinner/examples/multi/jobs.rb +11 -0
  57. data/lib/tty-spinner/examples/multi/multi.rb +19 -0
  58. data/lib/tty-spinner/examples/multi/multi_top_level.rb +20 -0
  59. data/lib/tty-spinner/examples/multi/pause.rb +28 -0
  60. data/lib/tty-spinner/examples/multi/threaded.rb +30 -0
  61. data/lib/tty-spinner/examples/pause.rb +24 -0
  62. data/lib/tty-spinner/examples/run.rb +20 -0
  63. data/lib/tty-spinner/examples/success.rb +11 -0
  64. data/lib/tty-spinner/examples/threaded.rb +13 -0
  65. data/lib/tty-spinner/examples/update.rb +13 -0
  66. data/lib/tty-spinner/lib/tty/spinner/formats.rb +274 -0
  67. data/lib/tty-spinner/lib/tty/spinner/multi.rb +352 -0
  68. data/lib/tty-spinner/lib/tty/spinner/version.rb +7 -0
  69. data/lib/tty-spinner/lib/tty/spinner.rb +604 -0
  70. data/lib/tty-spinner/lib/tty-spinner.rb +2 -0
  71. data/lib/tty-spinner/spec/spec_helper.rb +52 -0
  72. data/lib/tty-spinner/spec/unit/auto_spin_spec.rb +25 -0
  73. data/lib/tty-spinner/spec/unit/clear_spec.rb +16 -0
  74. data/lib/tty-spinner/spec/unit/error_spec.rb +53 -0
  75. data/lib/tty-spinner/spec/unit/events_spec.rb +35 -0
  76. data/lib/tty-spinner/spec/unit/formats_spec.rb +9 -0
  77. data/lib/tty-spinner/spec/unit/frames_spec.rb +31 -0
  78. data/lib/tty-spinner/spec/unit/hide_cursor_spec.rb +51 -0
  79. data/lib/tty-spinner/spec/unit/job_spec.rb +12 -0
  80. data/lib/tty-spinner/spec/unit/join_spec.rb +10 -0
  81. data/lib/tty-spinner/spec/unit/log_spec.rb +60 -0
  82. data/lib/tty-spinner/spec/unit/multi/auto_spin_spec.rb +32 -0
  83. data/lib/tty-spinner/spec/unit/multi/error_spec.rb +107 -0
  84. data/lib/tty-spinner/spec/unit/multi/line_inset_spec.rb +57 -0
  85. data/lib/tty-spinner/spec/unit/multi/on_spec.rb +11 -0
  86. data/lib/tty-spinner/spec/unit/multi/register_spec.rb +46 -0
  87. data/lib/tty-spinner/spec/unit/multi/spin_spec.rb +101 -0
  88. data/lib/tty-spinner/spec/unit/multi/stop_spec.rb +95 -0
  89. data/lib/tty-spinner/spec/unit/multi/success_spec.rb +108 -0
  90. data/lib/tty-spinner/spec/unit/new_spec.rb +25 -0
  91. data/lib/tty-spinner/spec/unit/pause_spec.rb +43 -0
  92. data/lib/tty-spinner/spec/unit/reset_spec.rb +19 -0
  93. data/lib/tty-spinner/spec/unit/run_spec.rb +30 -0
  94. data/lib/tty-spinner/spec/unit/spin_spec.rb +117 -0
  95. data/lib/tty-spinner/spec/unit/stop_spec.rb +88 -0
  96. data/lib/tty-spinner/spec/unit/success_spec.rb +53 -0
  97. data/lib/tty-spinner/spec/unit/tty_spec.rb +8 -0
  98. data/lib/tty-spinner/spec/unit/update_spec.rb +85 -0
  99. data/lib/tty-spinner/tasks/console.rake +11 -0
  100. data/lib/tty-spinner/tasks/coverage.rake +11 -0
  101. data/lib/tty-spinner/tasks/spec.rake +29 -0
  102. data/lib/tty-spinner/tty-spinner.gemspec +36 -0
  103. data/scripts/runtests.sh +1 -1
  104. data/spec/cli_spec.rb +27 -0
  105. data/spec/planter/string_spec.rb +31 -4
  106. data/spec/spec_helper.rb +26 -0
  107. data/spec/templates/test/_planter.yml +3 -6
  108. data/src/_README.md +85 -4
  109. metadata +86 -2
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 0b4906c83392316be6ac61380bd0d2401a847f4c08f6afc91bdfbd4858980568
4
- data.tar.gz: 7253e2ec572be45e7543d82ad2b1dde685fc5b8ba7b256ec914b0575e44ad507
3
+ metadata.gz: d811f48053b6544dc93bec282620300d673dc5524cc534e0b4fe59d194d09c19
4
+ data.tar.gz: 8da8c9f74029c96f7286f25c80be21a35d87237c2942db24a9864d881d04f509
5
5
  SHA512:
6
- metadata.gz: 98fd1c334e3d2efdcdc10171d7a5b5f81898e9a03890bc08cd65e2f3c8a10941cfe46e5e5b23584f28c87aad3b85471ab544c3665049c5abfd911006b40bfbe8
7
- data.tar.gz: a7a86edaf7bf49cb65cde877ab4aec8bd92a6f27a90c09f526e50f9f1ddc84c27655e67bfea75b9463c8033601ae1961e4854bce76f4e268bfe87aa3171c7677
6
+ metadata.gz: 73daffea9046af25fb1c1d1839eb622ca87ec43ab2d5aab4bbf6f593cb4ea24176f9f443aadf4178610d181eb4cec5276ed92cd7ca880f1d5a72ab364e397ab9
7
+ data.tar.gz: 104ca18f851112b4142f5ef6e5b31eb564d8e83be9de87f0cb1107a42e35f180adf23858896a599f3aeee7eff2683b90b03fe4ffc6ae8dc3945bc237ffffaf14
data/.gitignore CHANGED
@@ -45,3 +45,5 @@ Gemfile.lock
45
45
  .history
46
46
  spec/test/
47
47
  spec/noop
48
+ bundle
49
+ .vscode
data/.gitmodules ADDED
@@ -0,0 +1,3 @@
1
+ [submodule "lib/planter/tty-spinner"]
2
+ path = lib/planter/tty-spinner
3
+ url = git@github.com:piotrmurach/tty-spinner.git
data/.rubocop.yml CHANGED
@@ -11,7 +11,7 @@ AllCops:
11
11
  - Gemfile
12
12
  - Guardfile
13
13
  - Rakefile
14
- - bin/planter
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,27 @@
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
+
15
+ ### 3.0.2
16
+
17
+ 2024-09-01 09:46
18
+
19
+ #### IMPROVED
20
+
21
+ - Add basic CLI tests
22
+ - Better debug output and output of info messages above spinner
23
+ - Handle Bash-style globs in file: filenames
24
+
1
25
  ### 3.0.1
2
26
 
3
27
  2024-08-31 14:19
data/README.md CHANGED
@@ -27,9 +27,9 @@ git_init: false
27
27
  preserve_tags: true
28
28
  ```
29
29
 
30
- ### Scripts.
30
+ ### Scripts
31
31
 
32
- Scripts for execution after planting can be stored in `~/.config/planter/scripts` and referenced by filename only. Alternatively, scripts may be stored within a template in a `_scritps` subfolder.
32
+ Scripts for execution after planting can be stored in `~/.config/planter/scripts` and referenced by filename only. Alternatively, scripts may be stored within a template in a `_scripts` subfolder.
33
33
 
34
34
  Scripts can be executable files in any language, and receive the template directory and the planted directory as arguments $1 and $2.
35
35
 
@@ -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,multiline,float,integer,number,date] defaults to 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,6 +153,8 @@ files:
74
153
  "%%title%%.md": overwrite
75
154
  ```
76
155
 
156
+ Filenames can include wildcards (`*`, `?`), and Bash-ish globbing (`[0-9]`, `[a-z]`, `{one,two,three}`).
157
+
77
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:
78
159
 
79
160
  ```
@@ -104,7 +185,7 @@ replacements:
104
185
  "(main|app)\.js": "%%script:lower%%.js"
105
186
  ```
106
187
 
107
- Replacements are performed on both file/directory names and file contents.
188
+ Replacements are performed on both file/directory names and file contents. This is especially handy when the source of the plant is a Git repo, allowing the replacement of elements without having to create %%templated%% filenames and contents.
108
189
 
109
190
  ### Finder Tags
110
191
 
data/bin/plant CHANGED
@@ -105,7 +105,7 @@ elsif ARGV.count.zero?
105
105
  end
106
106
 
107
107
  ARGV.each do |template|
108
- Planter.spinner.update(title: 'Initializing configuration')
108
+ # Planter.spinner.update(title: 'Initializing configuration')
109
109
  Planter.config = template
110
110
  app = Planter::Plant.new
111
111
  app.plant
data/docker/Dockerfile CHANGED
@@ -1,12 +1,10 @@
1
1
  FROM ruby:3.0.1
2
- # RUN curl -sS https://dl.yarnpkg.com/debian/pubkey.gpg | apt-key add -
3
2
  RUN mkdir /planter
4
3
  WORKDIR /planter
5
-
6
4
  RUN gem install bundler:2.2.29
7
- RUN apt-get update -y
5
+ COPY ./docker/sources.list /etc/apt/sources.list
6
+ RUN apt-get update -y --allow-insecure-repositories || true
8
7
  RUN apt-get install -y less vim
9
8
  COPY ./docker/inputrc /root/.inputrc
10
9
  COPY ./docker/bash_profile /root/.bash_profile
11
- RUN mkdir -p /root/.config/planter/templates/test
12
10
  CMD ["/planter/scripts/runtests.sh"]
@@ -1,12 +1,11 @@
1
1
  FROM ruby:2.6
2
- # RUN curl -sS https://dl.yarnpkg.com/debian/pubkey.gpg | apt-key add -
3
2
  RUN mkdir /planter
4
3
  WORKDIR /planter
5
- # COPY ./ /planter/
6
4
  RUN gem install bundler:2.2.29
7
- # RUN apt-get update -y
8
- # RUN apt-get install -y less vim
5
+ COPY ./docker/sources.list /etc/apt/sources.list
6
+ RUN apt-get update -y --allow-insecure-repositories || true
7
+ RUN apt-get install -y sudo || true
8
+ RUN sudo apt-get install -y less vim || true
9
9
  COPY ./docker/inputrc /root/.inputrc
10
10
  COPY ./docker/bash_profile /root/.bash_profile
11
- RUN mkdir -p /root/.config/planter/templates/test
12
11
  CMD ["/planter/scripts/runtests.sh"]
@@ -1,12 +1,11 @@
1
1
  FROM ruby:2.7
2
- # RUN curl -sS https://dl.yarnpkg.com/debian/pubkey.gpg | apt-key add -
3
2
  RUN mkdir /planter
4
3
  WORKDIR /planter
5
- # COPY ./ /planter/
6
4
  RUN gem install bundler:2.2.29
7
- RUN apt-get update -y
8
- RUN apt-get install -y less vim
5
+ COPY ./docker/sources.list /etc/apt/sources.list
6
+ RUN apt-get update -y --allow-insecure-repositories || true
7
+ RUN apt-get install -y sudo || true
8
+ RUN sudo apt-get install -y less vim || true
9
9
  COPY ./docker/inputrc /root/.inputrc
10
10
  COPY ./docker/bash_profile /root/.bash_profile
11
- RUN mkdir -p /root/.config/planter/templates/test
12
11
  CMD ["/planter/scripts/runtests.sh"]
@@ -1,11 +1,11 @@
1
1
  FROM ruby:3.0.0
2
- # RUN curl -sS https://dl.yarnpkg.com/debian/pubkey.gpg | apt-key add -
3
2
  RUN mkdir /planter
4
3
  WORKDIR /planter
5
- # COPY ./ /planter/
6
4
  RUN gem install bundler:2.2.29
7
- RUN apt-get update -y
8
- RUN apt-get install -y less vim
5
+ COPY ./docker/sources.list /etc/apt/sources.list
6
+ RUN apt-get update -y --allow-insecure-repositories || true
7
+ RUN apt-get install -y sudo || true
8
+ RUN sudo apt-get install -y less vim || true
9
9
  COPY ./docker/inputrc /root/.inputrc
10
10
  COPY ./docker/bash_profile /root/.bash_profile
11
11
  CMD ["/planter/scripts/runtests.sh"]
@@ -0,0 +1,12 @@
1
+ FROM ruby:3.3.0
2
+ # RUN curl -sS https://dl.yarnpkg.com/debian/pubkey.gpg | apt-key add -
3
+ RUN mkdir /planter
4
+ WORKDIR /planter
5
+ RUN gem install bundler:2.2.29
6
+ COPY ./docker/sources.list /etc/apt/sources.list
7
+ RUN apt-get update -y --allow-insecure-repositories || true
8
+ RUN apt-get install -y sudo || true
9
+ RUN sudo apt-get install -y less vim || true
10
+ COPY ./docker/inputrc /root/.inputrc
11
+ COPY ./docker/bash_profile /root/.bash_profile
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 install
15
+ bundle update
15
16
  gem install pkg/*.gem
@@ -0,0 +1,11 @@
1
+ deb http://archive.ubuntu.com/ubuntu/ focal main restricted
2
+ deb http://archive.ubuntu.com/ubuntu/ focal-updates main restricted
3
+ deb http://archive.ubuntu.com/ubuntu/ focal universe
4
+ deb http://archive.ubuntu.com/ubuntu/ focal-updates universe
5
+ deb http://archive.ubuntu.com/ubuntu/ focal multiverse
6
+ deb http://archive.ubuntu.com/ubuntu/ focal-updates multiverse
7
+ deb http://archive.ubuntu.com/ubuntu/ focal-backports main restricted universe multiverse
8
+
9
+ deb http://security.ubuntu.com/ubuntu focal-security main restricted
10
+ deb http://security.ubuntu.com/ubuntu focal-security universe
11
+ deb http://security.ubuntu.com/ubuntu focal-security multiversesudo apt update
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
  ##
@@ -39,7 +39,7 @@ module Planter
39
39
  handle_operator(file)
40
40
  end
41
41
  rescue StandardError => e
42
- Planter.notify("#{e}\n#{e.backtrace}", :debug)
42
+ Planter.notify("#{e}\n#{e.backtrace}", :debug, above_spinner: true)
43
43
  Planter.notify('Error copying files/directories', :error, exit_code: 128)
44
44
  end
45
45
 
@@ -141,7 +141,8 @@ module Planter
141
141
  # If there are any merge sections left, merge them with the target file
142
142
  if merges.count.positive?
143
143
  File.open(entry.target, 'w') { |f| f.puts "#{target_content.chomp}\n\n#{merges.join("\n\n")}" }
144
- Planter.notify("Merged #{entry.file} => #{entry.target} (#{merges.count} merges)", :debug)
144
+ Planter.notify("[Merged] #{entry.file} => #{entry.target} (#{merges.count} merges)", :debug,
145
+ above_spinner: true)
145
146
  else
146
147
  # If there are no merge sections left, copy the file instead
147
148
  copy_file(entry)
@@ -166,12 +167,12 @@ module Planter
166
167
  # Copy the file if it isn't a directory
167
168
  FileUtils.cp(file.file, file.target) unless File.directory?(file.file)
168
169
  # Log a message to the console
169
- Planter.notify("Copied #{file.file} => #{file.target}", :debug)
170
+ Planter.notify("[Copied] #{file.file} => #{file.target}", :debug, above_spinner: true)
170
171
  # Return true to indicate success
171
172
  true
172
173
  else
173
174
  # Log a message to the console
174
- Planter.notify("Skipped #{file.file} => #{file.target}", :debug)
175
+ Planter.notify("[Skipped] #{file.file} => #{file.target}", :debug, above_spinner: true)
175
176
  # Return false to indicate that the copy was skipped
176
177
  false
177
178
  end
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(' Planting complete!')
162
+ Planter.spinner.update(title: 'Planting complete!')
163
+ Planter.spinner.success
163
164
  end
164
165
 
165
166
  ##
@@ -182,7 +183,8 @@ module Planter
182
183
  next if File.binary?(file)
183
184
 
184
185
  content = IO.read(file)
185
- new_content = content.apply_variables.apply_regexes
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
 
@@ -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
@@ -54,10 +57,10 @@ module Planter
54
57
  else
55
58
  read_line
56
59
  end
57
- Planter.notify("{dw}#{prompt}: {dy}#{res}{x}", :debug)
60
+ Planter.notify("{dw}#{prompt} => {dy}#{res}{x}", :debug, newline: false)
58
61
  res
59
62
  rescue TTY::Reader::InputInterrupt
60
- raise Errors::InputError('Canceled')
63
+ raise Errors::InputError.new('Canceled')
61
64
  end
62
65
 
63
66
  private
@@ -116,7 +119,7 @@ module Planter
116
119
  default = date_default
117
120
 
118
121
  default = default ? " {bw}[#{default}]" : ''
119
- Planter.notify("{by}#{prompt} (natural language)#{default}")
122
+ Planter.notify("{by}#{prompt} (natural language)#{default}", newline: false)
120
123
  line = @gum ? read_line_gum : read_line_tty
121
124
  return default unless line
122
125
 
@@ -135,7 +138,7 @@ module Planter
135
138
  def read_line(prompt: nil)
136
139
  prompt ||= @prompt
137
140
  default = @default ? " {bw}[#{@default}]" : ''
138
- Planter.notify("{by}#{prompt}#{default}")
141
+ Planter.notify("{by}#{prompt}#{default}", newline: false)
139
142
 
140
143
  res = @gum ? read_line_gum : read_line_tty
141
144
 
@@ -156,7 +159,7 @@ module Planter
156
159
  def read_lines(prompt: nil)
157
160
  prompt ||= @prompt
158
161
  save = @gum ? 'Ctrl-J for newline, Enter' : 'Ctrl-D'
159
- Planter.notify("{by}#{prompt} {xc}({bw}#{save}{xc} to save)'")
162
+ Planter.notify("{by}#{prompt} {xc}({bw}#{save}{xc} to save)'", newline: false)
160
163
  res = @gum ? read_multiline_gum(prompt) : read_mutliline_tty
161
164
 
162
165
  return @default unless res
@@ -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] character of selected response, lowercased
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
- default = default_response.is_a?(String) ? default_response.downcase : nil
249
+ choices = choices.choices_to_hash if choices.is_a?(Array) && choices.first.is_a?(Hash)
247
250
 
248
- # if this isn't an interactive shell, answer default
249
- return default unless $stdout.isatty
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
- # If --defaults is set, return default
252
- return default if Planter.accept_defaults || ENV['PLANTER_DEBUG']
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
- desc = choices.map { |c| c.highlight_character(default: default) }
264
- abbr = choices.abbr_choices(default: default)
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
- res = $stdin.sysread 1
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)