prompts 0.2.0 → 0.3.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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 00154e514a6a247088b47d92dd842df0628e33a7b9abf56f31d8a402f6174a16
4
- data.tar.gz: 65a2a890904ef991dd55e042081efad7762106d63f0039f3db566e9df262c800
3
+ metadata.gz: 85f88312acdfb5be0eb5b24924d140c96d01453f742edbc1a1576a8759c37997
4
+ data.tar.gz: d82a75efffa864d9854a78c29a1f1ae110bbce7b61cd24770067ad1103c29c45
5
5
  SHA512:
6
- metadata.gz: 609f6e7c90beb7732f2b7853f25dbc44bce3e79ca5acdadd186a1455babea98e1364d2fff40dd380372974702f392774c8fd69b17e14241df8312ebc422e0e44
7
- data.tar.gz: aec86323aa427982ad95a58e9d4ab2fc73dfa0df358a4b8d88919fbf1c851321a25491a80b49b6618ed44af61ac24f819fc9f9fc55a9691858a41bc349587357
6
+ metadata.gz: ce5e838beb75e5d1e70fcfebcdc5eaf42e5dc77046465af025c2a8c9d78db70092f78a59737da631c12f89b9c292183cc80642c45f6b8b16036d54d5ad2bb83a
7
+ data.tar.gz: 4223935ce6374fa970f1e5634666a413fad19980105bce0258952a8caab283046a3a3473203ce0439ff168a408d355cea025cca10abe1adaeb80cc4816b7115b
data/CHANGELOG.md CHANGED
@@ -1,5 +1,15 @@
1
1
  ## [Unreleased]
2
2
 
