command_kit 0.1.0.pre1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.document +3 -0
- data/.github/workflows/ruby.yml +29 -0
- data/.gitignore +7 -0
- data/.rspec +1 -0
- data/.yardopts +1 -0
- data/ChangeLog.md +29 -0
- data/Gemfile +14 -0
- data/LICENSE.txt +20 -0
- data/README.md +283 -0
- data/Rakefile +23 -0
- data/command_kit.gemspec +60 -0
- data/gemspec.yml +14 -0
- data/lib/command_kit.rb +1 -0
- data/lib/command_kit/arguments.rb +161 -0
- data/lib/command_kit/arguments/argument.rb +111 -0
- data/lib/command_kit/arguments/argument_value.rb +81 -0
- data/lib/command_kit/arguments/usage.rb +6 -0
- data/lib/command_kit/colors.rb +355 -0
- data/lib/command_kit/command.rb +42 -0
- data/lib/command_kit/command_name.rb +95 -0
- data/lib/command_kit/commands.rb +299 -0
- data/lib/command_kit/commands/auto_load.rb +153 -0
- data/lib/command_kit/commands/auto_load/subcommand.rb +90 -0
- data/lib/command_kit/commands/auto_require.rb +138 -0
- data/lib/command_kit/commands/command.rb +12 -0
- data/lib/command_kit/commands/help.rb +43 -0
- data/lib/command_kit/commands/parent_command.rb +21 -0
- data/lib/command_kit/commands/subcommand.rb +51 -0
- data/lib/command_kit/console.rb +141 -0
- data/lib/command_kit/description.rb +89 -0
- data/lib/command_kit/env.rb +43 -0
- data/lib/command_kit/env/home.rb +71 -0
- data/lib/command_kit/env/path.rb +71 -0
- data/lib/command_kit/examples.rb +99 -0
- data/lib/command_kit/exception_handler.rb +55 -0
- data/lib/command_kit/help.rb +62 -0
- data/lib/command_kit/help/man.rb +125 -0
- data/lib/command_kit/inflector.rb +84 -0
- data/lib/command_kit/main.rb +103 -0
- data/lib/command_kit/options.rb +179 -0
- data/lib/command_kit/options/option.rb +171 -0
- data/lib/command_kit/options/option_value.rb +90 -0
- data/lib/command_kit/options/parser.rb +227 -0
- data/lib/command_kit/options/quiet.rb +53 -0
- data/lib/command_kit/options/usage.rb +6 -0
- data/lib/command_kit/options/verbose.rb +55 -0
- data/lib/command_kit/options/version.rb +62 -0
- data/lib/command_kit/os.rb +47 -0
- data/lib/command_kit/pager.rb +115 -0
- data/lib/command_kit/printing.rb +32 -0
- data/lib/command_kit/printing/indent.rb +78 -0
- data/lib/command_kit/program_name.rb +57 -0
- data/lib/command_kit/stdio.rb +138 -0
- data/lib/command_kit/usage.rb +102 -0
- data/lib/command_kit/version.rb +4 -0
- data/lib/command_kit/xdg.rb +138 -0
- data/spec/arguments/argument_spec.rb +169 -0
- data/spec/arguments/argument_value_spec.rb +126 -0
- data/spec/arguments_spec.rb +213 -0
- data/spec/colors_spec.rb +470 -0
- data/spec/command_kit_spec.rb +8 -0
- data/spec/command_name_spec.rb +130 -0
- data/spec/command_spec.rb +49 -0
- data/spec/commands/auto_load/subcommand_spec.rb +82 -0
- data/spec/commands/auto_load_spec.rb +128 -0
- data/spec/commands/auto_require_spec.rb +142 -0
- data/spec/commands/fixtures/test_auto_load/cli/commands/test1.rb +10 -0
- data/spec/commands/fixtures/test_auto_load/cli/commands/test2.rb +10 -0
- data/spec/commands/fixtures/test_auto_require/lib/test_auto_require/cli/commands/test1.rb +10 -0
- data/spec/commands/help_spec.rb +66 -0
- data/spec/commands/parent_command_spec.rb +40 -0
- data/spec/commands/subcommand_spec.rb +99 -0
- data/spec/commands_spec.rb +767 -0
- data/spec/console_spec.rb +201 -0
- data/spec/description_spec.rb +203 -0
- data/spec/env/home_spec.rb +46 -0
- data/spec/env/path_spec.rb +78 -0
- data/spec/env_spec.rb +123 -0
- data/spec/examples_spec.rb +235 -0
- data/spec/exception_handler_spec.rb +103 -0
- data/spec/help_spec.rb +119 -0
- data/spec/inflector_spec.rb +104 -0
- data/spec/main_spec.rb +179 -0
- data/spec/options/option_spec.rb +258 -0
- data/spec/options/option_value_spec.rb +67 -0
- data/spec/options/parser_spec.rb +265 -0
- data/spec/options_spec.rb +137 -0
- data/spec/os_spec.rb +46 -0
- data/spec/pager_spec.rb +154 -0
- data/spec/printing/indent_spec.rb +130 -0
- data/spec/printing_spec.rb +76 -0
- data/spec/program_name_spec.rb +62 -0
- data/spec/spec_helper.rb +6 -0
- data/spec/stdio_spec.rb +264 -0
- data/spec/usage_spec.rb +237 -0
- data/spec/xdg_spec.rb +191 -0
- metadata +156 -0
@@ -0,0 +1,171 @@
|
|
1
|
+
require 'command_kit/options/option_value'
|
2
|
+
|
3
|
+
require 'optparse'
|
4
|
+
require 'date'
|
5
|
+
require 'time'
|
6
|
+
require 'uri'
|
7
|
+
require 'shellwords'
|
8
|
+
|
9
|
+
module CommandKit
|
10
|
+
module Options
|
11
|
+
#
|
12
|
+
# Represents a defined option.
|
13
|
+
#
|
14
|
+
class Option
|
15
|
+
|
16
|
+
# @return [Symbol]
|
17
|
+
attr_reader :name
|
18
|
+
|
19
|
+
# @return [String, nil]
|
20
|
+
attr_reader :short
|
21
|
+
|
22
|
+
# @return [String]
|
23
|
+
attr_reader :long
|
24
|
+
|
25
|
+
# @return [Boolean]
|
26
|
+
attr_reader :equals
|
27
|
+
|
28
|
+
# @return [OptionValue, nil]
|
29
|
+
attr_reader :value
|
30
|
+
|
31
|
+
# @return [String]
|
32
|
+
attr_reader :desc
|
33
|
+
|
34
|
+
# @return [Proc, nil]
|
35
|
+
attr_reader :block
|
36
|
+
|
37
|
+
#
|
38
|
+
# Initializes the option.
|
39
|
+
#
|
40
|
+
# @param [Symbol] name
|
41
|
+
#
|
42
|
+
# @param [String, nil] short
|
43
|
+
#
|
44
|
+
# @param [String, nil] long
|
45
|
+
#
|
46
|
+
# @param [Boolean] equals
|
47
|
+
#
|
48
|
+
# @param [Hash{Symbol => Object}, nil] value
|
49
|
+
# Keyword arguments for {OptionValue#initialize}, or `nil` if the option
|
50
|
+
# has no additional value.
|
51
|
+
#
|
52
|
+
# @option value [Class, Hash, Array, Regexp] type
|
53
|
+
#
|
54
|
+
# @option value [String, nil] usage
|
55
|
+
#
|
56
|
+
# @param [String] desc
|
57
|
+
#
|
58
|
+
# @yield [(value)]
|
59
|
+
#
|
60
|
+
# @yieldparam [Object, nil] value
|
61
|
+
#
|
62
|
+
def initialize(name, short: nil,
|
63
|
+
long: self.class.default_long_opt(name),
|
64
|
+
equals: false,
|
65
|
+
value: nil,
|
66
|
+
desc: ,
|
67
|
+
&block)
|
68
|
+
@name = name
|
69
|
+
@short = short
|
70
|
+
@long = long
|
71
|
+
@equals = equals
|
72
|
+
@value = OptionValue.new(**value) if value
|
73
|
+
@desc = desc
|
74
|
+
@block = block
|
75
|
+
end
|
76
|
+
|
77
|
+
#
|
78
|
+
# The default long option (ex: `--long-opt`) for the given option name
|
79
|
+
# (ex: `:long_opt`).
|
80
|
+
#
|
81
|
+
# @param [Symbol] name
|
82
|
+
#
|
83
|
+
# @return [String]
|
84
|
+
#
|
85
|
+
def self.default_long_opt(name)
|
86
|
+
"--#{Inflector.dasherize(name)}"
|
87
|
+
end
|
88
|
+
|
89
|
+
#
|
90
|
+
# Specifies if the option is of the form `--opt=VALUE`.
|
91
|
+
#
|
92
|
+
# @return [Boolean]
|
93
|
+
#
|
94
|
+
def equals?
|
95
|
+
@equals
|
96
|
+
end
|
97
|
+
|
98
|
+
#
|
99
|
+
# The separator characer between the option and option value.
|
100
|
+
#
|
101
|
+
# @return ['=', ' ', nil]
|
102
|
+
#
|
103
|
+
def separator
|
104
|
+
if @value
|
105
|
+
if equals? then '='
|
106
|
+
else ' '
|
107
|
+
end
|
108
|
+
end
|
109
|
+
end
|
110
|
+
|
111
|
+
#
|
112
|
+
# Usage strings for the option.
|
113
|
+
#
|
114
|
+
# @return [Array<String>]
|
115
|
+
# The usage strings.
|
116
|
+
#
|
117
|
+
def usage
|
118
|
+
[*@short, "#{@long}#{separator}#{@value && @value.usage}"]
|
119
|
+
end
|
120
|
+
|
121
|
+
#
|
122
|
+
# Determines if the option has a value.
|
123
|
+
#
|
124
|
+
# @return [Boolean]
|
125
|
+
#
|
126
|
+
def value?
|
127
|
+
!@value.nil?
|
128
|
+
end
|
129
|
+
|
130
|
+
#
|
131
|
+
# The option value's type.
|
132
|
+
#
|
133
|
+
# @return [Class, nil]
|
134
|
+
#
|
135
|
+
# @see OptionValue#type
|
136
|
+
#
|
137
|
+
def type
|
138
|
+
@value && @value.type
|
139
|
+
end
|
140
|
+
|
141
|
+
#
|
142
|
+
# The option value's default value.
|
143
|
+
#
|
144
|
+
# @return [Object, nil]
|
145
|
+
#
|
146
|
+
# @see OptionValue#default_value
|
147
|
+
#
|
148
|
+
def default_value
|
149
|
+
@value && @value.default_value
|
150
|
+
end
|
151
|
+
|
152
|
+
#
|
153
|
+
# The option description.
|
154
|
+
#
|
155
|
+
# @return [String]
|
156
|
+
#
|
157
|
+
# @note
|
158
|
+
# If {#default_value} returns a value, the description will contain the
|
159
|
+
# `Default:` value the option will be initialized with.
|
160
|
+
#
|
161
|
+
def desc
|
162
|
+
if (value = default_value)
|
163
|
+
"#{@desc} (Default: #{value})"
|
164
|
+
else
|
165
|
+
@desc
|
166
|
+
end
|
167
|
+
end
|
168
|
+
|
169
|
+
end
|
170
|
+
end
|
171
|
+
end
|
@@ -0,0 +1,90 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'command_kit/arguments/argument_value'
|
4
|
+
require 'command_kit/inflector'
|
5
|
+
|
6
|
+
require 'optparse'
|
7
|
+
require 'date'
|
8
|
+
require 'time'
|
9
|
+
require 'uri'
|
10
|
+
require 'shellwords'
|
11
|
+
|
12
|
+
module CommandKit
|
13
|
+
module Options
|
14
|
+
#
|
15
|
+
# Represents an additional argument associated with an option flag.
|
16
|
+
#
|
17
|
+
class OptionValue < Arguments::ArgumentValue
|
18
|
+
|
19
|
+
USAGES = {
|
20
|
+
# NOTE: NilClass and Object are intentionally omitted
|
21
|
+
Date => 'DATE',
|
22
|
+
DateTime => 'DATE_TIME',
|
23
|
+
Time => 'TIME',
|
24
|
+
URI => 'URI',
|
25
|
+
Shellwords => 'STR',
|
26
|
+
String => 'STR',
|
27
|
+
Integer => 'INT',
|
28
|
+
Float => 'DEC',
|
29
|
+
Numeric => 'NUM',
|
30
|
+
OptionParser::DecimalInteger => 'INT',
|
31
|
+
OptionParser::OctalInteger => 'OCT',
|
32
|
+
OptionParser::DecimalNumeric => 'NUM|DEC',
|
33
|
+
TrueClass => 'BOOL',
|
34
|
+
FalseClass => 'BOOL',
|
35
|
+
Array => 'LIST[,...]',
|
36
|
+
Regexp => '/REGEXP/'
|
37
|
+
}
|
38
|
+
|
39
|
+
#
|
40
|
+
# Initializes the option value.
|
41
|
+
#
|
42
|
+
# @param [Class, Hash, Array, Regexp] type
|
43
|
+
#
|
44
|
+
# @param [String, nil] usage
|
45
|
+
#
|
46
|
+
# @param [Hash{Symbol => Object}] kwargs
|
47
|
+
#
|
48
|
+
def initialize(type: String,
|
49
|
+
usage: self.class.default_usage(type),
|
50
|
+
**kwargs)
|
51
|
+
super(type: type, usage: usage, **kwargs)
|
52
|
+
end
|
53
|
+
|
54
|
+
#
|
55
|
+
# Returns the default option value usage for the given type.
|
56
|
+
#
|
57
|
+
# @param [Class, Hash, Array, Regexp] type
|
58
|
+
#
|
59
|
+
# @return [String, nil]
|
60
|
+
#
|
61
|
+
# @raise [TypeError]
|
62
|
+
# The given type was not a Class, Hash, Array, or Regexp.
|
63
|
+
#
|
64
|
+
def self.default_usage(type)
|
65
|
+
USAGES.fetch(type) do
|
66
|
+
case type
|
67
|
+
when Class then Inflector.underscore(type.name).upcase
|
68
|
+
when Hash then type.keys.join('|')
|
69
|
+
when Array then type.join('|')
|
70
|
+
when Regexp then type.source
|
71
|
+
else
|
72
|
+
raise(TypeError,"unsupported option type: #{type.inspect}")
|
73
|
+
end
|
74
|
+
end
|
75
|
+
end
|
76
|
+
|
77
|
+
#
|
78
|
+
# The usage string for the argument.
|
79
|
+
#
|
80
|
+
# @return [String, nil]
|
81
|
+
#
|
82
|
+
def usage
|
83
|
+
string = @usage
|
84
|
+
string = "[#{string}]" if optional?
|
85
|
+
string
|
86
|
+
end
|
87
|
+
|
88
|
+
end
|
89
|
+
end
|
90
|
+
end
|
@@ -0,0 +1,227 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'command_kit/main'
|
4
|
+
require 'command_kit/usage'
|
5
|
+
require 'command_kit/printing'
|
6
|
+
|
7
|
+
require 'optparse'
|
8
|
+
|
9
|
+
module CommandKit
|
10
|
+
module Options
|
11
|
+
#
|
12
|
+
# Adds an [OptionParser] to the command class and automatically parses options
|
13
|
+
# before calling `main`.
|
14
|
+
#
|
15
|
+
# [OptionParser]: https://rubydoc.info/stdlib/optparse/OptionParser
|
16
|
+
#
|
17
|
+
# include CommandKit::OptParser
|
18
|
+
#
|
19
|
+
# def initialize
|
20
|
+
# @opts.on('-c','--custom','Custom option') do
|
21
|
+
# @custom = true
|
22
|
+
# end
|
23
|
+
# end
|
24
|
+
#
|
25
|
+
# def main(*argv)
|
26
|
+
# if @custom
|
27
|
+
# puts "Custom mode enabled"
|
28
|
+
# end
|
29
|
+
# end
|
30
|
+
#
|
31
|
+
module Parser
|
32
|
+
include Usage
|
33
|
+
include Main
|
34
|
+
include Printing
|
35
|
+
|
36
|
+
module ModuleMethods
|
37
|
+
#
|
38
|
+
# Sets {CommandKit::Usage::ClassMethods#usage .usage} or extends
|
39
|
+
# {ModuleMethods}, depending on whether {Options::Parser} is being
|
40
|
+
# included into a class or a module.
|
41
|
+
#
|
42
|
+
# @param [Class, Module] context
|
43
|
+
# The class or module including {Parser}.
|
44
|
+
#
|
45
|
+
def included(context)
|
46
|
+
super
|
47
|
+
|
48
|
+
if context.class == Module
|
49
|
+
context.extend ModuleMethods
|
50
|
+
else
|
51
|
+
context.usage '[options]'
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
extend ModuleMethods
|
57
|
+
|
58
|
+
# The option parser.
|
59
|
+
#
|
60
|
+
# @return [OptionParser]
|
61
|
+
attr_reader :option_parser
|
62
|
+
|
63
|
+
#
|
64
|
+
# The option parser.
|
65
|
+
#
|
66
|
+
# @return [OptionParser]
|
67
|
+
#
|
68
|
+
def initialize(**kwargs)
|
69
|
+
super(**kwargs)
|
70
|
+
|
71
|
+
@option_parser = OptionParser.new do |opts|
|
72
|
+
opts.banner = "Usage: #{usage}"
|
73
|
+
|
74
|
+
opts.separator ''
|
75
|
+
opts.separator 'Options:'
|
76
|
+
|
77
|
+
opts.on_tail('-h','--help','Print help information') do
|
78
|
+
help
|
79
|
+
exit(0)
|
80
|
+
end
|
81
|
+
end
|
82
|
+
end
|
83
|
+
|
84
|
+
#
|
85
|
+
# Parses the options and passes any additional non-option arguments
|
86
|
+
# to the superclass'es `#main` method.
|
87
|
+
#
|
88
|
+
# @param [Array<String>] argv
|
89
|
+
# The given arguments Array.
|
90
|
+
#
|
91
|
+
# @return [Integer]
|
92
|
+
# The exit status code.
|
93
|
+
#
|
94
|
+
def main(argv=[])
|
95
|
+
super(parse_options(argv))
|
96
|
+
rescue SystemExit => system_exit
|
97
|
+
system_exit.status
|
98
|
+
end
|
99
|
+
|
100
|
+
#
|
101
|
+
# Parses the given options.
|
102
|
+
#
|
103
|
+
# @param [Array<String>] argv
|
104
|
+
# The given command-line arguments.
|
105
|
+
#
|
106
|
+
# @return [Array<String>]
|
107
|
+
# The remaining non-option arguments.
|
108
|
+
#
|
109
|
+
def parse_options(argv)
|
110
|
+
begin
|
111
|
+
option_parser.parse(argv)
|
112
|
+
rescue OptionParser::InvalidOption => error
|
113
|
+
on_invalid_option(error)
|
114
|
+
rescue OptionParser::AmbiguousOption => error
|
115
|
+
on_ambiguous_option(error)
|
116
|
+
rescue OptionParser::InvalidArgument => error
|
117
|
+
on_invalid_argument(error)
|
118
|
+
rescue OptionParser::MissingArgument => error
|
119
|
+
on_missing_argument(error)
|
120
|
+
rescue OptionParser::NeedlessArgument => error
|
121
|
+
on_needless_argument(error)
|
122
|
+
rescue OptionParser::AmbiguousArgument => error
|
123
|
+
on_ambiguous_argument(error)
|
124
|
+
rescue OptionParser::ParseError => error
|
125
|
+
on_parse_error(error)
|
126
|
+
end
|
127
|
+
end
|
128
|
+
|
129
|
+
#
|
130
|
+
# Prints an option parsing error.
|
131
|
+
#
|
132
|
+
# @param [OptionParser::ParseError] error
|
133
|
+
# The error from `OptionParser`.
|
134
|
+
#
|
135
|
+
def on_parse_error(error)
|
136
|
+
print_error("#{command_name}: #{error.message}")
|
137
|
+
print_error("Try '#{command_name} --help' for more information.")
|
138
|
+
exit(1)
|
139
|
+
end
|
140
|
+
|
141
|
+
#
|
142
|
+
# Place-holder method for handling `OptionParser::InvalidOption` exceptions.
|
143
|
+
#
|
144
|
+
# @param [OptionParser::InvalidOption] error
|
145
|
+
#
|
146
|
+
# @see on_parse_error
|
147
|
+
#
|
148
|
+
def on_invalid_option(error)
|
149
|
+
on_parse_error(error)
|
150
|
+
end
|
151
|
+
|
152
|
+
#
|
153
|
+
# Place-holder method for handling `OptionParser::AmbiguousOption`
|
154
|
+
# exceptions.
|
155
|
+
#
|
156
|
+
# @param [OptionParser::AmbiguousOption] error
|
157
|
+
#
|
158
|
+
# @see on_parse_error
|
159
|
+
#
|
160
|
+
def on_ambiguous_option(error)
|
161
|
+
on_parse_error(error)
|
162
|
+
end
|
163
|
+
|
164
|
+
#
|
165
|
+
# Place-holder method for handling `OptionParser::InvalidArgument`
|
166
|
+
# exceptions.
|
167
|
+
#
|
168
|
+
# @param [OptionParser::InvalidArgument] error
|
169
|
+
#
|
170
|
+
# @see on_parse_error
|
171
|
+
#
|
172
|
+
def on_invalid_argument(error)
|
173
|
+
on_parse_error(error)
|
174
|
+
end
|
175
|
+
|
176
|
+
#
|
177
|
+
# Place-holder method for handling `OptionParser::MissingArgument`
|
178
|
+
# exceptions.
|
179
|
+
#
|
180
|
+
# @param [OptionParser::MissingArgument] error
|
181
|
+
#
|
182
|
+
# @see on_parse_error
|
183
|
+
#
|
184
|
+
def on_missing_argument(error)
|
185
|
+
on_parse_error(error)
|
186
|
+
end
|
187
|
+
|
188
|
+
#
|
189
|
+
# Place-holder method for handling `OptionParser::NeedlessArgument`
|
190
|
+
# exceptions.
|
191
|
+
#
|
192
|
+
# @param [OptionParser::NeedlessArgument] error
|
193
|
+
#
|
194
|
+
# @see on_parse_error
|
195
|
+
#
|
196
|
+
def on_needless_argument(error)
|
197
|
+
on_parse_error(error)
|
198
|
+
end
|
199
|
+
|
200
|
+
#
|
201
|
+
# Place-holder method for handling `OptionParser::AmbiguousArgument`
|
202
|
+
# exceptions.
|
203
|
+
#
|
204
|
+
# @param [OptionParser::AmbiguousArgument] error
|
205
|
+
#
|
206
|
+
# @see on_parse_error
|
207
|
+
#
|
208
|
+
def on_ambiguous_argument(error)
|
209
|
+
on_parse_error(error)
|
210
|
+
end
|
211
|
+
|
212
|
+
#
|
213
|
+
# Prints the `--help` output.
|
214
|
+
#
|
215
|
+
def help_options
|
216
|
+
puts option_parser
|
217
|
+
end
|
218
|
+
|
219
|
+
#
|
220
|
+
# @see #help_options
|
221
|
+
#
|
222
|
+
def help
|
223
|
+
help_options
|
224
|
+
end
|
225
|
+
end
|
226
|
+
end
|
227
|
+
end
|