tty 0.0.6 → 0.0.7

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 (81) hide show
  1. data/README.md +78 -12
  2. data/benchmarks/shell.rb +26 -0
  3. data/benchmarks/table.rb +35 -0
  4. data/lib/tty.rb +23 -1
  5. data/lib/tty/coercer.rb +13 -0
  6. data/lib/tty/coercer/boolean.rb +39 -0
  7. data/lib/tty/coercer/float.rb +23 -0
  8. data/lib/tty/coercer/integer.rb +23 -0
  9. data/lib/tty/coercer/range.rb +33 -0
  10. data/lib/tty/shell.rb +6 -2
  11. data/lib/tty/shell/question.rb +158 -138
  12. data/lib/tty/shell/reader.rb +92 -0
  13. data/lib/tty/shell/response.rb +219 -0
  14. data/lib/tty/shell/response_delegation.rb +53 -0
  15. data/lib/tty/table.rb +90 -16
  16. data/lib/tty/table/border.rb +34 -8
  17. data/lib/tty/table/border/ascii.rb +16 -25
  18. data/lib/tty/table/border/null.rb +0 -6
  19. data/lib/tty/table/border/unicode.rb +16 -25
  20. data/lib/tty/table/column_set.rb +1 -1
  21. data/lib/tty/table/error.rb +10 -0
  22. data/lib/tty/table/operation/wrapped.rb +0 -6
  23. data/lib/tty/table/orientation.rb +57 -0
  24. data/lib/tty/table/orientation/horizontal.rb +19 -0
  25. data/lib/tty/table/orientation/vertical.rb +19 -0
  26. data/lib/tty/table/renderer.rb +7 -0
  27. data/lib/tty/table/renderer/ascii.rb +1 -1
  28. data/lib/tty/table/renderer/basic.rb +2 -2
  29. data/lib/tty/table/renderer/unicode.rb +1 -1
  30. data/lib/tty/table/validatable.rb +20 -0
  31. data/lib/tty/terminal.rb +15 -14
  32. data/lib/tty/terminal/color.rb +1 -1
  33. data/lib/tty/terminal/echo.rb +41 -0
  34. data/lib/tty/terminal/home.rb +31 -0
  35. data/lib/tty/text.rb +85 -0
  36. data/lib/tty/text/truncation.rb +83 -0
  37. data/lib/tty/text/wrapping.rb +96 -0
  38. data/lib/tty/version.rb +1 -1
  39. data/spec/tty/coercer/boolean/coerce_spec.rb +113 -0
  40. data/spec/tty/coercer/float/coerce_spec.rb +32 -0
  41. data/spec/tty/coercer/integer/coerce_spec.rb +39 -0
  42. data/spec/tty/coercer/range/coerce_spec.rb +73 -0
  43. data/spec/tty/shell/ask_spec.rb +14 -1
  44. data/spec/tty/shell/question/argument_spec.rb +30 -0
  45. data/spec/tty/shell/question/character_spec.rb +16 -0
  46. data/spec/tty/shell/question/default_spec.rb +25 -0
  47. data/spec/tty/shell/question/in_spec.rb +23 -0
  48. data/spec/tty/shell/question/initialize_spec.rb +11 -211
  49. data/spec/tty/shell/question/modifier/whitespace_spec.rb +1 -1
  50. data/spec/tty/shell/question/modify_spec.rb +44 -0
  51. data/spec/tty/shell/question/valid_spec.rb +46 -0
  52. data/spec/tty/shell/question/validate_spec.rb +30 -0
  53. data/spec/tty/shell/reader/getc_spec.rb +40 -0
  54. data/spec/tty/shell/response/read_bool_spec.rb +41 -0
  55. data/spec/tty/shell/response/read_char_spec.rb +17 -0
  56. data/spec/tty/shell/response/read_date_spec.rb +20 -0
  57. data/spec/tty/shell/response/read_email_spec.rb +43 -0
  58. data/spec/tty/shell/response/read_multiple_spec.rb +24 -0
  59. data/spec/tty/shell/response/read_number_spec.rb +29 -0
  60. data/spec/tty/shell/response/read_range_spec.rb +29 -0
  61. data/spec/tty/shell/response/read_spec.rb +68 -0
  62. data/spec/tty/shell/response/read_string_spec.rb +19 -0
  63. data/spec/tty/table/access_spec.rb +6 -0
  64. data/spec/tty/table/border/new_spec.rb +3 -3
  65. data/spec/tty/table/initialize_spec.rb +17 -1
  66. data/spec/tty/table/options_spec.rb +7 -1
  67. data/spec/tty/table/orientation_spec.rb +98 -0
  68. data/spec/tty/table/renders_with_spec.rb +76 -0
  69. data/spec/tty/table/rotate_spec.rb +72 -0
  70. data/spec/tty/table/to_s_spec.rb +13 -1
  71. data/spec/tty/table/validatable/validate_options_spec.rb +34 -0
  72. data/spec/tty/terminal/color/remove_spec.rb +34 -1
  73. data/spec/tty/terminal/echo_spec.rb +22 -0
  74. data/spec/tty/text/truncate_spec.rb +13 -0
  75. data/spec/tty/text/truncation/initialize_spec.rb +29 -0
  76. data/spec/tty/text/truncation/truncate_spec.rb +73 -0
  77. data/spec/tty/text/wrap_spec.rb +14 -0
  78. data/spec/tty/text/wrapping/initialize_spec.rb +25 -0
  79. data/spec/tty/text/wrapping/wrap_spec.rb +80 -0
  80. data/tty.gemspec +1 -0
  81. metadata +101 -8
