topt 0.0.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 +7 -0
- data/LICENSE.md +24 -0
- data/README.md +51 -0
- data/examples/kat +51 -0
- data/lib/topt.rb +53 -0
- data/lib/topt/argument.rb +74 -0
- data/lib/topt/arguments.rb +174 -0
- data/lib/topt/hash_with_indifferent_access.rb +71 -0
- data/lib/topt/option.rb +123 -0
- data/lib/topt/options.rb +182 -0
- data/lib/topt/parser.rb +153 -0
- data/lib/topt/version.rb +3 -0
- data/topt.gemspec +30 -0
- metadata +147 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: dfc4bd34d4b41a577a80ec9e4b05bb518299d366
|
4
|
+
data.tar.gz: f8a0af259dc4659bddf91dabf79587e6c5383544
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 94c007f4b87d60f08688207a05b5aaa83ec4511adb2c47ab204212204c24a536461937b16a5f6ae82e746f711477587d59271cf05d6cc17b03bd6c13167601f2
|
7
|
+
data.tar.gz: 99e173354685f903170ee6a6112c4c8ca02775771f1c588f837d15b94482123eaf716619da3089db101d8db8a8b0635eb56320c1f94da8896c118af1fa234dea
|
data/LICENSE.md
ADDED
@@ -0,0 +1,24 @@
|
|
1
|
+
Substantial portions of this code were extracted from Thor, which is
|
2
|
+
Copyright (c) 2008 Yehuda Katz, Eric Hodel, et al.
|
3
|
+
|
4
|
+
Portions of this code not extracted from Thor
|
5
|
+
Copyright (c) 2013 Martin Emde
|
6
|
+
|
7
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
8
|
+
a copy of this software and associated documentation files (the
|
9
|
+
"Software"), to deal in the Software without restriction, including
|
10
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
11
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
12
|
+
permit persons to whom the Software is furnished to do so, subject to
|
13
|
+
the following conditions:
|
14
|
+
|
15
|
+
The above copyright notice and this permission notice shall be
|
16
|
+
included in all copies or substantial portions of the Software.
|
17
|
+
|
18
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
19
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
20
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
21
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
22
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
23
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
24
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,51 @@
|
|
1
|
+
# Topt
|
2
|
+
|
3
|
+
A Thor compatible replacement for Ruby's OptionParser (optparse).
|
4
|
+
|
5
|
+
Provides extended options parsing, compared to optparse, using a [Thor](https://github.com/wycats/thor) compatible option parser.
|
6
|
+
|
7
|
+
## About
|
8
|
+
|
9
|
+
This is still a very fresh extraction. I haven't created any specs yet, so use with caution.
|
10
|
+
|
11
|
+
Open an issue if you want to use this but you're scared.
|
12
|
+
|
13
|
+
## Example
|
14
|
+
|
15
|
+
See the examples directory from examples.
|
16
|
+
|
17
|
+
class Command
|
18
|
+
extend Topt
|
19
|
+
|
20
|
+
option :verbose, type: :boolean, aliases: %w[-v], :default => false, desc: "Be noisy"
|
21
|
+
argument :filename
|
22
|
+
|
23
|
+
def initialize(given_args=ARGV)
|
24
|
+
@arguments, @options, @extras = self.class.parse_options!(given_args)
|
25
|
+
rescue Topt::Error => e
|
26
|
+
puts e.message
|
27
|
+
puts "Usage: command [--verbose] filename"
|
28
|
+
exit 1
|
29
|
+
end
|
30
|
+
|
31
|
+
def call
|
32
|
+
filename = @arguments[:filename]
|
33
|
+
puts "Opening file #{filename}" if @options[:verbose]
|
34
|
+
|
35
|
+
File.open(filename) do |f|
|
36
|
+
f.each_line do |line|
|
37
|
+
puts "outputting line #{line}" if @options[:verbose]
|
38
|
+
puts line
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
puts "Done" if @options[:verbose]
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
Command.new(ARGV).call
|
47
|
+
|
48
|
+
## Credit
|
49
|
+
|
50
|
+
A substantial portion of this code is extracted directly from [Thor](https://github.com/wycats/thor)
|
51
|
+
by Yehuda Katz, José Valim, and more.
|
data/examples/kat
ADDED
@@ -0,0 +1,51 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
$:.unshift(File.expand_path('../lib', File.dirname(__FILE__)))
|
4
|
+
|
5
|
+
require 'topt'
|
6
|
+
|
7
|
+
class Kat
|
8
|
+
extend Topt
|
9
|
+
|
10
|
+
option :blank, type: :boolean, aliases: %w[-b],
|
11
|
+
desc: "Number the non-blank output lines, starting at 1."
|
12
|
+
|
13
|
+
option :number, type: :boolean, aliases: %w[-n],
|
14
|
+
desc: "Number the output lines, starting at 1."
|
15
|
+
|
16
|
+
# If we only wanted to accept one file, we could use this and then access it
|
17
|
+
# with @arguments[:filename], but we'd rather accept many files, so we'll
|
18
|
+
# use extras to collect extra command line args
|
19
|
+
#
|
20
|
+
# argument :filename
|
21
|
+
|
22
|
+
def initialize(given_args=ARGV)
|
23
|
+
@options, @arguments, @extras = self.class.parse_options!(given_args)
|
24
|
+
end
|
25
|
+
|
26
|
+
def call
|
27
|
+
if @extras.empty?
|
28
|
+
output $stdin
|
29
|
+
else
|
30
|
+
@extras.each do |file|
|
31
|
+
File.open(file) do |f|
|
32
|
+
output f
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
def output(input)
|
39
|
+
num = 1
|
40
|
+
input.each_line do |line|
|
41
|
+
if (@options[:number] || @options[:blank]) && !(@options[:blank] && line.strip.empty?)
|
42
|
+
puts "#{num.to_s.rjust(6)}\t#{line}"
|
43
|
+
num += 1
|
44
|
+
else
|
45
|
+
puts line
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
Kat.new(ARGV).call
|
data/lib/topt.rb
ADDED
@@ -0,0 +1,53 @@
|
|
1
|
+
require 'topt/parser'
|
2
|
+
|
3
|
+
# Extend your class with this module to add Thor-like options methods.
|
4
|
+
#
|
5
|
+
# This currently does not do any magical method based things, so you'll
|
6
|
+
# need to do one command per class or instantiate the parser directly.
|
7
|
+
#
|
8
|
+
# Instantiating directly can really make your command easy to understand,
|
9
|
+
# since there is very little magic going on at that point.
|
10
|
+
module Topt
|
11
|
+
class Error < StandardError
|
12
|
+
end
|
13
|
+
|
14
|
+
class UnknownArgumentError < Error
|
15
|
+
end
|
16
|
+
|
17
|
+
class RequiredArgumentMissingError < Error
|
18
|
+
end
|
19
|
+
|
20
|
+
class MalformattedArgumentError < Error
|
21
|
+
end
|
22
|
+
|
23
|
+
def options_parser
|
24
|
+
@parser ||= Parser.new
|
25
|
+
end
|
26
|
+
|
27
|
+
def options
|
28
|
+
options_parser.options
|
29
|
+
end
|
30
|
+
alias_method :method_options, :options # Thor compatibility
|
31
|
+
|
32
|
+
def option(name, options)
|
33
|
+
options_parser.option(name, options)
|
34
|
+
end
|
35
|
+
alias_method :method_option, :option # Thor compatibility
|
36
|
+
|
37
|
+
def arguments
|
38
|
+
options_parser.arguments
|
39
|
+
end
|
40
|
+
|
41
|
+
def argument(name, options={})
|
42
|
+
attr_accessor name
|
43
|
+
options_parser.argument(name, options)
|
44
|
+
end
|
45
|
+
|
46
|
+
def remove_argument(*names)
|
47
|
+
options_parser.remove_argument(*nname)
|
48
|
+
end
|
49
|
+
|
50
|
+
def parse_options!(given_args=ARGV, defaults_hash = {})
|
51
|
+
options_parser.parse(given_args, defaults_hash)
|
52
|
+
end
|
53
|
+
end
|
@@ -0,0 +1,74 @@
|
|
1
|
+
module Topt
|
2
|
+
class Argument #:nodoc:
|
3
|
+
VALID_TYPES = [ :numeric, :hash, :array, :string ]
|
4
|
+
|
5
|
+
attr_reader :name, :description, :enum, :required, :type, :default, :banner
|
6
|
+
alias :human_name :name
|
7
|
+
|
8
|
+
def initialize(name, options={})
|
9
|
+
class_name = self.class.name.split("::").last
|
10
|
+
|
11
|
+
type = options[:type]
|
12
|
+
|
13
|
+
raise ArgumentError, "#{class_name} name can't be nil." if name.nil?
|
14
|
+
raise ArgumentError, "Type :#{type} is not valid for #{class_name.downcase}s." if type && !valid_type?(type)
|
15
|
+
|
16
|
+
@name = name.to_s
|
17
|
+
@description = options[:desc]
|
18
|
+
@required = options.key?(:required) ? options[:required] : true
|
19
|
+
@type = (type || :string).to_sym
|
20
|
+
@default = options[:default]
|
21
|
+
@banner = options[:banner] || default_banner
|
22
|
+
@enum = options[:enum]
|
23
|
+
|
24
|
+
validate! # Trigger specific validations
|
25
|
+
end
|
26
|
+
|
27
|
+
def usage
|
28
|
+
required? ? banner : "[#{banner}]"
|
29
|
+
end
|
30
|
+
|
31
|
+
def required?
|
32
|
+
required
|
33
|
+
end
|
34
|
+
|
35
|
+
def show_default?
|
36
|
+
case default
|
37
|
+
when Array, String, Hash
|
38
|
+
!default.empty?
|
39
|
+
else
|
40
|
+
default
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
protected
|
45
|
+
|
46
|
+
def validate!
|
47
|
+
if required? && !default.nil?
|
48
|
+
raise ArgumentError, "An argument cannot be required and have default value."
|
49
|
+
elsif @enum && !@enum.is_a?(Array)
|
50
|
+
raise ArgumentError, "An argument cannot have an enum other than an array."
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
def valid_type?(type)
|
55
|
+
self.class::VALID_TYPES.include?(type.to_sym)
|
56
|
+
end
|
57
|
+
|
58
|
+
def default_banner
|
59
|
+
case type
|
60
|
+
when :boolean
|
61
|
+
nil
|
62
|
+
when :string, :default
|
63
|
+
human_name.upcase
|
64
|
+
when :numeric
|
65
|
+
"N"
|
66
|
+
when :hash
|
67
|
+
"key:value"
|
68
|
+
when :array
|
69
|
+
"one two three"
|
70
|
+
end
|
71
|
+
end
|
72
|
+
|
73
|
+
end
|
74
|
+
end
|
@@ -0,0 +1,174 @@
|
|
1
|
+
module Topt
|
2
|
+
class Arguments #:nodoc:
|
3
|
+
NUMERIC = /(\d*\.\d+|\d+)/
|
4
|
+
|
5
|
+
# Receives an array of args and returns two arrays, one with arguments
|
6
|
+
# and one with switches.
|
7
|
+
#
|
8
|
+
def self.split(args)
|
9
|
+
arguments = []
|
10
|
+
|
11
|
+
args.each do |item|
|
12
|
+
break if item =~ /^-/
|
13
|
+
arguments << item
|
14
|
+
end
|
15
|
+
|
16
|
+
return arguments, args[Range.new(arguments.size, -1)]
|
17
|
+
end
|
18
|
+
|
19
|
+
def self.parse(*args)
|
20
|
+
to_parse = args.pop
|
21
|
+
new(*args).parse(to_parse)
|
22
|
+
end
|
23
|
+
|
24
|
+
# Takes an array of Thor::Argument objects.
|
25
|
+
#
|
26
|
+
def initialize(arguments=[])
|
27
|
+
@assigns, @non_assigned_required = {}, []
|
28
|
+
@switches = arguments
|
29
|
+
|
30
|
+
arguments.each do |argument|
|
31
|
+
if argument.default != nil
|
32
|
+
@assigns[argument.human_name] = argument.default
|
33
|
+
elsif argument.required?
|
34
|
+
@non_assigned_required << argument
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
def parse(args)
|
40
|
+
@pile = args.dup
|
41
|
+
|
42
|
+
@switches.each do |argument|
|
43
|
+
break unless peek
|
44
|
+
@non_assigned_required.delete(argument)
|
45
|
+
@assigns[argument.human_name] = send(:"parse_#{argument.type}", argument.human_name)
|
46
|
+
end
|
47
|
+
|
48
|
+
check_requirement!
|
49
|
+
|
50
|
+
assigns = Topt::HashWithIndifferentAccess.new(@assigns)
|
51
|
+
assigns.freeze
|
52
|
+
assigns
|
53
|
+
end
|
54
|
+
|
55
|
+
def remaining
|
56
|
+
@pile
|
57
|
+
end
|
58
|
+
|
59
|
+
private
|
60
|
+
|
61
|
+
def no_or_skip?(arg)
|
62
|
+
arg =~ /^--(no|skip)-([-\w]+)$/
|
63
|
+
$2
|
64
|
+
end
|
65
|
+
|
66
|
+
def last?
|
67
|
+
@pile.empty?
|
68
|
+
end
|
69
|
+
|
70
|
+
def peek
|
71
|
+
@pile.first
|
72
|
+
end
|
73
|
+
|
74
|
+
def shift
|
75
|
+
@pile.shift
|
76
|
+
end
|
77
|
+
|
78
|
+
def unshift(arg)
|
79
|
+
unless arg.kind_of?(Array)
|
80
|
+
@pile.unshift(arg)
|
81
|
+
else
|
82
|
+
@pile = arg + @pile
|
83
|
+
end
|
84
|
+
end
|
85
|
+
|
86
|
+
def current_is_value?
|
87
|
+
peek && peek.to_s !~ /^-/
|
88
|
+
end
|
89
|
+
|
90
|
+
# Runs through the argument array getting strings that contains ":" and
|
91
|
+
# mark it as a hash:
|
92
|
+
#
|
93
|
+
# [ "name:string", "age:integer" ]
|
94
|
+
#
|
95
|
+
# Becomes:
|
96
|
+
#
|
97
|
+
# { "name" => "string", "age" => "integer" }
|
98
|
+
#
|
99
|
+
def parse_hash(name)
|
100
|
+
return shift if peek.is_a?(Hash)
|
101
|
+
hash = {}
|
102
|
+
|
103
|
+
while current_is_value? && peek.include?(?:)
|
104
|
+
key, value = shift.split(':',2)
|
105
|
+
hash[key] = value
|
106
|
+
end
|
107
|
+
hash
|
108
|
+
end
|
109
|
+
|
110
|
+
# Runs through the argument array getting all strings until no string is
|
111
|
+
# found or a switch is found.
|
112
|
+
#
|
113
|
+
# ["a", "b", "c"]
|
114
|
+
#
|
115
|
+
# And returns it as an array:
|
116
|
+
#
|
117
|
+
# ["a", "b", "c"]
|
118
|
+
#
|
119
|
+
def parse_array(name)
|
120
|
+
return shift if peek.is_a?(Array)
|
121
|
+
array = []
|
122
|
+
|
123
|
+
while current_is_value?
|
124
|
+
array << shift
|
125
|
+
end
|
126
|
+
array
|
127
|
+
end
|
128
|
+
|
129
|
+
# Check if the peek is numeric format and return a Float or Integer.
|
130
|
+
# Otherwise raises an error.
|
131
|
+
#
|
132
|
+
def parse_numeric(name)
|
133
|
+
return shift if peek.is_a?(Numeric)
|
134
|
+
|
135
|
+
unless peek =~ NUMERIC && $& == peek
|
136
|
+
raise MalformattedArgumentError, "Expected numeric value for '#{name}'; got #{peek.inspect}"
|
137
|
+
end
|
138
|
+
|
139
|
+
$&.index('.') ? shift.to_f : shift.to_i
|
140
|
+
end
|
141
|
+
|
142
|
+
# Parse string:
|
143
|
+
# for --string-arg, just return the current value in the pile
|
144
|
+
# for --no-string-arg, nil
|
145
|
+
#
|
146
|
+
def parse_string(name)
|
147
|
+
if no_or_skip?(name)
|
148
|
+
nil
|
149
|
+
else
|
150
|
+
value = shift
|
151
|
+
if @switches.is_a?(Hash) && switch = @switches[name]
|
152
|
+
if switch.enum && !switch.enum.include?(value)
|
153
|
+
raise MalformattedArgumentError, "Expected '#{name}' to be one of #{switch.enum.join(', ')}; got #{value}"
|
154
|
+
end
|
155
|
+
end
|
156
|
+
value
|
157
|
+
end
|
158
|
+
end
|
159
|
+
|
160
|
+
# Raises an error if @non_assigned_required array is not empty.
|
161
|
+
#
|
162
|
+
def check_requirement!
|
163
|
+
unless @non_assigned_required.empty?
|
164
|
+
names = @non_assigned_required.map do |o|
|
165
|
+
o.respond_to?(:switch_name) ? o.switch_name : o.human_name
|
166
|
+
end.join("', '")
|
167
|
+
|
168
|
+
class_name = self.class.name.split('::').last.downcase
|
169
|
+
raise RequiredArgumentMissingError, "No value provided for required #{class_name} '#{names}'"
|
170
|
+
end
|
171
|
+
end
|
172
|
+
|
173
|
+
end
|
174
|
+
end
|
@@ -0,0 +1,71 @@
|
|
1
|
+
module Topt
|
2
|
+
# A hash with indifferent access and magic predicates.
|
3
|
+
#
|
4
|
+
# hash = Thor::CoreExt::HashWithIndifferentAccess.new 'foo' => 'bar', 'baz' => 'bee', 'force' => true
|
5
|
+
#
|
6
|
+
# hash[:foo] #=> 'bar'
|
7
|
+
# hash['foo'] #=> 'bar'
|
8
|
+
# hash.foo? #=> true
|
9
|
+
#
|
10
|
+
class HashWithIndifferentAccess < ::Hash #:nodoc:
|
11
|
+
|
12
|
+
def initialize(hash={})
|
13
|
+
super()
|
14
|
+
hash.each do |key, value|
|
15
|
+
self[convert_key(key)] = value
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
def [](key)
|
20
|
+
super(convert_key(key))
|
21
|
+
end
|
22
|
+
|
23
|
+
def []=(key, value)
|
24
|
+
super(convert_key(key), value)
|
25
|
+
end
|
26
|
+
|
27
|
+
def delete(key)
|
28
|
+
super(convert_key(key))
|
29
|
+
end
|
30
|
+
|
31
|
+
def values_at(*indices)
|
32
|
+
indices.collect { |key| self[convert_key(key)] }
|
33
|
+
end
|
34
|
+
|
35
|
+
def merge(other)
|
36
|
+
dup.merge!(other)
|
37
|
+
end
|
38
|
+
|
39
|
+
def merge!(other)
|
40
|
+
other.each do |key, value|
|
41
|
+
self[convert_key(key)] = value
|
42
|
+
end
|
43
|
+
self
|
44
|
+
end
|
45
|
+
|
46
|
+
protected
|
47
|
+
|
48
|
+
def convert_key(key)
|
49
|
+
key.is_a?(Symbol) ? key.to_s : key
|
50
|
+
end
|
51
|
+
|
52
|
+
# Magic predicates. For instance:
|
53
|
+
#
|
54
|
+
# options.force? # => !!options['force']
|
55
|
+
# options.shebang # => "/usr/lib/local/ruby"
|
56
|
+
# options.test_framework?(:rspec) # => options[:test_framework] == :rspec
|
57
|
+
#
|
58
|
+
def method_missing(method, *args, &block)
|
59
|
+
method = method.to_s
|
60
|
+
if method =~ /^(\w+)\?$/
|
61
|
+
if args.empty?
|
62
|
+
!!self[$1]
|
63
|
+
else
|
64
|
+
self[$1] == args.first
|
65
|
+
end
|
66
|
+
else
|
67
|
+
self[method]
|
68
|
+
end
|
69
|
+
end
|
70
|
+
end
|
71
|
+
end
|
data/lib/topt/option.rb
ADDED
@@ -0,0 +1,123 @@
|
|
1
|
+
require 'topt/argument'
|
2
|
+
|
3
|
+
module Topt
|
4
|
+
class Option < Argument #:nodoc:
|
5
|
+
attr_reader :aliases, :group, :lazy_default, :hide
|
6
|
+
|
7
|
+
VALID_TYPES = [:boolean, :numeric, :hash, :array, :string]
|
8
|
+
|
9
|
+
def initialize(name, options={})
|
10
|
+
options[:required] = false unless options.key?(:required)
|
11
|
+
super
|
12
|
+
@lazy_default = options[:lazy_default]
|
13
|
+
@group = options[:group].to_s.capitalize if options[:group]
|
14
|
+
@aliases = Array(options[:aliases])
|
15
|
+
@hide = options[:hide]
|
16
|
+
end
|
17
|
+
|
18
|
+
# This parse quick options given as method_options. It makes several
|
19
|
+
# assumptions, but you can be more specific using the option method.
|
20
|
+
#
|
21
|
+
# parse :foo => "bar"
|
22
|
+
# #=> Option foo with default value bar
|
23
|
+
#
|
24
|
+
# parse [:foo, :baz] => "bar"
|
25
|
+
# #=> Option foo with default value bar and alias :baz
|
26
|
+
#
|
27
|
+
# parse :foo => :required
|
28
|
+
# #=> Required option foo without default value
|
29
|
+
#
|
30
|
+
# parse :foo => 2
|
31
|
+
# #=> Option foo with default value 2 and type numeric
|
32
|
+
#
|
33
|
+
# parse :foo => :numeric
|
34
|
+
# #=> Option foo without default value and type numeric
|
35
|
+
#
|
36
|
+
# parse :foo => true
|
37
|
+
# #=> Option foo with default value true and type boolean
|
38
|
+
#
|
39
|
+
# The valid types are :boolean, :numeric, :hash, :array and :string. If none
|
40
|
+
# is given a default type is assumed. This default type accepts arguments as
|
41
|
+
# string (--foo=value) or booleans (just --foo).
|
42
|
+
#
|
43
|
+
# By default all options are optional, unless :required is given.
|
44
|
+
#
|
45
|
+
def self.parse(key, value)
|
46
|
+
if key.is_a?(Array)
|
47
|
+
name, *aliases = key
|
48
|
+
else
|
49
|
+
name, aliases = key, []
|
50
|
+
end
|
51
|
+
|
52
|
+
name = name.to_s
|
53
|
+
default = value
|
54
|
+
|
55
|
+
type = case value
|
56
|
+
when Symbol
|
57
|
+
default = nil
|
58
|
+
if VALID_TYPES.include?(value)
|
59
|
+
value
|
60
|
+
elsif required = (value == :required)
|
61
|
+
:string
|
62
|
+
end
|
63
|
+
when TrueClass, FalseClass
|
64
|
+
:boolean
|
65
|
+
when Numeric
|
66
|
+
:numeric
|
67
|
+
when Hash, Array, String
|
68
|
+
value.class.name.downcase.to_sym
|
69
|
+
end
|
70
|
+
self.new(name.to_s, :required => required, :type => type, :default => default, :aliases => aliases)
|
71
|
+
end
|
72
|
+
|
73
|
+
def switch_name
|
74
|
+
@switch_name ||= dasherized? ? name : dasherize(name)
|
75
|
+
end
|
76
|
+
|
77
|
+
def human_name
|
78
|
+
@human_name ||= dasherized? ? undasherize(name) : name
|
79
|
+
end
|
80
|
+
|
81
|
+
def usage(padding=0)
|
82
|
+
sample = if banner && !banner.to_s.empty?
|
83
|
+
"#{switch_name}=#{banner}"
|
84
|
+
else
|
85
|
+
switch_name
|
86
|
+
end
|
87
|
+
|
88
|
+
sample = "[#{sample}]" unless required?
|
89
|
+
|
90
|
+
if aliases.empty?
|
91
|
+
(" " * padding) << sample
|
92
|
+
else
|
93
|
+
"#{aliases.join(', ')}, #{sample}"
|
94
|
+
end
|
95
|
+
end
|
96
|
+
|
97
|
+
VALID_TYPES.each do |type|
|
98
|
+
class_eval <<-RUBY, __FILE__, __LINE__ + 1
|
99
|
+
def #{type}?
|
100
|
+
self.type == #{type.inspect}
|
101
|
+
end
|
102
|
+
RUBY
|
103
|
+
end
|
104
|
+
|
105
|
+
protected
|
106
|
+
|
107
|
+
def validate!
|
108
|
+
raise ArgumentError, "An option cannot be boolean and required." if boolean? && required?
|
109
|
+
end
|
110
|
+
|
111
|
+
def dasherized?
|
112
|
+
name.index('-') == 0
|
113
|
+
end
|
114
|
+
|
115
|
+
def undasherize(str)
|
116
|
+
str.sub(/^-{1,2}/, '')
|
117
|
+
end
|
118
|
+
|
119
|
+
def dasherize(str)
|
120
|
+
(str.length > 1 ? "--" : "-") + str.gsub('_', '-')
|
121
|
+
end
|
122
|
+
end
|
123
|
+
end
|
data/lib/topt/options.rb
ADDED
@@ -0,0 +1,182 @@
|
|
1
|
+
require 'topt/arguments'
|
2
|
+
require 'topt/hash_with_indifferent_access'
|
3
|
+
|
4
|
+
module Topt
|
5
|
+
class Options < Arguments #:nodoc:
|
6
|
+
LONG_RE = /^(--\w+(?:-\w+)*)$/
|
7
|
+
SHORT_RE = /^(-[a-z])$/i
|
8
|
+
EQ_RE = /^(--\w+(?:-\w+)*|-[a-z])=(.*)$/i
|
9
|
+
SHORT_SQ_RE = /^-([a-z]{2,})$/i # Allow either -x -v or -xv style for single char args
|
10
|
+
SHORT_NUM = /^(-[a-z])#{NUMERIC}$/i
|
11
|
+
|
12
|
+
# Receives a hash and makes it switches.
|
13
|
+
def self.to_switches(options)
|
14
|
+
options.map do |key, value|
|
15
|
+
case value
|
16
|
+
when true
|
17
|
+
"--#{key}"
|
18
|
+
when Array
|
19
|
+
"--#{key} #{value.map{ |v| v.inspect }.join(' ')}"
|
20
|
+
when Hash
|
21
|
+
"--#{key} #{value.map{ |k,v| "#{k}:#{v}" }.join(' ')}"
|
22
|
+
when nil, false
|
23
|
+
""
|
24
|
+
else
|
25
|
+
"--#{key} #{value.inspect}"
|
26
|
+
end
|
27
|
+
end.join(" ")
|
28
|
+
end
|
29
|
+
|
30
|
+
# Takes a hash of Thor::Option and a hash with defaults.
|
31
|
+
def initialize(hash_options={}, defaults={})
|
32
|
+
options = hash_options.values
|
33
|
+
super(options)
|
34
|
+
|
35
|
+
# Add defaults
|
36
|
+
defaults.each do |key, value|
|
37
|
+
@assigns[key.to_s] = value
|
38
|
+
@non_assigned_required.delete(hash_options[key])
|
39
|
+
end
|
40
|
+
|
41
|
+
@shorts, @switches, @extra = {}, {}, []
|
42
|
+
|
43
|
+
options.each do |option|
|
44
|
+
@switches[option.switch_name] = option
|
45
|
+
|
46
|
+
option.aliases.each do |short|
|
47
|
+
name = short.to_s.sub(/^(?!\-)/, '-')
|
48
|
+
@shorts[name] ||= option.switch_name
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
def remaining
|
54
|
+
@extra
|
55
|
+
end
|
56
|
+
|
57
|
+
def parse(args)
|
58
|
+
@pile = args.dup
|
59
|
+
|
60
|
+
while peek
|
61
|
+
match, is_switch = current_is_switch?
|
62
|
+
shifted = shift
|
63
|
+
|
64
|
+
if is_switch
|
65
|
+
case shifted
|
66
|
+
when SHORT_SQ_RE
|
67
|
+
unshift($1.split('').map { |f| "-#{f}" })
|
68
|
+
next
|
69
|
+
when EQ_RE, SHORT_NUM
|
70
|
+
unshift($2)
|
71
|
+
switch = $1
|
72
|
+
when LONG_RE, SHORT_RE
|
73
|
+
switch = $1
|
74
|
+
end
|
75
|
+
|
76
|
+
switch = normalize_switch(switch)
|
77
|
+
option = switch_option(switch)
|
78
|
+
@assigns[option.human_name] = parse_peek(switch, option)
|
79
|
+
elsif match
|
80
|
+
@extra << shifted
|
81
|
+
@extra << shift while peek && peek !~ /^-/
|
82
|
+
else
|
83
|
+
@extra << shifted
|
84
|
+
end
|
85
|
+
end
|
86
|
+
|
87
|
+
check_requirement!
|
88
|
+
|
89
|
+
assigns = Topt::HashWithIndifferentAccess.new(@assigns)
|
90
|
+
assigns.freeze
|
91
|
+
assigns
|
92
|
+
end
|
93
|
+
|
94
|
+
def check_unknown!
|
95
|
+
# an unknown option starts with - or -- and has no more --'s afterward.
|
96
|
+
unknown = @extra.select { |str| str =~ /^--?(?:(?!--).)*$/ }
|
97
|
+
raise UnknownArgumentError, "Unknown switches '#{unknown.join(', ')}'" unless unknown.empty?
|
98
|
+
end
|
99
|
+
|
100
|
+
protected
|
101
|
+
|
102
|
+
# Returns true if the current value in peek is a registered switch.
|
103
|
+
#
|
104
|
+
def current_is_switch?
|
105
|
+
case peek
|
106
|
+
when LONG_RE, SHORT_RE, EQ_RE, SHORT_NUM
|
107
|
+
[true, switch?($1)]
|
108
|
+
when SHORT_SQ_RE
|
109
|
+
[true, $1.split('').any? { |f| switch?("-#{f}") }]
|
110
|
+
else
|
111
|
+
[false, false]
|
112
|
+
end
|
113
|
+
end
|
114
|
+
|
115
|
+
def current_is_switch_formatted?
|
116
|
+
case peek
|
117
|
+
when LONG_RE, SHORT_RE, EQ_RE, SHORT_NUM, SHORT_SQ_RE
|
118
|
+
true
|
119
|
+
else
|
120
|
+
false
|
121
|
+
end
|
122
|
+
end
|
123
|
+
|
124
|
+
def switch?(arg)
|
125
|
+
switch_option(normalize_switch(arg))
|
126
|
+
end
|
127
|
+
|
128
|
+
def switch_option(arg)
|
129
|
+
if match = no_or_skip?(arg)
|
130
|
+
@switches[arg] || @switches["--#{match}"]
|
131
|
+
else
|
132
|
+
@switches[arg]
|
133
|
+
end
|
134
|
+
end
|
135
|
+
|
136
|
+
# Check if the given argument is actually a shortcut.
|
137
|
+
#
|
138
|
+
def normalize_switch(arg)
|
139
|
+
(@shorts[arg] || arg).tr('_', '-')
|
140
|
+
end
|
141
|
+
|
142
|
+
# Parse boolean values which can be given as --foo=true, --foo or --no-foo.
|
143
|
+
#
|
144
|
+
def parse_boolean(switch)
|
145
|
+
if current_is_value?
|
146
|
+
if ["true", "TRUE", "t", "T", true].include?(peek)
|
147
|
+
shift
|
148
|
+
true
|
149
|
+
elsif ["false", "FALSE", "f", "F", false].include?(peek)
|
150
|
+
shift
|
151
|
+
false
|
152
|
+
else
|
153
|
+
true
|
154
|
+
end
|
155
|
+
else
|
156
|
+
@switches.key?(switch) || !no_or_skip?(switch)
|
157
|
+
end
|
158
|
+
end
|
159
|
+
|
160
|
+
# Parse the value at the peek analyzing if it requires an input or not.
|
161
|
+
#
|
162
|
+
def parse_peek(switch, option)
|
163
|
+
if current_is_switch_formatted? || last?
|
164
|
+
if option.boolean?
|
165
|
+
# No problem for boolean types
|
166
|
+
elsif no_or_skip?(switch)
|
167
|
+
return nil # User set value to nil
|
168
|
+
elsif option.string? && !option.required?
|
169
|
+
# Return the default if there is one, else the human name
|
170
|
+
return option.lazy_default || option.default || option.human_name
|
171
|
+
elsif option.lazy_default
|
172
|
+
return option.lazy_default
|
173
|
+
else
|
174
|
+
raise MalformattedArgumentError, "No value provided for option '#{switch}'"
|
175
|
+
end
|
176
|
+
end
|
177
|
+
|
178
|
+
@non_assigned_required.delete(option)
|
179
|
+
send(:"parse_#{option.type}", switch)
|
180
|
+
end
|
181
|
+
end
|
182
|
+
end
|
data/lib/topt/parser.rb
ADDED
@@ -0,0 +1,153 @@
|
|
1
|
+
require 'topt/arguments'
|
2
|
+
require 'topt/argument'
|
3
|
+
require 'topt/options'
|
4
|
+
require 'topt/option'
|
5
|
+
|
6
|
+
module Topt
|
7
|
+
class Parser
|
8
|
+
attr_reader :options
|
9
|
+
attr_reader :arguments
|
10
|
+
|
11
|
+
# Create a parser for parsing ARGV input into options.
|
12
|
+
#
|
13
|
+
def initialize
|
14
|
+
@options = {}
|
15
|
+
@arguments = []
|
16
|
+
yield self if block_given?
|
17
|
+
end
|
18
|
+
|
19
|
+
# Adds an option to the set of method options. If :for is given as option,
|
20
|
+
# it allows you to change the options from a previous defined task.
|
21
|
+
#
|
22
|
+
# parser.option :foo => :bar
|
23
|
+
#
|
24
|
+
# ==== Parameters
|
25
|
+
# name<Symbol>:: The name of the argument.
|
26
|
+
# options<Hash>:: Described below.
|
27
|
+
#
|
28
|
+
# ==== Options
|
29
|
+
# :desc - Description for the argument.
|
30
|
+
# :required - If the argument is required or not.
|
31
|
+
# :default - Default value for this argument. It cannot be required and have default values.
|
32
|
+
# :aliases - Aliases for this option.
|
33
|
+
# :type - The type of the argument, can be :string, :hash, :array, :numeric or :boolean.
|
34
|
+
# :banner - String to show on usage notes.
|
35
|
+
# :hide - If you want to hide this option from the help.
|
36
|
+
#
|
37
|
+
def option(name, opts)
|
38
|
+
@options[name] = Topt::Option.new(name, opts)
|
39
|
+
end
|
40
|
+
|
41
|
+
# Adds an argument to the class and creates an attr_accessor for it.
|
42
|
+
#
|
43
|
+
# Arguments are different from options in several aspects. The first one
|
44
|
+
# is how they are parsed from the command line, arguments are retrieved
|
45
|
+
# from position:
|
46
|
+
#
|
47
|
+
# thor task NAME
|
48
|
+
#
|
49
|
+
# Instead of:
|
50
|
+
#
|
51
|
+
# thor task --name=NAME
|
52
|
+
#
|
53
|
+
# Besides, arguments are used inside your code as an accessor (self.argument),
|
54
|
+
# while options are all kept in a hash (self.options).
|
55
|
+
#
|
56
|
+
# Finally, arguments cannot have type :default or :boolean but can be
|
57
|
+
# optional (supplying :optional => :true or :required => false), although
|
58
|
+
# you cannot have a required argument after a non-required argument. If you
|
59
|
+
# try it, an error is raised.
|
60
|
+
#
|
61
|
+
# ==== Parameters
|
62
|
+
# name<Symbol>:: The name of the argument.
|
63
|
+
# options<Hash>:: Described below.
|
64
|
+
#
|
65
|
+
# ==== Options
|
66
|
+
# :desc - Description for the argument.
|
67
|
+
# :required - If the argument is required or not.
|
68
|
+
# :optional - If the argument is optional or not.
|
69
|
+
# :type - The type of the argument, can be :string, :hash, :array, :numeric.
|
70
|
+
# :default - Default value for this argument. It cannot be required and have default values.
|
71
|
+
# :banner - String to show on usage notes.
|
72
|
+
#
|
73
|
+
# ==== Errors
|
74
|
+
# ArgumentError:: Raised if you supply a required argument after a non required one.
|
75
|
+
#
|
76
|
+
def argument(name, opts={})
|
77
|
+
required = if opts.key?(:optional)
|
78
|
+
!opts[:optional]
|
79
|
+
elsif opts.key?(:required)
|
80
|
+
opts[:required]
|
81
|
+
else
|
82
|
+
opts[:default].nil?
|
83
|
+
end
|
84
|
+
|
85
|
+
remove_argument name
|
86
|
+
|
87
|
+
if required
|
88
|
+
@arguments.each do |argument|
|
89
|
+
next if argument.required?
|
90
|
+
raise ArgumentError,
|
91
|
+
"You cannot have #{name.to_s.inspect} as required argument after " \
|
92
|
+
"the non-required argument #{argument.human_name.inspect}."
|
93
|
+
end
|
94
|
+
end
|
95
|
+
|
96
|
+
opts[:required] = required
|
97
|
+
|
98
|
+
@arguments << Topt::Argument.new(name, opts)
|
99
|
+
end
|
100
|
+
|
101
|
+
# Removes a previous defined argument.
|
102
|
+
#
|
103
|
+
# ==== Parameters
|
104
|
+
# names<Array>:: Arguments to be removed
|
105
|
+
#
|
106
|
+
# ==== Examples
|
107
|
+
#
|
108
|
+
# parser.remove_argument :foo
|
109
|
+
# parser.remove_argument :foo, :bar, :baz
|
110
|
+
#
|
111
|
+
def remove_argument(*names)
|
112
|
+
names.each do |name|
|
113
|
+
@arguments.delete_if { |a| a.name == name.to_s }
|
114
|
+
end
|
115
|
+
end
|
116
|
+
|
117
|
+
# Parse the given argv-style arguments and return options, arguments,
|
118
|
+
# and any remaining unnamed arguments
|
119
|
+
def parse(given_args=ARGV, defaults_hash = {})
|
120
|
+
# split inbound arguments at the first argument
|
121
|
+
# that looks like an option (starts with - or --).
|
122
|
+
argv_args, argv_switches = Topt::Options.split(given_args.dup)
|
123
|
+
|
124
|
+
# Let Thor::Options parse the options first, so it can remove
|
125
|
+
# declared options from the array. This will leave us with
|
126
|
+
# a list of arguments that weren't declared.
|
127
|
+
parsed_options, remaining = parse_options(argv_switches, defaults_hash)
|
128
|
+
|
129
|
+
# Add the remaining arguments from the options parser to the
|
130
|
+
# arguments from argv_args. Then remove any positional
|
131
|
+
# arguments declared using #argument. This will leave us with
|
132
|
+
# the remaining positional arguments.
|
133
|
+
to_parse = argv_args + remaining
|
134
|
+
parsed_arguments, parsed_remaining = parse_arguments(to_parse)
|
135
|
+
|
136
|
+
[parsed_options, parsed_arguments, parsed_remaining]
|
137
|
+
end
|
138
|
+
|
139
|
+
# Parse option switches array into options and remaining non-option
|
140
|
+
# positional arguments.
|
141
|
+
def parse_options(argv_switches, defaults_hash)
|
142
|
+
options_parser = Topt::Options.new(@options, defaults_hash)
|
143
|
+
[options_parser.parse(argv_switches), options_parser.remaining]
|
144
|
+
end
|
145
|
+
|
146
|
+
# Parse declared arguments from the given argument array, returning
|
147
|
+
# a hash of argument name and values, and an array of remaining args.
|
148
|
+
def parse_arguments(to_parse)
|
149
|
+
args_parser = Topt::Arguments.new(@arguments)
|
150
|
+
[args_parser.parse(to_parse), args_parser.remaining]
|
151
|
+
end
|
152
|
+
end
|
153
|
+
end
|
data/lib/topt/version.rb
ADDED
data/topt.gemspec
ADDED
@@ -0,0 +1,30 @@
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
2
|
+
require File.expand_path('../lib/topt/version', __FILE__)
|
3
|
+
|
4
|
+
Gem::Specification.new do |s|
|
5
|
+
s.add_development_dependency 'bundler', '~> 1.0'
|
6
|
+
s.add_development_dependency 'fakeweb', '~> 1.3'
|
7
|
+
s.add_development_dependency 'rake', '~> 0.9'
|
8
|
+
s.add_development_dependency 'rdoc', '~> 3.9'
|
9
|
+
s.add_development_dependency 'rspec', '~> 2.3'
|
10
|
+
s.add_development_dependency 'simplecov', '~> 0.4'
|
11
|
+
#s.add_development_dependency 'childlabor'
|
12
|
+
s.authors = ['Yehuda Katz', 'José Valim', 'Martin Emde']
|
13
|
+
s.description = %q{Thor compatible command line option parser. A replacement for Ruby's OptionParser (optparse).}
|
14
|
+
s.summary = <<-SUMMARY
|
15
|
+
A replacement for OptionParser ('optparse') and Thor that supports extended options parsing based on Thor's parsing rules.
|
16
|
+
The goal of topt is a drop in replacement for Thor command line parsing where backwards compatibility is important.
|
17
|
+
SUMMARY
|
18
|
+
s.email = 'me@martinemde.com'
|
19
|
+
s.executables = `git ls-files -- bin/*`.split("\n").map{|f| File.basename(f)}
|
20
|
+
s.extra_rdoc_files = ['LICENSE.md', 'README.md']
|
21
|
+
s.license = 'MIT'
|
22
|
+
s.files = `git ls-files`.split("\n")
|
23
|
+
s.homepage = 'https://github.com/martinemde/topt'
|
24
|
+
s.name = 'topt'
|
25
|
+
s.rdoc_options = ['--charset=UTF-8']
|
26
|
+
s.require_paths = ['lib']
|
27
|
+
s.required_rubygems_version = Gem::Requirement.new('>= 1.3.6')
|
28
|
+
s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
|
29
|
+
s.version = Topt::VERSION
|
30
|
+
end
|
metadata
ADDED
@@ -0,0 +1,147 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: topt
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.0.1
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Yehuda Katz
|
8
|
+
- José Valim
|
9
|
+
- Martin Emde
|
10
|
+
autorequire:
|
11
|
+
bindir: bin
|
12
|
+
cert_chain: []
|
13
|
+
date: 2014-09-30 00:00:00.000000000 Z
|
14
|
+
dependencies:
|
15
|
+
- !ruby/object:Gem::Dependency
|
16
|
+
name: bundler
|
17
|
+
requirement: !ruby/object:Gem::Requirement
|
18
|
+
requirements:
|
19
|
+
- - "~>"
|
20
|
+
- !ruby/object:Gem::Version
|
21
|
+
version: '1.0'
|
22
|
+
type: :development
|
23
|
+
prerelease: false
|
24
|
+
version_requirements: !ruby/object:Gem::Requirement
|
25
|
+
requirements:
|
26
|
+
- - "~>"
|
27
|
+
- !ruby/object:Gem::Version
|
28
|
+
version: '1.0'
|
29
|
+
- !ruby/object:Gem::Dependency
|
30
|
+
name: fakeweb
|
31
|
+
requirement: !ruby/object:Gem::Requirement
|
32
|
+
requirements:
|
33
|
+
- - "~>"
|
34
|
+
- !ruby/object:Gem::Version
|
35
|
+
version: '1.3'
|
36
|
+
type: :development
|
37
|
+
prerelease: false
|
38
|
+
version_requirements: !ruby/object:Gem::Requirement
|
39
|
+
requirements:
|
40
|
+
- - "~>"
|
41
|
+
- !ruby/object:Gem::Version
|
42
|
+
version: '1.3'
|
43
|
+
- !ruby/object:Gem::Dependency
|
44
|
+
name: rake
|
45
|
+
requirement: !ruby/object:Gem::Requirement
|
46
|
+
requirements:
|
47
|
+
- - "~>"
|
48
|
+
- !ruby/object:Gem::Version
|
49
|
+
version: '0.9'
|
50
|
+
type: :development
|
51
|
+
prerelease: false
|
52
|
+
version_requirements: !ruby/object:Gem::Requirement
|
53
|
+
requirements:
|
54
|
+
- - "~>"
|
55
|
+
- !ruby/object:Gem::Version
|
56
|
+
version: '0.9'
|
57
|
+
- !ruby/object:Gem::Dependency
|
58
|
+
name: rdoc
|
59
|
+
requirement: !ruby/object:Gem::Requirement
|
60
|
+
requirements:
|
61
|
+
- - "~>"
|
62
|
+
- !ruby/object:Gem::Version
|
63
|
+
version: '3.9'
|
64
|
+
type: :development
|
65
|
+
prerelease: false
|
66
|
+
version_requirements: !ruby/object:Gem::Requirement
|
67
|
+
requirements:
|
68
|
+
- - "~>"
|
69
|
+
- !ruby/object:Gem::Version
|
70
|
+
version: '3.9'
|
71
|
+
- !ruby/object:Gem::Dependency
|
72
|
+
name: rspec
|
73
|
+
requirement: !ruby/object:Gem::Requirement
|
74
|
+
requirements:
|
75
|
+
- - "~>"
|
76
|
+
- !ruby/object:Gem::Version
|
77
|
+
version: '2.3'
|
78
|
+
type: :development
|
79
|
+
prerelease: false
|
80
|
+
version_requirements: !ruby/object:Gem::Requirement
|
81
|
+
requirements:
|
82
|
+
- - "~>"
|
83
|
+
- !ruby/object:Gem::Version
|
84
|
+
version: '2.3'
|
85
|
+
- !ruby/object:Gem::Dependency
|
86
|
+
name: simplecov
|
87
|
+
requirement: !ruby/object:Gem::Requirement
|
88
|
+
requirements:
|
89
|
+
- - "~>"
|
90
|
+
- !ruby/object:Gem::Version
|
91
|
+
version: '0.4'
|
92
|
+
type: :development
|
93
|
+
prerelease: false
|
94
|
+
version_requirements: !ruby/object:Gem::Requirement
|
95
|
+
requirements:
|
96
|
+
- - "~>"
|
97
|
+
- !ruby/object:Gem::Version
|
98
|
+
version: '0.4'
|
99
|
+
description: Thor compatible command line option parser. A replacement for Ruby's
|
100
|
+
OptionParser (optparse).
|
101
|
+
email: me@martinemde.com
|
102
|
+
executables: []
|
103
|
+
extensions: []
|
104
|
+
extra_rdoc_files:
|
105
|
+
- LICENSE.md
|
106
|
+
- README.md
|
107
|
+
files:
|
108
|
+
- LICENSE.md
|
109
|
+
- README.md
|
110
|
+
- examples/kat
|
111
|
+
- lib/topt.rb
|
112
|
+
- lib/topt/argument.rb
|
113
|
+
- lib/topt/arguments.rb
|
114
|
+
- lib/topt/hash_with_indifferent_access.rb
|
115
|
+
- lib/topt/option.rb
|
116
|
+
- lib/topt/options.rb
|
117
|
+
- lib/topt/parser.rb
|
118
|
+
- lib/topt/version.rb
|
119
|
+
- topt.gemspec
|
120
|
+
homepage: https://github.com/martinemde/topt
|
121
|
+
licenses:
|
122
|
+
- MIT
|
123
|
+
metadata: {}
|
124
|
+
post_install_message:
|
125
|
+
rdoc_options:
|
126
|
+
- "--charset=UTF-8"
|
127
|
+
require_paths:
|
128
|
+
- lib
|
129
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
130
|
+
requirements:
|
131
|
+
- - ">="
|
132
|
+
- !ruby/object:Gem::Version
|
133
|
+
version: '0'
|
134
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
135
|
+
requirements:
|
136
|
+
- - ">="
|
137
|
+
- !ruby/object:Gem::Version
|
138
|
+
version: 1.3.6
|
139
|
+
requirements: []
|
140
|
+
rubyforge_project:
|
141
|
+
rubygems_version: 2.2.2
|
142
|
+
signing_key:
|
143
|
+
specification_version: 4
|
144
|
+
summary: A replacement for OptionParser ('optparse') and Thor that supports extended
|
145
|
+
options parsing based on Thor's parsing rules. The goal of topt is a drop in replacement
|
146
|
+
for Thor command line parsing where backwards compatibility is important.
|
147
|
+
test_files: []
|