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 +4 -4
- data/ChangeLog.md +5 -2
- data/README.md +7 -6
- data/gemspec.yml +2 -1
- data/lib/command_kit/interactive.rb +178 -0
- data/lib/command_kit/pager.rb +7 -7
- data/lib/command_kit/terminal.rb +142 -0
- data/lib/command_kit/version.rb +1 -1
- data/spec/interactive_spec.rb +415 -0
- data/spec/pager_spec.rb +4 -4
- data/spec/{console_spec.rb → terminal_spec.rb} +35 -35
- metadata +7 -5
- data/lib/command_kit/console.rb +0 -141
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 5c8944ddc49470bbe03b23cdf0df4cc9e5f94010208e15b373430453556ff2ed
|
4
|
+
data.tar.gz: bfebe1ddb5ae5a078e5910de55f626ae69dc8716f3d5152690e9002b68878bfe
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: a8f00bba78443b945ea4c41efeaf4488eb6c65867ac637b4ae368d6a53724802395f0f9d5d464241fbe9d7f22fd1657e50161df4846fcdcce174f4abf206abb7
|
7
|
+
data.tar.gz: 497da95cec49705d426da9dfa27aa5f7ca9e462375dba94375244e5180f9ac9f1735850022fdfd61876cb6661a5988c568eda518dac010859be69589c3780843
|
data/ChangeLog.md
CHANGED
@@ -1,13 +1,13 @@
|
|
1
|
-
### 0.1.0 / 2021-
|
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
|
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
|
-
*
|
22
|
-
*
|
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
|
data/lib/command_kit/pager.rb
CHANGED
@@ -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
|
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
|
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 >
|
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
|
data/lib/command_kit/version.rb
CHANGED
@@ -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(:
|
98
|
+
let(:terminal_height) { 10 }
|
99
99
|
|
100
100
|
before do
|
101
|
-
expect(subject).to receive(:
|
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#{$/}" *
|
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'] *
|
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/
|
2
|
+
require 'command_kit/terminal'
|
3
3
|
|
4
4
|
require 'stringio'
|
5
5
|
|
6
|
-
describe
|
7
|
-
module
|
6
|
+
describe Terminal do
|
7
|
+
module TestTerminal
|
8
8
|
class TestCommand
|
9
|
-
include CommandKit::
|
9
|
+
include CommandKit::Terminal
|
10
10
|
end
|
11
11
|
end
|
12
12
|
|
13
|
-
let(:command_class) {
|
13
|
+
let(:command_class) { TestTerminal::TestCommand }
|
14
14
|
subject { command_class.new }
|
15
15
|
|
16
|
-
describe "#
|
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.
|
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.
|
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.
|
41
|
+
expect(subject.terminal?).to be(false)
|
42
42
|
end
|
43
43
|
end
|
44
44
|
end
|
45
45
|
|
46
|
-
describe "#
|
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.
|
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.
|
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.
|
71
|
+
expect(subject.terminal).to be(nil)
|
72
72
|
end
|
73
73
|
end
|
74
74
|
end
|
75
75
|
|
76
|
-
describe "#
|
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.
|
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
|
91
|
-
expect(subject.
|
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.
|
101
|
+
expect(subject.terminal_height).to eq(lines)
|
102
102
|
end
|
103
103
|
end
|
104
104
|
end
|
105
105
|
end
|
106
106
|
|
107
|
-
describe "#
|
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.
|
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
|
122
|
-
expect(subject.
|
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.
|
132
|
+
expect(subject.terminal_width).to eq(columns)
|
133
133
|
end
|
134
134
|
end
|
135
135
|
end
|
136
136
|
end
|
137
137
|
|
138
|
-
describe "#
|
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.
|
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 [
|
153
|
-
expect(subject.
|
154
|
-
[described_class::
|
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,
|
165
|
-
expect(subject.
|
166
|
-
[lines, described_class::
|
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 [
|
178
|
-
expect(subject.
|
179
|
-
[described_class::
|
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.
|
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.
|
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-
|
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
|
data/lib/command_kit/console.rb
DELETED
@@ -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
|