create-ruby-gem 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/CHANGELOG.md +22 -0
- data/LICENSE.txt +21 -0
- data/README.md +150 -0
- data/exe/create-ruby-gem +13 -0
- data/lib/create_ruby_gem/cli.rb +360 -0
- data/lib/create_ruby_gem/command_builder.rb +54 -0
- data/lib/create_ruby_gem/compatibility/matrix.rb +118 -0
- data/lib/create_ruby_gem/config/store.rb +125 -0
- data/lib/create_ruby_gem/detection/bundler_defaults.rb +88 -0
- data/lib/create_ruby_gem/detection/bundler_version.rb +31 -0
- data/lib/create_ruby_gem/detection/runtime.rb +33 -0
- data/lib/create_ruby_gem/error.rb +15 -0
- data/lib/create_ruby_gem/options/catalog.rb +49 -0
- data/lib/create_ruby_gem/options/validator.rb +102 -0
- data/lib/create_ruby_gem/runner.rb +39 -0
- data/lib/create_ruby_gem/ui/back_navigation_patch.rb +41 -0
- data/lib/create_ruby_gem/ui/palette.rb +73 -0
- data/lib/create_ruby_gem/ui/prompter.rb +77 -0
- data/lib/create_ruby_gem/version.rb +6 -0
- data/lib/create_ruby_gem/wizard.rb +339 -0
- data/lib/create_ruby_gem.rb +29 -0
- metadata +96 -0
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module CreateRubyGem
|
|
4
|
+
module UI
|
|
5
|
+
# Color constants for terminal output.
|
|
6
|
+
#
|
|
7
|
+
# Maps semantic roles (e.g. +:summary_label+, +:arg_name+) to ANSI 256-color
|
|
8
|
+
# codes or +cli-ui+ basic color names, depending on terminal capabilities.
|
|
9
|
+
class Palette
|
|
10
|
+
# ANSI reset sequence.
|
|
11
|
+
RESET = "\e[0m"
|
|
12
|
+
|
|
13
|
+
# Role-to-color mappings for 256-color terminals.
|
|
14
|
+
#
|
|
15
|
+
# @return [Hash{Symbol => Integer}]
|
|
16
|
+
ROLE_COLORS_256 = {
|
|
17
|
+
control_back: 45,
|
|
18
|
+
control_exit: 203,
|
|
19
|
+
summary_label: 213,
|
|
20
|
+
runtime_name: 111,
|
|
21
|
+
runtime_value: 190,
|
|
22
|
+
command_base: 39,
|
|
23
|
+
command_gem: 82,
|
|
24
|
+
arg_name: 117,
|
|
25
|
+
arg_eq: 250,
|
|
26
|
+
arg_value: 214
|
|
27
|
+
}.freeze
|
|
28
|
+
|
|
29
|
+
# Role-to-color mappings for basic (8/16-color) terminals.
|
|
30
|
+
#
|
|
31
|
+
# @return [Hash{Symbol => String}]
|
|
32
|
+
ROLE_COLORS_BASIC = {
|
|
33
|
+
control_back: 'cyan',
|
|
34
|
+
control_exit: 'red',
|
|
35
|
+
summary_label: 'magenta',
|
|
36
|
+
runtime_name: 'blue',
|
|
37
|
+
runtime_value: 'green',
|
|
38
|
+
command_base: 'blue',
|
|
39
|
+
command_gem: 'green',
|
|
40
|
+
arg_name: 'blue',
|
|
41
|
+
arg_eq: 'white',
|
|
42
|
+
arg_value: 'yellow'
|
|
43
|
+
}.freeze
|
|
44
|
+
|
|
45
|
+
# @param env [Hash] environment variables (defaults to +ENV+)
|
|
46
|
+
def initialize(env: ENV)
|
|
47
|
+
@env = env
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
# Wraps text in the appropriate ANSI color for the given role.
|
|
51
|
+
#
|
|
52
|
+
# @param role [Symbol] a key from {ROLE_COLORS_256}/{ROLE_COLORS_BASIC}
|
|
53
|
+
# @param text [String] the text to colorize
|
|
54
|
+
# @return [String]
|
|
55
|
+
def color(role, text)
|
|
56
|
+
if supports_256_colors?
|
|
57
|
+
"\e[38;5;#{ROLE_COLORS_256.fetch(role)}m#{text}#{RESET}"
|
|
58
|
+
else
|
|
59
|
+
"{{#{ROLE_COLORS_BASIC.fetch(role)}:#{text}}}"
|
|
60
|
+
end
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
private
|
|
64
|
+
|
|
65
|
+
# @return [Boolean]
|
|
66
|
+
def supports_256_colors?
|
|
67
|
+
term = @env.fetch('TERM', '')
|
|
68
|
+
colorterm = @env.fetch('COLORTERM', '')
|
|
69
|
+
term.include?('256color') || colorterm.match?(/truecolor|24bit|256/i)
|
|
70
|
+
end
|
|
71
|
+
end
|
|
72
|
+
end
|
|
73
|
+
end
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'cli/ui'
|
|
4
|
+
require_relative 'back_navigation_patch'
|
|
5
|
+
|
|
6
|
+
module CreateRubyGem
|
|
7
|
+
module UI
|
|
8
|
+
# Thin wrapper around +cli-ui+ for all user interaction.
|
|
9
|
+
#
|
|
10
|
+
# Every prompt the wizard issues goes through this class, making it
|
|
11
|
+
# easy to inject a test double.
|
|
12
|
+
class Prompter
|
|
13
|
+
# Enables the +cli-ui+ stdout router and applies the Ctrl+B patch.
|
|
14
|
+
#
|
|
15
|
+
# Call once before creating a Prompter instance. Idempotent.
|
|
16
|
+
#
|
|
17
|
+
# @return [void]
|
|
18
|
+
def self.setup!
|
|
19
|
+
::CLI::UI::StdoutRouter.enable
|
|
20
|
+
BackNavigationPatch.apply!
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
# @param out [IO] output stream
|
|
24
|
+
def initialize(out: $stdout)
|
|
25
|
+
@out = out
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
# Opens a visual frame with a title.
|
|
29
|
+
#
|
|
30
|
+
# @param title [String]
|
|
31
|
+
# @yield block executed inside the frame
|
|
32
|
+
# @return [void]
|
|
33
|
+
def frame(title, &)
|
|
34
|
+
::CLI::UI::Frame.open(title, &)
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
# Presents a single-choice list.
|
|
38
|
+
#
|
|
39
|
+
# @param question [String]
|
|
40
|
+
# @param options [Array<String>]
|
|
41
|
+
# @param default [String, nil]
|
|
42
|
+
# @return [String] selected option, or {Wizard::BACK} on Ctrl+B
|
|
43
|
+
def choose(question, options:, default: nil)
|
|
44
|
+
::CLI::UI.ask(question, options: options, default: default, filter_ui: false)
|
|
45
|
+
rescue BackKeyPressed
|
|
46
|
+
Wizard::BACK
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
# Prompts for free-text input.
|
|
50
|
+
#
|
|
51
|
+
# @param question [String]
|
|
52
|
+
# @param default [String, nil]
|
|
53
|
+
# @param allow_empty [Boolean]
|
|
54
|
+
# @return [String]
|
|
55
|
+
def text(question, default: nil, allow_empty: true)
|
|
56
|
+
::CLI::UI.ask(question, default: default, allow_empty: allow_empty)
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
# Prompts for yes/no confirmation.
|
|
60
|
+
#
|
|
61
|
+
# @param question [String]
|
|
62
|
+
# @param default [Boolean]
|
|
63
|
+
# @return [Boolean]
|
|
64
|
+
def confirm(question, default: true)
|
|
65
|
+
::CLI::UI.confirm(question, default: default)
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
# Prints a formatted message to the output stream.
|
|
69
|
+
#
|
|
70
|
+
# @param message [String] message with optional +cli-ui+ formatting tags
|
|
71
|
+
# @return [void]
|
|
72
|
+
def say(message)
|
|
73
|
+
@out.puts(::CLI::UI.fmt(message))
|
|
74
|
+
end
|
|
75
|
+
end
|
|
76
|
+
end
|
|
77
|
+
end
|
|
@@ -0,0 +1,339 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module CreateRubyGem
|
|
4
|
+
# Step-by-step interactive prompt loop for choosing +bundle gem+ options.
|
|
5
|
+
#
|
|
6
|
+
# Walks through each supported option in {Options::Catalog::ORDER},
|
|
7
|
+
# presenting only the options supported by the detected Bundler version.
|
|
8
|
+
# Supports back-navigation via +Ctrl+B+.
|
|
9
|
+
#
|
|
10
|
+
# @see CLI#run_interactive_wizard!
|
|
11
|
+
class Wizard
|
|
12
|
+
# Sentinel returned by the prompter when the user presses Ctrl+B.
|
|
13
|
+
BACK = Object.new.freeze
|
|
14
|
+
|
|
15
|
+
# Sentinel indicating the user wants Bundler's built-in default.
|
|
16
|
+
BUNDLER_DEFAULT = Object.new.freeze
|
|
17
|
+
|
|
18
|
+
# Human-readable labels for each option key.
|
|
19
|
+
#
|
|
20
|
+
# @return [Hash{Symbol => String}]
|
|
21
|
+
LABELS = {
|
|
22
|
+
exe: 'Create executable',
|
|
23
|
+
coc: 'Add CODE_OF_CONDUCT.md',
|
|
24
|
+
changelog: 'Add CHANGELOG.md',
|
|
25
|
+
ext: 'Native extension',
|
|
26
|
+
git: 'Initialize git',
|
|
27
|
+
github_username: 'GitHub username',
|
|
28
|
+
mit: 'Include MIT license',
|
|
29
|
+
test: 'Test framework',
|
|
30
|
+
ci: 'CI provider',
|
|
31
|
+
linter: 'Linter',
|
|
32
|
+
edit: 'Editor command',
|
|
33
|
+
bundle_install: 'Run bundle install'
|
|
34
|
+
}.freeze
|
|
35
|
+
|
|
36
|
+
# Short explanations shown below each wizard step.
|
|
37
|
+
#
|
|
38
|
+
# @return [Hash{Symbol => String}]
|
|
39
|
+
HELP_TEXT = {
|
|
40
|
+
exe: 'Adds an executable file in exe/ so users can run your gem as a command.',
|
|
41
|
+
coc: 'Adds a code of conduct template for contributors.',
|
|
42
|
+
changelog: 'Adds CHANGELOG.md to track release notes.',
|
|
43
|
+
ext: 'Sets up native extension scaffolding for C, Go, or Rust.',
|
|
44
|
+
git: 'Initializes a git repository for the new gem.',
|
|
45
|
+
github_username: 'Used in links and metadata for your GitHub account.',
|
|
46
|
+
mit: 'Adds the MIT license file.',
|
|
47
|
+
test: 'Chooses which test framework files to generate.',
|
|
48
|
+
ci: 'Chooses CI pipeline config to include.',
|
|
49
|
+
linter: 'Chooses linting setup for style and quality checks.',
|
|
50
|
+
edit: 'Sets your preferred command for opening files.',
|
|
51
|
+
bundle_install: 'Runs bundle install after generating the gem.'
|
|
52
|
+
}.freeze
|
|
53
|
+
|
|
54
|
+
# Per-choice hints displayed next to enum/string choices.
|
|
55
|
+
#
|
|
56
|
+
# @return [Hash{Symbol => Hash{String => String}}]
|
|
57
|
+
CHOICE_HELP = {
|
|
58
|
+
ext: {
|
|
59
|
+
'c' => 'classic native extension path',
|
|
60
|
+
'go' => 'Go-based extension via FFI/tooling',
|
|
61
|
+
'rust' => 'Rust extension path',
|
|
62
|
+
'none' => 'no native extension'
|
|
63
|
+
},
|
|
64
|
+
test: {
|
|
65
|
+
'minitest' => 'small built-in Ruby test style',
|
|
66
|
+
'rspec' => 'popular behavior-style testing',
|
|
67
|
+
'test-unit' => 'xUnit-style test framework',
|
|
68
|
+
'none' => 'no test framework files'
|
|
69
|
+
},
|
|
70
|
+
ci: {
|
|
71
|
+
'circle' => 'CircleCI config',
|
|
72
|
+
'github' => 'GitHub Actions workflow',
|
|
73
|
+
'gitlab' => 'GitLab CI pipeline',
|
|
74
|
+
'none' => 'no CI config'
|
|
75
|
+
},
|
|
76
|
+
linter: {
|
|
77
|
+
'rubocop' => 'full-featured Ruby linting',
|
|
78
|
+
'standard' => 'zero-config style linting',
|
|
79
|
+
'none' => 'no linter config'
|
|
80
|
+
},
|
|
81
|
+
github_username: {
|
|
82
|
+
'set' => 'enter a value now',
|
|
83
|
+
'none' => 'leave unset'
|
|
84
|
+
},
|
|
85
|
+
edit: {
|
|
86
|
+
'set' => 'enter a value now',
|
|
87
|
+
'none' => 'leave unset'
|
|
88
|
+
}
|
|
89
|
+
}.freeze
|
|
90
|
+
|
|
91
|
+
# @param compatibility_entry [Compatibility::Matrix::Entry]
|
|
92
|
+
# @param defaults [Hash{Symbol => Object}] initial default values (e.g. last-used)
|
|
93
|
+
# @param prompter [UI::Prompter]
|
|
94
|
+
# @param bundler_defaults [Hash{Symbol => Object}] Bundler's own defaults
|
|
95
|
+
def initialize(compatibility_entry:, defaults:, prompter:, bundler_defaults: {})
|
|
96
|
+
@compatibility_entry = compatibility_entry
|
|
97
|
+
@bundler_defaults = symbolize_keys(bundler_defaults)
|
|
98
|
+
@prompter = prompter
|
|
99
|
+
@values = sanitize_defaults(defaults)
|
|
100
|
+
end
|
|
101
|
+
|
|
102
|
+
# Runs the wizard and returns the selected options.
|
|
103
|
+
#
|
|
104
|
+
# @return [Hash{Symbol => Object}]
|
|
105
|
+
def run
|
|
106
|
+
keys = Options::Catalog::ORDER.select { |key| @compatibility_entry.supports_option?(key) }
|
|
107
|
+
index = 0
|
|
108
|
+
while index < keys.length
|
|
109
|
+
key = keys[index]
|
|
110
|
+
answer = ask_for(key, index:, total: keys.length)
|
|
111
|
+
case answer
|
|
112
|
+
when BACK
|
|
113
|
+
index -= 1 if index.positive?
|
|
114
|
+
else
|
|
115
|
+
assign_value(key, answer)
|
|
116
|
+
index += 1
|
|
117
|
+
end
|
|
118
|
+
end
|
|
119
|
+
@values.dup
|
|
120
|
+
end
|
|
121
|
+
|
|
122
|
+
private
|
|
123
|
+
|
|
124
|
+
# @param key [Symbol]
|
|
125
|
+
# @param index [Integer]
|
|
126
|
+
# @param total [Integer]
|
|
127
|
+
# @return [Object] user answer or {BACK}
|
|
128
|
+
def ask_for(key, index:, total:)
|
|
129
|
+
definition = Options::Catalog.fetch(key)
|
|
130
|
+
label = LABELS.fetch(key)
|
|
131
|
+
question = render_question(index: index, total: total, key: key, label: label)
|
|
132
|
+
case definition[:type]
|
|
133
|
+
when :toggle
|
|
134
|
+
ask_toggle(question, key)
|
|
135
|
+
when :flag
|
|
136
|
+
ask_flag(question, key)
|
|
137
|
+
when :enum
|
|
138
|
+
ask_enum(question, key, definition)
|
|
139
|
+
when :string
|
|
140
|
+
ask_string(question, key)
|
|
141
|
+
else
|
|
142
|
+
raise Error, "Unknown option type for #{key}"
|
|
143
|
+
end
|
|
144
|
+
end
|
|
145
|
+
|
|
146
|
+
# @param question [String]
|
|
147
|
+
# @param key [Symbol]
|
|
148
|
+
# @return [true, false, nil, String]
|
|
149
|
+
def ask_toggle(question, key)
|
|
150
|
+
choices = %w[yes no]
|
|
151
|
+
current = @values[key]
|
|
152
|
+
default_choice = toggle_default_choice(current, key)
|
|
153
|
+
answer = choose_with_default_marker(question, key:, choices:, default_choice:)
|
|
154
|
+
return BACK if answer == BACK
|
|
155
|
+
return true if answer == 'yes'
|
|
156
|
+
return false if answer == 'no'
|
|
157
|
+
|
|
158
|
+
nil
|
|
159
|
+
end
|
|
160
|
+
|
|
161
|
+
# @param question [String]
|
|
162
|
+
# @param key [Symbol]
|
|
163
|
+
# @return [true, String, nil]
|
|
164
|
+
def ask_flag(question, key)
|
|
165
|
+
choices = %w[yes no]
|
|
166
|
+
current = @values[key]
|
|
167
|
+
default_choice = current == true ? 'yes' : flag_default_choice(key)
|
|
168
|
+
answer = choose_with_default_marker(question, key:, choices:, default_choice:)
|
|
169
|
+
return BACK if answer == BACK
|
|
170
|
+
return true if answer == 'yes'
|
|
171
|
+
return BUNDLER_DEFAULT if answer == 'no'
|
|
172
|
+
|
|
173
|
+
nil
|
|
174
|
+
end
|
|
175
|
+
|
|
176
|
+
# @param question [String]
|
|
177
|
+
# @param key [Symbol]
|
|
178
|
+
# @param definition [Hash]
|
|
179
|
+
# @return [String, false, String]
|
|
180
|
+
def ask_enum(question, key, definition)
|
|
181
|
+
choices = @compatibility_entry.allowed_values(key).dup
|
|
182
|
+
choices << 'none' if definition[:none]
|
|
183
|
+
current = @values[key]
|
|
184
|
+
default_choice = enum_default_choice(key, current, choices)
|
|
185
|
+
answer = choose_with_default_marker(question, key:, choices:, default_choice:)
|
|
186
|
+
return BACK if answer == BACK
|
|
187
|
+
return false if answer == 'none'
|
|
188
|
+
|
|
189
|
+
answer
|
|
190
|
+
end
|
|
191
|
+
|
|
192
|
+
# @param question [String]
|
|
193
|
+
# @param key [Symbol]
|
|
194
|
+
# @return [String, nil, String]
|
|
195
|
+
def ask_string(question, key)
|
|
196
|
+
has_current = @values[key].is_a?(String) && !@values[key].empty?
|
|
197
|
+
choices =
|
|
198
|
+
if has_current
|
|
199
|
+
%w[keep set]
|
|
200
|
+
else
|
|
201
|
+
%w[set none]
|
|
202
|
+
end
|
|
203
|
+
default_choice = has_current ? 'keep' : 'none'
|
|
204
|
+
answer = choose_with_default_marker(question, key:, choices:, default_choice:)
|
|
205
|
+
return BACK if answer == BACK
|
|
206
|
+
return BUNDLER_DEFAULT if answer == 'none'
|
|
207
|
+
return @values[key] if answer == 'keep'
|
|
208
|
+
|
|
209
|
+
default_value = key == :github_username ? nil : @values[key]
|
|
210
|
+
allow_empty = key != :github_username
|
|
211
|
+
value = @prompter.text("#{LABELS.fetch(key)}:", default: default_value, allow_empty: allow_empty)
|
|
212
|
+
value.empty? ? nil : value
|
|
213
|
+
end
|
|
214
|
+
|
|
215
|
+
# @param key [Symbol]
|
|
216
|
+
# @param value [Object]
|
|
217
|
+
# @return [void]
|
|
218
|
+
def assign_value(key, value)
|
|
219
|
+
if value.nil? || value == BUNDLER_DEFAULT
|
|
220
|
+
@values.delete(key)
|
|
221
|
+
else
|
|
222
|
+
@values[key] = value
|
|
223
|
+
end
|
|
224
|
+
end
|
|
225
|
+
|
|
226
|
+
# @param hash [Hash]
|
|
227
|
+
# @return [Hash{Symbol => Object}]
|
|
228
|
+
def symbolize_keys(hash)
|
|
229
|
+
hash.transform_keys(&:to_sym)
|
|
230
|
+
end
|
|
231
|
+
|
|
232
|
+
# Filters defaults to only supported options and removes false flags.
|
|
233
|
+
#
|
|
234
|
+
# @param hash [Hash]
|
|
235
|
+
# @return [Hash{Symbol => Object}]
|
|
236
|
+
def sanitize_defaults(hash)
|
|
237
|
+
symbolize_keys(hash)
|
|
238
|
+
.select { |key, _| @compatibility_entry.supports_option?(key) }
|
|
239
|
+
.each_with_object({}) do |(key, value), acc|
|
|
240
|
+
definition = Options::Catalog.fetch(key)
|
|
241
|
+
acc[key] = value unless definition[:type] == :flag && value == false
|
|
242
|
+
end
|
|
243
|
+
end
|
|
244
|
+
|
|
245
|
+
# @param current [Object]
|
|
246
|
+
# @param key [Symbol]
|
|
247
|
+
# @return [String]
|
|
248
|
+
def toggle_default_choice(current, key)
|
|
249
|
+
return 'yes' if current == true
|
|
250
|
+
return 'no' if current == false
|
|
251
|
+
|
|
252
|
+
@bundler_defaults[key] == true ? 'yes' : 'no'
|
|
253
|
+
end
|
|
254
|
+
|
|
255
|
+
# @param key [Symbol]
|
|
256
|
+
# @return [String]
|
|
257
|
+
def flag_default_choice(key)
|
|
258
|
+
@bundler_defaults[key] == true ? 'yes' : 'no'
|
|
259
|
+
end
|
|
260
|
+
|
|
261
|
+
# @param key [Symbol]
|
|
262
|
+
# @param current [Object]
|
|
263
|
+
# @param choices [Array<String>]
|
|
264
|
+
# @return [String]
|
|
265
|
+
def enum_default_choice(key, current, choices)
|
|
266
|
+
return 'none' if current == false && choices.include?('none')
|
|
267
|
+
return current if current.is_a?(String) && choices.include?(current)
|
|
268
|
+
|
|
269
|
+
bundler_default = @bundler_defaults[key]
|
|
270
|
+
return 'none' if bundler_default == false && choices.include?('none')
|
|
271
|
+
return bundler_default if bundler_default.is_a?(String) && choices.include?(bundler_default)
|
|
272
|
+
|
|
273
|
+
choices.first
|
|
274
|
+
end
|
|
275
|
+
|
|
276
|
+
# Presents a choice list with the default marked and reordered first.
|
|
277
|
+
#
|
|
278
|
+
# @param question [String]
|
|
279
|
+
# @param key [Symbol]
|
|
280
|
+
# @param choices [Array<String>]
|
|
281
|
+
# @param default_choice [String]
|
|
282
|
+
# @return [String] the raw choice value, or {BACK}
|
|
283
|
+
def choose_with_default_marker(question, key:, choices:, default_choice:)
|
|
284
|
+
selected_default = choices.include?(default_choice) ? default_choice : choices.first
|
|
285
|
+
ordered_choices = reorder_default_first(choices, selected_default)
|
|
286
|
+
rendered_pairs = ordered_choices.map do |choice|
|
|
287
|
+
[render_choice_label(key, choice, default_choice: selected_default), choice]
|
|
288
|
+
end
|
|
289
|
+
rendered = rendered_pairs.map(&:first)
|
|
290
|
+
answer = @prompter.choose(question, options: rendered, default: rendered.first)
|
|
291
|
+
return BACK if answer == BACK
|
|
292
|
+
|
|
293
|
+
rendered_index = rendered.index(answer)
|
|
294
|
+
return ordered_choices[rendered_index] if rendered_index
|
|
295
|
+
|
|
296
|
+
selected_default
|
|
297
|
+
end
|
|
298
|
+
|
|
299
|
+
# @param choices [Array<String>]
|
|
300
|
+
# @param default_choice [String]
|
|
301
|
+
# @return [Array<String>]
|
|
302
|
+
def reorder_default_first(choices, default_choice)
|
|
303
|
+
return choices.dup unless choices.include?(default_choice)
|
|
304
|
+
|
|
305
|
+
[default_choice] + choices.reject { |choice| choice == default_choice }
|
|
306
|
+
end
|
|
307
|
+
|
|
308
|
+
# @param index [Integer]
|
|
309
|
+
# @param total [Integer]
|
|
310
|
+
# @param key [Symbol]
|
|
311
|
+
# @param label [String]
|
|
312
|
+
# @return [String]
|
|
313
|
+
def render_question(index:, total:, key:, label:)
|
|
314
|
+
step = format('%<current>02d/%<total>02d', current: index + 1, total: total)
|
|
315
|
+
"{{cyan:#{step}}} {{bold:#{label}}} - #{HELP_TEXT.fetch(key)}"
|
|
316
|
+
end
|
|
317
|
+
|
|
318
|
+
# @param key [Symbol]
|
|
319
|
+
# @param choice [String]
|
|
320
|
+
# @param default_choice [String]
|
|
321
|
+
# @return [String]
|
|
322
|
+
def render_choice_label(key, choice, default_choice:)
|
|
323
|
+
label = choice
|
|
324
|
+
hint = choice_hint(key, choice)
|
|
325
|
+
label = "#{label} - #{hint}" if hint
|
|
326
|
+
label = "#{label} (default)" if choice == default_choice
|
|
327
|
+
label
|
|
328
|
+
end
|
|
329
|
+
|
|
330
|
+
# @param key [Symbol]
|
|
331
|
+
# @param choice [String]
|
|
332
|
+
# @return [String, nil]
|
|
333
|
+
def choice_hint(key, choice)
|
|
334
|
+
return "use #{@values[key]}" if choice == 'keep' && @values[key].is_a?(String) && !@values[key].empty?
|
|
335
|
+
|
|
336
|
+
CHOICE_HELP.dig(key, choice)
|
|
337
|
+
end
|
|
338
|
+
end
|
|
339
|
+
end
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative 'create_ruby_gem/version'
|
|
4
|
+
require_relative 'create_ruby_gem/error'
|
|
5
|
+
require_relative 'create_ruby_gem/detection/bundler_version'
|
|
6
|
+
require_relative 'create_ruby_gem/detection/bundler_defaults'
|
|
7
|
+
require_relative 'create_ruby_gem/detection/runtime'
|
|
8
|
+
require_relative 'create_ruby_gem/options/catalog'
|
|
9
|
+
require_relative 'create_ruby_gem/compatibility/matrix'
|
|
10
|
+
require_relative 'create_ruby_gem/options/validator'
|
|
11
|
+
require_relative 'create_ruby_gem/command_builder'
|
|
12
|
+
require_relative 'create_ruby_gem/config/store'
|
|
13
|
+
require_relative 'create_ruby_gem/runner'
|
|
14
|
+
require_relative 'create_ruby_gem/ui/palette'
|
|
15
|
+
require_relative 'create_ruby_gem/ui/prompter'
|
|
16
|
+
require_relative 'create_ruby_gem/wizard'
|
|
17
|
+
require_relative 'create_ruby_gem/cli'
|
|
18
|
+
|
|
19
|
+
# Interactive TUI wizard for +bundle gem+.
|
|
20
|
+
#
|
|
21
|
+
# Detects the user's Ruby/Bundler versions, shows only compatible options
|
|
22
|
+
# via a static compatibility matrix, and builds the correct +bundle gem+
|
|
23
|
+
# command. Config (presets, last-used options) is stored in
|
|
24
|
+
# +~/.config/create-ruby-gem/config.yml+.
|
|
25
|
+
#
|
|
26
|
+
# @see CreateRubyGem::CLI Entry point
|
|
27
|
+
# @see CreateRubyGem::Compatibility::Matrix Bundler version compatibility
|
|
28
|
+
module CreateRubyGem
|
|
29
|
+
end
|
metadata
ADDED
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
|
2
|
+
name: create-ruby-gem
|
|
3
|
+
version: !ruby/object:Gem::Version
|
|
4
|
+
version: 0.1.0
|
|
5
|
+
platform: ruby
|
|
6
|
+
authors:
|
|
7
|
+
- Leonid Svyatov
|
|
8
|
+
bindir: exe
|
|
9
|
+
cert_chain: []
|
|
10
|
+
date: 1980-01-02 00:00:00.000000000 Z
|
|
11
|
+
dependencies:
|
|
12
|
+
- !ruby/object:Gem::Dependency
|
|
13
|
+
name: cli-kit
|
|
14
|
+
requirement: !ruby/object:Gem::Requirement
|
|
15
|
+
requirements:
|
|
16
|
+
- - '='
|
|
17
|
+
- !ruby/object:Gem::Version
|
|
18
|
+
version: 5.0.1
|
|
19
|
+
type: :runtime
|
|
20
|
+
prerelease: false
|
|
21
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
22
|
+
requirements:
|
|
23
|
+
- - '='
|
|
24
|
+
- !ruby/object:Gem::Version
|
|
25
|
+
version: 5.0.1
|
|
26
|
+
- !ruby/object:Gem::Dependency
|
|
27
|
+
name: cli-ui
|
|
28
|
+
requirement: !ruby/object:Gem::Requirement
|
|
29
|
+
requirements:
|
|
30
|
+
- - '='
|
|
31
|
+
- !ruby/object:Gem::Version
|
|
32
|
+
version: 2.7.0
|
|
33
|
+
type: :runtime
|
|
34
|
+
prerelease: false
|
|
35
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
36
|
+
requirements:
|
|
37
|
+
- - '='
|
|
38
|
+
- !ruby/object:Gem::Version
|
|
39
|
+
version: 2.7.0
|
|
40
|
+
description: Interactive CLI wizard for bundle gem. Walks you through every option,
|
|
41
|
+
saves presets, and remembers your choices. No more bundle gem flags look-ups!
|
|
42
|
+
email:
|
|
43
|
+
- leonid@svyatov.com
|
|
44
|
+
executables:
|
|
45
|
+
- create-ruby-gem
|
|
46
|
+
extensions: []
|
|
47
|
+
extra_rdoc_files: []
|
|
48
|
+
files:
|
|
49
|
+
- CHANGELOG.md
|
|
50
|
+
- LICENSE.txt
|
|
51
|
+
- README.md
|
|
52
|
+
- exe/create-ruby-gem
|
|
53
|
+
- lib/create_ruby_gem.rb
|
|
54
|
+
- lib/create_ruby_gem/cli.rb
|
|
55
|
+
- lib/create_ruby_gem/command_builder.rb
|
|
56
|
+
- lib/create_ruby_gem/compatibility/matrix.rb
|
|
57
|
+
- lib/create_ruby_gem/config/store.rb
|
|
58
|
+
- lib/create_ruby_gem/detection/bundler_defaults.rb
|
|
59
|
+
- lib/create_ruby_gem/detection/bundler_version.rb
|
|
60
|
+
- lib/create_ruby_gem/detection/runtime.rb
|
|
61
|
+
- lib/create_ruby_gem/error.rb
|
|
62
|
+
- lib/create_ruby_gem/options/catalog.rb
|
|
63
|
+
- lib/create_ruby_gem/options/validator.rb
|
|
64
|
+
- lib/create_ruby_gem/runner.rb
|
|
65
|
+
- lib/create_ruby_gem/ui/back_navigation_patch.rb
|
|
66
|
+
- lib/create_ruby_gem/ui/palette.rb
|
|
67
|
+
- lib/create_ruby_gem/ui/prompter.rb
|
|
68
|
+
- lib/create_ruby_gem/version.rb
|
|
69
|
+
- lib/create_ruby_gem/wizard.rb
|
|
70
|
+
homepage: https://github.com/svyatov/create-ruby-gem
|
|
71
|
+
licenses:
|
|
72
|
+
- MIT
|
|
73
|
+
metadata:
|
|
74
|
+
allowed_push_host: https://rubygems.org
|
|
75
|
+
changelog_uri: https://github.com/svyatov/create-ruby-gem/blob/main/CHANGELOG.md
|
|
76
|
+
documentation_uri: https://rubydoc.info/gems/create-ruby-gem
|
|
77
|
+
bug_tracker_uri: https://github.com/svyatov/create-ruby-gem/issues
|
|
78
|
+
rubygems_mfa_required: 'true'
|
|
79
|
+
rdoc_options: []
|
|
80
|
+
require_paths:
|
|
81
|
+
- lib
|
|
82
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
|
83
|
+
requirements:
|
|
84
|
+
- - ">="
|
|
85
|
+
- !ruby/object:Gem::Version
|
|
86
|
+
version: '3.2'
|
|
87
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
|
88
|
+
requirements:
|
|
89
|
+
- - ">="
|
|
90
|
+
- !ruby/object:Gem::Version
|
|
91
|
+
version: '0'
|
|
92
|
+
requirements: []
|
|
93
|
+
rubygems_version: 4.0.4
|
|
94
|
+
specification_version: 4
|
|
95
|
+
summary: Create Ruby gems with an interactive CLI wizard that remembers your choices
|
|
96
|
+
test_files: []
|