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.
Files changed (41) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +1 -1
  3. data/README.md +1653 -1
  4. data/lib/tty/option.rb +63 -4
  5. data/lib/tty/option/aggregate_errors.rb +95 -0
  6. data/lib/tty/option/conversions.rb +126 -0
  7. data/lib/tty/option/converter.rb +63 -0
  8. data/lib/tty/option/deep_dup.rb +48 -0
  9. data/lib/tty/option/dsl.rb +105 -0
  10. data/lib/tty/option/dsl/arity.rb +49 -0
  11. data/lib/tty/option/dsl/conversion.rb +17 -0
  12. data/lib/tty/option/error_aggregator.rb +35 -0
  13. data/lib/tty/option/errors.rb +144 -0
  14. data/lib/tty/option/formatter.rb +389 -0
  15. data/lib/tty/option/inflection.rb +50 -0
  16. data/lib/tty/option/param_conversion.rb +34 -0
  17. data/lib/tty/option/param_permitted.rb +30 -0
  18. data/lib/tty/option/param_validation.rb +48 -0
  19. data/lib/tty/option/parameter.rb +310 -0
  20. data/lib/tty/option/parameter/argument.rb +18 -0
  21. data/lib/tty/option/parameter/environment.rb +20 -0
  22. data/lib/tty/option/parameter/keyword.rb +15 -0
  23. data/lib/tty/option/parameter/option.rb +99 -0
  24. data/lib/tty/option/parameters.rb +157 -0
  25. data/lib/tty/option/params.rb +122 -0
  26. data/lib/tty/option/parser.rb +57 -3
  27. data/lib/tty/option/parser/arguments.rb +166 -0
  28. data/lib/tty/option/parser/arity_check.rb +34 -0
  29. data/lib/tty/option/parser/environments.rb +169 -0
  30. data/lib/tty/option/parser/keywords.rb +158 -0
  31. data/lib/tty/option/parser/options.rb +273 -0
  32. data/lib/tty/option/parser/param_types.rb +51 -0
  33. data/lib/tty/option/parser/required_check.rb +36 -0
  34. data/lib/tty/option/pipeline.rb +38 -0
  35. data/lib/tty/option/result.rb +46 -0
  36. data/lib/tty/option/section.rb +26 -0
  37. data/lib/tty/option/sections.rb +56 -0
  38. data/lib/tty/option/usage.rb +166 -0
  39. data/lib/tty/option/usage_wrapper.rb +58 -0
  40. data/lib/tty/option/version.rb +3 -3
  41. 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
@@ -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
- end
7
- end
8
- end
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