command_kit 0.1.0.pre2 → 0.1.0.rc1

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