tty 0.0.6 → 0.0.7

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