command_kit 0.1.0.pre2 → 0.1.0.rc1

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: baf0cab5c4519bef85a5ac27597c1c67b28a77cbe86d6c34b72687a91e1ef906
4
- data.tar.gz: 1b816bb979dcff48caf5c09ac7f4dcd9006871c94b9fd3bee5a7c2ce1763815a
3
+ metadata.gz: 5c8944ddc49470bbe03b23cdf0df4cc9e5f94010208e15b373430453556ff2ed
4
+ data.tar.gz: bfebe1ddb5ae5a078e5910de55f626ae69dc8716f3d5152690e9002b68878bfe
5
5
  SHA512:
6
- metadata.gz: 284c9a6ff6bb1584add29558658054fc0814cfc0b1a1609b16a577154bc85e7011d9989abc75418bbd631f00a5f6c444dd104b3d938be347ad32ddf08e84ec83
7
- data.tar.gz: f0df98d7654058aa5be0aa884703ef540be3a1b7e2be52cd4a975913731ee4572d30cfc1a7b5213b9a165e7a18f95f4bbd3d9eb1a217362370a6c3b9e6c7ea93
6
+ metadata.gz: a8f00bba78443b945ea4c41efeaf4488eb6c65867ac637b4ae368d6a53724802395f0f9d5d464241fbe9d7f22fd1657e50161df4846fcdcce174f4abf206abb7
7
+ data.tar.gz: 497da95cec49705d426da9dfa27aa5f7ca9e462375dba94375244e5180f9ac9f1735850022fdfd61876cb6661a5988c568eda518dac010859be69589c3780843
data/ChangeLog.md CHANGED
@@ -1,13 +1,13 @@
1
- ### 0.1.0 / 2021-05-XX
1
+ ### 0.1.0 / 2021-07-XX
2
2
 
3
3
  * Initial release:
4
4
  * {CommandKit::Arguments}
5
5
  * {CommandKit::Colors}
6
+ * {CommandKit::Command}
6
7
  * {CommandKit::CommandName}
7
8
  * {CommandKit::Commands}
8
9
  * {CommandKit::Commands::AutoLoad}
9
10
  * {CommandKit::Commands::AutoRequire}
10
- * {CommandKit::Console}
11
11
  * {CommandKit::Description}
12
12
  * {CommandKit::Env}
13
13
  * {CommandKit::Env::Home}
@@ -16,14 +16,17 @@
16
16
  * {CommandKit::ExceptionHandler}
17
17
  * {CommandKit::Help}
18
18
  * {CommandKit::Help::Man}
19
+ * {CommandKit::Interactive}
19
20
  * {CommandKit::Main}
20
21
  * {CommandKit::Options}
21
22
  * {CommandKit::Options::Quiet}
22
23
  * {CommandKit::Options::Verbose}
23
24
  * {CommandKit::Pager}
24
25
  * {CommandKit::Printing}
26
+ * {CommandKit::Printing::Indent}
25
27
  * {CommandKit::ProgramName}
26
28
  * {CommandKit::Stdio}
29
+ * {CommandKit::Terminal}
27
30
  * {CommandKit::Usage}
28
31
  * {CommandKit::XDG}
29
32
 
data/README.md CHANGED
@@ -7,8 +7,8 @@
7
7
 
8
8
  ## Description
9
9
 
10
- A Ruby toolkit for building clean, correct, and robust CLI commands as Ruby
11
- classes.
10
+ A Ruby toolkit for building clean, correct, and robust CLI commands as
11
+ plain-old Ruby classes.
12
12
 
13
13
  ## Features
14
14
 
@@ -18,13 +18,13 @@ classes.
18
18
  * Supports subcommands (explicit or lazy-loaded) and command aliases.
19
19
  * Correctly handles Ctrl^C and SIGINT interrupts (aka exit 130).
20
20
  * Correctly handles broken pipes (aka `mycmd | head`).
21
- * Uses [OptionParser][optparse] for option parsing.
22
- * Provides ANSI coloring support.
21
+ * Correctly handles when stdout or stdin is redirected to a file.
22
+ * Uses [OptionParser][optparse] for POSIX option parsing.
23
+ * Supports optional ANSI coloring.
23
24
  * Supports optionally displaying a man-page instead of `--help`
24
25
  (see {CommandKit::Help::Man}).
25
26
  * Supports XDG directories (`~/.config/`, `~/.local/share/`, `~/.cache/`).
26
27
  * Easy to test (ex: `MyCmd.main(arg1, arg2, options: {foo: foo}) # => 0`)
27
- * Modular design (everything is a module).
28
28
 
29
29
  ### API
30
30
 
