tty-prompt 0.2.0 → 0.3.0

Sign up to get free protection for your applications and to get access to all the features.
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