tty-prompt 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.
Files changed (108) hide show
  1. checksums.yaml +4 -4
  2. data/.travis.yml +9 -6
  3. data/CHANGELOG.md +40 -3
  4. data/Gemfile +0 -1
  5. data/README.md +246 -65
  6. data/examples/ask.rb +7 -0
  7. data/examples/echo.rb +7 -0
  8. data/examples/in.rb +7 -0
  9. data/examples/mask.rb +9 -0
  10. data/examples/multi_select.rb +8 -0
  11. data/examples/select.rb +8 -0
  12. data/examples/validation.rb +9 -0
  13. data/examples/yes_no.rb +7 -0
  14. data/lib/tty-prompt.rb +6 -4
  15. data/lib/tty/prompt.rb +100 -25
  16. data/lib/tty/prompt/choice.rb +1 -1
  17. data/lib/tty/prompt/converter_dsl.rb +19 -0
  18. data/lib/tty/prompt/converter_registry.rb +56 -0
  19. data/lib/tty/prompt/converters.rb +77 -0
  20. data/lib/tty/prompt/evaluator.rb +29 -0
  21. data/lib/tty/prompt/list.rb +38 -36
  22. data/lib/tty/prompt/mask_question.rb +85 -0
  23. data/lib/tty/prompt/multi_list.rb +21 -32
  24. data/lib/tty/prompt/question.rb +184 -162
  25. data/lib/tty/prompt/question/checks.rb +85 -0
  26. data/lib/tty/prompt/question/modifier.rb +4 -5
  27. data/lib/tty/prompt/question/validation.rb +29 -35
  28. data/lib/tty/prompt/reader.rb +98 -52
  29. data/lib/tty/prompt/reader/codes.rb +63 -0
  30. data/lib/tty/prompt/reader/key_event.rb +67 -0
  31. data/lib/tty/prompt/reader/mode.rb +66 -0
  32. data/lib/tty/prompt/reader/mode/echo.rb +43 -0
  33. data/lib/tty/prompt/reader/mode/raw.rb +43 -0
  34. data/lib/tty/prompt/result.rb +42 -0
  35. data/lib/tty/prompt/statement.rb +9 -14
  36. data/lib/tty/prompt/suggestion.rb +4 -2
  37. data/lib/tty/prompt/symbols.rb +13 -0
  38. data/lib/tty/prompt/test.rb +3 -2
  39. data/lib/tty/prompt/utils.rb +1 -1
  40. data/lib/tty/prompt/version.rb +1 -1
  41. data/spec/unit/ask_spec.rb +31 -48
  42. data/spec/unit/choice/eql_spec.rb +0 -2
  43. data/spec/unit/choice/from_spec.rb +0 -2
  44. data/spec/unit/choices/add_spec.rb +0 -2
  45. data/spec/unit/choices/each_spec.rb +0 -2
  46. data/spec/unit/choices/new_spec.rb +0 -2
  47. data/spec/unit/choices/pluck_spec.rb +0 -2
  48. data/spec/unit/converters/convert_bool_spec.rb +58 -0
  49. data/spec/unit/{response/read_char_spec.rb → converters/convert_char_spec.rb} +2 -4
  50. data/spec/unit/converters/convert_custom_spec.rb +14 -0
  51. data/spec/unit/converters/convert_date_spec.rb +25 -0
  52. data/spec/unit/converters/convert_file_spec.rb +14 -0
  53. data/spec/unit/{response/read_number_spec.rb → converters/convert_number_spec.rb} +5 -7
  54. data/spec/unit/converters/convert_path_spec.rb +15 -0
  55. data/spec/unit/{response/read_range_spec.rb → converters/convert_range_spec.rb} +3 -5
  56. data/spec/unit/converters/convert_regex_spec.rb +12 -0
  57. data/spec/unit/converters/convert_string_spec.rb +21 -0
  58. data/spec/unit/distance/distance_spec.rb +0 -2
  59. data/spec/unit/error_spec.rb +0 -6
  60. data/spec/unit/evaluator_spec.rb +67 -0
  61. data/spec/unit/keypress_spec.rb +19 -0
  62. data/spec/unit/mask_spec.rb +95 -0
  63. data/spec/unit/multi_select_spec.rb +36 -24
  64. data/spec/unit/multiline_spec.rb +19 -0
  65. data/spec/unit/new_spec.rb +18 -0
  66. data/spec/unit/ok_spec.rb +10 -0
  67. data/spec/unit/question/default_spec.rb +17 -4
  68. data/spec/unit/question/echo_spec.rb +31 -0
  69. data/spec/unit/question/in_spec.rb +48 -16
  70. data/spec/unit/question/initialize_spec.rb +2 -9
  71. data/spec/unit/question/modifier/apply_to_spec.rb +9 -16
  72. data/spec/unit/question/modifier/letter_case_spec.rb +0 -2
  73. data/spec/unit/question/modifier/whitespace_spec.rb +12 -20
  74. data/spec/unit/question/modify_spec.rb +3 -7
  75. data/spec/unit/question/required_spec.rb +20 -14
  76. data/spec/unit/question/validate_spec.rb +20 -19
  77. data/spec/unit/question/validation/call_spec.rb +15 -6
  78. data/spec/unit/question/validation/coerce_spec.rb +17 -11
  79. data/spec/unit/reader/publish_keypress_event_spec.rb +81 -0
  80. data/spec/unit/reader/read_keypress_spec.rb +22 -0
  81. data/spec/unit/reader/read_line_spec.rb +31 -0
  82. data/spec/unit/reader/read_multiline_spec.rb +37 -0
  83. data/spec/unit/result_spec.rb +40 -0
  84. data/spec/unit/say_spec.rb +18 -23
  85. data/spec/unit/select_spec.rb +37 -32
  86. data/spec/unit/statement/initialize_spec.rb +4 -4
  87. data/spec/unit/suggest_spec.rb +0 -2
  88. data/spec/unit/warn_spec.rb +0 -5
  89. data/spec/unit/yes_no_spec.rb +70 -0
  90. data/tty-prompt.gemspec +7 -4
  91. metadata +123 -40
  92. data/lib/tty/prompt/codes.rb +0 -32
  93. data/lib/tty/prompt/cursor.rb +0 -131
  94. data/lib/tty/prompt/error.rb +0 -26
  95. data/lib/tty/prompt/mode.rb +0 -64
  96. data/lib/tty/prompt/mode/echo.rb +0 -41
  97. data/lib/tty/prompt/mode/raw.rb +0 -41
  98. data/lib/tty/prompt/response.rb +0 -247
  99. data/lib/tty/prompt/response_delegation.rb +0 -42
  100. data/spec/unit/cursor/new_spec.rb +0 -74
  101. data/spec/unit/question/character_spec.rb +0 -13
  102. data/spec/unit/reader/getc_spec.rb +0 -42
  103. data/spec/unit/response/read_bool_spec.rb +0 -58
  104. data/spec/unit/response/read_date_spec.rb +0 -16
  105. data/spec/unit/response/read_email_spec.rb +0 -45
  106. data/spec/unit/response/read_multiple_spec.rb +0 -21
  107. data/spec/unit/response/read_spec.rb +0 -69
  108. data/spec/unit/response/read_string_spec.rb +0 -14
