command_kit 0.1.0.pre2 → 0.2.1
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/.github/workflows/ruby.yml +15 -0
- data/.rubocop.yml +141 -0
- data/ChangeLog.md +98 -2
- data/Gemfile +3 -0
- data/README.md +189 -117
- data/Rakefile +3 -2
- data/command_kit.gemspec +4 -4
- data/examples/command.rb +1 -1
- data/gemspec.yml +10 -2
- data/lib/command_kit/arguments/argument.rb +2 -0
- data/lib/command_kit/arguments/argument_value.rb +2 -0
- data/lib/command_kit/arguments.rb +23 -4
- data/lib/command_kit/colors.rb +253 -45
- data/lib/command_kit/command.rb +6 -1
- data/lib/command_kit/command_name.rb +9 -0
- data/lib/command_kit/commands/auto_load.rb +24 -1
- data/lib/command_kit/commands/auto_require.rb +16 -0
- data/lib/command_kit/commands/command.rb +3 -0
- data/lib/command_kit/commands/help.rb +5 -2
- data/lib/command_kit/commands/parent_command.rb +7 -0
- data/lib/command_kit/commands/subcommand.rb +13 -1
- data/lib/command_kit/commands.rb +54 -9
- data/lib/command_kit/description.rb +12 -1
- data/lib/command_kit/env/home.rb +9 -0
- data/lib/command_kit/env/path.rb +16 -1
- data/lib/command_kit/env.rb +4 -0
- data/lib/command_kit/examples.rb +12 -1
- data/lib/command_kit/exception_handler.rb +4 -0
- data/lib/command_kit/help/man.rb +26 -30
- data/lib/command_kit/help.rb +7 -1
- data/lib/command_kit/inflector.rb +49 -17
- data/lib/command_kit/interactive.rb +248 -0
- data/lib/command_kit/main.rb +18 -9
- data/lib/command_kit/man.rb +44 -0
- data/lib/command_kit/open_app.rb +69 -0
- data/lib/command_kit/options/option.rb +3 -6
- data/lib/command_kit/options/option_value.rb +5 -2
- data/lib/command_kit/options/parser.rb +46 -19
- data/lib/command_kit/options/quiet.rb +3 -0
- data/lib/command_kit/options/verbose.rb +5 -0
- data/lib/command_kit/options/version.rb +6 -0
- data/lib/command_kit/options.rb +32 -7
- data/lib/command_kit/os/linux.rb +157 -0
- data/lib/command_kit/os.rb +165 -11
- data/lib/command_kit/package_manager.rb +200 -0
- data/lib/command_kit/pager.rb +80 -11
- data/lib/command_kit/printing/indent.rb +27 -4
- data/lib/command_kit/printing.rb +35 -1
- data/lib/command_kit/program_name.rb +7 -0
- data/lib/command_kit/stdio.rb +24 -0
- data/lib/command_kit/sudo.rb +40 -0
- data/lib/command_kit/terminal.rb +159 -0
- data/lib/command_kit/usage.rb +14 -0
- data/lib/command_kit/version.rb +1 -1
- data/lib/command_kit/xdg.rb +13 -0
- data/lib/command_kit.rb +1 -0
- data/spec/arguments/argument_spec.rb +2 -2
- data/spec/arguments_spec.rb +53 -27
- data/spec/colors_spec.rb +277 -13
- data/spec/command_name_spec.rb +1 -1
- data/spec/command_spec.rb +79 -5
- data/spec/commands/auto_load/subcommand_spec.rb +1 -1
- data/spec/commands/auto_load_spec.rb +34 -3
- data/spec/commands/auto_require_spec.rb +2 -2
- data/spec/commands/help_spec.rb +1 -1
- data/spec/commands/parent_command_spec.rb +1 -1
- data/spec/commands/subcommand_spec.rb +1 -1
- data/spec/commands_spec.rb +103 -29
- data/spec/description_spec.rb +1 -25
- data/spec/env/home_spec.rb +1 -1
- data/spec/env/path_spec.rb +7 -1
- data/spec/examples_spec.rb +1 -25
- data/spec/exception_handler_spec.rb +1 -1
- data/spec/help/man_spec.rb +45 -58
- data/spec/help_spec.rb +0 -25
- data/spec/inflector_spec.rb +71 -9
- data/spec/interactive_spec.rb +415 -0
- data/spec/main_spec.rb +7 -7
- data/spec/man_spec.rb +46 -0
- data/spec/open_app_spec.rb +85 -0
- data/spec/options/option_spec.rb +5 -5
- data/spec/options/option_value_spec.rb +56 -1
- data/spec/options_spec.rb +283 -1
- data/spec/os/linux_spec.rb +164 -0
- data/spec/os_spec.rb +201 -14
- data/spec/package_manager_spec.rb +806 -0
- data/spec/pager_spec.rb +76 -11
- data/spec/printing/indent_spec.rb +8 -6
- data/spec/printing_spec.rb +33 -3
- data/spec/program_name_spec.rb +1 -1
- data/spec/spec_helper.rb +0 -3
- data/spec/sudo_spec.rb +51 -0
- data/spec/{console_spec.rb → terminal_spec.rb} +65 -35
- data/spec/usage_spec.rb +2 -2
- data/spec/xdg_spec.rb +1 -1
- metadata +26 -8
- data/lib/command_kit/console.rb +0 -141
data/lib/command_kit/help/man.rb
CHANGED
@@ -3,6 +3,7 @@
|
|
3
3
|
require 'command_kit/command_name'
|
4
4
|
require 'command_kit/help'
|
5
5
|
require 'command_kit/stdio'
|
6
|
+
require 'command_kit/man'
|
6
7
|
|
7
8
|
module CommandKit
|
8
9
|
module Help
|
@@ -23,7 +24,11 @@ module CommandKit
|
|
23
24
|
include CommandName
|
24
25
|
include Help
|
25
26
|
include Stdio
|
27
|
+
include CommandKit::Man
|
26
28
|
|
29
|
+
#
|
30
|
+
# @api private
|
31
|
+
#
|
27
32
|
module ModuleMethods
|
28
33
|
#
|
29
34
|
# Extends {ClassMethods} or {ModuleMethods}, depending on whether
|
@@ -61,9 +66,11 @@ module CommandKit
|
|
61
66
|
# @example
|
62
67
|
# man_dir "#{__dir__}/../../man"
|
63
68
|
#
|
69
|
+
# @api public
|
70
|
+
#
|
64
71
|
def man_dir(new_man_dir=nil)
|
65
72
|
if new_man_dir
|
66
|
-
@man_dir = new_man_dir
|
73
|
+
@man_dir = File.expand_path(new_man_dir)
|
67
74
|
else
|
68
75
|
@man_dir || if superclass.kind_of?(ClassMethods)
|
69
76
|
superclass.man_dir
|
@@ -80,6 +87,8 @@ module CommandKit
|
|
80
87
|
# @return [String]
|
81
88
|
# The class'es or superclass'es man-page file name.
|
82
89
|
#
|
90
|
+
# @api public
|
91
|
+
#
|
83
92
|
def man_page(new_man_page=nil)
|
84
93
|
if new_man_page
|
85
94
|
@man_page = new_man_page
|
@@ -89,27 +98,6 @@ module CommandKit
|
|
89
98
|
end
|
90
99
|
end
|
91
100
|
|
92
|
-
#
|
93
|
-
# Displays the given man page.
|
94
|
-
#
|
95
|
-
# @param [String] page
|
96
|
-
# The man page file name.
|
97
|
-
#
|
98
|
-
# @param [Integer, String, nil] section
|
99
|
-
# The optional section number to specify.
|
100
|
-
#
|
101
|
-
# @return [Boolean, nil]
|
102
|
-
# Specifies whether the `man` command was successful or not.
|
103
|
-
# Returns `nil` when the `man` command is not installed.
|
104
|
-
#
|
105
|
-
def man(page, section: nil)
|
106
|
-
if section
|
107
|
-
system('man',section.to_s,page.to_s)
|
108
|
-
else
|
109
|
-
system('man',page.to_s)
|
110
|
-
end
|
111
|
-
end
|
112
|
-
|
113
101
|
#
|
114
102
|
# Provides help information by showing one of the man pages within
|
115
103
|
# {ClassMethods#man_dir .man_dir}.
|
@@ -124,11 +112,9 @@ module CommandKit
|
|
124
112
|
# @raise [NotImplementedError]
|
125
113
|
# {ClassMethods#man_dir .man_dir} does not have a value.
|
126
114
|
#
|
115
|
+
# @api semipublic
|
116
|
+
#
|
127
117
|
def help_man(man_page=self.class.man_page)
|
128
|
-
unless self.class.man_dir
|
129
|
-
raise(NotImplementedError,"#{self.class}.man_dir not set")
|
130
|
-
end
|
131
|
-
|
132
118
|
man_path = File.join(self.class.man_dir,man_page)
|
133
119
|
|
134
120
|
man(man_path)
|
@@ -142,16 +128,26 @@ module CommandKit
|
|
142
128
|
# {ClassMethods#man_dir .man_dir} does not have a value.
|
143
129
|
#
|
144
130
|
# @note
|
145
|
-
# if `TERM` is `dumb` or `$stdout` is not a TTY,
|
146
|
-
# the usual `--help` output.
|
131
|
+
# if `TERM` is `dumb` or `$stdout` is not a TTY, will fall back to
|
132
|
+
# printing the usual `--help` output.
|
133
|
+
#
|
134
|
+
# @api public
|
147
135
|
#
|
148
136
|
def help
|
149
137
|
if stdout.tty?
|
150
|
-
if
|
151
|
-
|
138
|
+
if self.class.man_dir
|
139
|
+
status = help_man
|
140
|
+
|
141
|
+
if status.nil?
|
142
|
+
# the `man` command is not installed
|
143
|
+
super
|
144
|
+
end
|
145
|
+
else
|
146
|
+
# man_dir was not set
|
152
147
|
super
|
153
148
|
end
|
154
149
|
else
|
150
|
+
# stdout is not a TTY
|
155
151
|
super
|
156
152
|
end
|
157
153
|
end
|
data/lib/command_kit/help.rb
CHANGED
@@ -15,6 +15,9 @@ module CommandKit
|
|
15
15
|
# MyCmd.help
|
16
16
|
#
|
17
17
|
module Help
|
18
|
+
#
|
19
|
+
# @api private
|
20
|
+
#
|
18
21
|
module ModuleMethods
|
19
22
|
#
|
20
23
|
# Extends {ClassMethods} or {ModuleMethods}, depending on whether {Help}
|
@@ -48,6 +51,8 @@ module CommandKit
|
|
48
51
|
#
|
49
52
|
# @see Help#help
|
50
53
|
#
|
54
|
+
# @api public
|
55
|
+
#
|
51
56
|
def help(**kwargs)
|
52
57
|
new(**kwargs).help
|
53
58
|
end
|
@@ -58,8 +63,9 @@ module CommandKit
|
|
58
63
|
#
|
59
64
|
# @abstract
|
60
65
|
#
|
66
|
+
# @api public
|
67
|
+
#
|
61
68
|
def help
|
62
|
-
super if defined?(super)
|
63
69
|
end
|
64
70
|
end
|
65
71
|
end
|
@@ -1,5 +1,7 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
+
require 'strscan'
|
4
|
+
|
3
5
|
module CommandKit
|
4
6
|
#
|
5
7
|
# A very simple inflector.
|
@@ -8,6 +10,8 @@ module CommandKit
|
|
8
10
|
# If you need something more powerful, checkout
|
9
11
|
# [dry-inflector](https://dry-rb.org/gems/dry-inflector/0.1/)
|
10
12
|
#
|
13
|
+
# @api semipublic
|
14
|
+
#
|
11
15
|
module Inflector
|
12
16
|
#
|
13
17
|
# Removes the namespace from a constant name.
|
@@ -31,16 +35,37 @@ module CommandKit
|
|
31
35
|
# @return [String]
|
32
36
|
# The resulting under_scored name.
|
33
37
|
#
|
38
|
+
# @raise [ArgumentError]
|
39
|
+
# The given string contained non-alpha-numeric characters.
|
40
|
+
#
|
34
41
|
def self.underscore(name)
|
35
|
-
|
36
|
-
|
42
|
+
scanner = StringScanner.new(name.to_s)
|
43
|
+
new_string = String.new
|
37
44
|
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
45
|
+
until scanner.eos?
|
46
|
+
if (separator = scanner.scan(/[_-]+/))
|
47
|
+
new_string << ('_' * separator.length)
|
48
|
+
else
|
49
|
+
if (capitalized = scanner.scan(/[A-Z][a-z\d]+/))
|
50
|
+
new_string << capitalized
|
51
|
+
elsif (uppercase = scanner.scan(/[A-Z][A-Z\d]*(?=[A-Z_-]|$)/))
|
52
|
+
new_string << uppercase
|
53
|
+
elsif (lowercase = scanner.scan(/[a-z][a-z\d]*/))
|
54
|
+
new_string << lowercase
|
55
|
+
else
|
56
|
+
raise(ArgumentError,"cannot convert string to underscored: #{scanner.string.inspect}")
|
57
|
+
end
|
42
58
|
|
43
|
-
|
59
|
+
if (separator = scanner.scan(/[_-]+/))
|
60
|
+
new_string << ('_' * separator.length)
|
61
|
+
elsif !scanner.eos?
|
62
|
+
new_string << '_'
|
63
|
+
end
|
64
|
+
end
|
65
|
+
end
|
66
|
+
|
67
|
+
new_string.downcase!
|
68
|
+
new_string
|
44
69
|
end
|
45
70
|
|
46
71
|
#
|
@@ -65,20 +90,27 @@ module CommandKit
|
|
65
90
|
# @return [String]
|
66
91
|
# The CamelCased name.
|
67
92
|
#
|
93
|
+
# @raise [ArgumentError]
|
94
|
+
# The given under_scored string contained non-alpha-numeric characters.
|
95
|
+
#
|
68
96
|
def self.camelize(name)
|
69
|
-
|
70
|
-
|
71
|
-
# sourced from: https://github.com/dry-rb/dry-inflector/blob/c918f967ff82611da374eb0847a77b7e012d3fa8/lib/dry/inflector.rb#L329-L334
|
72
|
-
name.sub!(/^[a-z\d]*/,&:capitalize)
|
73
|
-
name.gsub!(%r{(?:[_-]|(/))([a-z\d]*)}i) do |match|
|
74
|
-
slash = Regexp.last_match(1)
|
75
|
-
word = Regexp.last_match(2)
|
97
|
+
scanner = StringScanner.new(name.to_s)
|
98
|
+
new_string = String.new
|
76
99
|
|
77
|
-
|
100
|
+
until scanner.eos?
|
101
|
+
if (word = scanner.scan(/[A-Za-z\d]+/))
|
102
|
+
word.capitalize!
|
103
|
+
new_string << word
|
104
|
+
elsif scanner.scan(/[_-]+/)
|
105
|
+
# skip
|
106
|
+
elsif scanner.scan(/\//)
|
107
|
+
new_string << '::'
|
108
|
+
else
|
109
|
+
raise(ArgumentError,"cannot convert string to CamelCase: #{scanner.string.inspect}")
|
110
|
+
end
|
78
111
|
end
|
79
112
|
|
80
|
-
|
81
|
-
name
|
113
|
+
new_string
|
82
114
|
end
|
83
115
|
end
|
84
116
|
end
|
@@ -0,0 +1,248 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'command_kit/stdio'
|
4
|
+
|
5
|
+
module CommandKit
|
6
|
+
#
|
7
|
+
# Provides methods for asking the user for input.
|
8
|
+
#
|
9
|
+
# ## Examples
|
10
|
+
#
|
11
|
+
# first_name = ask("First name")
|
12
|
+
# last_name = ask("Last name")
|
13
|
+
#
|
14
|
+
# ### Asking for secret input
|
15
|
+
#
|
16
|
+
# password = ask_secret("Password")
|
17
|
+
#
|
18
|
+
# ### Asking Y/N?
|
19
|
+
#
|
20
|
+
# if ask_yes_or_no("Proceed anyways?")
|
21
|
+
# # ...
|
22
|
+
# else
|
23
|
+
# stderr.puts "Aborting!"
|
24
|
+
# end
|
25
|
+
#
|
26
|
+
# ### Asking multi-choice questions
|
27
|
+
#
|
28
|
+
# ask_multiple_choice("Select a flavor", %w[Apple Orange Lemon Lime])
|
29
|
+
# # 1) Apple
|
30
|
+
# # 2) Orange
|
31
|
+
# # 3) Lemon
|
32
|
+
# # 4) Lime
|
33
|
+
# # Select a flavor: 4
|
34
|
+
# #
|
35
|
+
# # => "Lime"
|
36
|
+
#
|
37
|
+
module Interactive
|
38
|
+
include Stdio
|
39
|
+
|
40
|
+
#
|
41
|
+
# Asks the user for input.
|
42
|
+
#
|
43
|
+
# @param [String] prompt
|
44
|
+
# The prompt that will be printed before reading input.
|
45
|
+
#
|
46
|
+
# @param [String, nil] default
|
47
|
+
# The default value to return if no input is given.
|
48
|
+
#
|
49
|
+
# @param [Boolean] required
|
50
|
+
# Requires non-empty input.
|
51
|
+
#
|
52
|
+
# @return [String]
|
53
|
+
# The user input.
|
54
|
+
#
|
55
|
+
# @example
|
56
|
+
# first_name = ask("First name")
|
57
|
+
# last_name = ask("Last name")
|
58
|
+
#
|
59
|
+
# @example Default value:
|
60
|
+
# ask("Country", default: "EU")
|
61
|
+
# # Country [EU]: <enter>
|
62
|
+
# # => "EU"
|
63
|
+
#
|
64
|
+
# @example Required non-empty input:
|
65
|
+
# ask("Email", required: true)
|
66
|
+
# # Email: <enter>
|
67
|
+
# # Email: bob@example.com<enter>
|
68
|
+
# # => "bob@example.com"
|
69
|
+
#
|
70
|
+
# @api public
|
71
|
+
#
|
72
|
+
def ask(prompt, default: nil, required: false)
|
73
|
+
prompt = prompt.chomp
|
74
|
+
prompt << " [#{default}]" if default
|
75
|
+
prompt << ": "
|
76
|
+
|
77
|
+
stdout.print(prompt)
|
78
|
+
|
79
|
+
loop do
|
80
|
+
value = stdin.gets
|
81
|
+
value ||= '' # convert nil values (ctrl^D) to an empty String
|
82
|
+
|
83
|
+
if value.empty?
|
84
|
+
if required
|
85
|
+
next
|
86
|
+
else
|
87
|
+
return (default || value)
|
88
|
+
end
|
89
|
+
else
|
90
|
+
return value
|
91
|
+
end
|
92
|
+
end
|
93
|
+
end
|
94
|
+
|
95
|
+
#
|
96
|
+
# Asks the user a yes or no question.
|
97
|
+
#
|
98
|
+
# @param [String] prompt
|
99
|
+
# The prompt that will be printed before reading input.
|
100
|
+
#
|
101
|
+
# @param [true, false, nil] default
|
102
|
+
#
|
103
|
+
# @return [Boolean]
|
104
|
+
# Specifies whether the user entered Y/yes.
|
105
|
+
#
|
106
|
+
# @example
|
107
|
+
# ask_yes_or_no("Proceed anyways?")
|
108
|
+
# # Proceed anyways? (Y/N): Y
|
109
|
+
# # => true
|
110
|
+
#
|
111
|
+
# @example Default value:
|
112
|
+
# ask_yes_or_no("Proceed anyways?", default: true)
|
113
|
+
# # Proceed anyways? (Y/N) [Y]: <enter>
|
114
|
+
# # => true
|
115
|
+
#
|
116
|
+
# @api public
|
117
|
+
#
|
118
|
+
def ask_yes_or_no(prompt, default: nil, **kwargs)
|
119
|
+
default = case default
|
120
|
+
when true then 'Y'
|
121
|
+
when false then 'N'
|
122
|
+
when nil then nil
|
123
|
+
else
|
124
|
+
raise(ArgumentError,"invalid default: #{default.inspect}")
|
125
|
+
end
|
126
|
+
|
127
|
+
prompt = "#{prompt} (Y/N)"
|
128
|
+
|
129
|
+
loop do
|
130
|
+
answer = ask(prompt, **kwargs, default: default)
|
131
|
+
|
132
|
+
case answer.downcase
|
133
|
+
when 'y', 'yes'
|
134
|
+
return true
|
135
|
+
else
|
136
|
+
return false
|
137
|
+
end
|
138
|
+
end
|
139
|
+
end
|
140
|
+
|
141
|
+
#
|
142
|
+
# Asks the user to select a choice from a list of options.
|
143
|
+
#
|
144
|
+
# @param [String] prompt
|
145
|
+
# The prompt that will be printed before reading input.
|
146
|
+
#
|
147
|
+
# @param [Hash{String => String}, Array<String>] choices
|
148
|
+
# The choices to select from.
|
149
|
+
#
|
150
|
+
# @param [Hash{Symbol => Object}] kwargs
|
151
|
+
# Additional keyword arguments for {#ask}.
|
152
|
+
#
|
153
|
+
# @option kwargs [String, nil] default
|
154
|
+
# The default option to fallback to, if no input is given.
|
155
|
+
#
|
156
|
+
# @option kwargs [Boolean] required
|
157
|
+
# Requires non-empty input.
|
158
|
+
#
|
159
|
+
# @return [String]
|
160
|
+
# The selected choice.
|
161
|
+
#
|
162
|
+
# @example Array of choices:
|
163
|
+
# ask_multiple_choice("Select a flavor", %w[Apple Orange Lemon Lime])
|
164
|
+
# # 1) Apple
|
165
|
+
# # 2) Orange
|
166
|
+
# # 3) Lemon
|
167
|
+
# # 4) Lime
|
168
|
+
# # Select a flavor: 4
|
169
|
+
# #
|
170
|
+
# # => "Lime"
|
171
|
+
#
|
172
|
+
# @example Hash of choices:
|
173
|
+
# ask_multiple_choice("Select an option", {'A' => 'Foo',
|
174
|
+
# 'B' => 'Bar',
|
175
|
+
# 'X' => 'All of the above'})
|
176
|
+
# # A) Foo
|
177
|
+
# # B) Bar
|
178
|
+
# # X) All of the above
|
179
|
+
# # Select an option: X
|
180
|
+
# #
|
181
|
+
# # => "All of the above"
|
182
|
+
#
|
183
|
+
# @api public
|
184
|
+
#
|
185
|
+
def ask_multiple_choice(prompt,choices,**kwargs)
|
186
|
+
choices = case choices
|
187
|
+
when Array
|
188
|
+
Hash[choices.each_with_index.map { |value,i|
|
189
|
+
[(i+1).to_s, value]
|
190
|
+
}]
|
191
|
+
when Hash
|
192
|
+
choices
|
193
|
+
else
|
194
|
+
raise(TypeError,"unsupported choices class #{choices.class}: #{choices.inspect}")
|
195
|
+
end
|
196
|
+
|
197
|
+
prompt = "#{prompt} (#{choices.keys.join(', ')})"
|
198
|
+
|
199
|
+
loop do
|
200
|
+
# print the choices
|
201
|
+
choices.each do |choice,value|
|
202
|
+
stdout.puts " #{choice}) #{value}"
|
203
|
+
end
|
204
|
+
stdout.puts
|
205
|
+
|
206
|
+
# read the choice
|
207
|
+
choice = ask(prompt,**kwargs)
|
208
|
+
|
209
|
+
if choices.has_key?(choice)
|
210
|
+
# if a valid choice is given, return the value
|
211
|
+
return choices[choice]
|
212
|
+
else
|
213
|
+
stderr.puts "Invalid selection: #{choice}"
|
214
|
+
end
|
215
|
+
end
|
216
|
+
end
|
217
|
+
|
218
|
+
#
|
219
|
+
# Asks the user for secret input.
|
220
|
+
#
|
221
|
+
# @param [String] prompt
|
222
|
+
# The prompt that will be printed before reading input.
|
223
|
+
#
|
224
|
+
# @param [Boolean] required
|
225
|
+
# Requires non-empty input.
|
226
|
+
#
|
227
|
+
# @return [String]
|
228
|
+
# The user input.
|
229
|
+
#
|
230
|
+
# @example
|
231
|
+
# ask_secret("Password")
|
232
|
+
# # Password:
|
233
|
+
# # => "s3cr3t"
|
234
|
+
#
|
235
|
+
# @api public
|
236
|
+
#
|
237
|
+
def ask_secret(prompt, required: true)
|
238
|
+
if stdin.respond_to?(:noecho)
|
239
|
+
stdin.noecho do
|
240
|
+
ask(prompt, required: required)
|
241
|
+
end
|
242
|
+
else
|
243
|
+
ask(prompt, required: required)
|
244
|
+
end
|
245
|
+
end
|
246
|
+
|
247
|
+
end
|
248
|
+
end
|
data/lib/command_kit/main.rb
CHANGED
@@ -12,6 +12,9 @@ module CommandKit
|
|
12
12
|
# end
|
13
13
|
#
|
14
14
|
module Main
|
15
|
+
#
|
16
|
+
# @api private
|
17
|
+
#
|
15
18
|
module ModuleMethods
|
16
19
|
#
|
17
20
|
# Extends {ClassMethods} or {ModuleMethods}, depending on whether {Main}
|
@@ -43,16 +46,16 @@ module CommandKit
|
|
43
46
|
# @param [Array<String>] argv
|
44
47
|
# The Array of command-line arguments.
|
45
48
|
#
|
49
|
+
# @api public
|
50
|
+
#
|
46
51
|
def start(argv=ARGV, **kwargs)
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
exit 0
|
55
|
-
end
|
52
|
+
exit main(argv, **kwargs)
|
53
|
+
rescue Interrupt
|
54
|
+
# https://tldp.org/LDP/abs/html/exitcodes.html
|
55
|
+
exit 130
|
56
|
+
rescue Errno::EPIPE
|
57
|
+
# STDOUT pipe broken
|
58
|
+
exit 0
|
56
59
|
end
|
57
60
|
|
58
61
|
#
|
@@ -68,6 +71,8 @@ module CommandKit
|
|
68
71
|
# @return [Integer]
|
69
72
|
# The exit status of the command.
|
70
73
|
#
|
74
|
+
# @api public
|
75
|
+
#
|
71
76
|
def main(argv=[], **kwargs)
|
72
77
|
new(**kwargs).main(argv)
|
73
78
|
end
|
@@ -84,6 +89,8 @@ module CommandKit
|
|
84
89
|
#
|
85
90
|
# @note `argv` is splatted into {#run}.
|
86
91
|
#
|
92
|
+
# @api public
|
93
|
+
#
|
87
94
|
def main(argv=[])
|
88
95
|
run(*argv)
|
89
96
|
return 0
|
@@ -99,6 +106,8 @@ module CommandKit
|
|
99
106
|
#
|
100
107
|
# @abstract
|
101
108
|
#
|
109
|
+
# @api public
|
110
|
+
#
|
102
111
|
def run(*args)
|
103
112
|
end
|
104
113
|
end
|
@@ -0,0 +1,44 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module CommandKit
|
4
|
+
#
|
5
|
+
# Allows displaying man pages.
|
6
|
+
#
|
7
|
+
# ## Examples
|
8
|
+
#
|
9
|
+
# man "passwd"
|
10
|
+
# man "passwd", section: 5
|
11
|
+
#
|
12
|
+
# @since 0.2.0
|
13
|
+
#
|
14
|
+
module Man
|
15
|
+
#
|
16
|
+
# Displays the given man page.
|
17
|
+
#
|
18
|
+
# @param [String] page
|
19
|
+
# The man page file name.
|
20
|
+
#
|
21
|
+
# @param [Integer, String, nil] section
|
22
|
+
# The optional section number to specify.
|
23
|
+
#
|
24
|
+
# @return [Boolean, nil]
|
25
|
+
# Specifies whether the `man` command was successful or not.
|
26
|
+
# Returns `nil` when the `man` command is not installed.
|
27
|
+
#
|
28
|
+
# @example
|
29
|
+
# man "passwd"
|
30
|
+
#
|
31
|
+
# @example Display a man-page from a specific section:
|
32
|
+
# man "passwd", section: 5
|
33
|
+
#
|
34
|
+
# @api public
|
35
|
+
#
|
36
|
+
def man(page, section: nil)
|
37
|
+
if section
|
38
|
+
system('man',section.to_s,page.to_s)
|
39
|
+
else
|
40
|
+
system('man',page.to_s)
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
@@ -0,0 +1,69 @@
|
|
1
|
+
require 'command_kit/os'
|
2
|
+
require 'command_kit/env/path'
|
3
|
+
|
4
|
+
module CommandKit
|
5
|
+
#
|
6
|
+
# Allows opening a file or a URI with the system's preferred application for
|
7
|
+
# that file type or URI scheme.
|
8
|
+
#
|
9
|
+
# ## Examples
|
10
|
+
#
|
11
|
+
# open_app_for "movie.avi"
|
12
|
+
# open_app_for "https://github.com/postmodern/command_kit.rb#readme"
|
13
|
+
#
|
14
|
+
# @since 0.2.0
|
15
|
+
#
|
16
|
+
module OpenApp
|
17
|
+
include OS
|
18
|
+
include Env::Path
|
19
|
+
|
20
|
+
#
|
21
|
+
# Initializes the command and determines which open command to use.
|
22
|
+
#
|
23
|
+
# @param [Hash{Symbol => Object}] kwargs
|
24
|
+
# Additional keyword arguments.
|
25
|
+
#
|
26
|
+
# @api public
|
27
|
+
#
|
28
|
+
def initialize(**kwargs)
|
29
|
+
super(**kwargs)
|
30
|
+
|
31
|
+
@open_command = if macos?
|
32
|
+
'open'
|
33
|
+
elsif linux? || bsd?
|
34
|
+
if command_installed?('xdg-open')
|
35
|
+
'xdg-open'
|
36
|
+
end
|
37
|
+
elsif windows?
|
38
|
+
if command_installed?('invoke-item')
|
39
|
+
'invoke-item'
|
40
|
+
else
|
41
|
+
'start'
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
#
|
47
|
+
# Opens a file or URI using the system's preferred application for that
|
48
|
+
# file type or URI scheme.
|
49
|
+
#
|
50
|
+
# @param [String, URI] file_or_uri
|
51
|
+
# The file path or URI to open.
|
52
|
+
#
|
53
|
+
# @return [Boolean, nil]
|
54
|
+
# Specifies whether the file or URI was successfully opened or not.
|
55
|
+
# If the open command could not be determined, `nil` is returned.
|
56
|
+
#
|
57
|
+
# @example Open a file:
|
58
|
+
# open_app_for "movie.avi"
|
59
|
+
#
|
60
|
+
# @example Open a URI:
|
61
|
+
# open_app_for "https://github.com/postmodern/command_kit.rb"
|
62
|
+
#
|
63
|
+
def open_app_for(file_or_uri)
|
64
|
+
if @open_command
|
65
|
+
system(@open_command,file_or_uri.to_s)
|
66
|
+
end
|
67
|
+
end
|
68
|
+
end
|
69
|
+
end
|
@@ -11,6 +11,8 @@ module CommandKit
|
|
11
11
|
#
|
12
12
|
# Represents a defined option.
|
13
13
|
#
|
14
|
+
# @api private
|
15
|
+
#
|
14
16
|
class Option
|
15
17
|
|
16
18
|
# The option's name.
|
@@ -33,11 +35,6 @@ module CommandKit
|
|
33
35
|
# @return [OptionValue, nil]
|
34
36
|
attr_reader :value
|
35
37
|
|
36
|
-
# The option's description.
|
37
|
-
#
|
38
|
-
# @return [String]
|
39
|
-
attr_reader :desc
|
40
|
-
|
41
38
|
# The optional block that will receive the parsed option value.
|
42
39
|
#
|
43
40
|
# @return [Proc, nil]
|
@@ -126,7 +123,7 @@ module CommandKit
|
|
126
123
|
end
|
127
124
|
|
128
125
|
#
|
129
|
-
# The separator
|
126
|
+
# The separator character between the option and option value.
|
130
127
|
#
|
131
128
|
# @return ['=', ' ', nil]
|
132
129
|
#
|