data/README.md CHANGED
@@ -50,12 +50,19 @@ To instantiate table pass 2-dimensional array:
50
50
  table = TTY::Table.new header: ['h1', 'h2'], rows: [['a1', 'a2'], ['b1', 'b2']]
51
51
  ```
52
52
 
53
+ or cross header with rows inside a hash like so
54
+
55
+ ```ruby
56
+ table = TTY::Table.new [{'h1' => ['a1', 'a2'], 'h2' => ['b1', 'b2']}]
57
+ ```
58
+
53
59
  Apart from `rows` and `header`, you can provide other customization options such as
54
60
 
55
61
  ```ruby
56
- column_widths # enforce maximum columns widths
62
+ column_widths # array of maximum columns widths
57
63
  column_aligns # array of cell alignments out of :left, :center and :right
58
64
  renderer # enforce display type out of :basic, :color, :unicode, :ascii
65
+ orientation # either :horizontal or :vertical
59
66
  ```
60
67
 
61
68
  Table behaves like an Array so `<<`, `each` and familiar methods can be used
@@ -106,13 +113,38 @@ To print border around data table you need to specify `renderer` type out of `ba
106
113
  └───────┴───────┘
107
114
  ```
108
115
 
116
+ You can also create your own custom border by subclassing `TTY::Table::Border`
117
+
118
+ ```ruby
119
+ class MyBorder < TTY::Table::Border
120
+ def_border do
121
+ {
122
+ 'bottom' => ' ',
123
+ 'bottom_mid' => '*',
124
+ 'bottom_left' => '*',
125
+ 'bottom_right' => '*',
126
+ 'left' => '$',
127
+ 'right' => '$'
128
+ }
129
+ end
130
+ end
131
+ ```
132
+ Next pass the border to your table
133
+
134
+ ```ruby
135
+ table.renders_with MyBorder
136
+ ```
137
+
109
138
  ### Terminal
110
139
 
140
+ To read general terminal properties you can use on of the helpers
141
+
111
142
  ```ruby
112
143
  term = TTY::Terminal.new