@@ -0,0 +1,67 @@
1
+ # encoding: utf-8
2
+
3
+ require 'tty/prompt/reader/codes'
4
+
5
+ module TTY
6
+ class Prompt
7
+ class Reader
8
+ # Responsible for meta-data information about key pressed
9
+ #
10
+ # @api private
11
+ class Key < Struct.new(:name, :ctrl, :meta, :shift)
12
+ def initialize(*)
13
+ super
14
+ @ctrl = false
15
+ @meta = false
16
+ @shift = false
17
+ end
18
+ end
19
+
20
+ # Represents key event emitted during keyboard press
21
+ #
22
+ # @api public
23
+ class KeyEvent < Struct.new(:value, :key)
24
+ META_KEY_CODE_RE = /^(?:\x1b+)(O|N|\[|\[\[)(?:(\d+)(?:;(\d+))?([~^$])|(?:1;)?(\d+)?([a-zA-Z]))/
25
+
26
+ def self.from(char)
27
+ key = Key.new
28
+ case char
29
+ when Codes::RETURN
30
+ key.name = :return
31
+ when Codes::LINEFEED
32
+ key.name = :enter
33
+ when Codes::TAB
34
+ key.name = :tab
35
+ when Codes::BACKSPACE
36
+ key.name = :backspace
37
+ when Codes::DELETE
38
+ key.name = :delete
39
+ when Codes::SPACE
40
+ key.name = :space
41
+ when Codes::CTRL_C, Codes::ESCAPE
42
+ key.name = :escape
43
+ when proc { |c| c <= "\x1a" }
44
+ codes = char.each_codepoint.to_a
45
+ key.name = "#{codes}"
46
+ key.ctrl = true
47
+ when /\d/
48
+ key.name = :num
49
+ when META_KEY_CODE_RE
50
+ key.meta = true
51
+ case char
52
+ when Codes::KEY_UP, Codes::CTRL_K, Codes::CTRL_P
53
+ key.name = :up
54
+ when Codes::KEY_DOWN, Codes::CTRL_J, Codes::CTRL_N
55
+ key.name = :down
56
+ when Codes::KEY_RIGHT, Codes::CTRL_L
57
+ key.name = :right
58
+ when Codes::KEY_LEFT, Codes::CTRL_H
59
+ key.name = :left
60
+ end
61
+ end
62
+ new(char, key)
63
+ end
64
+ end # KeyEvent
65
+ end # Reader
66
+ end # Prompt
67
+ end # TTY
@@ -0,0 +1,66 @@
1
+ # encoding: utf-8
2
+
3
+ require 'tty/prompt/reader/mode/echo'
4
+ require 'tty/prompt/reader/mode/raw'
5
+
6
+ module TTY
7
+ class Prompt
8
+ class Reader
9
+ class Mode
10
+ # Initialize a Terminal
11
+ #
12
+ # @api public
13
+ def initialize(options = {})
14
+ @echo = Echo.new
15
+ @raw = Raw.new
16
+ end
17
+
18
+ # Switch echo on
19
+ #
20
+ # @api public
21
+ def echo_on
22
+ @echo.on
23
+ end
24
+
25
+ # Switch echo off
26
+ #
27
+ # @api public
28
+ def echo_off
29
+ @echo.off
30
+ end
31
+
32
+ # Echo given block
33
+ #
34
+ # @param [Boolean] is_on
35
+ #
36
+ # @api public
37
+ def echo(is_on = true, &block)
38
+ @echo.echo(is_on, &block)
39
+ end
40
+
41
+ # Switch raw mode on
42
+ #
43
+ # @api public
44
+ def raw_on
45
+ @raw.on
46
+ end
47
+
48
+ # Switch raw mode off
49
+ #
50
+ # @api public
51
+ def raw_off
52
+ @raw.off
53
+ end
54
+
55
+ # Use raw mode in the given block
56
+ #
57
+ # @param [Boolean] is_on
58
+ #
59
+ # @api public
60
+ def raw(is_on = true, &block)
61
+ @raw.raw(is_on, &block)
62
+ end
63
+ end # Mode
64
+ end # Reader
65
+ end # Prompt
66
+ end # TTY
@@ -0,0 +1,43 @@
1
+ # encoding: utf-8
2
+
3
+ module TTY
4
+ class Prompt
5
+ class Reader
6
+ class Mode
7
+ # A class responsible for toggling echo.
8
+ class Echo
9
+ # Turn echo on
10
+ #
11
+ # @api public
12
+ def on
13
+ %x{stty echo} if TTY::Platform.unix?
14
+ end
15
+
16
+ # Turn echo off
17
+ #
18
+ # @api public
19
+ def off
20
+ %x{stty -echo} if TTY::Platform.unix?
21
+ end
22
+
23
+ # Wrap code block inside echo
24
+ #
25
+ # @api public
26
+ def echo(is_on=true, &block)
27
+ value = nil
28
+ off unless is_on
29
+ value = block.call if block_given?
30
+ rescue NoMethodError, Interrupt => error
31
+ puts "#{error.class} #{error.message}"
32
+ puts error.backtrace
33
+ on
34
+ exit
35
+ ensure
36
+ on
37
+ value
38
+ end
39
+ end # Echo
40
+ end # Mode
41
+ end # Reader
42
+ end # Prompt
43
+ end # TTY
@@ -0,0 +1,43 @@
1
+ # encoding: utf-8
2
+
3
+ module TTY
4
+ class Prompt
5
+ class Reader
6
+ class Mode
7
+ # A class responsible for toggling raw mode.
8
+ class Raw
9
+ # Turn raw mode on
10
+ #
11
+ # @api public
12
+ def on
13
+ %x{stty raw} if TTY::Platform.unix?
14
+ end
15
+
16
+ # Turn raw mode off
17
+ #
18
+ # @api public
19
+ def off
20
+ %x{stty -raw} if TTY::Platform.unix?
21
+ end
22
+
23
+ # Wrap code block inside raw mode
24
+ #
25
+ # @api public
26
+ def raw(is_on=true, &block)
27
+ value = nil
28
+ on if is_on
29
+ value = block.call if block_given?
30
+ rescue NoMethodError, Interrupt => error
31
+ puts "#{error.class} #{error.message}"
32
+ puts error.backtrace
33
+ off
34
+ exit
35
+ ensure
36
+ off
37
+ value
38
+ end
39
+ end # Raw
40
+ end # Mode
41
+ end # Reader
42
+ end # Prompt
43
+ end # TTY
@@ -0,0 +1,42 @@
1
+ # encoding: utf-8
2
+
3
+ module TTY
4
+ class Prompt
5
+ # Accumulates errors
6
+ class Result
7
+ attr_reader :question, :value, :errors
8
+
9
+ def initialize(question, value, errors = [])
10
+ @question = question
11
+ @value = value
12
+ @errors = errors
13
+ end
14
+
15
+ def with(condition = nil, &block)
16
+ validator = (condition || block)
17
+ (new_value, validation_error) = validator.call(question, value)
18
+ accumulated_errors = errors + Array(validation_error)
19
+
20
+ if accumulated_errors.empty?
21
+ Success.new(question, new_value)
22
+ else
23
+ Failure.new(question, new_value, accumulated_errors)
24
+ end
25
+ end
26
+
27
+ def success?
28
+ is_a?(Success)
29
+ end
30
+
31
+ def failure?
32
+ is_a?(Failure)
33
+ end
34
+
35
+ class Success < Result
36
+ end
37
+
38
+ class Failure < Result
39
+ end
40
+ end
41
+ end # Prompt
42
+ end # TTY
@@ -5,10 +5,6 @@ module TTY
5
5
  class Prompt
6
6
  # A class representing a statement output to prompt.
7
7
  class Statement
8
- # @api private
9
- attr_reader :prompt
10
- private :prompt
11
-
12
8
  # Flag to display newline
13
9
  #
14
10
  # @api public
@@ -32,11 +28,10 @@ module TTY
32
28
  # change the message display to color
33
29
  #
34
30
  # @api public
35
- def initialize(prompt = Prompt.new, options = {})
36
- @prompt = prompt
37
- @pastel = Pastel.new
38
- @newline = options.fetch(:newline, true)
39
- @color = options.fetch(:color, false)
31
+ def initialize(prompt, options = {})
32
+ @prompt = prompt
33
+ @newline = options.fetch(:newline) { true }
34
+ @color = options.fetch(:color) { false }
40
35
  end
41
36
 
42
37
  # Output the message to the prompt
@@ -45,14 +40,14 @@ module TTY
45
40
  # the message to be printed to stdout
46
41
  #
47
42
  # @api public
48
- def declare(message)
49
- message = @pastel.decorate message, *color if color
43
+ def call(message)
44
+ message = @prompt.decorate(message, *color) if color
50
45
 
51
46
  if newline && /( |\t)(\e\[\d+(;\d+)*m)?\Z/ !~ message
52
- prompt.output.puts message
47
+ @prompt.puts message
53
48
  else
54
- prompt.output.print message
55
- prompt.output.flush
49
+ @prompt.print message
50
+ @prompt.flush
56
51
  end
57
52
  end
58
53
  end # Statement
@@ -3,9 +3,11 @@
3
3
  require 'tty/prompt/distance'
4
4
 
5
5
  module TTY
6
- # A class responsible for shell prompt interactions.
6
+ # A class responsible for terminal prompt interactions.
7
7
  class Prompt
8
- # A class representing a suggestion
8
+ # A class representing a suggestion out of possible choices
9
+ #
10
+ # @api public
9
11
  class Suggestion
10
12
  DEFAULT_INDENT = 8
11
13
 
@@ -0,0 +1,13 @@
1
+ # encoding: utf-8
2
+
3
+ module TTY
4
+ class Prompt
5
+ module Symbols
6
+ SPACE = " "
7
+ ITEM_SECURE = "•"
8
+ ITEM_SELECTED = "‣"
9
+ RADIO_CHECKED = "⬢"
10
+ RADIO_UNCHECKED = "⬡"
11
+ end # Symbols
12
+ end # Prompt
13
+ end # TTY
@@ -8,8 +8,9 @@ module TTY
8
8
  def initialize(options = {})
9
9
  @input = StringIO.new
10
10
  @output = StringIO.new
11
-
12
- super(@input, @output, options)
11
+ options.merge!({input: @input, output: @output})
12
+ super(options)
13
+ @pastel = Pastel.new(enabled: true)
13
14
  end
14
15
  end # TestPrompt
15
16
  end # TTY
@@ -2,7 +2,7 @@
2
2
 
3
3
  module TTY
4
4
  module Utils
5
- extend self
5
+ module_function
6
6
 
7
7
  def extract_options!(args)
8
8
  args.last.respond_to?(:to_hash) ? args.pop : {}
@@ -2,6 +2,6 @@
2
2
 
3
3
  module TTY
4
4
  class Prompt
5
- VERSION = "0.2.0"
5
+ VERSION = "0.3.0"
6
6
  end # Prompt
7
7
  end # TTY
@@ -1,74 +1,57 @@
1
1
  # encoding: utf-8
2
2
 
3
- require 'spec_helper'
4
-
5
3
  RSpec.describe TTY::Prompt, '#ask' do
6
-
7
4
  subject(:prompt) { TTY::TestPrompt.new }
8
5
 
9
- it 'prints message' do
10
- prompt.ask("What is your name?")
11
- expect(prompt.output.string).to eql("What is your name?")
6
+ it 'asks question' do
7
+ prompt.ask('What is your name?')
8
+ expect(prompt.output.string).to eq([
9
+ "What is your name? ",
10
+ "\e[1000D\e[K\e[1A",
11
+ "\e[1000D\e[K",
12
+ "What is your name? \n"
13
+ ].join)
12
14
  end
13
15
 
14
- it 'prints an empty message ' do
16
+ it 'asks an empty question ' do
15
17
  prompt.ask('')
16
18
  expect(prompt.output.string).to eql('')
17
19
  end
18
20
 
19
- it 'prints an empty message and returns nil if EOF is sent to stdin' do
21
+ it 'asks an empty question and returns nil if EOF is sent to stdin' do
20
22
  prompt.input << nil
21
23
  prompt.input.rewind
22
- response = prompt.ask("")
23
- expect(response).to eql(nil)
24
+ answer = prompt.ask('')
25
+ expect(answer).to eql(nil)
24
26
  expect(prompt.output.string).to eq('')
25
27
  end
26
28
 
27
- it "asks a question with a prefix '>'" do
28
- prompt = TTY::TestPrompt.new(prefix: ' > ')
29
- prompt.input << ''
29
+ it "asks a question with a prefix [?]" do
30
+ prompt = TTY::TestPrompt.new(prefix: "[?] ")
31
+ prompt.input << "\r"
30
32
  prompt.input.rewind
31
- response = prompt.ask "Are you Polish?"
32
- expect(response).to eq(nil)
33
- expect(prompt.output.string).to eql " > Are you Polish?"
33
+ answer = prompt.ask 'Are you Polish?'
34
+ expect(answer).to eq(nil)
35
+ expect(prompt.output.string).to eq([
36
+ "[?] Are you Polish? ",
37
+ "\e[1000D\e[K\e[1A",
38
+ "\e[1000D\e[K",
39
+ "[?] Are you Polish? \n"
40
+ ].join)
34
41
  end
35
42
 
36
43
  it 'asks a question with block' do
37
44
  prompt.input << ''
38
45
  prompt.input.rewind
39
- value = prompt.ask "What is your name?" do |q|
46
+ answer = prompt.ask "What is your name?" do |q|
40
47
  q.default 'Piotr'
41
48
  end
42
- expect(value).to eql "Piotr"
43
- expect(prompt.output.string).to eq('What is your name?')
44
- end
45
-
46
- context 'yes?' do
47
- it 'agrees' do
48
- prompt.input << 'yes'
49
- prompt.input.rewind
50
- expect(prompt.yes?("Are you a human?")).to eq(true)
51
- expect(prompt.output.string).to eq('Are you a human?')
52
- end
53
-
54
- it 'disagrees' do
55
- prompt.input << 'no'
56
- prompt.input.rewind
57
- expect(prompt.yes?("Are you a human?")).to eq(false)
58
- end
59
- end
60
-
61
- context 'no?' do
62
- it 'agrees' do
63
- prompt.input << 'no'
64
- prompt.input.rewind
65
- expect(prompt.no?("Are you a human?")).to eq(true)
66
- end
67
-
68
- it 'disagrees' do
69
- prompt.input << 'yes'
70
- prompt.input.rewind
71
- expect(prompt.no?("Are you a human?")).to eq(false)
72
- end
49
+ expect(answer).to eq('Piotr')
50
+ expect(prompt.output.string).to eq([
51
+ "What is your name? \e[90m(Piotr)\e[0m ",
52
+ "\e[1000D\e[K\e[1A",
53
+ "\e[1000D\e[K",
54
+ "What is your name? \e[32mPiotr\e[0m\n"
55
+ ].join)
73
56
  end
74
57
  end