tty-option 0.0.0 → 0.1.0
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 +1 -1
- data/README.md +1653 -1
- data/lib/tty/option.rb +63 -4
- data/lib/tty/option/aggregate_errors.rb +95 -0
- data/lib/tty/option/conversions.rb +126 -0
- data/lib/tty/option/converter.rb +63 -0
- data/lib/tty/option/deep_dup.rb +48 -0
- data/lib/tty/option/dsl.rb +105 -0
- data/lib/tty/option/dsl/arity.rb +49 -0
- data/lib/tty/option/dsl/conversion.rb +17 -0
- data/lib/tty/option/error_aggregator.rb +35 -0
- data/lib/tty/option/errors.rb +144 -0
- data/lib/tty/option/formatter.rb +389 -0
- data/lib/tty/option/inflection.rb +50 -0
- data/lib/tty/option/param_conversion.rb +34 -0
- data/lib/tty/option/param_permitted.rb +30 -0
- data/lib/tty/option/param_validation.rb +48 -0
- data/lib/tty/option/parameter.rb +310 -0
- data/lib/tty/option/parameter/argument.rb +18 -0
- data/lib/tty/option/parameter/environment.rb +20 -0
- data/lib/tty/option/parameter/keyword.rb +15 -0
- data/lib/tty/option/parameter/option.rb +99 -0
- data/lib/tty/option/parameters.rb +157 -0
- data/lib/tty/option/params.rb +122 -0
- data/lib/tty/option/parser.rb +57 -3
- data/lib/tty/option/parser/arguments.rb +166 -0
- data/lib/tty/option/parser/arity_check.rb +34 -0
- data/lib/tty/option/parser/environments.rb +169 -0
- data/lib/tty/option/parser/keywords.rb +158 -0
- data/lib/tty/option/parser/options.rb +273 -0
- data/lib/tty/option/parser/param_types.rb +51 -0
- data/lib/tty/option/parser/required_check.rb +36 -0
- data/lib/tty/option/pipeline.rb +38 -0
- data/lib/tty/option/result.rb +46 -0
- data/lib/tty/option/section.rb +26 -0
- data/lib/tty/option/sections.rb +56 -0
- data/lib/tty/option/usage.rb +166 -0
- data/lib/tty/option/usage_wrapper.rb +58 -0
- data/lib/tty/option/version.rb +3 -3
- metadata +37 -3
@@ -0,0 +1,18 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative "../parameter"
|
4
|
+
|
5
|
+
module TTY
|
6
|
+
module Option
|
7
|
+
class Parameter
|
8
|
+
class Argument < Parameter
|
9
|
+
# Required by default unless the arity allows any
|
10
|
+
#
|
11
|
+
# @api public
|
12
|
+
def required?
|
13
|
+
@settings.fetch(:required) { arity != -1 }
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end # Parameter
|
17
|
+
end # Option
|
18
|
+
end # TTY
|
@@ -0,0 +1,20 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module TTY
|
4
|
+
module Option
|
5
|
+
class Parameter
|
6
|
+
class Environment < Parameter
|
7
|
+
def default_name
|
8
|
+
key.to_s.tr("-", "_").upcase
|
9
|
+
end
|
10
|
+
|
11
|
+
# Compare this env var to another
|
12
|
+
#
|
13
|
+
# @api public
|
14
|
+
def <=>(other)
|
15
|
+
name <=> other.name
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end # Parameter
|
19
|
+
end # Option
|
20
|
+
end # TTY
|
@@ -0,0 +1,15 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative "../parameter"
|
4
|
+
|
5
|
+
module TTY
|
6
|
+
module Option
|
7
|
+
class Parameter
|
8
|
+
class Keyword < Parameter
|
9
|
+
def required?
|
10
|
+
@settings.fetch(:required) { false }
|
11
|
+
end
|
12
|
+
end
|
13
|
+
end # Parameter
|
14
|
+
end # Option
|
15
|
+
end # TTY
|
@@ -0,0 +1,99 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module TTY
|
4
|
+
module Option
|
5
|
+
class Parameter
|
6
|
+
class Option < Parameter
|
7
|
+
# Matches "-f string"
|
8
|
+
SHORT_ARGUMENT_REQUIRED_RE = /^-.(\s*?|\=)[^\[]+$/.freeze
|
9
|
+
|
10
|
+
# Matches "--foo string"
|
11
|
+
LONG_ARGUMENT_REQUIRED_RE = /^--\S+(\s+|\=)([^\[])+?$/.freeze
|
12
|
+
|
13
|
+
# Matches "-f [string]"
|
14
|
+
SHORT_ARGUMENT_OPTIONAL_RE = /^-.\s*\[\S+\]\s*$/.freeze
|
15
|
+
|
16
|
+
# Matches "--foo [string]"
|
17
|
+
LONG_ARGUMENT_OPTIONAL_RE = /^--\S+\s*\[\S+\]\s*$/.freeze
|
18
|
+
|
19
|
+
# Return long name if present, otherwise short name
|
20
|
+
#
|
21
|
+
# @api private
|
22
|
+
def default_name
|
23
|
+
[long_name, short_name].reject(&:empty?).first
|
24
|
+
end
|
25
|
+
|
26
|
+
def short(value = (not_set = true))
|
27
|
+
if not_set
|
28
|
+
@settings[:short]
|
29
|
+
else
|
30
|
+
@settings[:short] = value
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
def short?
|
35
|
+
@settings.key?(:short) && !@settings[:short].nil?
|
36
|
+
end
|
37
|
+
|
38
|
+
# Extract short flag name
|
39
|
+
#
|
40
|
+
# @api public
|
41
|
+
def short_name
|
42
|
+
short.to_s.sub(/^(-.).*$/, "\\1")
|
43
|
+
end
|
44
|
+
|
45
|
+
def long(value = (not_set = true))
|
46
|
+
if not_set
|
47
|
+
@settings.fetch(:long) { default_long }
|
48
|
+
else
|
49
|
+
@settings[:long] = value
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
def default_long
|
54
|
+
"--#{key.to_s.gsub("_", "-")}" unless short?
|
55
|
+
end
|
56
|
+
|
57
|
+
def long?
|
58
|
+
!long.nil?
|
59
|
+
end
|
60
|
+
|
61
|
+
# Extract long flag name
|
62
|
+
#
|
63
|
+
# @api public
|
64
|
+
def long_name
|
65
|
+
long.to_s.sub(/^(--.+?)(\s+|\=|\[).*$/, "\\1")
|
66
|
+
end
|
67
|
+
|
68
|
+
# Check if argument is required
|
69
|
+
#
|
70
|
+
# @return [Boolean]
|
71
|
+
#
|
72
|
+
# @api public
|
73
|
+
def argument_required?
|
74
|
+
!short.to_s.match(SHORT_ARGUMENT_REQUIRED_RE).nil? ||
|
75
|
+
!long.to_s.match(LONG_ARGUMENT_REQUIRED_RE).nil?
|
76
|
+
end
|
77
|
+
|
78
|
+
# Check if argument is optional
|
79
|
+
#
|
80
|
+
# @return [Boolean]
|
81
|
+
#
|
82
|
+
# @api public
|
83
|
+
def argument_optional?
|
84
|
+
!short.to_s.match(SHORT_ARGUMENT_OPTIONAL_RE).nil? ||
|
85
|
+
!long.to_s.match(LONG_ARGUMENT_OPTIONAL_RE).nil?
|
86
|
+
end
|
87
|
+
|
88
|
+
# Compare this option short and long names
|
89
|
+
#
|
90
|
+
# @api public
|
91
|
+
def <=>(other)
|
92
|
+
left = long? ? long_name : short_name
|
93
|
+
right = other.long? ? other.long_name : other.short_name
|
94
|
+
left <=> right
|
95
|
+
end
|
96
|
+
end # Option
|
97
|
+
end # Parameter
|
98
|
+
end # Option
|
99
|
+
end # TTY
|
@@ -0,0 +1,157 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "set"
|
4
|
+
|
5
|
+
module TTY
|
6
|
+
module Option
|
7
|
+
# A collection to hold all parameters
|
8
|
+
class Parameters
|
9
|
+
include Enumerable
|
10
|
+
|
11
|
+
# Define a query for parameter types
|
12
|
+
#
|
13
|
+
# @api private
|
14
|
+
def self.define_query(name)
|
15
|
+
define_method(:"#{name}?") do
|
16
|
+
!self.public_send(name).empty?
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
# A list of arguments
|
21
|
+
attr_reader :arguments
|
22
|
+
|
23
|
+
# A list of keywords
|
24
|
+
attr_reader :keywords
|
25
|
+
|
26
|
+
# A list of environments
|
27
|
+
attr_reader :environments
|
28
|
+
|
29
|
+
# A list of options
|
30
|
+
attr_reader :options
|
31
|
+
|
32
|
+
# A list of all parameters
|
33
|
+
attr_reader :list
|
34
|
+
|
35
|
+
define_query :arguments
|
36
|
+
define_query :keywords
|
37
|
+
define_query :options
|
38
|
+
define_query :environments
|
39
|
+
|
40
|
+
# A parameters list
|
41
|
+
#
|
42
|
+
# @api private
|
43
|
+
def initialize
|
44
|
+
@arguments = []
|
45
|
+
@environments = []
|
46
|
+
@keywords = []
|
47
|
+
@options = []
|
48
|
+
@list = []
|
49
|
+
|
50
|
+
@registered_keys = Set.new
|
51
|
+
@registered_shorts = Set.new
|
52
|
+
@registered_longs = Set.new
|
53
|
+
end
|
54
|
+
|
55
|
+
# Add parameter
|
56
|
+
#
|
57
|
+
# @param [TTY::Option::Parameter] parameter
|
58
|
+
#
|
59
|
+
# @api public
|
60
|
+
def <<(parameter)
|
61
|
+
check_key_uniqueness!(parameter.key)
|
62
|
+
|
63
|
+
if parameter.to_sym == :option
|
64
|
+
check_short_option_uniqueness!(parameter.short_name)
|
65
|
+
check_long_option_uniqueness!(parameter.long_name)
|
66
|
+
end
|
67
|
+
|
68
|
+
@list << parameter
|
69
|
+
arr = instance_variable_get("@#{parameter.to_sym}s")
|
70
|
+
arr.send :<<, parameter
|
71
|
+
self
|
72
|
+
end
|
73
|
+
alias add <<
|
74
|
+
|
75
|
+
# Delete a parameter from the list
|
76
|
+
#
|
77
|
+
# @example
|
78
|
+
# delete(:foo, :bar, :baz)
|
79
|
+
#
|
80
|
+
# @param [Array<Symbol>] keys
|
81
|
+
# the keys to delete
|
82
|
+
#
|
83
|
+
# @api public
|
84
|
+
def delete(*keys)
|
85
|
+
deleted = []
|
86
|
+
@list.delete_if { |p| keys.include?(p.key) && (deleted << p) }
|
87
|
+
deleted.each do |param|
|
88
|
+
params_list = instance_variable_get("@#{param.to_sym}s")
|
89
|
+
params_list.delete(param)
|
90
|
+
end
|
91
|
+
@registered_keys.subtract(keys)
|
92
|
+
@registered_shorts.replace(@options.map(&:short))
|
93
|
+
@registered_longs.replace(@options.map(&:long))
|
94
|
+
deleted
|
95
|
+
end
|
96
|
+
|
97
|
+
# Enumerate all parameters
|
98
|
+
#
|
99
|
+
# @api public
|
100
|
+
def each(&block)
|
101
|
+
if block_given?
|
102
|
+
@list.each(&block)
|
103
|
+
else
|
104
|
+
to_enum(:each)
|
105
|
+
end
|
106
|
+
end
|
107
|
+
|
108
|
+
# Make a deep copy of the list of parameters
|
109
|
+
#
|
110
|
+
# @api public
|
111
|
+
def dup
|
112
|
+
super.tap do |params|
|
113
|
+
params.instance_variables.each do |var|
|
114
|
+
dupped = DeepDup.deep_dup(params.instance_variable_get(var))
|
115
|
+
params.instance_variable_set(var, dupped)
|
116
|
+
end
|
117
|
+
end
|
118
|
+
end
|
119
|
+
|
120
|
+
private
|
121
|
+
|
122
|
+
# @api private
|
123
|
+
def check_key_uniqueness!(key)
|
124
|
+
if @registered_keys.include?(key)
|
125
|
+
raise ParameterConflict,
|
126
|
+
"already registered parameter #{key.inspect}"
|
127
|
+
else
|
128
|
+
@registered_keys << key
|
129
|
+
end
|
130
|
+
end
|
131
|
+
|
132
|
+
# @api private
|
133
|
+
def check_short_option_uniqueness!(short_name)
|
134
|
+
return if short_name.empty?
|
135
|
+
|
136
|
+
if @registered_shorts.include?(short_name)
|
137
|
+
raise ParameterConflict,
|
138
|
+
"already registered short option #{short_name}"
|
139
|
+
else
|
140
|
+
@registered_shorts << short_name
|
141
|
+
end
|
142
|
+
end
|
143
|
+
|
144
|
+
# @api private
|
145
|
+
def check_long_option_uniqueness!(long_name)
|
146
|
+
return if long_name.empty?
|
147
|
+
|
148
|
+
if @registered_longs.include?(long_name)
|
149
|
+
raise ParameterConflict,
|
150
|
+
"already registered long option #{long_name}"
|
151
|
+
else
|
152
|
+
@registered_longs << long_name
|
153
|
+
end
|
154
|
+
end
|
155
|
+
end # Parameters
|
156
|
+
end # Option
|
157
|
+
end # TTY
|
@@ -0,0 +1,122 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "forwardable"
|
4
|
+
|
5
|
+
require_relative "aggregate_errors"
|
6
|
+
|
7
|
+
module TTY
|
8
|
+
module Option
|
9
|
+
class Params
|
10
|
+
extend Forwardable
|
11
|
+
|
12
|
+
def self.create(parameters = {}, remaining = [], errors = [])
|
13
|
+
new(parameters, remaining: remaining, errors: errors)
|
14
|
+
end
|
15
|
+
|
16
|
+
def_delegators :@parameters,
|
17
|
+
:keys, :key?, :has_key?, :member?, :value?, :has_value?,
|
18
|
+
:empty?, :include?, :each_key, :each_value
|
19
|
+
|
20
|
+
# The remaining unparsed arguments
|
21
|
+
#
|
22
|
+
# @api public
|
23
|
+
attr_reader :remaining
|
24
|
+
|
25
|
+
# The parameter parsing errors
|
26
|
+
#
|
27
|
+
# @api public
|
28
|
+
attr_reader :errors
|
29
|
+
|
30
|
+
# Create Params
|
31
|
+
#
|
32
|
+
# @api private
|
33
|
+
def initialize(parameters, remaining: [], errors: [])
|
34
|
+
@parameters = parameters
|
35
|
+
@parameters.default_proc = ->(hash, key) do
|
36
|
+
return hash[key] if hash.key?(key)
|
37
|
+
|
38
|
+
case key
|
39
|
+
when Symbol
|
40
|
+
hash[key.to_s] if hash.key?(key.to_s)
|
41
|
+
when String
|
42
|
+
hash[key.to_sym] if hash.key?(key.to_sym)
|
43
|
+
end
|
44
|
+
end
|
45
|
+
@remaining = remaining
|
46
|
+
@errors = AggregateErrors.new(errors)
|
47
|
+
end
|
48
|
+
|
49
|
+
# Access a given value for a key
|
50
|
+
#
|
51
|
+
# @api public
|
52
|
+
def [](key)
|
53
|
+
@parameters[key]
|
54
|
+
end
|
55
|
+
|
56
|
+
# Assign value to a key
|
57
|
+
#
|
58
|
+
# @api public
|
59
|
+
def []=(key, value)
|
60
|
+
@parameters[key] = value
|
61
|
+
end
|
62
|
+
|
63
|
+
# Access a given value for a key
|
64
|
+
#
|
65
|
+
# @api public
|
66
|
+
def fetch(key, *args, &block)
|
67
|
+
value = self[key]
|
68
|
+
return value unless value.nil?
|
69
|
+
|
70
|
+
@parameters.fetch(key, *args, &block)
|
71
|
+
end
|
72
|
+
|
73
|
+
def merge(other_params)
|
74
|
+
@parameters.merge(other_params)
|
75
|
+
end
|
76
|
+
|
77
|
+
def merge!(other_params)
|
78
|
+
@parameters.merge!(other_params)
|
79
|
+
end
|
80
|
+
|
81
|
+
# Check if params have any errors
|
82
|
+
#
|
83
|
+
# @api public
|
84
|
+
def valid?
|
85
|
+
@errors.empty?
|
86
|
+
end
|
87
|
+
|
88
|
+
def ==(other)
|
89
|
+
return false unless other.kind_of?(TTY::Option::Params)
|
90
|
+
|
91
|
+
@parameters == other.to_h
|
92
|
+
end
|
93
|
+
alias eql? ==
|
94
|
+
|
95
|
+
def hash
|
96
|
+
@parameters.hash
|
97
|
+
end
|
98
|
+
|
99
|
+
def to_h
|
100
|
+
@parameters.to_h
|
101
|
+
end
|
102
|
+
|
103
|
+
# String representation of this params
|
104
|
+
#
|
105
|
+
# @return [String]
|
106
|
+
#
|
107
|
+
# @api public
|
108
|
+
def inspect
|
109
|
+
"#<#{self.class}#{to_h.inspect}>"
|
110
|
+
end
|
111
|
+
|
112
|
+
# String representation of the parameters
|
113
|
+
#
|
114
|
+
# @return [String]
|
115
|
+
#
|
116
|
+
# @api public
|
117
|
+
def to_s
|
118
|
+
to_h.to_s
|
119
|
+
end
|
120
|
+
end # Params
|
121
|
+
end # Option
|
122
|
+
end # TTY
|
data/lib/tty/option/parser.rb
CHANGED
@@ -1,8 +1,62 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
+
require_relative "params"
|
4
|
+
require_relative "parser/arguments"
|
5
|
+
require_relative "parser/environments"
|
6
|
+
require_relative "parser/keywords"
|
7
|
+
require_relative "parser/options"
|
8
|
+
require_relative "pipeline"
|
9
|
+
|
3
10
|
module TTY
|
4
11
|
module Option
|
5
12
|
class Parser
|
6
|
-
|
7
|
-
|
8
|
-
|
13
|
+
PARAMETER_PARSERS = {
|
14
|
+
options: TTY::Option::Parser::Options,
|
15
|
+
keywords: TTY::Option::Parser::Keywords,
|
16
|
+
arguments: TTY::Option::Parser::Arguments,
|
17
|
+
environments: TTY::Option::Parser::Environments
|
18
|
+
}
|
19
|
+
|
20
|
+
ARGUMENT_SEPARATOR = /^-{2,}$/.freeze
|
21
|
+
|
22
|
+
attr_reader :parameters
|
23
|
+
|
24
|
+
attr_reader :config
|
25
|
+
|
26
|
+
def initialize(parameters, **config)
|
27
|
+
@parameters = parameters
|
28
|
+
@config = config
|
29
|
+
end
|
30
|
+
|
31
|
+
def parse(argv, env)
|
32
|
+
argv = argv.dup
|
33
|
+
params = {}
|
34
|
+
errors = []
|
35
|
+
ignored = []
|
36
|
+
|
37
|
+
# split argv into processable args and leftovers
|
38
|
+
stop_index = argv.index { |arg| arg.match(ARGUMENT_SEPARATOR) }
|
39
|
+
|
40
|
+
if stop_index
|
41
|
+
ignored = argv.slice!(stop_index..-1)
|
42
|
+
ignored.shift
|
43
|
+
end
|
44
|
+
|
45
|
+
PARAMETER_PARSERS.each do |name, parser_type|
|
46
|
+
parser = parser_type.new(parameters.send(name), **config)
|
47
|
+
if name == :environments
|
48
|
+
parsed, argv, err = parser.parse(argv, env)
|
49
|
+
else
|
50
|
+
parsed, argv, err = parser.parse(argv)
|
51
|
+
end
|
52
|
+
params.merge!(parsed)
|
53
|
+
errors.concat(err)
|
54
|
+
end
|
55
|
+
|
56
|
+
argv += ignored unless ignored.empty?
|
57
|
+
|
58
|
+
[params, argv, errors]
|
59
|
+
end
|
60
|
+
end # Parser
|
61
|
+
end # Option
|
62
|
+
end # TTY
|