113
- term.width # => 140
114
- term.height # => 60
115
- term.color? # => true or false
144
+ term.width # => 140
145
+ term.height # => 60
146
+ term.color? # => true or false
147
+ term.echo(false) { } # switch off echo for the block
116
148
  ```
117
149
 
118
150
  To colorize your output do
@@ -127,6 +159,19 @@ To colorize your output do
127
159
 
128
160
  Main responsibility is to interact with the prompt and provide convenience methods.
129
161
 
162
+ Available methods are
163
+
164
+ ```ruby
165
+ shell = TTY::Shell.new
166
+ shell.ask # print question
167
+ shell.read # read from stdin
168
+ shell.say # print message to stdout
169
+ shell.confirm # print message(s) in green
170
+ shell.warn # print message(s) in yellow
171
+ shell.error # print message(s) in red
172
+ shell.print_table # print table to stdout
173
+ ```
174
+
130
175
  In order to ask question and parse answers:
131
176
 
132
177
  ```ruby
@@ -137,12 +182,16 @@ In order to ask question and parse answers:
137
182
  The library provides small DSL to help with parsing and asking precise questions
138
183
 
139
184
  ```ruby
140
- default # default value used if none is provided
141
185
  argument # :required or :optional
186
+ character # turn character based input, otherwise line (default: false)
187
+ clean # reset question
188
+ default # default value used if none is provided
189
+ echo # turn echo on and off (default: true)
190
+ mask # mask characters i.e '****' (default: false)
191
+ modify # apply answer modification :upcase, :downcase, :trim, :chomp etc..
192
+ range # specify range '0-9', '0..9', '0...9' or negative '-1..-9'
142
193
  validate # regex against which stdin input is checked
143
194
  valid # a list of expected valid options
144
- modify # apply answer modification :upcase, :downcase, :trim, :chomp etc..
145
- clean # reset question
146
195
  ```
147
196
 
148
197
  You can chain question methods or configure them inside a block
@@ -162,14 +211,31 @@ You can chain question methods or configure them inside a block
162
211
  Reading answers and converting them into required types can be done with custom readers
163
212
 
164
213
  ```ruby
165
- read_string # return string
166
214
  read_bool # return true or false for strings such as "Yes", "No"
167
- read_int # return integer or error if cannot convert
168
- read_float # return decimal or error if cannot convert
169
215
  read_date # return date type
170
216
  read_datetime # return datetime type
171
- read_multiple # return multiple line string
172
217
  read_email # validate answer against email regex
218
+ read_float # return decimal or error if cannot convert
219
+ read_int # return integer or error if cannot convert
220
+ read_multiple # return multiple line string
221
+ read_password # return string with echo turned off
222
+ read_range # return range type
223
+ read_string # return string
224
+ ```
225
+
226
+ For example, if we wanted to ask a user for a single digit in given range
227
+
228
+ ```ruby
229
+ ask("Provide number in range: 0-9") do
230
+ range '0-9'
231
+ on_error :retry
232
+ end.read_int
233
+ ```
234
+
235
+ on the other hand, if we are interested in range answer then
236
+
237
+ ```ruby
238
+ ask("Provide range of numbers?").read_range
173
239
  ```
174
240
 
175
241
  ### System
@@ -189,4 +255,4 @@ Reading answers and converting them into required types can be done with custom
189
255
 
190
256
  ## Copyright
191
257
 
