austb-tty-prompt 0.13.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (80) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +14 -0
  3. data/.rspec +3 -0
  4. data/.travis.yml +25 -0
  5. data/CHANGELOG.md +218 -0
  6. data/CODE_OF_CONDUCT.md +49 -0
  7. data/Gemfile +19 -0
  8. data/LICENSE.txt +22 -0
  9. data/README.md +1132 -0
  10. data/Rakefile +8 -0
  11. data/appveyor.yml +23 -0
  12. data/benchmarks/speed.rb +27 -0
  13. data/examples/ask.rb +15 -0
  14. data/examples/collect.rb +19 -0
  15. data/examples/echo.rb +11 -0
  16. data/examples/enum.rb +8 -0
  17. data/examples/enum_paged.rb +9 -0
  18. data/examples/enum_select.rb +7 -0
  19. data/examples/expand.rb +29 -0
  20. data/examples/in.rb +9 -0
  21. data/examples/inputs.rb +10 -0
  22. data/examples/key_events.rb +11 -0
  23. data/examples/keypress.rb +9 -0
  24. data/examples/mask.rb +13 -0
  25. data/examples/multi_select.rb +8 -0
  26. data/examples/multi_select_paged.rb +9 -0
  27. data/examples/multiline.rb +9 -0
  28. data/examples/pause.rb +7 -0
  29. data/examples/select.rb +18 -0
  30. data/examples/select_paginated.rb +9 -0
  31. data/examples/slider.rb +6 -0
  32. data/examples/validation.rb +9 -0
  33. data/examples/yes_no.rb +7 -0
  34. data/lib/tty-prompt.rb +4 -0
  35. data/lib/tty/prompt.rb +535 -0
  36. data/lib/tty/prompt/answers_collector.rb +59 -0
  37. data/lib/tty/prompt/choice.rb +90 -0
  38. data/lib/tty/prompt/choices.rb +110 -0
  39. data/lib/tty/prompt/confirm_question.rb +129 -0
  40. data/lib/tty/prompt/converter_dsl.rb +22 -0
  41. data/lib/tty/prompt/converter_registry.rb +64 -0
  42. data/lib/tty/prompt/converters.rb +77 -0
  43. data/lib/tty/prompt/distance.rb +49 -0
  44. data/lib/tty/prompt/enum_list.rb +337 -0
  45. data/lib/tty/prompt/enum_paginator.rb +56 -0
  46. data/lib/tty/prompt/evaluator.rb +29 -0
  47. data/lib/tty/prompt/expander.rb +292 -0
  48. data/lib/tty/prompt/keypress.rb +94 -0
  49. data/lib/tty/prompt/list.rb +317 -0
  50. data/lib/tty/prompt/mask_question.rb +91 -0
  51. data/lib/tty/prompt/multi_list.rb +108 -0
  52. data/lib/tty/prompt/multiline.rb +71 -0
  53. data/lib/tty/prompt/paginator.rb +88 -0
  54. data/lib/tty/prompt/question.rb +333 -0
  55. data/lib/tty/prompt/question/checks.rb +87 -0
  56. data/lib/tty/prompt/question/modifier.rb +94 -0
  57. data/lib/tty/prompt/question/validation.rb +72 -0
  58. data/lib/tty/prompt/reader.rb +352 -0
  59. data/lib/tty/prompt/reader/codes.rb +121 -0
  60. data/lib/tty/prompt/reader/console.rb +57 -0
  61. data/lib/tty/prompt/reader/history.rb +145 -0
  62. data/lib/tty/prompt/reader/key_event.rb +91 -0
  63. data/lib/tty/prompt/reader/line.rb +162 -0
  64. data/lib/tty/prompt/reader/mode.rb +44 -0
  65. data/lib/tty/prompt/reader/win_api.rb +29 -0
  66. data/lib/tty/prompt/reader/win_console.rb +53 -0
  67. data/lib/tty/prompt/result.rb +42 -0
  68. data/lib/tty/prompt/slider.rb +182 -0
  69. data/lib/tty/prompt/statement.rb +55 -0
  70. data/lib/tty/prompt/suggestion.rb +115 -0
  71. data/lib/tty/prompt/symbols.rb +61 -0
  72. data/lib/tty/prompt/timeout.rb +69 -0
  73. data/lib/tty/prompt/utils.rb +44 -0
  74. data/lib/tty/prompt/version.rb +7 -0
  75. data/lib/tty/test_prompt.rb +20 -0
  76. data/tasks/console.rake +11 -0
  77. data/tasks/coverage.rake +11 -0
  78. data/tasks/spec.rake +29 -0
  79. data/tty-prompt.gemspec +32 -0
  80. metadata +243 -0
