tty-prompt 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (63) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +14 -0
  3. data/.rspec +3 -0
  4. data/.ruby-version +1 -0
  5. data/.travis.yml +24 -0
  6. data/Gemfile +16 -0
  7. data/LICENSE.txt +22 -0
  8. data/README.md +199 -0
  9. data/Rakefile +8 -0
  10. data/lib/tty-prompt.rb +15 -0
  11. data/lib/tty/prompt.rb +206 -0
  12. data/lib/tty/prompt/distance.rb +49 -0
  13. data/lib/tty/prompt/error.rb +26 -0
  14. data/lib/tty/prompt/history.rb +16 -0
  15. data/lib/tty/prompt/mode.rb +64 -0
  16. data/lib/tty/prompt/mode/echo.rb +40 -0
  17. data/lib/tty/prompt/mode/raw.rb +40 -0
  18. data/lib/tty/prompt/question.rb +338 -0
  19. data/lib/tty/prompt/question/modifier.rb +93 -0
  20. data/lib/tty/prompt/question/validation.rb +92 -0
  21. data/lib/tty/prompt/reader.rb +113 -0
  22. data/lib/tty/prompt/response.rb +252 -0
  23. data/lib/tty/prompt/response_delegation.rb +41 -0
  24. data/lib/tty/prompt/statement.rb +60 -0
  25. data/lib/tty/prompt/suggestion.rb +113 -0
  26. data/lib/tty/prompt/utils.rb +16 -0
  27. data/lib/tty/prompt/version.rb +7 -0
  28. data/spec/spec_helper.rb +45 -0
  29. data/spec/unit/ask_spec.rb +77 -0
  30. data/spec/unit/distance/distance_spec.rb +75 -0
  31. data/spec/unit/error_spec.rb +30 -0
  32. data/spec/unit/question/argument_spec.rb +30 -0
  33. data/spec/unit/question/character_spec.rb +24 -0
  34. data/spec/unit/question/default_spec.rb +25 -0
  35. data/spec/unit/question/in_spec.rb +23 -0
  36. data/spec/unit/question/initialize_spec.rb +24 -0
  37. data/spec/unit/question/modifier/apply_to_spec.rb +31 -0
  38. data/spec/unit/question/modifier/letter_case_spec.rb +22 -0
  39. data/spec/unit/question/modifier/whitespace_spec.rb +33 -0
  40. data/spec/unit/question/modify_spec.rb +44 -0
  41. data/spec/unit/question/valid_spec.rb +46 -0
  42. data/spec/unit/question/validate_spec.rb +30 -0
  43. data/spec/unit/question/validation/coerce_spec.rb +24 -0
  44. data/spec/unit/question/validation/valid_value_spec.rb +22 -0
  45. data/spec/unit/reader/getc_spec.rb +42 -0
  46. data/spec/unit/response/read_bool_spec.rb +47 -0
  47. data/spec/unit/response/read_char_spec.rb +16 -0
  48. data/spec/unit/response/read_date_spec.rb +20 -0
  49. data/spec/unit/response/read_email_spec.rb +42 -0
  50. data/spec/unit/response/read_multiple_spec.rb +23 -0
  51. data/spec/unit/response/read_number_spec.rb +28 -0
  52. data/spec/unit/response/read_range_spec.rb +26 -0
  53. data/spec/unit/response/read_spec.rb +68 -0
  54. data/spec/unit/response/read_string_spec.rb +19 -0
  55. data/spec/unit/say_spec.rb +66 -0
  56. data/spec/unit/statement/initialize_spec.rb +19 -0
  57. data/spec/unit/suggest_spec.rb +33 -0
  58. data/spec/unit/warn_spec.rb +30 -0
  59. data/tasks/console.rake +10 -0
  60. data/tasks/coverage.rake +11 -0
  61. data/tasks/spec.rake +29 -0
  62. data/tty-prompt.gemspec +26 -0
  63. metadata +194 -0
