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