@@ -35,7 +35,6 @@ classes.
35
35
  * [CommandKit::Commands](https://rubydoc.info/github/postmodern/command_kit/main/CommandKit/Commands)
36
36
  * [CommandKit::Commands::AutoLoad](https://rubydoc.info/github/postmodern/command_kit/main/CommandKit/Commands/AutoLoad)
37
37
  * [CommandKit::Commands::AutoRequire](https://rubydoc.info/github/postmodern/command_kit/main/CommandKit/Commands/AutoRequire)
38
- * [CommandKit::Console](https://rubydoc.info/github/postmodern/command_kit/main/CommandKit/Console)
39
38
  * [CommandKit::Description](https://rubydoc.info/github/postmodern/command_kit/main/CommandKit/Description)
40
39
  * [CommandKit::Env](https://rubydoc.info/github/postmodern/command_kit/main/CommandKit/Env)
41
40
  * [CommandKit::Env::Home](https://rubydoc.info/github/postmodern/command_kit/main/CommandKit/Env/Home)
@@ -44,6 +43,7 @@ classes.
44
43
  * [CommandKit::ExceptionHandler](https://rubydoc.info/github/postmodern/command_kit/main/CommandKit/ExceptionHandler)
45
44
  * [CommandKit::Help](https://rubydoc.info/github/postmodern/command_kit/main/CommandKit/Help)
46
45
  * [CommandKit::Help::Man](https://rubydoc.info/github/postmodern/command_kit/main/CommandKit/Help/Man)
46
+ * [CommandKit::Interactive](https://rubydoc.info/github/postmodern/command_kit/main/CommandKit/Interactive)
47
47
  * [CommandKit::Main](https://rubydoc.info/github/postmodern/command_kit/main/CommandKit/Main)
48
48
  * [CommandKit::Options](https://rubydoc.info/github/postmodern/command_kit/main/CommandKit/Options)
49
49
  * [CommandKit::Options::Quiet](https://rubydoc.info/github/postmodern/command_kit/main/CommandKit/Options/Quiet)
@@ -53,6 +53,7 @@ classes.
53
53
  * [CommandKit::Printing::Indent](https://rubydoc.info/github/postmodern/command_kit/main/CommandKit/Printing/Indent)
54
54
  * [CommandKit::ProgramName](https://rubydoc.info/github/postmodern/command_kit/main/CommandKit/ProgramName)
55
55
  * [CommandKit::Stdio](https://rubydoc.info/github/postmodern/command_kit/main/CommandKit/Stdio)
56
+ * [CommandKit::Terminal](https://rubydoc.info/github/postmodern/command_kit/main/CommandKit/Terminal)
56
57
  * [CommandKit::Usage](https://rubydoc.info/github/postmodern/command_kit/main/CommandKit/Usage)
57
58
  * [CommandKit::XDG](https://rubydoc.info/github/postmodern/command_kit/main/CommandKit/XDG)
58
59
 
data/gemspec.yml CHANGED
@@ -2,7 +2,8 @@ name: command_kit
2
2
  summary: A toolkit for building Ruby CLI commands
3
3
  description:
4
4
  A Ruby toolkit for building clean, correct, and robust CLI commands as
5
- Ruby classes.
5
+ plain-old Ruby classes.
6
+
6
7
  license: MIT
7
8
  authors: Postmodern
8
9
  email: postmodern.mod3@gmail.com
@@ -0,0 +1,178 @@
1
+ require 'command_kit/stdio'
2
+
3
+ module CommandKit
4
+ module Interactive
5
+ include Stdio
6
+
7
+ #
8
+ # Asks the user for input.
9
+ #
10
+ # @param [String] prompt
11
+ # The prompt that will be printed before reading input.
12
+ #
13
+ # @param [String, nil] default
14
+ # The default value to return if no input is given.
15
+ #
16
+ # @param [Boolean] required
17
+ # Requires non-empty input.
18
+ #
19
+ # @return [String]
20
+ # The user input.
21
+ #
22
+ def ask(prompt, default: nil, required: false)
23
+ prompt = prompt.chomp
24
+ prompt << " [#{default}]" if default
25
+ prompt << ": "
26
+
27
+ stdout.print(prompt)
28
+
29
+ loop do
30
+ value = stdin.gets
31
+ value ||= '' # convert nil values (ctrl^D) to an empty String
32
+
33
+ if value.empty?
34
+ if required
35
+ next
36
+ else
37
+ return (default || value)
38
+ end
39
+ else
40
+ return value
41
+ end
42
+ end
43
+ end
44
+
45
+ #
46
+ # Asks the user a yes or no question.
47
+ #
48
+ # @param [String] prompt
49
+ # The prompt that will be printed before reading input.
50
+ #
51
+ # @param [true, false, nil] default
52
+ #
53
+ # @return [Boolean]
54
+ # Specifies whether the user entered Y/yes.
55
+ #
56
+ # @example
57
+ # ask_yes_or_no("Proceed anyways?")
58
+ # # Proceed anyways? (Y/N): Y
59
+ # # => :yes
60
+ #
61
+ def ask_yes_or_no(prompt, default: nil, **kwargs)
62
+ default = case default
63
+ when true then 'Y'
64
+ when false then 'N'
65
+ when nil then nil
66
+ else
67
+ raise(ArgumentError,"invalid default: #{default.inspect}")
68
+ end
69
+
70
+ prompt = "#{prompt} (Y/N)"
71
+
72
+ loop do
73
+ answer = ask(prompt, **kwargs, default: default)
74
+
75
+ case answer.downcase
76
+ when 'y', 'yes'
77
+ return true
78
+ else
79
+ return false
80
+ end
81
+ end
82
+ end
83
+
84
+ #
85
+ # Asks the user to select a choice from a list of options.
86
+ #
87
+ # @param [String] prompt
88
+ # The prompt that will be printed before reading input.
89
+ #
90
+ # @param [Hash{String => String}, Array<String>] choices
91
+ # The choices to select from.
92
+ #
93
+ # @param [Hash{Symbol => Object}] kwargs
94
+ # Additional keyword arguments for {#ask}.
95
+ #
96
+ # @option kwargs [String, nil] default
97
+ # The default option to fallback to, if no input is given.
98
+ #
99
+ # @option kwargs [Boolean] required
100
+ # Requires non-empty input.
101
+ #
102
+ # @return [String]
103
+ # The selected choice.
104
+ #
105
+ # @example Array of choices:
106
+ # ask_multiple_choice("Select a flavor", %w[Apple Orange Lemon Lime])
107
+ # # 1) Apple
108
+ # # 2) Orange
109
+ # # 3) Lemon
110
+ # # 4) Lime
111
+ # # Select a flavor: 4
112
+ # #
113
+ # # => "Lime"
114
+ #
115
+ # @example Hash of choices:
116
+ # ask_multiple_choice("Select an option", {'A' => 'Foo',
117
+ # 'B' => 'Bar',
118
+ # 'X' => 'All of the above'})
119
+ # # A) Foo
120
+ # # B) Bar
121
+ # # X) All of the above
122
+ # # Select an option: X
123
+ # #
124
+ # # => "All of the above"
125
+ #
126
+ def ask_multiple_choice(prompt,choices,**kwargs)
127
+ choices = case choices
128
+ when Array
129
+ Hash[choices.each_with_index.map { |value,i|
130
+ [(i+1).to_s, value]
131
+ }]
132
+ when Hash
133
+ choices
134
+ else
135
+ raise(TypeError,"unsupported choices class #{choices.class}: #{choices.inspect}")
136
+ end
137
+
138
+ prompt = "#{prompt} (#{choices.keys.join(', ')})"
139
+
140
+ loop do
141
+ # print the choices
142
+ choices.each do |choice,value|
143
+ stdout.puts " #{choice}) #{value}"
144
+ end
145
+ stdout.puts
146
+
147
+ # read the choice
148
+ choice = ask(prompt,**kwargs)
149
+
150
+ if choices.has_key?(choice)
151
+ # if a valid choice is given, return the value
152
+ return choices[choice]
153
+ else
154
+ stderr.puts "Invalid selection: #{choice}"
155
+ end
156
+ end
157
+ end
158
+
159
+ #
160
+ # Asks the user for secret input.
161
+ #
162
+ # @example
163
+ # ask_secret("Password")
164
+ # # Password:
165
+ # # => "s3cr3t"
166
+ #
167
+ def ask_secret(prompt, required: true)
168
+ if stdin.respond_to?(:noecho)
169
+ stdin.noecho do
170
+ ask(prompt, required: required)
171
+ end
172
+ else
173
+ ask(prompt, required: required)
174
+ end
175
+ end
176
+
177
+ end
178
+ end
@@ -1,9 +1,9 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require 'command_kit/stdio'
4
- require 'command_kit/console'
5
3
  require 'command_kit/env'
6
4
  require 'command_kit/env/path'
5
+ require 'command_kit/stdio'
6
+ require 'command_kit/terminal'
7
7
 
8
8
  module CommandKit
9
9
  #
@@ -35,10 +35,10 @@ module CommandKit
35
35
  # * [tty-pager](https://github.com/piotrmurach/tty-pager#readme)
36
36
  #
37
37
  module Pager
38
- include Stdio
39
- include Console
40
38
  include Env
41
39
  include Env::Path
40
+ include Stdio
41
+ include Terminal
42
42
 
43
43
  # Common pager commands.
44
44
  PAGERS = ['less -r', 'more -r']
@@ -76,7 +76,7 @@ module CommandKit
76
76
  #
77
77
  def pager
78
78
  if !stdout.tty? || @pager.nil?
79
- # fallback to stdout if the process does not have a console or we could
79
+ # fallback to stdout if the process does not have a terminal or we could
80
80
  # not find a suitable pager command.
81
81
  yield stdout
82
82
  return
@@ -99,7 +99,7 @@ module CommandKit
99
99
  end
100
100
 
101
101
  #
102
- # Pages the data if it's longer the console's height, otherwise prints the
102
+ # Pages the data if it's longer the terminal's height, otherwise prints the
103
103
  # data to {Stdio#stdout stdout}.
104
104
  #
105
105
  # @param [Array<String>, #to_s] data
@@ -111,7 +111,7 @@ module CommandKit
111
111
  else data.to_s.each_line.count
112
112
  end
113
113
 
114
- if line_count > console_height
114
+ if line_count > terminal_height
115
115
  pager { |io| io.puts(data) }
116
116
  else
117
117
  stdout.puts(data)
@@ -0,0 +1,142 @@
1
+ require 'command_kit/stdio'
2
+ require 'command_kit/env'
3
+
4
+ begin
5
+ require 'io/console'
6
+ rescue LoadError
7
+ end
8
+
9
+ module CommandKit
10
+ #
11
+ # Provides direct access to the terminal.
12
+ #
13
+ # ## Environment Variables
14
+ #
15
+ # * `LINES` - The explicit number of lines or rows the console should have.
16
+ # * `COLUMNS` - The explicit number of columns the console should have.
17
+ #
18
+ # @see https://rubydoc.info/gems/io-console/IO
19
+ #
20
+ module Terminal
21
+ include Stdio
22
+ include Env
23
+
24
+ # The default terminal height to fallback to.
25
+ DEFAULT_TERMINAL_HEIGHT = 25
26
+
27
+ # The default terminal width to fallback to.
28
+ DEFAULT_TERMINAL_WIDTH = 80
29
+
30
+ #
31
+ # Initializes any terminal settings.
32
+ #
33
+ # @param [Hash{Symbol => Object}] kwargs
34
+ # Additional keyword arguments.
35
+ #
36
+ # @note
37
+ # If the `$LINES` env variable is set, and is non-zero, it will be
38
+ # returned by {#terminal_height}.
39
+ #
40
+ # @note
41
+ # If the `$COLUMNS` env variable is set, and is non-zero, it will be
42
+ # returned by {#terminal_width}.
43
+ #
44
+ def initialize(**kwargs)
45
+ super(**kwargs)
46
+
47
+ @terminal_height = if (lines = env['LINES'])
48
+ lines.to_i
49
+ else
50
+ DEFAULT_TERMINAL_HEIGHT
51
+ end
52
+
53
+ @terminal_width = if (columns = env['COLUMNS'])
54
+ columns.to_i
55
+ else
56
+ DEFAULT_TERMINAL_WIDTH
57
+ end
58
+ end
59
+
60
+ #
61
+ # Determines if program is running in a terminal.
62
+ #
63
+ # @return [Boolean]
64
+ # Specifies whether {Stdio#stdout stdout} is connected to a terminal.
65
+ #
66
+ def terminal?
67
+ IO.respond_to?(:console) && stdout.tty?
68
+ end
69
+
70
+ #
71
+ # Returns the terminal object, if {Stdio#stdout stdout} is connected to a
72
+ # terminal.
73
+ #
74
+ # @return [IO, nil]
75
+ # The IO objects or `nil` if {Stdio#stdout stdout} is not connected to a
76
+ # terminal.
77
+ #
78
+ # @example
79
+ # terminal
80
+ # # => #<File:/dev/tty>
81
+ #
82
+ # @see https://rubydoc.info/gems/io-console/IO
83
+ #
84
+ def terminal
85
+ IO.console if terminal?
86
+ end
87
+
88
+ #
89
+ # Returns the terminal's height in number of lines.
90
+ #
91
+ # @return [Integer]
92
+ # The terminal's height in number of lines.
93
+ #
94
+ # @example
95
+ # terminal_height
96
+ # # => 22
97
+ #
98
+ def terminal_height
99
+ if (terminal = self.terminal)
100
+ terminal.winsize[0]
101
+ else
102
+ @terminal_height
103
+ end
104
+ end
105
+
106
+ #
107
+ # Returns the terminal's width in number of lines.
108
+ #
109
+ # @return [Integer]
110
+ # The terminal's width in number of columns.
111
+ #
112
+ # @example
113
+ # terminal_width
114
+ # # => 91
115
+ #
116
+ def terminal_width
117
+ if (terminal = self.terminal)
118
+ terminal.winsize[1]
119
+ else
120
+ @terminal_width
121
+ end
122
+ end
123
+
124
+ #
125
+ # The terminal height (lines) and width (columns).
126
+ #
127
+ # @return [(Integer, Integer)]
128
+ # Returns the height and width of the terminal.
129
+ #
130
+ # @example
131
+ # terminal_size
132
+ # # => [23, 91]
133
+ #
134
+ def terminal_size
135
+ if (terminal = self.terminal)
136
+ terminal.winsize
137
+ else
138
+ [@terminal_height, @terminal_width]
139
+ end
140
+ end
141
+ end
142
+ end
@@ -1,4 +1,4 @@
1
1
  module CommandKit
2
2
  # command_kit version
3
- VERSION = "0.1.0.pre2"
3
+ VERSION = "0.1.0.rc1"
4
4
  end
@@ -0,0 +1,415 @@
1
+ require 'spec_helper'
2
+ require 'command_kit/interactive'
3
+
4
+ describe CommandKit::Interactive do
5
+ module TestInteractive
6
+ class TestCommand
7
+ include CommandKit::Interactive
8
+ end
9
+ end
10
+
11
+ let(:command_class) { TestInteractive::TestCommand }
12
+
13
+ describe "#included" do
14
+ subject { command_class }
15
+
16
+ it { expect(subject).to include(CommandKit::Stdio) }
17
+ end
18
+
19
+ let(:stdout) { StringIO.new }
20
+ let(:stdin) { StringIO.new }
21
+ let(:stderr) { StringIO.new }
22
+
23
+ subject do
24
+ command_class.new(stdout: stdout, stdin: stdin, stderr: stderr)
25
+ end
26
+
27
+ let(:prompt) { 'Prompt' }
28
+
29
+ describe "#ask" do
30
+ let(:input) { 'foo' }
31
+
32
+ it "must print a prompt, read input, and return the input" do
33
+ expect(stdout).to receive(:print).with("#{prompt}: ")
34
+ expect(stdin).to receive(:gets).and_return(input)
35
+
36
+ expect(subject.ask(prompt)).to eq(input)
37
+ end
38
+
39
+ it "must accept empty user input by default" do
40
+ expect(stdin).to receive(:gets).and_return("")
41
+
42
+ expect(subject.ask(prompt)).to eq("")
43
+ end
44
+
45
+ context "when Ctrl^C is entered" do
46
+ it "must return \"\"" do
47
+ expect(stdin).to receive(:gets).and_return(nil) # simulate Ctrl^C
48
+
49
+ expect(subject.ask(prompt)).to eq("")
50
+ end
51
+ end
52
+
53
+ context "when default: is given" do
54
+ let(:default) { 'bar' }
55
+
56
+ it "must include the default: value in the prompt" do
57
+ expect(stdout).to receive(:print).with("#{prompt} [#{default}]: ")
58
+ expect(stdin).to receive(:gets).and_return(input)
59
+
60
+ expect(subject.ask(prompt, default: default)).to eq(input)
61
+ end
62
+
63
+ context "and non-empty user input is given" do
64
+ it "must return the non-empty user input" do
65
+ expect(stdin).to receive(:gets).and_return(input)
66
+
67
+ expect(subject.ask(prompt, default: default)).to eq(input)
68
+ end
69
+ end
70
+
71
+ context "and empty user input is given" do
72
+ it "must return the default value" do
73
+ expect(stdin).to receive(:gets).and_return("")
74
+
75
+ expect(subject.ask(prompt, default: default)).to eq(default)
76
+ end
77
+ end
78
+ end
79
+
80
+ context "when required: is given" do
81
+ context "and empty user input is given" do
82
+ it "must ask for input again, until non-empty input is given" do
83
+ expect(stdin).to receive(:gets).and_return("")
84
+ expect(stdin).to receive(:gets).and_return("")
85
+ expect(stdin).to receive(:gets).and_return(input)
86
+
87
+ expect(subject.ask(prompt, required: true)).to eq(input)
88
+ end
89
+ end
90
+
91
+ context "and non-empty user input is given" do
92
+ it "must return the non-empty user input" do
93
+ expect(stdin).to receive(:gets).and_return(input)
94
+
95
+ expect(subject.ask(prompt, required: true)).to eq(input)
96
+ end
97
+ end
98
+ end
99
+ end
100
+
101
+ describe "#ask_yes_or_no" do
102
+ let(:input) { 'Y' }
103
+
104
+ it "must print a prompt indicating Y/N, and then accept input" do
105
+ expect(stdout).to receive(:print).with("#{prompt} (Y/N): ")
106
+ expect(stdin).to receive(:gets).and_return(input)
107
+
108
+ subject.ask_yes_or_no(prompt)
109
+ end
110
+
111
+ context "when 'Y' is entered" do
112
+ let(:input) { 'Y' }
113
+
114
+ it "must return true" do
115
+ expect(stdin).to receive(:gets).and_return(input)
116
+
117
+ expect(subject.ask_yes_or_no(prompt)).to eq(true)
118
+ end
119
+ end
120
+
121
+ context "when 'y' is entered" do
122
+ let(:input) { 'y' }
123
+
124
+ it "must return true" do
125
+ expect(stdin).to receive(:gets).and_return(input)
126
+
127
+ expect(subject.ask_yes_or_no(prompt)).to eq(true)
128
+ end
129
+ end
130
+
131
+ context "when 'YES' is entered" do
132
+ let(:input) { 'YES' }
133
+
134
+ it "must return true" do
135
+ expect(stdin).to receive(:gets).and_return(input)
136
+
137
+ expect(subject.ask_yes_or_no(prompt)).to eq(true)
138
+ end
139
+ end
140
+
141
+ context "when 'Yes' is entered" do
142
+ let(:input) { 'Yes' }
143
+
144
+ it "must return true" do
145
+ expect(stdin).to receive(:gets).and_return(input)
146
+
147
+ expect(subject.ask_yes_or_no(prompt)).to eq(true)
148
+ end
149
+ end
150
+
151
+ context "when 'yes' is entered" do
152
+ let(:input) { 'yes' }
153
+
154
+ it "must return true" do
155
+ expect(stdin).to receive(:gets).and_return(input)
156
+
157
+ expect(subject.ask_yes_or_no(prompt)).to eq(true)
158
+ end
159
+ end
160
+
161
+ context "when 'N' is entered" do
162
+ let(:input) { 'N' }
163
+
164
+ it "must return false" do
165
+ expect(stdin).to receive(:gets).and_return(input)
166
+
167
+ expect(subject.ask_yes_or_no(prompt)).to eq(false)
168
+ end
169
+ end
170
+
171
+ context "when 'n' is entered" do
172
+ let(:input) { 'n' }
173
+
174
+ it "must return false" do
175
+ expect(stdin).to receive(:gets).and_return(input)
176
+
177
+ expect(subject.ask_yes_or_no(prompt)).to eq(false)
178
+ end
179
+ end
180
+
181
+ context "when 'NO' is entered" do
182
+ let(:input) { 'NO' }
183
+
184
+ it "must return false" do
185
+ expect(stdin).to receive(:gets).and_return(input)
186
+
187
+ expect(subject.ask_yes_or_no(prompt)).to eq(false)
188
+ end
189
+ end
190
+
191
+ context "when 'No' is entered" do
192
+ let(:input) { 'No' }
193
+
194
+ it "must return false" do
195
+ expect(stdin).to receive(:gets).and_return(input)
196
+
197
+ expect(subject.ask_yes_or_no(prompt)).to eq(false)
198
+ end
199
+ end
200
+
201
+ context "when 'no' is entered" do
202
+ let(:input) { 'no' }
203
+
204
+ it "must return false" do
205
+ expect(stdin).to receive(:gets).and_return(input)
206
+
207
+ expect(subject.ask_yes_or_no(prompt)).to eq(false)
208
+ end
209
+ end
210
+
211
+ context "when input besides y/n/yes/no is entered" do
212
+ let(:input) { 'jflksjfls' }
213
+
214
+ it "must return false" do
215
+ expect(stdin).to receive(:gets).and_return(input)
216
+
217
+ expect(subject.ask_yes_or_no(prompt)).to eq(false)
218
+ end
219
+ end
220
+
221
+ context "when defualt: is given" do
222
+ context "and is true" do
223
+ let(:default) { true }
224
+
225
+ it "must include [Y] in the prompt" do
226
+ expect(stdout).to receive(:print).with("#{prompt} (Y/N) [Y]: ")
227
+ expect(stdin).to receive(:gets).and_return(input)
228
+
229
+ subject.ask_yes_or_no(prompt, default: default)
230
+ end
231
+
232
+ context "and empty user-input is given" do
233
+ let(:input) { "" }
234
+
235
+ it "must return true" do
236
+ expect(stdout).to receive(:print).with("#{prompt} (Y/N) [Y]: ")
237
+ expect(stdin).to receive(:gets).and_return(input)
238
+
239
+ expect(subject.ask_yes_or_no(prompt, default: default)).to eq(default)
240
+ end
241
+ end
242
+ end
243
+
244
+ context "and is false" do
245
+ let(:default) { false }
246
+
247
+ it "must include [N] in the prompt" do
248
+ expect(stdout).to receive(:print).with("#{prompt} (Y/N) [N]: ")
249
+ expect(stdin).to receive(:gets).and_return(input)
250
+
251
+ subject.ask_yes_or_no(prompt, default: default)
252
+ end
253
+
254
+ context "and empty user-input is given" do
255
+ let(:input) { "" }
256
+
257
+ it "must return false" do
258
+ expect(stdout).to receive(:print).with("#{prompt} (Y/N) [N]: ")
259
+ expect(stdin).to receive(:gets).and_return(input)
260
+
261
+ expect(subject.ask_yes_or_no(prompt, default: default)).to eq(default)
262
+ end
263
+ end
264
+ end
265
+ end
266
+ end
267
+
268
+ describe "#ask_multiple_choice" do
269
+ context "when given an Array" do
270
+ let(:choices) do
271
+ [
272
+ "foo",
273
+ "bar",
274
+ "baz"
275
+ ]
276
+ end
277
+
278
+ let(:input) { "2" }
279
+
280
+ it "must print the numbered choices, a prompt with the choices, read input, and return the choice" do
281
+ expect(stdout).to receive(:puts).with(" 1) #{choices[0]}")
282
+ expect(stdout).to receive(:puts).with(" 2) #{choices[1]}")
283
+ expect(stdout).to receive(:puts).with(" 3) #{choices[2]}")
284
+ expect(stdout).to receive(:puts).with(no_args)
285
+ expect(stdout).to receive(:print).with("#{prompt} (1, 2, 3): ")
286
+ expect(stdin).to receive(:gets).and_return(input)
287
+
288
+ expect(subject.ask_multiple_choice(prompt,choices)).to eq(choices[input.to_i - 1])
289
+ end
290
+
291
+ context "when empty user-input is given" do
292
+ it "must ask for input again, until non-empty input is given" do
293
+ expect(stdin).to receive(:gets).and_return("")
294
+ expect(stdin).to receive(:gets).and_return("")
295
+ expect(stdin).to receive(:gets).and_return(input)
296
+
297
+ expect(subject.ask_multiple_choice(prompt,choices)).to eq(choices[input.to_i - 1])
298
+ end
299
+ end
300
+
301
+ context "and default: is given" do
302
+ let(:default) { '3' }
303
+
304
+ it "must include the default: choice in the prompt" do
305
+ expect(stdout).to receive(:print).with("#{prompt} (1, 2, 3) [#{default}]: ")
306
+ expect(stdin).to receive(:gets).and_return(input)
307
+
308
+ subject.ask_multiple_choice(prompt,choices, default: default)
309
+ end
310
+
311
+ context "and empty user-input is given" do
312
+ it "must return the value for the default choice" do
313
+ expect(stdin).to receive(:gets).and_return("")
314
+
315
+ expect(subject.ask_multiple_choice(prompt,choices, default: default)).to eq(choices[default.to_i - 1])
316
+ end
317
+ end
318
+ end
319
+ end
320
+
321
+ context "when given a Hash" do
322
+ let(:choices) do
323
+ {
324
+ "A" => "foo",
325
+ "B" => "bar",
326
+ "C" => "baz"
327
+ }
328
+ end
329
+
330
+ let(:input) { "B" }
331
+
332
+ it "must print the labeled choices, a prompt with the choices, read input, and return the choice" do
333
+ expect(stdout).to receive(:puts).with(" #{choices.keys[0]}) #{choices.values[0]}")
334
+ expect(stdout).to receive(:puts).with(" #{choices.keys[1]}) #{choices.values[1]}")
335
+ expect(stdout).to receive(:puts).with(" #{choices.keys[2]}) #{choices.values[2]}")
336
+ expect(stdout).to receive(:puts).with(no_args)
337
+ expect(stdout).to receive(:print).with("#{prompt} (#{choices.keys[0]}, #{choices.keys[1]}, #{choices.keys[2]}): ")
338
+ expect(stdin).to receive(:gets).and_return(input)
339
+
340
+ expect(subject.ask_multiple_choice(prompt,choices)).to eq(choices[input])
341
+ end
342
+
343
+ context "when empty user-input is given" do
344
+ it "must ask for input again, until non-empty input is given" do
345
+ expect(stdin).to receive(:gets).and_return("")
346
+ expect(stdin).to receive(:gets).and_return("")
347
+ expect(stdin).to receive(:gets).and_return(input)
348
+
349
+ expect(subject.ask_multiple_choice(prompt,choices)).to eq(choices[input])
350
+ end
351
+ end
352
+
353
+ context "and default: is given" do
354
+ let(:default) { 'C' }
355
+
356
+ it "must include the default: choice in the prompt" do
357
+ expect(stdout).to receive(:print).with("#{prompt} (#{choices.keys[0]}, #{choices.keys[1]}, #{choices.keys[2]}) [#{default}]: ")
358
+ expect(stdin).to receive(:gets).and_return(input)
359
+
360
+ subject.ask_multiple_choice(prompt,choices, default: default)
361
+ end
362
+
363
+ context "and empty user-input is given" do
364
+ it "must return the value for the default choice" do
365
+ expect(stdin).to receive(:gets).and_return("")
366
+
367
+ expect(subject.ask_multiple_choice(prompt,choices, default: default)).to eq(choices[default])
368
+ end
369
+ end
370
+ end
371
+ end
372
+ end
373
+
374
+ describe "#ask_secret" do
375
+ let(:input) { 's3cr3t' }
376
+
377
+ context "when stdin supports to #noecho" do
378
+ it "must call #noecho, read input, then return the input" do
379
+ allow(stdin).to receive(:noecho).and_yield
380
+ expect(stdin).to receive(:gets).and_return(input)
381
+
382
+ expect(subject.ask_secret(prompt)).to eq(input)
383
+ end
384
+ end
385
+
386
+ context "when stdin does not support #noecho" do
387
+ it "must fallback to reading input" do
388
+ expect(stdin).to receive(:gets).and_return(input)
389
+
390
+ expect(subject.ask_secret(prompt)).to eq(input)
391
+ end
392
+ end
393
+
394
+ context "when empty user-input is given" do
395
+ it "must ask for input again, until non-empty input is given" do
396
+ expect(stdin).to receive(:gets).and_return("")
397
+ expect(stdin).to receive(:gets).and_return("")
398
+ expect(stdin).to receive(:gets).and_return("")
399
+ expect(stdin).to receive(:gets).and_return(input)
400
+
401
+ expect(subject.ask_secret(prompt)).to eq(input)
402
+ end
403
+ end
404
+
405
+ context "when required: is false" do
406
+ context "and empty user-input is given" do
407
+ it "must return the empty user-input" do
408
+ expect(stdin).to receive(:gets).and_return("")
409
+
410
+ expect(subject.ask_secret(prompt, required: false)).to eq("")
411
+ end
412
+ end
413
+ end
414
+ end
415
+ end
data/spec/pager_spec.rb CHANGED
@@ -95,10 +95,10 @@ describe Pager do
95
95
  end
96
96
 
97
97
  describe "#print_or_page" do
98
- let(:console_height) { 10 }
98
+ let(:terminal_height) { 10 }
99
99
 
100
100
  before do
101
- expect(subject).to receive(:console_height).and_return(console_height)
101
+ expect(subject).to receive(:terminal_height).and_return(terminal_height)
102
102
  end
103
103
 
104
104
  context "when given a String" do
@@ -113,7 +113,7 @@ describe Pager do
113
113
  end
114
114
 
115
115
  context "and the number of lines is greater than the console's height" do
116
- let(:string) { "foo#{$/}bar#{$/}" * console_height }
116
+ let(:string) { "foo#{$/}bar#{$/}" * terminal_height }
117
117
 
118
118
  it "must spawn a pager and puts the String to the pager" do
119
119
  pager = double('pager')
@@ -137,7 +137,7 @@ describe Pager do
137
137
  end
138
138
 
139
139
  context "and the number of lines is greater than the console's height" do
140
- let(:array) { ['foo', 'bar'] * console_height }
140
+ let(:array) { ['foo', 'bar'] * terminal_height }
141
141
 
142
142
  it "must spawn a pager and puts the Array of Strings to the pager" do
143
143
  pager = double('pager')
@@ -1,26 +1,26 @@
1
1
  require 'spec_helper'
2
- require 'command_kit/console'
2
+ require 'command_kit/terminal'
3
3
 
4
4
  require 'stringio'
5
5
 
6
- describe Console do
7
- module TestConsole
6
+ describe Terminal do
7
+ module TestTerminal
8
8
  class TestCommand
9
- include CommandKit::Console
9
+ include CommandKit::Terminal
10
10
  end
11
11
  end
12
12
 
13
- let(:command_class) { TestConsole::TestCommand }
13
+ let(:command_class) { TestTerminal::TestCommand }
14
14
  subject { command_class.new }
15
15
 
16
- describe "#console?" do
16
+ describe "#terminal?" do
17
17
  context "when stdout is connected to a TTY" do
18
18
  subject { command_class.new(stdout: STDOUT) }
19
19
 
20
20
  it do
21
21
  skip "STDOUT is not a TTY" unless STDOUT.tty?
22
22
 
23
- expect(subject.console?).to be(true)
23
+ expect(subject.terminal?).to be(true)
24
24
  end
25
25
  end
26
26
 
@@ -28,7 +28,7 @@ describe Console do
28
28
  subject { command_class.new(stdout: StringIO.new) }
29
29
 
30
30
  it do
31
- expect(subject.console?).to be(false)
31
+ expect(subject.terminal?).to be(false)
32
32
  end
33
33
  end
34
34
 
@@ -38,19 +38,19 @@ describe Console do
38
38
  end
39
39
 
40
40
  it do
41
- expect(subject.console?).to be(false)
41
+ expect(subject.terminal?).to be(false)
42
42
  end
43
43
  end
44
44
  end
45
45
 
46
- describe "#console" do
46
+ describe "#terminal" do
47
47
  context "when stdout is connected to a TTY" do
48
48
  subject { command_class.new(stdout: STDOUT) }
49
49
 
50
50
  it do
51
51
  skip "STDOUT is not a TTY" unless STDOUT.tty?
52
52
 
53
- expect(subject.console).to eq(IO.console)
53
+ expect(subject.terminal).to eq(IO.console)
54
54
  end
55
55
  end
56
56
 
@@ -58,7 +58,7 @@ describe Console do
58
58
  subject { command_class.new(stdout: StringIO.new) }
59
59
 
60
60
  it do
61
- expect(subject.console).to eq(nil)
61
+ expect(subject.terminal).to eq(nil)
62
62
  end
63
63
  end
64
64
 
@@ -68,27 +68,27 @@ describe Console do
68
68
  end
69
69
 
70
70
  it do
71
- expect(subject.console).to be(nil)
71
+ expect(subject.terminal).to be(nil)
72
72
  end
73
73
  end
74
74
  end
75
75
 
76
- describe "#console_height" do
76
+ describe "#terminal_height" do
77
77
  context "when stdout is connected to a TTY" do
78
78
  subject { command_class.new(stdout: STDOUT) }
79
79
 
80
80
  it do
81
81
  skip "STDOUT is not a TTY" unless STDOUT.tty?
82
82
 
83
- expect(subject.console_height).to eq(STDOUT.winsize[0])
83
+ expect(subject.terminal_height).to eq(STDOUT.winsize[0])
84
84
  end
85
85
  end
86
86
 
87
87
  context "when stdout is not connected to a TTY" do
88
88
  subject { command_class.new(stdout: StringIO.new) }
89
89
 
90
- it "must fallback to DEFAULT_HEIGHT" do
91
- expect(subject.console_height).to eq(described_class::DEFAULT_HEIGHT)
90
+ it "must fallback to DEFAULT_TERMINAL_HEIGHT" do
91
+ expect(subject.terminal_height).to eq(described_class::DEFAULT_TERMINAL_HEIGHT)
92
92
  end
93
93
 
94
94
  context "but the LINES env variable was set" do
@@ -98,28 +98,28 @@ describe Console do
98
98
  subject { command_class.new(stdout: StringIO.new, env: env) }
99
99
 
100
100
  it "must fallback to the LINES environment variable" do
101
- expect(subject.console_height).to eq(lines)
101
+ expect(subject.terminal_height).to eq(lines)
102
102
  end
103
103
  end
104
104
  end
105
105
  end
106
106
 
107
- describe "#console_width" do
107
+ describe "#terminal_width" do
108
108
  context "when stdout is connected to a TTY" do
109
109
  subject { command_class.new(stdout: STDOUT) }
110
110
 
111
111
  it do
112
112
  skip "STDOUT is not a TTY" unless STDOUT.tty?
113
113
 
114
- expect(subject.console_width).to eq(STDOUT.winsize[1])
114
+ expect(subject.terminal_width).to eq(STDOUT.winsize[1])
115
115
  end
116
116
  end
117
117
 
118
118
  context "when stdout is not connected to a TTY" do
119
119
  subject { command_class.new(stdout: StringIO.new) }
120
120
 
121
- it "must fallback to DEFAULT_WIDTH" do
122
- expect(subject.console_width).to eq(described_class::DEFAULT_WIDTH)
121
+ it "must fallback to DEFAULT_TERMINAL_WIDTH" do
122
+ expect(subject.terminal_width).to eq(described_class::DEFAULT_TERMINAL_WIDTH)
123
123
  end
124
124
 
125
125
  context "but the COLUMNS env variable was set" do
@@ -129,29 +129,29 @@ describe Console do
129
129
  subject { command_class.new(stdout: StringIO.new, env: env) }
130
130
 
131
131
  it "must fallback to the COLUMNS environment variable" do
132
- expect(subject.console_width).to eq(columns)
132
+ expect(subject.terminal_width).to eq(columns)
133
133
  end
134
134
  end
135
135
  end
136
136
  end
137
137
 
138
- describe "#console_size" do
138
+ describe "#terminal_size" do
139
139
  context "when stdout is connected to a TTY" do
140
140
  subject { command_class.new(stdout: STDOUT) }
141
141
 
142
142
  it do
143
143
  skip "STDOUT is not a TTY" unless STDOUT.tty?
144
144
 
145
- expect(subject.console_size).to eq(STDOUT.winsize)
145
+ expect(subject.terminal_size).to eq(STDOUT.winsize)
146
146
  end
147
147
  end
148
148
 
149
149
  context "when stdout is not connected to a TTY" do
150
150
  subject { command_class.new(stdout: StringIO.new) }
151
151
 
152
- it "must fallback to [DEFAULT_HEIGHT, DEFAULT_WIDTH]" do
153
- expect(subject.console_size).to eq(
154
- [described_class::DEFAULT_HEIGHT, described_class::DEFAULT_WIDTH]
152
+ it "must fallback to [DEFAULT_TERMINAL_HEIGHT, DEFAULT_TERMINAL_WIDTH]" do
153
+ expect(subject.terminal_size).to eq(
154
+ [described_class::DEFAULT_TERMINAL_HEIGHT, described_class::DEFAULT_TERMINAL_WIDTH]
155
155
  )
156
156
  end
157
157
 
@@ -161,9 +161,9 @@ describe Console do
161
161
 
162
162
  subject { command_class.new(stdout: StringIO.new, env: env) }
163
163
 
164
- it "must fallback to the [$LINES, DEFAULT_WIDTH]" do
165
- expect(subject.console_size).to eq(
166
- [lines, described_class::DEFAULT_WIDTH]
164
+ it "must fallback to the [$LINES, DEFAULT_TERMINAL_WIDTH]" do
165
+ expect(subject.terminal_size).to eq(
166
+ [lines, described_class::DEFAULT_TERMINAL_WIDTH]
167
167
  )
168
168
  end
169
169
  end
@@ -174,9 +174,9 @@ describe Console do
174
174
 
175
175
  subject { command_class.new(stdout: StringIO.new, env: env) }
176
176
 
177
- it "must fallback to the [DEFAULT_HEIGHT, COLUMNS]" do
178
- expect(subject.console_size).to eq(
179
- [described_class::DEFAULT_HEIGHT, columns]
177
+ it "must fallback to the [DEFAULT_TERMINAL_HEIGHT, COLUMNS]" do
178
+ expect(subject.terminal_size).to eq(
179
+ [described_class::DEFAULT_TERMINAL_HEIGHT, columns]
180
180
  )
181
181
  end
182
182
  end
@@ -191,7 +191,7 @@ describe Console do
191
191
  subject { command_class.new(stdout: StringIO.new, env: env) }
192
192
 
193
193
  it "must fallback to the [LINES, COLUMNS]" do
194
- expect(subject.console_size).to eq(
194
+ expect(subject.terminal_size).to eq(
195
195
  [lines, columns]
196
196
  )
197
197
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: command_kit
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.0.pre2
4
+ version: 0.1.0.rc1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Postmodern
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2021-05-28 00:00:00.000000000 Z
11
+ date: 2021-07-02 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: bundler
@@ -25,7 +25,7 @@ dependencies:
25
25
  - !ruby/object:Gem::Version
26
26
  version: '2.0'
27
27
  description: A Ruby toolkit for building clean, correct, and robust CLI commands as
28
- Ruby classes.
28
+ plain-old Ruby classes.
29
29
  email: postmodern.mod3@gmail.com
30
30
  executables: []
31
31
  extensions: []
@@ -64,7 +64,6 @@ files:
64
64
  - lib/command_kit/commands/help.rb
65
65
  - lib/command_kit/commands/parent_command.rb
66
66
  - lib/command_kit/commands/subcommand.rb
67
- - lib/command_kit/console.rb
68
67
  - lib/command_kit/description.rb
69
68
  - lib/command_kit/env.rb
70
69
  - lib/command_kit/env/home.rb
@@ -74,6 +73,7 @@ files:
74
73
  - lib/command_kit/help.rb
75
74
  - lib/command_kit/help/man.rb
76
75
  - lib/command_kit/inflector.rb
76
+ - lib/command_kit/interactive.rb
77
77
  - lib/command_kit/main.rb
78
78
  - lib/command_kit/options.rb
79
79
  - lib/command_kit/options/option.rb
@@ -88,6 +88,7 @@ files:
88
88
  - lib/command_kit/printing/indent.rb
89
89
  - lib/command_kit/program_name.rb
90
90
  - lib/command_kit/stdio.rb
91
+ - lib/command_kit/terminal.rb
91
92
  - lib/command_kit/usage.rb
92
93
  - lib/command_kit/version.rb
93
94
  - lib/command_kit/xdg.rb
@@ -108,7 +109,6 @@ files:
108
109
  - spec/commands/parent_command_spec.rb
109
110
  - spec/commands/subcommand_spec.rb
110
111
  - spec/commands_spec.rb
111
- - spec/console_spec.rb
112
112
  - spec/description_spec.rb
113
113
  - spec/env/home_spec.rb
114
114
  - spec/env/path_spec.rb
@@ -118,6 +118,7 @@ files:
118
118
  - spec/help/man_spec.rb
119
119
  - spec/help_spec.rb
120
120
  - spec/inflector_spec.rb
121
+ - spec/interactive_spec.rb
121
122
  - spec/main_spec.rb
122
123
  - spec/options/option_spec.rb
123
124
  - spec/options/option_value_spec.rb
@@ -130,6 +131,7 @@ files:
130
131
  - spec/program_name_spec.rb
131
132
  - spec/spec_helper.rb
132
133
  - spec/stdio_spec.rb
134
+ - spec/terminal_spec.rb
133
135
  - spec/usage_spec.rb
134
136
  - spec/xdg_spec.rb
135
137
  homepage: https://github.com/postmodern/command_kit#readme
@@ -1,141 +0,0 @@
1
- require 'command_kit/stdio'
2
- require 'command_kit/env'
3
-
4
- begin
5
- require 'io/console'
6
- rescue LoadError
7
- end
8
-
9
- module CommandKit
10
- #
11
- # Provides access to [IO.console] and [IO.console_size].
12
- #
13
- # ## Environment Variables
14
- #
15
- # * `LINES` - The explicit number of lines or rows the console should have.
16
- # * `COLUMNS` - The explicit number of columns the console should have.
17
- #
18
- # [IO.console]: https://rubydoc.info/gems/io-console/IO#console-class_method
19
- # [IO#winsize]: https://rubydoc.info/gems/io-console/IO#winsize-instance_method
20
- #
21
- module Console
22
- include Stdio
23
- include Env
24
-
25
- # The default console height to fallback to.
26
- DEFAULT_HEIGHT = 25
27
-
28
- # The default console width to fallback to.
29
- DEFAULT_WIDTH = 80
30
-
31
- #
32
- # Initializes any console settings.
33
- #
34
- # @param [Hash{Symbol => Object}] kwargs
35
- # Additional keyword arguments.
36
- #
37
- # @note
38
- # If the `$LINES` env variable is set, and is non-zero, it will be
39
- # returned by {#console_height}.
40
- #
41
- # @note
42
- # If the `$COLUMNS` env variable is set, and is non-zero, it will be
43
- # returned by {#console_width}.
44
- #
45
- def initialize(**kwargs)
46
- super(**kwargs)
47
-
48
- @default_console_height = if (lines = env['LINES'])
49
- lines.to_i
50
- else
51
- DEFAULT_HEIGHT
52
- end
53
-
54
- @default_console_width = if (columns = env['COLUMNS'])
55
- columns.to_i
56
- else
57
- DEFAULT_WIDTH
58
- end
59
- end
60
-
61
- #
62
- # Determines if there is a console present.
63
- #
64
- # @return [Boolean]
65
- # Specifies whether {Stdio#stdout stdout} is connected to a console.
66
- #
67
- def console?
68
- IO.respond_to?(:console) && stdout.tty?
69
- end
70
-
71
- #
72
- # Returns the console object, if {Stdio#stdout stdout} is connected to a
73
- # console.
74
- #
75
- # @return [IO, nil]
76
- # The IO objects or `nil` if {Stdio#stdout stdout} is not connected to a
77
- # console.
78
- #
79
- # @example
80
- # console
81
- # # => #<File:/dev/tty>
82
- #
83
- def console
84
- IO.console if console?
85
- end
86
-
87
- #
88
- # Returns the console's height in number of lines.
89
- #
90
- # @return [Integer]
91
- # The console's height in number of lines.
92
- #
93
- # @example
94
- # console_height
95
- # # => 22
96
- #
97
- def console_height
98
- if (console = self.console)
99
- console.winsize[0]
100
- else
101
- @default_console_height
102
- end
103
- end
104
-
105
- #
106
- # Returns the console's width in number of lines.
107
- #
108
- # @return [Integer]
109
- # The console's width in number of columns.
110
- #
111
- # @example
112
- # console_width
113
- # # => 91
114
- #
115
- def console_width
116
- if (console = self.console)
117
- console.winsize[1]
118
- else
119
- @default_console_width
120
- end
121
- end
122
-
123
- #
124
- # The console height (lines) and width (columns).
125
- #
126
- # @return [(Integer, Integer)]
127
- # Returns the height and width of the console.
128
- #
129
- # @example
130
- # console_size
131
- # # => [23, 91]
132
- #
133
- def console_size
134
- if (console = self.console)
135
- console.winsize
136
- else
137
- [@default_console_height, @default_console_width]
138
- end
139
- end
140
- end
141
- end