192
- Copyright (c) 2012 Piotr Murach. See LICENSE for further details.
258
+ Copyright (c) 2012-2013 Piotr Murach. See LICENSE for further details.
@@ -0,0 +1,26 @@
1
+ # -*- encoding: utf-8 -*-
2
+
3
+ # Benchmark speed of shell operations
4
+
5
+ $LOAD_PATH.unshift File.expand_path('../../lib', __FILE__)
6
+
7
+ require 'tty'
8
+ require 'benchmark'
9
+ require 'benchmark/ips'
10
+ require 'stringio'
11
+
12
+ input = ::StringIO.new
13
+ output = ::StringIO.new
14
+ shell = TTY::Shell.new(input, output)
15
+
16
+ Benchmark.ips do |r|
17
+
18
+ r.report("Ruby #puts") do
19
+ output.puts "What is your name?"
20
+ end
21
+
22
+ r.report("TTY #ask") do
23
+ shell.ask("What is your name?")
24
+ end
25
+
26
+ end
@@ -0,0 +1,35 @@
1
+ # -*- encoding: utf-8 -*-
2
+
3
+ # Benchmark speed of table operations
4
+
5
+ $LOAD_PATH.unshift File.expand_path('../../lib', __FILE__)
6
+
7
+ require 'tty'
8
+ require 'benchmark'
9
+ require 'benchmark/ips'
10
+
11
+ header = [:name, :color]
12
+ rows = (1..100).map { |n| ["row#{n}", "red"] }
13
+ table = TTY::Table.new(header, rows)
14
+ table_ascii = TTY::Table.new(header, rows, :renderer => :ascii)
15
+ table_unicode = TTY::Table.new(header, rows, :renderer => :unicode)
16
+
17
+ Benchmark.ips do |r|
18
+
19
+ r.report("Ruby #to_s") do
20
+ rows.to_s
21
+ end
22
+
23
+ r.report("TTY #to_s") do
24
+ table.to_s
25
+ end
26
+
27
+ r.report("TTY ASCII #to_s") do
28
+ table_ascii.to_s
29
+ end
30
+
31
+ r.report("TTY Unicode #to_s") do
32
+ table_unicode.to_s
33
+ end
34
+
35
+ end
data/lib/tty.rb CHANGED
@@ -10,16 +10,32 @@ require 'tty/support/equatable'
10
10
  require 'tty/support/unicode'
11
11
 
12
12
  require 'tty/terminal'
13
- require 'tty/terminal/color'
14
13
  require 'tty/system'
15
14
  require 'tty/table'
15
+ require 'tty/text'
16
16
  require 'tty/vector'
17
17
  require 'tty/shell'
18
+ require 'tty/coercer'
19
+
20
+ require 'tty/coercer/range'
21
+ require 'tty/coercer/integer'
22
+ require 'tty/coercer/float'
23
+ require 'tty/coercer/boolean'
18
24
 
25
+ require 'tty/shell/response_delegation'
19
26
  require 'tty/shell/question'
20
27
  require 'tty/shell/question/validation'
21
28
  require 'tty/shell/question/modifier'
22
29
  require 'tty/shell/statement'
30
+ require 'tty/shell/reader'
31
+ require 'tty/shell/response'
32
+
33
+ require 'tty/terminal/color'
34
+ require 'tty/terminal/echo'
35
+ require 'tty/terminal/home'
36
+
37
+ require 'tty/text/wrapping'
38
+ require 'tty/text/truncation'
23
39
 
24
40
  require 'tty/table/border'
25
41
  require 'tty/table/border/unicode'
@@ -27,6 +43,9 @@ require 'tty/table/border/ascii'
27
43
  require 'tty/table/border/null'
28
44
 
29
45
  require 'tty/table/column_set'
46
+ require 'tty/table/orientation'
47
+ require 'tty/table/orientation/horizontal'
48
+ require 'tty/table/orientation/vertical'
30
49
 
31
50
  require 'tty/table/operation/alignment_set'
32
51
  require 'tty/table/operation/alignment'
@@ -47,6 +66,9 @@ module TTY
47
66
  # Raised when the argument is not expected
48
67
  class InvalidArgument < ArgumentError; end
49
68
 
69
+ # Raised when the table orientation is unkown
70
+ class InvalidOrientationError < ArgumentError; end
71
+
50
72
  # Raised when the passed in validation argument is of wrong type
51
73
  class ValidationCoercion < TypeError; end
52
74
 
