tty-option 0.0.0 → 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
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