3
+ ## [0.3.0] - 2024-10-08
4
+
5
+ - Update `fmt` gem and use new syntax ([@KonnorRogers](https://github.com/fractaledmind/prompts/pull/9))
6
+
7
+ ## [0.2.1] - 2024-08-22
8
+
9
+ - Add `Form.submit` method
10
+ - All passing keyword arguments to `Form` prompt methods
11
+ - Return the key of the options hash when using `SelectPrompt`
12
+
3
13
  ## [0.2.0] - 2024-08-20
4
14
 
5
15
  - Add `TextPrompt`
data/README.md CHANGED
@@ -256,6 +256,32 @@ which generates a terminal screen like this (this representation doesn't show co
256
256
  <b>Press Enter ⏎ to continue...</b> |
257
257
  </pre>
258
258
 
259
+ ## Forms
260
+
261
+ Often, you will have multiple prompts that will be displayed in sequence to collect information before performing additional actions. You may use the `Prompts::Form` class to create a grouped set of prompts for the user to complete:
262
+
263
+ ```ruby
264
+ responses = Prompts::Form.submit do |form|
265
+ form.text(
266
+ label: "What is your name?",
267
+ required: true
268
+ )
269
+ form.select(
270
+ label: "What role should the user have?",
271
+ options: {
272
+ member: "Member",
273
+ contributor: "Contributor",
274
+ owner: "Owner",
275
+ }
276
+ )
277
+ form.confirm(
278
+ label: 'Do you accept the terms?'
279
+ )
280
+ end
281
+ ```
282
+
283
+ The `submit` method will return an array containing all of the responses from the form's prompts.
284
+
259
285
  ## Development
260
286
 
261
287
  After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake test` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
data/lib/prompts/box.rb CHANGED
@@ -4,10 +4,10 @@ module Prompts
4
4
  class Box
5
5
  include TextUtils
6
6
 
7
- SOLID_BORDER = { top_left: "┌", top_right: "┐", bottom_left: "└", bottom_right: "┘", horizontal: "─", vertical: "│" }.freeze
8
- DOUBLE_BORDER = { top_left: "╔", top_right: "╗", bottom_left: "╚", bottom_right: "╝", horizontal: "═", vertical: "║" }.freeze
9
- HEAVY_BORDER = { top_left: "┏", top_right: "┓", bottom_left: "┗", bottom_right: "┛", horizontal: "━", vertical: "┃" }.freeze
10
- ROUNDED_BORDER = { top_left: "╭", top_right: "╮", bottom_left: "╰", bottom_right: "╯", horizontal: "─", vertical: "│" }.freeze
7
+ SOLID_BORDER = {top_left: "┌", top_right: "┐", bottom_left: "└", bottom_right: "┘", horizontal: "─", vertical: "│"}.freeze
8
+ DOUBLE_BORDER = {top_left: "╔", top_right: "╗", bottom_left: "╚", bottom_right: "╝", horizontal: "═", vertical: "║"}.freeze
9
+ HEAVY_BORDER = {top_left: "┏", top_right: "┓", bottom_left: "┗", bottom_right: "┛", horizontal: "━", vertical: "┃"}.freeze
10
+ ROUNDED_BORDER = {top_left: "╭", top_right: "╮", bottom_left: "╰", bottom_right: "╯", horizontal: "─", vertical: "│"}.freeze
11
11
 
12
12
  def initialize(width: MAX_WIDTH, padded: false, border_color: nil, border_style: :rounded)
13
13
  @width = width
@@ -15,11 +15,11 @@ module Prompts
15
15
  @border_color = border_color
16
16
  @line_padding = SPACE * 1
17
17
  @border_parts = case border_style
18
- when :solid then SOLID_BORDER
19
- when :double then DOUBLE_BORDER
20
- when :heavy then HEAVY_BORDER
21
- else ROUNDED_BORDER
22
- end
18
+ when :solid then SOLID_BORDER
19
+ when :double then DOUBLE_BORDER
20
+ when :heavy then HEAVY_BORDER
21
+ else ROUNDED_BORDER
22
+ end
23
23
  @content = []
24
24
  end
25
25
 
@@ -53,19 +53,19 @@ module Prompts
53
53
 
54
54
  private
55
55
 
56
- def top_border
57
- border = @border_parts[:top_left] + @border_parts[:horizontal] * (@width - 2) + @border_parts[:top_right]
58
- Fmt("#{@line_padding}%{border}#{@border_color}", border: border)
59
- end
56
+ def top_border
57
+ border = @border_parts[:top_left] + @border_parts[:horizontal] * (@width - 2) + @border_parts[:top_right]
58
+ Fmt("#{@line_padding}%{border}|>#{@border_color}", border: border)
59
+ end
60
60
 
61
- def bottom_border
62
- border = @border_parts[:bottom_left] + @border_parts[:horizontal] * (@width - 2) + @border_parts[:bottom_right]
63
- Fmt("#{@line_padding}%{border}#{@border_color}", border: border)
64
- end
61
+ def bottom_border
62
+ border = @border_parts[:bottom_left] + @border_parts[:horizontal] * (@width - 2) + @border_parts[:bottom_right]
63
+ Fmt("#{@line_padding}%{border}|>#{@border_color}", border: border)
64
+ end
65
65
 
66
- def align(text, alignment, between: @border_parts[:vertical])
67
- formatted_boundary = Fmt("%{boundary}#{@border_color}", boundary: between)
68
- wrap_text(text, width: @width, line_prefix: formatted_boundary + SPACE, line_suffix: SPACE + formatted_boundary, alignment: alignment)
69
- end
66
+ def align(text, alignment, between: @border_parts[:vertical])
67
+ formatted_boundary = Fmt("%{boundary}|>#{@border_color}", boundary: between)
68
+ wrap_text(text, width: @width, line_prefix: formatted_boundary + SPACE, line_suffix: SPACE + formatted_boundary, alignment: alignment)
69
+ end
70
70
  end
71
- end
71
+ end
@@ -3,7 +3,7 @@
3
3
  module Prompts
4
4
  class ConfirmPrompt < Prompt
5
5
  def initialize(...)
6
- super(...)
6
+ super
7
7
 
8
8
  @prompt = if @default == false
9
9
  "Choose [y/N]:"
@@ -20,12 +20,12 @@ module Prompts
20
20
 
21
21
  private
22
22
 
23
- def resolve_choice_from(response)
24
- case response
25
- when "y", "Y" then true
26
- when "n", "N" then false
27
- when "" then @default_boolean
28
- end
23
+ def resolve_choice_from(response)
24
+ case response
25
+ when "y", "Y" then true
26
+ when "n", "N" then false
27
+ when "" then @default_boolean
29
28
  end
29
+ end
30
30
  end
31
31
  end
@@ -43,28 +43,28 @@ module Prompts
43
43
 
44
44
  private
45
45
 
46
- def clear_screen
47
- jump_cursor_to_top
48
- erase_down
49
- end
46
+ def clear_screen
47
+ jump_cursor_to_top
48
+ erase_down
49
+ end
50
50
 
51
- def render_frame
52
- @frame_stack << @slots.dup
53
- OUTPUT.puts SPACE
51
+ def render_frame
52
+ @frame_stack << @slots.dup
53
+ OUTPUT.puts SPACE
54
54
 
55
- return if @slots.empty?
55
+ return if @slots.empty?
56
56
 
57
- OUTPUT.puts @slots.join("\n")
58
- OUTPUT.puts SPACE
59
- @slots.clear
60
- end
57
+ OUTPUT.puts @slots.join("\n")
58
+ OUTPUT.puts SPACE
59
+ @slots.clear
60
+ end
61
61
 
62
- def jump_cursor_to_top
63
- OUTPUT.print "\033[H"
64
- end
62
+ def jump_cursor_to_top
63
+ OUTPUT.print "\033[H"
64
+ end
65
65
 
66
- def erase_down
67
- OUTPUT.print "\e[J"
68
- end
66
+ def erase_down
67
+ OUTPUT.print "\e[J"
68
+ end
69
69
  end
70
- end
70
+ end
data/lib/prompts/form.rb CHANGED
@@ -2,47 +2,52 @@
2
2
 
3
3
  module Prompts
4
4
  class Form
5
- def initialize()
6
- @content = nil
5
+ def self.submit(&block)
6
+ instance = new
7
+ yield instance if block
8
+ instance.submit
9
+ end
10
+
11
+ def initialize
12
+ @content = Prompts::Content.new
7
13
  @prompts = []
8
14
  @results = []
9
15
  end
10
16
 
11
17
  def content(&block)
12
- @content = Prompts::Content.new
13
18
  yield @content
14
19
  @content
15
20
  end
16
21
 
17
- def text(&block)
18
- prompt = TextPrompt.new
19
- yield(prompt)
22
+ def text(label: nil, prompt: "> ", hint: nil, default: nil, required: false, validate: nil, &block)
23
+ prompt = TextPrompt.new(label: label, prompt: prompt, hint: hint, default: default, required: required, validate: validate)
24
+ yield(prompt) if block
20
25
  prepend_form_content_to_prompt(prompt)
21
26
  @prompts << prompt
22
27
  end
23
28
 
24
- def select(&block)
25
- prompt = SelectPrompt.new
26
- yield(prompt)
29
+ def select(label: nil, options: nil, prompt: "> ", hint: nil, default: nil, validate: nil, &block)
30
+ prompt = SelectPrompt.new(label: label, options: options, prompt: prompt, hint: hint, default: default, validate: validate)
31
+ yield(prompt) if block
27
32
  prepend_form_content_to_prompt(prompt)
28
33
  @prompts << prompt
29
34
  end
30
35
 
31
- def pause(&block)
32
- prompt = PausePrompt.new
33
- yield(prompt)
36
+ def pause(label: nil, prompt: "> ", hint: nil, default: nil, required: false, validate: nil, &block)
37
+ prompt = PausePrompt.new(label: label, prompt: prompt, hint: hint, default: default, required: required, validate: validate)
38
+ yield(prompt) if block
34
39
  prepend_form_content_to_prompt(prompt)
35
40
  @prompts << prompt
36
41
  end
37
42
 
38
- def confirm(&block)
39
- prompt = ConfirmPrompt.new
40
- yield(prompt)
43
+ def confirm(label: nil, prompt: "> ", hint: nil, default: nil, required: false, validate: nil, &block)
44
+ prompt = ConfirmPrompt.new(label: label, prompt: prompt, hint: hint, default: default, required: required, validate: validate)
45
+ yield(prompt) if block
41
46
  prepend_form_content_to_prompt(prompt)
42
47
  @prompts << prompt
43
48
  end
44
49
 
45
- def start
50
+ def submit
46
51
  @prompts.each do |prompt|
47
52
  @results << prompt.ask
48
53
  end
@@ -51,10 +56,10 @@ module Prompts
51
56
 
52
57
  private
53
58
 
54
- def prepend_form_content_to_prompt(prompt)
55
- prompt.prepare_content
56
- @content.gap
57
- prompt.prepend_content(*@content.slots)
58
- end
59
+ def prepend_form_content_to_prompt(prompt)
60
+ prompt.prepare_content
61
+ @content.gap
62
+ prompt.prepend_content(*@content.slots)
63
+ end
59
64
  end
60
- end
65
+ end
@@ -16,4 +16,4 @@ module Prompts
16
16
  wrap_text(@text, width: @width, line_prefix: @line_padding, alignment: :none)
17
17
  end
18
18
  end
19
- end
19
+ end
@@ -3,7 +3,7 @@
3
3
  module Prompts
4
4
  class PausePrompt < Prompt
5
5
  def initialize(...)
6
- super(...)
6
+ super
7
7
 
8
8
  @prompt = "Press Enter ⏎ to continue..."
9
9
  end
@@ -33,6 +33,7 @@ module Prompts
33
33
  @content
34
34
  end
35
35
 
36
+ # standard:disable Style/TrivialAccessors
36
37
  def label(label)
37
38
  @label = label
38
39
  end
@@ -44,6 +45,7 @@ module Prompts
44
45
  def default(default)
45
46
  @default = default
46
47
  end
48
+ # standard:enable Style/TrivialAccessors
47
49
 
48
50
  def ask
49
51
  prepare_content if !@content_prepared
@@ -54,12 +56,12 @@ module Prompts
54
56
  @content.render
55
57
  *initial_prompt_lines, last_prompt_line = formatted_prompt
56
58
  puts initial_prompt_lines.join("\n") if initial_prompt_lines.any?
57
- response = Reline.readline(last_prompt_line, history = false).chomp
59
+ response = Reline.readline(last_prompt_line, _history = false).chomp
58
60
  @choice = resolve_choice_from(response)
59
61
 
60
62
  if (@error = ensure_validity(response))
61
63
  @content.reset!
62
- @content.paragraph Fmt("%{error}red|bold", error: @error + " Try again (×#{@attempts})...")
64
+ @content.paragraph Fmt("%{error}|>red|>bold", error: @error + " Try again (×#{@attempts})...")
63
65
  @attempts += 1
64
66
  next
65
67
  else
@@ -87,53 +89,53 @@ module Prompts
87
89
 
88
90
  private
89
91
 
90
- def prepare_default
91
- Reline.pre_input_hook = -> do
92
- Reline.insert_text @default.to_s
93
- # Remove the hook right away.
94
- Reline.pre_input_hook = nil
95
- end
92
+ def prepare_default
93
+ Reline.pre_input_hook = -> do
94
+ Reline.insert_text @default.to_s
95
+ # Remove the hook right away.
96
+ Reline.pre_input_hook = nil
96
97
  end
98
+ end
97
99
 
98
- def prepare_validations
99
- if @required
100
- error_message = @required.is_a?(String) ? @required : "Value cannot be empty."
101
- @validations << ->(input) { error_message if input.empty? }
102
- end
103
-
104
- if @validate
105
- @validations << @validate
106
- end
100
+ def prepare_validations
101
+ if @required
102
+ error_message = @required.is_a?(String) ? @required : "Value cannot be empty."
103
+ @validations << ->(input) { error_message if input.empty? }
107
104
  end
108
105
 
109
- def resolve_choice_from(response)
110
- response
106
+ if @validate
107
+ @validations << @validate
111
108
  end
109
+ end
112
110
 
113
- def formatted_prompt
114
- prompt_with_space = @prompt.end_with?(SPACE) ? @prompt : @prompt + SPACE
115
- ansi_prompt = Fmt("%{prompt}faint|bold", prompt: prompt_with_space)
116
- @formatted_prompt ||= Paragraph.new(ansi_prompt, width: MAX_WIDTH).lines
117
- end
111
+ def resolve_choice_from(response)
112
+ response
113
+ end
118
114
 
119
- def formatted_label
120
- Fmt("%{label}cyan|bold %{instructions}faint|italic", label: @label, instructions: @instructions ? "(#{@instructions})" : "")
121
- end
115
+ def formatted_prompt
116
+ prompt_with_space = @prompt.end_with?(SPACE) ? @prompt : @prompt + SPACE
117
+ ansi_prompt = Fmt("%{prompt}|>faint|>bold", prompt: prompt_with_space)
118
+ @formatted_prompt ||= Paragraph.new(ansi_prompt, width: MAX_WIDTH).lines
119
+ end
122
120
 
123
- def formatted_hint
124
- Fmt("%{hint}faint|bold", hint: @hint)
125
- end
121
+ def formatted_label
122
+ Fmt("%{label}|>cyan|>bold %{instructions}|>faint|>italic", label: @label, instructions: @instructions ? "(#{@instructions})" : "")
123
+ end
126
124
 
127
- def formatted_error
128
- Fmt("%{error}red|bold", error: @error + " Try again (×#{@attempts})...")
129
- end
125
+ def formatted_hint
126
+ Fmt("%{hint}|>faint|>bold", hint: @hint)
127
+ end
130
128
 
131
- def ensure_validity(response)
132
- @validations.each do |validation|
133
- result = validation.call(response)
134
- return result if result
135
- end
136
- nil
129
+ def formatted_error
130
+ Fmt("%{error}|>red|>bold", error: @error + " Try again (×#{@attempts})...")
131
+ end
132
+
133
+ def ensure_validity(response)
134
+ @validations.each do |validation|
135
+ result = validation.call(response)
136
+ return result if result
137
137
  end
138
+ nil
139
+ end
138
140
  end
139
141
  end
@@ -12,34 +12,34 @@ module Prompts
12
12
  super(**kwargs)
13
13
 
14
14
  @options = options.is_a?(Array) ? options.to_h { |item| [item, item] } : options
15
- if (index = @options.keys.index(@default))
16
- @default = index + 1
17
- else
18
- @default = nil
15
+ @default = if (index = @options.keys.index(@default))
16
+ index + 1
19
17
  end
20
18
  @instructions = "Enter the number of your choice"
21
19
  @hint ||= "Type your response and press Enter ⏎"
22
20
  @validations << ->(choice) { "Invalid choice." if !choice.to_i.between?(1, @options.size) }
23
21
  end
24
22
 
23
+ # standard:disable Style/TrivialAccessors
25
24
  def options(options)
26
25
  @options = options
27
26
  end
27
+ # standard:enable Style/TrivialAccessors
28
28
 
29
29
  def prepare_content
30
30
  super
31
31
  @options.each_with_index do |(key, value), index|
32
- @content.paragraph Fmt("%{prefix}faint|bold %{option}", prefix: "#{index + 1}.", option: value)
32
+ @content.paragraph Fmt("%{prefix}|>faint|>bold %{option}", prefix: "#{index + 1}.", option: value)
33
33
  end
34
34
  @content
35
35
  end
36
36
 
37
37
  private
38
38
 
39
- def resolve_choice_from(response)
40
- choice = response.to_i
41
- key, value = @options.to_a[choice - 1]
42
- value
43
- end
39
+ def resolve_choice_from(response)
40
+ choice = response.to_i
41
+ key, _value = @options.to_a[choice - 1]
42
+ key
43
+ end
44
44
  end
45
45
  end
@@ -3,7 +3,7 @@
3
3
  module Prompts
4
4
  class TextPrompt < Prompt
5
5
  def initialize(...)
6
- super(...)
6
+ super
7
7
 
8
8
  @instructions = "Press Enter to submit"
9
9
  @hint ||= "Type your response and press Enter ⏎"
@@ -5,7 +5,7 @@ require "unicode/emoji"
5
5
 
6
6
  module Prompts
7
7
  module TextUtils
8
- ANSI_REGEX = /\e\[[0-9;]*[a-zA-Z]/.freeze
8
+ ANSI_REGEX = /\e\[[0-9;]*[a-zA-Z]/
9
9
 
10
10
  def wrap_text(text, width:, line_prefix: EMPTY, line_suffix: EMPTY, alignment: :left)
11
11
  words = text.scan(Regexp.union(/\S+/, ANSI_REGEX))
@@ -55,4 +55,4 @@ module Prompts
55
55
  text.gsub(ANSI_REGEX, EMPTY)
56
56
  end
57
57
  end
58
- end
58
+ end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Prompts
4
- VERSION = "0.2.0"
4
+ VERSION = "0.3.0"
5
5
  end
data/lib/prompts.rb CHANGED
@@ -2,10 +2,8 @@
2
2
 
3
3
  require "io/console"
4
4
  require "reline"
5
+ require "rainbow" # this needs to come before require "fmt"
5
6
  require "fmt"
6
- require "rainbow"
7
-
8
- Fmt.add_rainbow_filters
9
7
 
10
8
  require_relative "prompts/version"
11
9
  require_relative "prompts/prompt"
@@ -20,8 +18,8 @@ require_relative "prompts/select_prompt"
20
18
  require_relative "prompts/form"
21
19
 
22
20
  module Prompts
23
- EMPTY = "".freeze
24
- SPACE = " ".freeze
21
+ EMPTY = ""
22
+ SPACE = " "
25
23
  MAX_WIDTH = 80
26
24
  OUTPUT = $stdout
27
25
 
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: prompts
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.2.0
4
+ version: 0.3.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Stephen Margheim
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2024-08-20 00:00:00.000000000 Z
11
+ date: 2024-10-08 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: unicode-display_width
@@ -58,14 +58,14 @@ dependencies:
58
58
  requirements:
59
59
  - - ">="
60
60
  - !ruby/object:Gem::Version
61
- version: '0'
61
+ version: 0.3.0
62
62
  type: :runtime
63
63
  prerelease: false
64
64
  version_requirements: !ruby/object:Gem::Requirement
65
65
  requirements:
66
66
  - - ">="
67
67
  - !ruby/object:Gem::Version
68
- version: '0'
68
+ version: 0.3.0
69
69
  - !ruby/object:Gem::Dependency
70
70
  name: rainbow
71
71
  requirement: !ruby/object:Gem::Requirement
@@ -128,7 +128,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
128
128
  - !ruby/object:Gem::Version
129
129
  version: '0'
130
130
  requirements: []
131
- rubygems_version: 3.5.17
131
+ rubygems_version: 3.5.11
132
132
  signing_key:
133
133
  specification_version: 4
134
134
  summary: Beautiful and user-friendly forms for your command-line Ruby applications.