@@ -0,0 +1,13 @@
1
+ # -*- encoding: utf-8 -*-
2
+
3
+ module TTY
4
+
5
+ # Abstract class for type coercions
6
+ #
7
+ # @abstract
8
+ class Coercer
9
+ include TTY::Equatable
10
+
11
+ end # Coercer
12
+
13
+ end # TTY
@@ -0,0 +1,39 @@
1
+ # -*- encoding: utf-8 -*-
2
+
3
+ module TTY
4
+ class Coercer
5
+
6
+ # A class responsible for boolean type coercion
7
+ class Boolean
8
+
9
+ # Coerce value to boolean type including range of strings such as
10
+ #
11
+ # @param [Object] value
12
+ #
13
+ # @example
14
+ # coerce("True") # => true
15
+ #
16
+ # other values coerced to true are:
17
+ # 1, t, T, TRUE, true, True, y, Y, YES, yes, Yes
18
+ #
19
+ # coerce("False") # => false
20
+ #
21
+ # other values coerced to false are:
22
+ # 0, f, F, FALSE, false, False, n, N, No, no, No
23
+ #
24
+ # @api public
25
+ def self.coerce(value)
26
+ case value.to_s
27
+ when %r/^(yes|y|t(rue)?|1)$/i
28
+ return true
29
+ when %r/^(no|n|f(alse)?|0)$/i
30
+ return false
31
+ else
32
+ raise TypeError, "Expected boolean type, got #{value}"
33
+ end
34
+ end
35
+
36
+ end # Boolean
37
+
38
+ end # Coercer
39
+ end # TTY
@@ -0,0 +1,23 @@
1
+ # -*- encoding: utf-8 -*-
2
+
3
+ module TTY
4
+ class Coercer
5
+
6
+ class Float
7
+
8
+ def self.coerce(value, strict=true)
9
+ begin
10
+ Kernel.send(:Float, value.to_s)
11
+ rescue
12
+ if strict
13
+ raise InvalidArgument, "#{value} could not be coerced into Float"
14
+ else
15
+ value.to_f
16
+ end
17
+ end
18
+ end
19
+
20
+ end # Float
21
+
22
+ end # Coercer
23
+ end # TTY
@@ -0,0 +1,23 @@
1
+ # -*- encoding: utf-8 -*-
2
+
3
+ module TTY
4
+ class Coercer
5
+
6
+ class Integer
7
+
8
+ def self.coerce(value, strict=true)
9
+ begin
10
+ Kernel.send(:Integer, value.to_s)
11
+ rescue
12
+ if strict
13
+ raise InvalidArgument, "#{value} could not be coerced into Integer"
14
+ else
15
+ value.to_i
16
+ end
17
+ end
18
+ end
19
+
20
+ end # Integer
21
+
22
+ end # Coercer
23
+ end # TTY
@@ -0,0 +1,33 @@
1
+ # -*- encoding: utf-8 -*-
2
+
3
+ module TTY
4
+ class Coercer
5
+
6
+ # A class responsible for range type coercion
7
+ class Range
8
+
9
+ # Coerce value to Range type with possible ranges
10
+ #
11
+ # @param [Object] value
12
+ #
13
+ # @example
14
+ # coerce('0-9') # => (0..9)
15
+ #
16
+ # @api public
17
+ def self.coerce(value)
18
+ case value.to_s
19
+ when /\A(\-?\d+)\Z/
20
+ ::Range.new($1.to_i, $1.to_i)
21
+ when /\A(-?\d+?)(\.{2}\.?|-|,)(-?\d+)\Z/
22
+ ::Range.new($1.to_i, $3.to_i, $2 == '...')
23
+ when /\A(\w)(\.{2}\.?|-|,)(\w)\Z/
24
+ ::Range.new($1.to_s, $3.to_s, $2 == '...')
25
+ else
26
+ raise InvalidArgument, "#{value} could not be coerced into Range type"
27
+ end
28
+ end
29
+
30
+ end # Range
31
+
32
+ end # Coercer
33
+ end # TTY
@@ -11,12 +11,16 @@ module TTY
11
11
  # @api private
12
12
  attr_reader :output
13
13
 
14
+ # @api private
15
+ attr_reader :prefix
16
+
14
17
  # Initialize a Shell
15
18
  #
16
19
  # @api public
17
- def initialize(input=stdin, output=stdout)
20
+ def initialize(input=stdin, output=stdout, options={})
18
21
  @input = input
19
22
  @output = output
23
+ @prefix = options.fetch(:prefix) { '' }
20
24
  end
21
25
 
22
26
  # Ask a question.