@@ -0,0 +1,30 @@
1
+ # encoding: utf-8
2
+
3
+ require 'spec_helper'
4
+
5
+ RSpec.describe TTY::Prompt::Question, '#argument' do
6
+ let(:input) { StringIO.new }
7
+ let(:output) { StringIO.new }
8
+ let(:prompt) { TTY::Prompt.new(input, output) }
9
+
10
+ it 'requires value to be present with helper' do
11
+ input << ''
12
+ input.rewind
13
+ q = prompt.ask("What is your name?").argument(:required)
14
+ expect { q.read }.to raise_error(ArgumentError)
15
+ end
16
+
17
+ it 'requires value to be present with option' do
18
+ input << ''
19
+ input.rewind
20
+ q = prompt.ask("What is your name?", required: true)
21
+ expect { q.read }.to raise_error(ArgumentError)
22
+ end
23
+
24
+ it "doesn't require value to be present" do
25
+ input << ''
26
+ input.rewind
27
+ q = prompt.ask("What is your name?").argument(:optional)
28
+ expect(q.read).to be_nil
29
+ end
30
+ end
@@ -0,0 +1,24 @@
1
+ # encoding: utf-8
2
+
3
+ require 'spec_helper'
4
+
5
+ RSpec.describe TTY::Prompt::Question, '#character' do
6
+ let(:input) { StringIO.new }
7
+ let(:output) { StringIO.new }
8
+ let(:prompt) { TTY::Prompt.new(input, output) }
9
+
10
+ it 'switches to character input' do
11
+ input << "abcd"
12
+ input.rewind
13
+ q = prompt.ask("Which one do you prefer a, b, c or d?").char(true)
14
+ expect(q.character)
15
+ expect(q.read_string).to eq("abcd")
16
+ end
17
+
18
+ it 'acts as reader without arguments' do
19
+ input << "abcd"
20
+ input.rewind
21
+ q = prompt.ask("Which one do you prefer a, b, c or d?")
22
+ expect(q.char).to eq(false)
23
+ end
24
+ end
@@ -0,0 +1,25 @@
1
+ # encoding: utf-8
2
+
3
+ require 'spec_helper'
4
+
5
+ RSpec.describe TTY::Prompt::Question, '#default' do
6
+ let(:input) { StringIO.new }
7
+ let(:output) { StringIO.new }
8
+ let(:prompt) { TTY::Prompt.new(input, output) }
9
+
10
+ it 'uses default value' do
11
+ name = 'Anonymous'
12
+ q = prompt.ask("What is your name?").default(name)
13
+ answer = q.read
14
+ expect(answer).to eq(name)
15
+ end
16
+
17
+ it 'uses default value in block' do
18
+ name = 'Anonymous'
19
+ q = prompt.ask "What is your name?" do
20
+ default name
21
+ end
22
+ answer = q.read
23
+ expect(answer).to eq(name)
24
+ end
25
+ end
@@ -0,0 +1,23 @@
1
+ # encoding: utf-8
2
+
3
+ require 'spec_helper'
4
+
5
+ RSpec.describe TTY::Prompt::Question, '#in' do
6
+ let(:input) { StringIO.new }
7
+ let(:output) { StringIO.new }
8
+ let(:prompt) { TTY::Prompt.new(input, output) }
9
+
10
+ it 'reads number within range' do
11
+ input << 8
12
+ input.rewind
13
+ q = prompt.ask("How do you like it on scale 1-10").in('1-10')
14
+ expect(q.read_int).to eql 8
15
+ end
16
+
17
+ it "reads number outside of range" do
18
+ input << 12
19
+ input.rewind
20
+ q = prompt.ask("How do you like it on scale 1-10").in('1-10')
21
+ expect { q.read_int }.to raise_error(ArgumentError)
22
+ end
23
+ end
@@ -0,0 +1,24 @@
1
+ # encoding: utf-8
2
+
3
+ require 'spec_helper'
4
+
5
+ RSpec.describe TTY::Prompt::Question, '#initialize' do
6
+ let(:input) { StringIO.new }
7
+ let(:output) { StringIO.new }
8
+ let(:prompt) { TTY::Prompt.new(input, output) }
9
+ let(:message) { 'Do you like me?' }
10
+
11
+ subject(:question) { described_class.new prompt }
12
+
13
+ it { expect(question.required?).to eq(false) }
14
+
15
+ it { expect(question.echo).to eq(true) }
16
+
17
+ it { expect(question.mask).to eq(false) }
18
+
19
+ it { expect(question.character).to eq(false) }
20
+
21
+ it { expect(question.modifier).to be_kind_of(TTY::Prompt::Question::Modifier) }
22
+
23
+ it { expect(question.validation).to be_kind_of(TTY::Prompt::Question::Validation) }
24
+ end
@@ -0,0 +1,31 @@
1
+ # encoding: utf-8
2
+
3
+ require 'spec_helper'
4
+
5
+ RSpec.describe TTY::Prompt::Question::Modifier, '#apply_to' do
6
+ let(:instance) { described_class.new modifiers }
7
+ let(:string) { "Text to be modified"}
8
+
9
+ it "doesn't apply modifiers" do
10
+ modifier = described_class.new([])
11
+ expect(modifier.apply_to(string)).to eq(string)
12
+ end
13
+
14
+ it 'applies letter case modifications' do
15
+ modifiers = [:down, :capitalize]
16
+ allow(described_class).to receive(:letter_case)
17
+ modifier = described_class.new(modifiers)
18
+ modifier.apply_to(string)
19
+ expect(described_class).to have_received(:letter_case).
20
+ with(modifiers, string)
21
+ end
22
+
23
+ it 'applies whitespace modifications' do
24
+ modifiers = [:up, :capitalize]
25
+ allow(described_class).to receive(:whitespace)
26
+ modifier = described_class.new(modifiers)
27
+ modifier.apply_to(string)
28
+ expect(described_class).to have_received(:whitespace).
29
+ with(modifiers, string)
30
+ end
31
+ end
@@ -0,0 +1,22 @@
1
+ # encoding: utf-8
2
+
3
+ require 'spec_helper'
4
+
5
+ RSpec.describe TTY::Prompt::Question::Modifier, '#letter_case' do
6
+ let(:string) { 'text to modify' }
7
+
8
+ it "changes to uppercase" do
9
+ modified = described_class.letter_case(:up, string)
10
+ expect(modified).to eq('TEXT TO MODIFY')
11
+ end
12
+
13
+ it "changes to lower case" do
14
+ modified = described_class.letter_case(:down, string)
15
+ expect(modified).to eq('text to modify')
16
+ end
17
+
18
+ it "capitalizes text" do
19
+ modified = described_class.letter_case(:capitalize, string)
20
+ expect(modified).to eq('Text to modify')
21
+ end
22
+ end
@@ -0,0 +1,33 @@
1
+ # encoding: utf-8
2
+
3
+ require 'spec_helper'
4
+
5
+ RSpec.describe TTY::Prompt::Question::Modifier, '#whitespace' do
6
+ let(:string) { " text\t \n to\t modify\r\n" }
7
+
8
+ subject { described_class.whitespace modifier, string}
9
+
10
+ context 'when stripping whitespace' do
11
+ let(:modifier) { :trim }
12
+
13
+ it { is_expected.to eq("text\t \n to\t modify") }
14
+ end
15
+
16
+ context 'when chomping whitespace' do
17
+ let(:modifier) { :chomp }
18
+
19
+ it { is_expected.to eq(" text\t \n to\t modify") }
20
+ end
21
+
22
+ context 'when capitalize' do
23
+ let(:modifier) { :collapse }
24
+
25
+ it { is_expected.to eq(" text to modify ") }
26
+ end
27
+
28
+ context 'when removing whitespace' do
29
+ let(:modifier) { :remove }
30
+
31
+ it { is_expected.to eq("texttomodify") }
32
+ end
33
+ end
@@ -0,0 +1,44 @@
1
+ # encoding: utf-8
2
+
3
+ require 'spec_helper'
4
+
5
+ RSpec.describe TTY::Prompt::Question, '#modify' do
6
+ let(:input) { StringIO.new }
7
+ let(:output) { StringIO.new }
8
+ let(:prompt) { TTY::Prompt.new(input, output) }
9
+
10
+ it 'preserves answer for unkown modification' do
11
+ input << 'piotr'
12
+ input.rewind
13
+ q = prompt.ask("What is your name?").modify(:none)
14
+ expect(q.read_string).to eql 'piotr'
15
+ end
16
+
17
+ it 'converts to upper case' do
18
+ input << 'piotr'
19
+ input.rewind
20
+ q = prompt.ask("What is your name?").modify(:upcase)
21
+ expect(q.read_string).to eql 'PIOTR'
22
+ end
23
+
24
+ it 'trims whitespace' do
25
+ input << " Some white\t space\t \there! \n"
26
+ input.rewind
27
+ q = prompt.ask("Enter some text: ").modify(:trim)
28
+ expect(q.read_string).to eql "Some white\t space\t \there!"
29
+ end
30
+
31
+ it 'collapses whitespace' do
32
+ input << " Some white\t space\t \there! \n"
33
+ input.rewind
34
+ q = prompt.ask("Enter some text: ").modify(:collapse)
35
+ expect(q.read_string).to eql "Some white space here!"
36
+ end
37
+
38
+ it 'strips and collapses whitespace' do
39
+ input << " Some white\t space\t \there! \n"
40
+ input.rewind
41
+ q = prompt.ask("Enter some text: ").modify(:strip, :collapse)
42
+ expect(q.read_string).to eql "Some white space here!"
43
+ end
44
+ end
@@ -0,0 +1,46 @@
1
+ # encoding: utf-8
2
+
3
+ require 'spec_helper'
4
+
5
+ RSpec.describe TTY::Prompt::Question, '#valid' do
6
+ let(:input) { StringIO.new }
7
+ let(:output) { StringIO.new }
8
+ let(:prompt) { TTY::Prompt.new(input, output) }
9
+
10
+ let(:cards) { %w[ club diamond spade heart ] }
11
+
12
+ it 'reads valid optios with helper' do
13
+ input << 'club'
14
+ input.rewind
15
+ q = prompt.ask("What is your card suit sir?").valid(cards)
16
+ expect(q.read_choice).to eq('club')
17
+ end
18
+
19
+ it 'reads valid options with option hash' do
20
+ input << 'club'
21
+ input.rewind
22
+ q = prompt.ask("What is your card suit sir?", valid: cards)
23
+ expect(q.read_choice).to eq('club')
24
+ end
25
+
26
+ it 'reads invalid option' do
27
+ input << 'clover'
28
+ input.rewind
29
+ q = prompt.ask("What is your card suit sir?").valid(cards)
30
+ expect { q.read_choice }.to raise_error(TTY::Prompt::InvalidArgument)
31
+ end
32
+
33
+ it 'needs argument' do
34
+ input << ''
35
+ input.rewind
36
+ q = prompt.ask("What is your card suit sir?").valid(cards)
37
+ expect { q.read_choice }.to raise_error(TTY::Prompt::ArgumentRequired)
38
+ end
39
+
40
+ it 'reads with default' do
41
+ input << ''
42
+ input.rewind
43
+ q = prompt.ask("What is your card suit sir?").valid(cards).default('club')
44
+ expect(q.read_choice).to eq('club')
45
+ end
46
+ end
@@ -0,0 +1,30 @@
1
+ # encoding: utf-8
2
+
3
+ require 'spec_helper'
4
+
5
+ RSpec.describe TTY::Prompt::Question, '#validate' do
6
+ let(:input) { StringIO.new }
7
+ let(:output) { StringIO.new }
8
+ let(:prompt) { TTY::Prompt.new(input, output) }
9
+
10
+ it 'fails to validate input' do
11
+ input << 'piotrmurach'
12
+ input.rewind
13
+ q = prompt.ask("What is your username?").validate(/^[^\.]+\.[^\.]+/)
14
+ expect { q.read_string }.to raise_error(ArgumentError)
15
+ end
16
+
17
+ it 'validates input with regex' do
18
+ input << 'piotr.murach'
19
+ input.rewind
20
+ q = prompt.ask("What is your username?").validate(/^[^\.]+\.[^\.]+/)
21
+ expect(q.read_string).to eq('piotr.murach')
22
+ end
23
+
24
+ it 'validates input in block' do
25
+ input << 'piotr.murach'
26
+ input.rewind
27
+ q = prompt.ask("What is your username?").validate { |arg| arg =~ /^[^\.]+\.[^\.]+/ }
28
+ expect(q.read_string).to eq('piotr.murach')
29
+ end
30
+ end
@@ -0,0 +1,24 @@
1
+ # encoding: utf-8
2
+
3
+ require 'spec_helper'
4
+
5
+ RSpec.describe TTY::Prompt::Question::Validation, '#coerce' do
6
+ let(:instance) { described_class.new }
7
+
8
+ it "coerces into proc" do
9
+ validation = lambda { "^[^\.]+\.[^\.]+" }
10
+ expect(instance.coerce(validation)).to be_kind_of(Proc)
11
+ end
12
+
13
+ it "cources into regex" do
14
+ validation = "^[^\.]+\.[^\.]+"
15
+ expect(instance.coerce(validation)).to be_kind_of(Regexp)
16
+ end
17
+
18
+ it "fails to coerce validation" do
19
+ validation = Object.new
20
+ expect {
21
+ instance.coerce(validation)
22
+ }.to raise_error(TTY::Prompt::ValidationCoercion)
23
+ end
24
+ end
@@ -0,0 +1,22 @@
1
+ # encoding: utf-8
2
+
3
+ require 'spec_helper'
4
+
5
+ RSpec.describe TTY::Prompt::Question::Validation, '#valid_value?' do
6
+ let(:validation) { /^[^\.]+\.[^\.]+/ }
7
+ let(:instance) { described_class.new validation }
8
+
9
+ it "validates nil input" do
10
+ expect(instance.valid_value?(nil)).to eq(false)
11
+ end
12
+
13
+ it "validates successfully when the value matches pattern" do
14
+ expect(instance.valid_value?('piotr.murach')).to eq(true)
15
+ end
16
+
17
+ it "fails validation when not maching pattern" do
18
+ expect {
19
+ instance.valid_value?('piotrmurach')
20
+ }.to raise_error(TTY::Prompt::InvalidArgument)
21
+ end
22
+ end
@@ -0,0 +1,42 @@
1
+ # encoding: utf-8
2
+
3
+ require 'spec_helper'
4
+
5
+ RSpec.describe TTY::Prompt::Reader, '#getc' do
6
+ let(:instance) { described_class.new(prompt) }
7
+
8
+ let(:input) { StringIO.new }
9
+ let(:output) { StringIO.new }
10
+ let(:prompt) { TTY::Prompt.new(input, output) }
11
+
12
+ subject(:reader) { instance.getc mask }
13
+
14
+ context 'with mask' do
15
+ let(:mask) { '*'}
16
+
17
+ it 'masks characters' do
18
+ input << "password\n"
19
+ input.rewind
20
+ expect(reader).to eql "password"
21
+ expect(output.string).to eql("********")
22
+ end
23
+ end
24
+
25
+ context "without mask" do
26
+ let(:mask) { }
27
+
28
+ it 'masks characters' do
29
+ input << "password\n"
30
+ input.rewind
31
+ expect(reader).to eql "password"
32
+ expect(output.string).to eql("password")
33
+ end
34
+
35
+ it 'deletes characters when backspace pressed' do
36
+ input << "\b\b"
37
+ input.rewind
38
+ expect(reader).to eql ''
39
+ expect(output.string).to eql('')
40
+ end
41
+ end
42
+ end
@@ -0,0 +1,47 @@
1
+ # encoding: utf-8
2
+
3
+ require 'spec_helper'
4
+
5
+ RSpec.describe TTY::Prompt::Question, '#read_bool' do
6
+ let(:input) { StringIO.new }
7
+ let(:output) { StringIO.new }
8
+ let(:prompt) { TTY::Prompt.new(input, output) }
9
+
10
+ it 'fails to read boolean' do
11
+ input << 'invalid'
12
+ input.rewind
13
+ q = prompt.ask("Do you read books?")
14
+ expect { q.read_bool }.to raise_error(Necromancer::ConversionTypeError)
15
+ end
16
+
17
+ it "handles default values" do
18
+ input << "\n"
19
+ input.rewind
20
+ q = prompt.ask("Do you read books?").default(true)
21
+ expect(q.read_bool).to eql(true)
22
+ end
23
+
24
+ it 'reads negative boolean' do
25
+ input << 'No'
26
+ input.rewind
27
+ q = prompt.ask("Do you read books?")
28
+ answer = q.read_bool
29
+ expect(answer).to eql false
30
+ end
31
+
32
+ it 'reads positive boolean' do
33
+ input << 'Yes'
34
+ input.rewind
35
+ q = prompt.ask("Do you read books?")
36
+ answer = q.read_bool
37
+ expect(answer).to eql true
38
+ end
39
+
40
+ it 'reads single positive boolean' do
41
+ input << 'y'
42
+ input.rewind
43
+ q = prompt.ask("Do you read books?")
44
+ answer = q.read_bool
45
+ expect(answer).to eql true
46
+ end
47
+ end