tty-prompt 0.1.0

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 (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