@@ -39,7 +43,7 @@ module TTY
39
43
  def ask(statement, *args, &block)
40
44
  options = Utils.extract_options!(args)
41
45
 
42
- question = Question.new self, statement, options
46
+ question = Question.new self, options
43
47
  question.instance_eval(&block) if block_given?
44
48
  question.prompt(statement)
45
49
  end
@@ -1,21 +1,23 @@
1
1
  # -*- encoding: utf-8 -*-
2
2
 
3
+ require 'date'
4
+
3
5
  module TTY
6
+ # A class responsible for shell prompt interactions.
4
7
  class Shell
5
8
 
6
9
  # A class representing a question.
7
10
  class Question
11
+ include TTY::Shell::ResponseDelegation
8
12
 
9
13
  PREFIX = " + "
10
14
  MULTIPLE_PREFIX = " * "
11
15
  ERROR_PREFIX = " ERROR:"
12
16
 
13
- VALID_TYPES = [:boolean, :string, :symbol, :integer, :float, :date, :datetime]
14
-
15
- # Store question.
17
+ # Store statement.
16
18
  #
17
19
  # @api private
18
- attr_accessor :question
20
+ attr_accessor :statement
19
21
 
20
22
  # Store default value.
21
23
  #
@@ -27,40 +29,50 @@ module TTY
27
29
 
28
30
  attr_reader :validation
29
31
 
30
- # Controls character processing to the answer
32
+ # Controls character processing of the answer
31
33
  #
32
34
  # @api public
33
35
  attr_reader :modifier
34
36
 
37
+ # Returns valid answers
38
+ #
39
+ # @api public
35
40
  attr_reader :valid_values
36
41
 
37
42
  attr_reader :error
38
43
 
39
- attr_reader :statement
44
+ # Returns echo mode
45
+ #
46
+ # @api public
47
+ attr_reader :echo
40
48
 
41
- # Expected answer type
49
+ # Returns character mask
42
50
  #
43
- # @api private
44
- attr_reader :type
51
+ # @api public
52
+ attr_reader :mask
53
+
54
+ # Returns character mode
55
+ #
56
+ # @api public
57
+ attr_reader :character
45
58
 
46
59
  # @api private
47
60
  attr_reader :shell
48
61
  private :shell
49
62
 
50
- def initialize(shell, statement, options={})
51
- @shell = shell || Shell.new
52
- @statement = statement
53
- @required = options.fetch :required, false
54
- @modifier = Modifier.new options.fetch(:modifier, [])
55
- @valid_values = options.fetch :valid, []
56
- @validation = Validation.new options.fetch(:validation, nil)
57
- end
58
-
59
- # Check if required argument present.
63
+ # Initialize a Question
60
64
  #
61
- # @api private
62
- def required?
63
- required
65
+ # @api public
66
+ def initialize(shell, options={})
67
+ @shell = shell || Shell.new
68
+ @required = options.fetch(:required) { false }
69
+ @echo = options.fetch(:echo) { true }
70
+ @mask = options.fetch(:mask) { false }
71
+ @character = options.fetch(:character) { false }
72
+ @in = options.fetch(:in) { false }
73
+ @modifier = Modifier.new options.fetch(:modifier) { [] }
74
+ @valid_values = options.fetch(:valid) { [] }
75
+ @validation = Validation.new options.fetch(:validation) { nil }
64
76
  end
65
77
 
66
78
  # Set a new prompt
@@ -70,8 +82,8 @@ module TTY
70
82
  # @return [self]
71
83
  #
72
84
  def prompt(message)
73
- self.question = message
74
- shell.say question
85
+ self.statement = message
86
+ shell.say shell.prefix + statement
75
87
  self
76
88
  end
77
89
 
@@ -84,6 +96,11 @@ module TTY
84
96
  self
85
97
  end
86
98
 
99
+ # Check if default value is set
100
+ #
101
+ # @return [Boolean]
102
+ #
103
+ # @api public
87
104
  def default?
88
105
  !!@default_value
89
106
  end
@@ -103,6 +120,15 @@ module TTY
103
120
  self
