simple_input 0.2.1 → 0.2.2
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.
- checksums.yaml +4 -4
- data/README.md +89 -0
- data/examples/area_calculator.rb +65 -0
- data/examples/area_calculator_spec.rb +54 -0
- data/lib/simple_prompt/input.rb +20 -6
- data/lib/simple_prompt/version.rb +1 -1
- metadata +3 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 067ce86ac266a279491b935536c473fe2b257e9a3cd3556877c7affe2be3311c
|
|
4
|
+
data.tar.gz: e0f078fa1ee462081ed0d2da0d76f44461b6e90d1d595cf1beea1186574937e6
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: c488763914f58752e2f230e78df81e55bc88b53587218ddfeee91ac855c35ffb18a4c8260911615652c058803fd36acf2ee64a53840b704245af23d9a01da736
|
|
7
|
+
data.tar.gz: f165c355abc6a8776b38bdbc47f2b35991cee593c8ccc776304fca1cf60260186a8b361b7b559225cd2a080aebf31f4544c7eaf4e70f8f34146b23e2c8609391
|
data/README.md
CHANGED
|
@@ -167,6 +167,92 @@ age = Input.new_input
|
|
|
167
167
|
|
|
168
168
|
All methods (except `#run`) return `self` for chaining.
|
|
169
169
|
|
|
170
|
+
## Complete Example: Area Calculator
|
|
171
|
+
|
|
172
|
+
Here's a practical example that demonstrates validation, conversion, and dependency injection for testing:
|
|
173
|
+
|
|
174
|
+
```ruby
|
|
175
|
+
class AreaCalculator
|
|
176
|
+
def initialize(reader = $stdin, writer = $stdout)
|
|
177
|
+
@reader = reader
|
|
178
|
+
@writer = writer
|
|
179
|
+
end
|
|
180
|
+
|
|
181
|
+
# Reusable conversion function
|
|
182
|
+
def to_number
|
|
183
|
+
->(value) do
|
|
184
|
+
if value.to_i.to_s == value
|
|
185
|
+
value.to_i
|
|
186
|
+
elsif value.to_f.to_s == value
|
|
187
|
+
value.to_f
|
|
188
|
+
else
|
|
189
|
+
raise ArgumentError, "Invalid number: #{value}"
|
|
190
|
+
end
|
|
191
|
+
end
|
|
192
|
+
end
|
|
193
|
+
|
|
194
|
+
def input_length
|
|
195
|
+
Input.new_input
|
|
196
|
+
.send(:with_context, @reader, @writer)
|
|
197
|
+
.title('What is the length of the room in feet?')
|
|
198
|
+
.validate { |value| value.to_i > 0 ? nil : 'Length must be a positive number' }
|
|
199
|
+
.convert_func(to_number)
|
|
200
|
+
.run
|
|
201
|
+
end
|
|
202
|
+
|
|
203
|
+
def input_width
|
|
204
|
+
Input.new_input
|
|
205
|
+
.send(:with_context, @reader, @writer)
|
|
206
|
+
.title('What is the width of the room in feet?')
|
|
207
|
+
.validate { |value| value.to_i > 0 ? nil : 'Width must be a positive number' }
|
|
208
|
+
.convert_func(to_number)
|
|
209
|
+
.run
|
|
210
|
+
end
|
|
211
|
+
|
|
212
|
+
def calculate_area
|
|
213
|
+
length = input_length
|
|
214
|
+
width = input_width
|
|
215
|
+
area = length * width
|
|
216
|
+
|
|
217
|
+
@writer.puts "\nYou entered dimensions of #{length} feet by #{width} feet."
|
|
218
|
+
@writer.puts "The area is #{area} square feet"
|
|
219
|
+
end
|
|
220
|
+
end
|
|
221
|
+
```
|
|
222
|
+
|
|
223
|
+
This example can be tested with RSpec using mocks:
|
|
224
|
+
|
|
225
|
+
```ruby
|
|
226
|
+
RSpec.describe AreaCalculator do
|
|
227
|
+
let(:writer) { double('writer') }
|
|
228
|
+
let(:reader) { double('reader') }
|
|
229
|
+
subject(:calculator) { AreaCalculator.new(reader, writer) }
|
|
230
|
+
|
|
231
|
+
before do
|
|
232
|
+
allow(writer).to receive(:puts)
|
|
233
|
+
allow(writer).to receive(:print)
|
|
234
|
+
end
|
|
235
|
+
|
|
236
|
+
it 'returns integer for valid integer input' do
|
|
237
|
+
allow(reader).to receive(:gets).and_return("10\n")
|
|
238
|
+
|
|
239
|
+
result = calculator.input_length
|
|
240
|
+
|
|
241
|
+
expect(result).to eq(10)
|
|
242
|
+
end
|
|
243
|
+
|
|
244
|
+
it 'returns float for valid float input' do
|
|
245
|
+
allow(reader).to receive(:gets).and_return("10.5\n")
|
|
246
|
+
|
|
247
|
+
result = calculator.input_length
|
|
248
|
+
|
|
249
|
+
expect(result).to eq(10.5)
|
|
250
|
+
end
|
|
251
|
+
end
|
|
252
|
+
```
|
|
253
|
+
|
|
254
|
+
See `examples/area_calculator.rb` and `examples/area_calculator_spec.rb` for the complete implementation.
|
|
255
|
+
|
|
170
256
|
## Running Examples
|
|
171
257
|
|
|
172
258
|
```bash
|
|
@@ -184,6 +270,9 @@ ruby examples/validation_patterns.rb
|
|
|
184
270
|
|
|
185
271
|
# Error handling and conversion safety
|
|
186
272
|
ruby examples/error_handling.rb
|
|
273
|
+
|
|
274
|
+
# Area calculator with numeric conversion (practical example)
|
|
275
|
+
ruby examples/area_calculator.rb
|
|
187
276
|
```
|
|
188
277
|
|
|
189
278
|
## Testing
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
#!/usr/bin/env ruby
|
|
2
|
+
# frozen_string_literal: true
|
|
3
|
+
|
|
4
|
+
require_relative '../lib/simple_prompt'
|
|
5
|
+
|
|
6
|
+
# Example: Area Calculator with Input Validation and Conversion
|
|
7
|
+
# This example demonstrates using Input with:
|
|
8
|
+
# - Custom validation for positive numbers
|
|
9
|
+
# - Value conversion from string to integer/float
|
|
10
|
+
# - Reusable conversion function
|
|
11
|
+
|
|
12
|
+
class AreaCalculator
|
|
13
|
+
def initialize(reader = $stdin, writer = $stdout)
|
|
14
|
+
@reader = reader
|
|
15
|
+
@writer = writer
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
# Conversion function that converts string to appropriate numeric type
|
|
19
|
+
def to_number
|
|
20
|
+
->(value) do
|
|
21
|
+
if value.to_i.to_s == value
|
|
22
|
+
value.to_i
|
|
23
|
+
elsif value.to_f.to_s == value
|
|
24
|
+
value.to_f
|
|
25
|
+
else
|
|
26
|
+
raise ArgumentError, "Invalid number: #{value}"
|
|
27
|
+
end
|
|
28
|
+
end
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
def input_length
|
|
32
|
+
Input.new_input
|
|
33
|
+
.send(:with_context, @reader, @writer)
|
|
34
|
+
.title('What is the length of the room in feet?')
|
|
35
|
+
.validate { |value| value.to_i > 0 ? nil : 'Length must be a positive number' }
|
|
36
|
+
.convert_func(to_number)
|
|
37
|
+
.run
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
def input_width
|
|
41
|
+
Input.new_input
|
|
42
|
+
.send(:with_context, @reader, @writer)
|
|
43
|
+
.title('What is the width of the room in feet?')
|
|
44
|
+
.validate { |value| value.to_i > 0 ? nil : 'Width must be a positive number' }
|
|
45
|
+
.convert_func(to_number)
|
|
46
|
+
.run
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
def calculate_area
|
|
50
|
+
length = input_length
|
|
51
|
+
width = input_width
|
|
52
|
+
area = length * width
|
|
53
|
+
|
|
54
|
+
@writer.puts "\nYou entered dimensions of #{length} feet by #{width} feet."
|
|
55
|
+
@writer.puts "The area is"
|
|
56
|
+
@writer.puts "#{area} square feet"
|
|
57
|
+
@writer.puts "#{(area * 0.09290304).round(3)} square meters"
|
|
58
|
+
end
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
# Run the calculator if this file is executed directly
|
|
62
|
+
if __FILE__ == $PROGRAM_NAME
|
|
63
|
+
calculator = AreaCalculator.new
|
|
64
|
+
calculator.calculate_area
|
|
65
|
+
end
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative '../lib/simple_prompt'
|
|
4
|
+
require_relative 'area_calculator'
|
|
5
|
+
|
|
6
|
+
# Example RSpec tests for area_calculator.rb
|
|
7
|
+
# This demonstrates how to test Input with mocks
|
|
8
|
+
|
|
9
|
+
RSpec.describe AreaCalculator do
|
|
10
|
+
let(:writer) { double('writer') }
|
|
11
|
+
let(:reader) { double('reader') }
|
|
12
|
+
subject(:calculator) { AreaCalculator.new(reader, writer) }
|
|
13
|
+
|
|
14
|
+
before do
|
|
15
|
+
allow(writer).to receive(:puts)
|
|
16
|
+
allow(writer).to receive(:print)
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
describe '#input_length' do
|
|
20
|
+
it 'returns integer for valid integer input' do
|
|
21
|
+
allow(reader).to receive(:gets).and_return("10\n")
|
|
22
|
+
|
|
23
|
+
result = calculator.input_length
|
|
24
|
+
|
|
25
|
+
expect(result).to eq(10)
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
it 'returns float for valid float input' do
|
|
29
|
+
allow(reader).to receive(:gets).and_return("10.5\n")
|
|
30
|
+
|
|
31
|
+
result = calculator.input_length
|
|
32
|
+
|
|
33
|
+
expect(result).to eq(10.5)
|
|
34
|
+
end
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
describe '#input_width' do
|
|
38
|
+
it 'returns integer for valid integer input' do
|
|
39
|
+
allow(reader).to receive(:gets).and_return("20\n")
|
|
40
|
+
|
|
41
|
+
result = calculator.input_width
|
|
42
|
+
|
|
43
|
+
expect(result).to eq(20)
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
it 'returns float for valid float input' do
|
|
47
|
+
allow(reader).to receive(:gets).and_return("20.3\n")
|
|
48
|
+
|
|
49
|
+
result = calculator.input_width
|
|
50
|
+
|
|
51
|
+
expect(result).to eq(20.3)
|
|
52
|
+
end
|
|
53
|
+
end
|
|
54
|
+
end
|
data/lib/simple_prompt/input.rb
CHANGED
|
@@ -10,6 +10,7 @@ class Input
|
|
|
10
10
|
|
|
11
11
|
ValidatorProc = T.type_alias { T.proc.params(arg0: String).returns(T.nilable(String)) }
|
|
12
12
|
ValidatorType = T.type_alias { T.any(Symbol, ValidatorProc) }
|
|
13
|
+
ConvertProc = T.type_alias { T.proc.params(arg0: String).returns(T.untyped) }
|
|
13
14
|
|
|
14
15
|
sig { returns(Input) }
|
|
15
16
|
def self.new_input; new; end
|
|
@@ -18,12 +19,13 @@ class Input
|
|
|
18
19
|
|
|
19
20
|
sig { void }
|
|
20
21
|
def initialize
|
|
21
|
-
@reader = T.let($stdin, T.
|
|
22
|
-
@writer = T.let($stdout, T.
|
|
22
|
+
@reader = T.let($stdin, T.untyped)
|
|
23
|
+
@writer = T.let($stdout, T.untyped)
|
|
23
24
|
@title = T.let('', String)
|
|
24
25
|
@prompt = T.let('> ', String)
|
|
25
26
|
@validators = T.let([], T::Array[ValidatorProc])
|
|
26
27
|
@validator_provider = T.let(DefaultValidators, T.untyped)
|
|
28
|
+
@convert_func = T.let(nil, T.nilable(ConvertProc))
|
|
27
29
|
end
|
|
28
30
|
|
|
29
31
|
sig { params(text: String).returns(T.self_type) }
|
|
@@ -35,6 +37,12 @@ class Input
|
|
|
35
37
|
sig { params(provider: T.untyped).returns(T.self_type) }
|
|
36
38
|
def with_validators(provider); @validator_provider = provider; self; end
|
|
37
39
|
|
|
40
|
+
sig { params(converter: T.nilable(ConvertProc), block: T.nilable(ConvertProc)).returns(T.self_type) }
|
|
41
|
+
def convert_func(converter = nil, &block)
|
|
42
|
+
@convert_func = block || converter
|
|
43
|
+
self
|
|
44
|
+
end
|
|
45
|
+
|
|
38
46
|
sig { params(validator: T.nilable(ValidatorType), block: T.nilable(ValidatorProc)).returns(T.self_type) }
|
|
39
47
|
def validate(validator = nil, &block)
|
|
40
48
|
if block
|
|
@@ -51,7 +59,7 @@ class Input
|
|
|
51
59
|
self
|
|
52
60
|
end
|
|
53
61
|
|
|
54
|
-
sig { returns(
|
|
62
|
+
sig { returns(T.untyped) }
|
|
55
63
|
def run
|
|
56
64
|
loop do
|
|
57
65
|
@writer.puts "\e[1m#{@title}\e[0m" unless @title.empty?
|
|
@@ -64,7 +72,13 @@ class Input
|
|
|
64
72
|
error = T.let(nil, T.nilable(String))
|
|
65
73
|
@validators.each { |v| break if (error = v.call(val)) }
|
|
66
74
|
|
|
67
|
-
|
|
75
|
+
unless error
|
|
76
|
+
begin
|
|
77
|
+
return @convert_func ? @convert_func.call(val) : val
|
|
78
|
+
rescue StandardError => e
|
|
79
|
+
error = "Conversion error: #{e.message} (please add validation)"
|
|
80
|
+
end
|
|
81
|
+
end
|
|
68
82
|
|
|
69
83
|
# 視認性のための余白
|
|
70
84
|
@writer.puts ''
|
|
@@ -78,6 +92,6 @@ class Input
|
|
|
78
92
|
|
|
79
93
|
private
|
|
80
94
|
|
|
81
|
-
sig { params(
|
|
82
|
-
def with_context(
|
|
95
|
+
sig { params(reader: T.untyped, writer: T.untyped).returns(T.self_type) }
|
|
96
|
+
def with_context(reader, writer); @reader = reader; @writer = writer; self; end
|
|
83
97
|
end
|
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: simple_input
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.2.
|
|
4
|
+
version: 0.2.2
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Hiroaki Satou
|
|
@@ -104,6 +104,8 @@ extra_rdoc_files: []
|
|
|
104
104
|
files:
|
|
105
105
|
- LICENSE
|
|
106
106
|
- README.md
|
|
107
|
+
- examples/area_calculator.rb
|
|
108
|
+
- examples/area_calculator_spec.rb
|
|
107
109
|
- examples/basic.rb
|
|
108
110
|
- examples/convert.rb
|
|
109
111
|
- examples/custom_validators.rb
|