@@ -0,0 +1,59 @@
1
+ # encoding: utf-8
2
+
3
+ module TTY
4
+ class Prompt
5
+ class AnswersCollector
6
+ # Initialize answer collector
7
+ #
8
+ # @api public
9
+ def initialize(prompt, options = {})
10
+ @prompt = prompt
11
+ @answers = options.fetch(:answers) { {} }
12
+ end
13
+
14
+ # Start gathering answers
15
+ #
16
+ # @return [Hash]
17
+ # the collection of all answers
18
+ #
19
+ # @api public
20
+ def call(&block)
21
+ instance_eval(&block)
22
+ @answers
23
+ end
24
+
25
+ # Create answer entry
26
+ #
27
+ # @example
28
+ # key(:name).ask('Name?')
29
+ #
30
+ # @api public
31
+ def key(name, &block)
32
+ @name = name
33
+ if block
34
+ answer = create_collector.(&block)
35
+ add_answer(answer)
36
+ end
37
+ self
38
+ end
39
+
40
+ # @api public
41
+ def create_collector
42
+ self.class.new(@prompt)
43
+ end
44
+
45
+ # @api public
46
+ def add_answer(answer)
47
+ @answers[@name] = answer
48
+ end
49
+
50
+ private
51
+
52
+ # @api private
53
+ def method_missing(method, *args, &block)
54
+ answer = @prompt.public_send(method, *args, &block)
55
+ add_answer(answer)
56
+ end
57
+ end # AnswersCollector
58
+ end # Prompt
59
+ end # TTY
@@ -0,0 +1,90 @@
1
+ # encoding: utf-8
2
+
3
+ module TTY
4
+ class Prompt
5
+ # A single choice option
6
+ #
7
+ # @api public
8
+ class Choice
9
+ # The label name
10
+ #
11
+ # @api public
12
+ attr_reader :name
13
+
14
+ attr_reader :key
15
+
16
+ # Create a Choice instance
17
+ #
18
+ # @api public
19
+ def initialize(name, value, key = nil)
20
+ @name = name
21
+ @value = value
22
+ @key = key
23
+ end
24
+
25
+ # Create choice from value
26
+ #
27
+ # @example
28
+ # Choice.from(:option_1)
29
+ # Choice.from([:option_1, 1])
30
+ #
31
+ # @param [Object] val
32
+ # the value to be converted
33
+ #
34
+ # @raise [ArgumentError]
35
+ #
36
+ # @return [Choice]
37
+ #
38
+ # @api public
39
+ def self.from(val)
40
+ case val
41
+ when Choice
42
+ val
43
+ when String, Symbol
44
+ new(val, val)
45
+ when Array
46
+ new("#{val.first}", val.last)
47
+ when Hash
48
+ if val.key?(:name)
49
+ new("#{val[:name]}", val[:value], val[:key])
50
+ else
51
+ new("#{val.keys.first}", val.values.first)
52
+ end
53
+ else
54
+ raise ArgumentError, "#{val} cannot be coerced into Choice"
55
+ end
56
+ end
57
+
58
+ # Read value and evaluate
59
+ #
60
+ # @api public
61
+ def value
62
+ case @value
63
+ when Proc
64
+ @value.call
65
+ else
66
+ @value
67
+ end
68
+ end
69
+
70
+ # Object equality comparison
71
+ #
72
+ # @return [Boolean]
73
+ #
74
+ # @api public
75
+ def ==(other)
76
+ return false unless other.is_a?(self.class)
77
+ name == other.name && value == other.value
78
+ end
79
+
80
+ # Object string representation
81
+ #
82
+ # @return [String]
83
+ #
84
+ # @api public
85
+ def to_s
86
+ "#{name}"
87
+ end
88
+ end # Choice
89
+ end # Prompt
90
+ end # TTY
@@ -0,0 +1,110 @@
1
+ # encoding: utf-8
2
+
3
+ require 'forwardable'
4
+
5
+ require_relative 'choice'
6
+
7
+ module TTY
8
+ class Prompt
9
+ # A class responsible for storing a collection of choices
10
+ #
11
+ # @api private
12
+ class Choices
13
+ include Enumerable
14
+ extend Forwardable
15
+
16
+ # The actual collection choices
17
+ #
18
+ # @return [Array[Choice]]
19
+ #
20
+ # @api public
21
+ attr_reader :choices
22
+
23
+ def_delegators :choices, :length, :size, :to_ary, :empty?, :values_at
24
+
25
+ # Convenience for creating choices
26
+ #
27
+ # @param [Array[Object]] choices
28
+ # the choice objects
29
+ #
30
+ # @return [Choices]
31
+ # the choices collection
32
+ #
33
+ # @api public
34
+ def self.[](*choices)
35
+ new(choices)
36
+ end
37
+
38
+ # Create Choices collection
39
+ #
40
+ # @param [Array[Choice]] choices
41
+ # the choices to add to collection
42
+ #
43
+ # @api public
44
+ def initialize(choices = [])
45
+ @choices = choices.map do |choice|
46
+ Choice.from(choice)
47
+ end
48
+ end
49
+
50
+ # Iterate over all choices in the collection
51
+ #
52
+ # @yield [Choice]
53
+ #
54
+ # @api public
55
+ def each(&block)
56
+ return to_enum unless block_given?
57
+ choices.each(&block)
58
+ end
59
+
60
+ # Add choice to collection
61
+ #
62
+ # @param [Object] choice
63
+ # the choice to add
64
+ #
65
+ # @api public
66
+ def <<(choice)
67
+ choices << Choice.from(choice)
68
+ end
69
+
70
+ # Access choice by index
71
+ #
72
+ # @param [Integer] index
73
+ #
74
+ # @return [Choice]
75
+ #
76
+ # @api public
77
+ def [](index)
78
+ @choices[index]
79
+ end
80
+
81
+ # Pluck a choice by its name from collection
82
+ #
83
+ # @param [String] name
84
+ # the label name for the choice
85
+ #
86
+ # @return [Choice]
87
+ #
88
+ # @api public
89
+ def pluck(name)
90
+ map { |choice| choice.public_send(name) }
91
+ end
92
+
93
+ # Find a matching choice
94
+ #
95
+ # @exmaple
96
+ # choices.find_by(:name, 'small')
97
+ #
98
+ # @param [Symbol] attr
99
+ # the attribute name
100
+ # @param [Object] value
101
+ #
102
+ # @return [Choice]
103
+ #
104
+ # @api public
105
+ def find_by(attr, value)
106
+ find { |choice| choice.public_send(attr) == value }
107
+ end
108
+ end # Choices
109
+ end # Prompt
110
+ end # TTY
@@ -0,0 +1,129 @@
1
+ # encoding: utf-8
2
+
3
+ require_relative 'question'
4
+ require_relative 'utils'
5
+
6
+ module TTY
7
+ class Prompt
8
+ class ConfirmQuestion < Question
9
+ # Create confirmation question
10
+ #
11
+ # @param [Hash] options
12
+ # @option options [String] :suffix
13
+ # @option options [String] :positive
14
+ # @option options [String] :negative
15
+ #
16
+ # @api public
17
+ def initialize(prompt, options = {})
18
+ super
19
+ @suffix = options.fetch(:suffix) { UndefinedSetting }
20
+ @positive = options.fetch(:positive) { UndefinedSetting }
21
+ @negative = options.fetch(:negative) { UndefinedSetting }
22
+ end
23
+
24
+ def positive?
25
+ @positive != UndefinedSetting
26
+ end
27
+
28
+ def negative?
29
+ @negative != UndefinedSetting
30
+ end
31
+
32
+ def suffix?
33
+ @suffix != UndefinedSetting
34
+ end
35
+
36
+ # Set question suffix
37
+ #
38
+ # @api public
39
+ def suffix(value = (not_set = true))
40
+ return @negative if not_set
41
+ @suffix = value
42
+ end
43
+
44
+ # Set value for matching positive choice
45
+ #
46
+ # @api public
47
+ def positive(value = (not_set = true))
48
+ return @positive if not_set
49
+ @positive = value
50
+ end
51
+
52
+ # Set value for matching negative choice
53
+ #
54
+ # @api public
55
+ def negative(value = (not_set = true))
56
+ return @negative if not_set
57
+ @negative = value
58
+ end
59
+
60
+ def call(message, &block)
61
+ return if Utils.blank?(message)
62
+ @message = message
63
+ block.call(self) if block
64
+ setup_defaults
65
+ render
66
+ end
67
+
68
+ # Render confirmation question
69
+ #
70
+ # @return [String]
71
+ #
72
+ # @api private
73
+ def render_question
74
+ header = "#{@prefix}#{message} "
75
+ if !@done
76
+ header += @prompt.decorate("(#{@suffix})", @help_color) + ' '
77
+ else
78
+ answer = convert_result(@input)
79
+ label = answer ? @positive : @negative
80
+ header += @prompt.decorate(label, @active_color)
81
+ end
82
+ header << "\n" if @done
83
+ header
84
+ end
85
+
86
+ protected
87
+
88
+ # @api private
89
+ def setup_defaults
90
+ return if suffix? && positive?
91
+
92
+ if suffix? && (!positive? || !negative?)
93
+ parts = @suffix.split('/')
94
+ @positive = parts[0]
95
+ @negative = parts[1]
96
+ @convert = conversion
97
+ elsif !suffix? && positive?
98
+ @suffix = create_suffix
99
+ @convert = conversion
100
+ else
101
+ create_default_labels
102
+ @convert = :bool
103
+ end
104
+ end
105
+
106
+ # @api private
107
+ def create_default_labels
108
+ @suffix = default ? 'Y/n' : 'y/N'
109
+ @positive = default ? 'Yes' : 'yes'
110
+ @negative = default ? 'no' : 'No'
111
+ end
112
+
113
+ # @api private
114
+ def create_suffix
115
+ result = ''
116
+ result << "#{default ? positive.capitalize : positive.downcase}"
117
+ result << '/'
118
+ result << "#{default ? negative.downcase : negative.capitalize}"
119
+ end
120
+
121
+ # Create custom conversion
122
+ #
123
+ # @api private
124
+ def conversion
125
+ proc { |input| !input.match(/^#{positive}|#{positive[0]}$/i).nil? }
126
+ end
127
+ end # ConfirmQuestion
128
+ end # Prompt
129
+ end # TTY
@@ -0,0 +1,22 @@
1
+ # encoding: utf-8
2
+
3
+ require_relative 'converter_registry'
4
+
5
+ module TTY
6
+ class Prompt
7
+ module ConverterDSL
8
+ def converter_registry
9
+ @converter_registry ||= ConverterRegistry.new
10
+ end
11
+
12
+ def converter(name, &block)
13
+ @converter_registry = converter_registry.register(name, &block)
14
+ self
15
+ end
16
+
17
+ def convert(name, data)
18
+ @converter_registry[name, data]
19
+ end
20
+ end # ConverterDSL
21
+ end # Prompt
22
+ end # TTY
@@ -0,0 +1,64 @@
1
+ # encoding: utf-8
2
+
3
+ module TTY
4
+ class Prompt
5
+ # Immutable collection of converters for type transformation
6
+ #
7
+ # @api private
8
+ class ConverterRegistry
9
+ # Create a registry of conversions
10
+ #
11
+ # @param [Hash] registry
12
+ #
13
+ # @api private
14
+ def initialize(registry = {})
15
+ @_registry = registry.dup.freeze
16
+ freeze
17
+ end
18
+
19
+ # Register converter
20
+ #
21
+ # @param [Symbol] name
22
+ # the converter name
23
+ #
24
+ # @api public
25
+ def register(name, contents = nil, &block)
26
+ item = block_given? ? block : contents
27
+
28
+ if key?(name)
29
+ raise ArgumentError,
30
+ "Converter for #{name.inspect} already registered"
31
+ end
32
+ self.class.new(@_registry.merge(name => item))
33
+ end
34
+
35
+ # Check if converter is registered
36
+ #
37
+ # @return [Boolean]
38
+ #
39
+ # @api public
40
+ def key?(key)
41
+ @_registry.key?(key)
42
+ end
43
+
44
+ # Execute converter
45
+ #
46
+ # @api public
47
+ def call(name, input)
48
+ if name.respond_to?(:call)
49
+ converter = name
50
+ else
51
+ converter = @_registry.fetch(name) do
52
+ raise ArgumentError, "#{name.inspect} is not registered"
53
+ end
54
+ end
55
+ converter[input]
56
+ end
57
+ alias [] call
58
+
59
+ def inspect
60
+ @_registry.inspect
61
+ end
62
+ end # ConverterRegistry
63
+ end # Prompt
64
+ end # TTY