104
121
  end
105
122
 
123
+ # Check if required argument present.
124
+ #
125
+ # @return [Boolean]
126
+ #
127
+ # @api private
128
+ def required?
129
+ required
130
+ end
131
+
106
132
  # Set validation rule for an argument
107
133
  #
108
134
  # @param [Object] value
@@ -115,26 +141,24 @@ module TTY
115
141
  self
116
142
  end
117
143
 
144
+ # Set expected values
145
+ #
146
+ # @param [Array] values
147
+ #
148
+ # @return [self]
149
+ #
118
150
  # @api public
119
- def valid(value)
120
- @valid_values = value
151
+ def valid(values)
152
+ @valid_values = values
121
153
  self
122
154
  end
123
155
 
124
- # @api private
125
- def check_valid(value)
126
- if Array(value).all? { |val| @valid_values.include? val }
127
- return value
128
- else raise InvalidArgument, "Valid values are: #{@valid_values.join(', ')}"
129
- end
130
- end
131
156
 
132
157
  # Reset question object.
133
158
  #
134
159
  # @api public
135
160
  def clean
136
- @question = nil
137
- @type = nil
161
+ @statement = nil
138
162
  @default_value = nil
139
163
  @required = false
140
164
  @modifier = nil
@@ -150,152 +174,148 @@ module TTY
150
174
  self
151
175
  end
152
176
 
177
+ # Setup behaviour when error(s) occur
178
+ #
153
179
  # @api public
154
180
  def on_error(action=nil)
155
181
  @error = action
156
182
  self
157
183
  end
158
184
 
159
- # @api private
160
- def read(type=nil)
161
- result = shell.input.gets
162
- if !result && default?
163
- return default_value
164
- end
165
- if required? && !default? && !result
166
- raise ArgumentRequired, 'No value provided for required'
167
- end
168
- validation.valid_value? result
169
- modifier.apply_to result
170
- end
171
-
172
- # Read answer and cast to String type
173
- #
174
- # @param [String] error
175
- # error to display on failed conversion to string type
185
+ # Check if error behaviour is set
176
186
  #
177
187
  # @api public
178
- def read_string(error=nil)
179
- String(read)
188
+ def error?
189
+ !!@error
180
190
  end
181
191
 
182
- # Read multiple line answer and cast to String type
183
- def read_text
184
- String(read)
185
- end
186
-
187
- # Read ansewr and cast to Symbol type
188
- def read_symbol(error=nil)
189
- read.to_sym
190
- end
191
-
192
- def read_int(error=nil)
193
- Kernel.send(:Integer, read)
194
- end
195
-
196
- def read_float(error=nil)
197
- Kernel.send(:Float, read)
198
- end
199
-
200
- def read_regex(error=nil)
201
- Kernel.send(:Regex, read)
192
+ # Turn terminal echo on or off. This is used to secure the display so
193
+ # that the entered characters are not echoed back to the screen.
194
+ #
195
+ # @api public
196
+ def echo(value=(not_set=true))
197
+ return @echo if not_set
198
+ @echo = value
199
+ self
202
200
  end
203
201
 
204
- def read_date
205
- Date.parse(read)
202
+ # Chec if echo is set
203
+ #
204
+ # @api public
205
+ def echo?
206
+ !!@echo
206
207
  end
207
208
 
208
- def read_datetime
209
- DateTime.parse(read)
209
+ # Set character for masking the STDIN input
210
+ #
211
+ # @param [String] character
212
+ #
213
+ # @return [self]
214
+ #
215
+ # @api public
216
+ def mask(character=(not_set=true))
217
+ return @mask if not_set
218
+ @mask = character
219
+ self
210
220
  end
211
221
 
212
- def read_bool(error=nil)
213
- parse_boolean read
222
+ # Check if character mask is set
223
+ #
224
+ # @return [Boolean]
225
+ #
226
+ # @api public
227
+ def mask?
228
+ !!@mask
214
229
  end
215
230
 
216
- def read_choice(type=nil)
217
- @required = true unless default?
218
- check_valid read
231
+ # Set if the input is character based or not
232
+ #
233
+ # @param [Boolean] value
234
+ #
235
+ # @return [self]
236
+ #
237
+ # @api public
238
+ def character(value=(not_set=true))
239
+ return @character if not_set
240
+ @character = value
241
+ self
219
242
  end
220
243
 
221
- def read_file(error=nil)
222
- File.open(File.join(directory, read))
244
+ # Check if character intput is set
245
+ #
246
+ # @return [Boolean]
247
+ #
248
+ # @api public
249
+ def character?
250
+ !!@character
223
251
  end
224
252
 
225
- # Ignore exception
253
+ # Set expect range of values
226
254
  #
227
- # @api private
228
- def with_exception(&block)
229
- yield
230
- rescue
231
- block.call
255
+ # @param [String] value
256
+ #
257
+ # @api public
258
+ def in(value=(not_set=true))
259
+ return @in if not_set
260
+ @in = TTY::Coercer::Range.coerce value
261
+ self
232
262
  end
233
263
 
234
- # Reads string answer and validates against email regex
264
+ # Check if range is set
235
265
  #
236
- # @return [String]
266
+ # @return [Boolean]
237
267
  #
238
268
  # @api public
239
- def read_email
240
- validate(/^[a-z0-9._%+-]+@([a-z0-9-]+\.)+[a-z]{2,6}$/i)
241
- if error
242
- self.prompt statement
243
- with_exception { read_string }
244
- else
245
- read_string
246
- end
269
+ def in?
270
+ !!@in
247
271
  end
248
272
 
249
- # Read answer provided on multiple lines
273
+ # Check if response matches all the requirements set by the question
250
274
  #
251
- # @api public
252
- def read_multiple
253
- response = ""
254
- loop do
255
- value = read
256
- break if !value || value == ""
257
- response << value
258
- end
259
- response
275
+ # @param [Object] value
276
+ #
277
+ # @return [Object]
278
+ #
279
+ # @api private
280
+ def evaluate_response(value)
281
+ return default_value if !value && default?
282
+
283
+ check_required value
284
+ check_valid value unless valid_values.empty?
285
+ within? value
286
+ validation.valid_value? value
287
+ modifier.apply_to value
260
288
  end
261
289
 
262
- protected
290
+ private
263
291
 
264
- # @param [Symbol] type
265
- # :boolean, :string, :numeric, :array
292
+ # Check if value is present
266
293
  #
267
294
  # @api private
268
- def read_type(type)
269
- raise TypeError, "Type #{type} is not valid" if type && !valid_type?(type)
270
- case type
271
- when :string
272
- read_string
273
- when :symbol
274
- read_symbol
275
- when :float
276
- read_float
295
+ def check_required(value)
296
+ if required? && !default? && !value
297
+ raise ArgumentRequired, 'No value provided for required'
277
298
  end
278
299
  end
279
300
 
280
- def valid_type?(type)
281
- self.class::VALID_TYPES.include? type.to_sym
301
+ # Check if value matches any of the expected values
302
+ #
303
+ # @api private
304
+ def check_valid(value)
305
+ if Array(value).all? { |val| valid_values.include? val }
306
+ return value
307
+ else raise InvalidArgument, "Valid values are: #{valid_values.join(', ')}"
308
+ end
282
309
  end
283
310
 
284
- # Convert message into boolean type
285
- #
286
- # @param [String] message
287
- #
288
- # @return [Boolean]
311
+ # Check if value is within expected range
289
312
  #
290
313
  # @api private
291
- def parse_boolean(message)
292
- case message.to_s
293
- when %r/^(yes|y)$/i
294
- return true
295
- when %r/^(no|n)$/i
296
- return false
297
- else
298
- raise TypeError, "Expected boolean type, got #{message}"
314
+ def within?(value)
315
+ if in? && value
316
+ if @in.include?(value)
317
+ else raise InvalidArgument, "Value #{value} is not included in the range #{@in}"
318
+ end
299
319
  end
300
320
  end
301
321