opt 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.
data/lib/opt/option.rb ADDED
@@ -0,0 +1,198 @@
1
+ module Opt
2
+ #
3
+ # A command line option consisting of multiple switches,
4
+ # possibly arguments and options about allowed numbers etc.
5
+ #
6
+ # @api private
7
+ #
8
+ class Option
9
+ #
10
+ # Option's name.
11
+ #
12
+ # @return [String] Frozen name string.
13
+ #
14
+ attr_reader :name
15
+
16
+ # Set of switches triggering this option.
17
+ #
18
+ # Avoid direct manipulation.
19
+ #
20
+ # @return [Set<Switch>] Set of switches.
21
+ #
22
+ attr_reader :switches
23
+
24
+ # Options passed to {#initialize}.
25
+ #
26
+ # @return [Hash] Option hash.
27
+ #
28
+ attr_reader :options
29
+
30
+ # Option default value.
31
+ #
32
+ # @return [Object] Default value.
33
+ #
34
+ attr_reader :default
35
+
36
+ # Option value returned if switch is given.
37
+ #
38
+ # Will be ignored if option takes arguments.
39
+ #
40
+ # @return [Object] Option value.
41
+ #
42
+ attr_reader :value
43
+
44
+ # Number of arguments as a range.
45
+ #
46
+ # @return [Range] Argument number range.
47
+ #
48
+ attr_reader :nargs
49
+
50
+ def initialize(definition, options = {})
51
+ @options = options
52
+ @default = options.fetch(:default, nil)
53
+ @value = options.fetch(:value, true)
54
+ @nargs = Option.parse_nargs options.fetch(:nargs, 0)
55
+
56
+ if definition.to_s =~ /\A[[:word:]]+\z/
57
+ @switches = Set.new
58
+ @name = options.fetch(:name, definition).to_s.freeze
59
+
60
+ unless nargs.first > 0 || nargs.size > 1
61
+ raise 'A text option must consist of at least one argument.'
62
+ end
63
+ else
64
+ @switches = Switch.parse(definition)
65
+ @name = options.fetch(:name, switches.first.name).to_s.freeze
66
+ end
67
+ end
68
+
69
+ # Check if option is triggered by at least on CLI switch.
70
+ #
71
+ def switch?
72
+ switches.any?
73
+ end
74
+
75
+ # Check if option is a free-text option.
76
+ #
77
+ def text?
78
+ !switch?
79
+ end
80
+
81
+ def collide?(option)
82
+ name == option.name || !switches.disjoint?(option.switches)
83
+ end
84
+
85
+ def parse!(argv, result)
86
+ if text?
87
+ parse_text!(argv, result)
88
+ else
89
+ parse_switches!(argv, result)
90
+ end
91
+ end
92
+
93
+ def parse_text!(argv, result)
94
+ return false unless argv.first.text?
95
+
96
+ parse_args!(argv, result)
97
+ true
98
+ end
99
+
100
+ def parse_switches!(argv, result)
101
+ switches.each do |switch|
102
+ next unless switch.match!(argv)
103
+
104
+ parse_args!(argv, result)
105
+ return true
106
+ end
107
+
108
+ false
109
+ end
110
+
111
+ def parse_args!(argv, result)
112
+ if nargs.first == 0 && nargs.last == 0
113
+ result[name] = value
114
+ else
115
+ args = []
116
+ if argv.any? && argv.first.text?
117
+ while argv.any? && argv.first.text? && args.size < nargs.last
118
+ args << argv.shift.value
119
+ end
120
+ elsif argv.any? && argv.first.short?
121
+ args << argv.shift.value
122
+ end
123
+
124
+ if nargs.include?(args.size)
125
+ if nargs.first == 1 && nargs.last == 1
126
+ result[name] = args.first
127
+ else
128
+ result[name] = args
129
+ end
130
+ else
131
+ # raise Opt::MissingArgument
132
+ raise "wrong number of arguments (#{args.size} for #{nargs})"
133
+ end
134
+ end
135
+ end
136
+
137
+ class << self
138
+ def parse_nargs(num)
139
+ case num
140
+ when Range
141
+ parse_nargs_range(num)
142
+ when Array
143
+ parse_nargs_array(num)
144
+ else
145
+ parse_nargs_obj(num)
146
+ end
147
+ end
148
+
149
+ def parse_nargs_obj(obj)
150
+ case obj.to_s.downcase
151
+ when '+'
152
+ 1..Float::INFINITY
153
+ when '*', 'inf', 'infinity'
154
+ 0..Float::INFINITY
155
+ else
156
+ i = Integer(obj.to_s)
157
+ parse_nargs_range i..i
158
+ end
159
+ end
160
+
161
+ def parse_nargs_range(range)
162
+ if range.first > range.last
163
+ if range.exclude_end?
164
+ range = Range.new(range.last + 1, range.first)
165
+ else
166
+ range = Range.new(range.last, range.first)
167
+ end
168
+ end
169
+
170
+ if range.first < 0
171
+ raise RuntimeError.new 'Argument number must not be less than zero.'
172
+ end
173
+
174
+ range
175
+ end
176
+
177
+ def parse_nargs_array(obj)
178
+ if obj.size == 2
179
+ parse_nargs_range Range.new(parse_nargs_array_obj(obj[0]),
180
+ parse_nargs_array_obj(obj[1]))
181
+ else
182
+
183
+ raise ArgumentError.new \
184
+ 'Argument number array count must be exactly two.'
185
+ end
186
+ end
187
+
188
+ def parse_nargs_array_obj(obj)
189
+ case obj.to_s.downcase
190
+ when '*', 'inf', 'infinity'
191
+ Float::INFINITY
192
+ else
193
+ Integer(obj.to_s)
194
+ end
195
+ end
196
+ end
197
+ end
198
+ end
@@ -0,0 +1,9 @@
1
+ module Opt
2
+ #
3
+ class Program < Command
4
+
5
+ def initialize(options = {})
6
+ super nil, options
7
+ end
8
+ end
9
+ end
data/lib/opt/switch.rb ADDED
@@ -0,0 +1,119 @@
1
+ require 'set'
2
+
3
+ module Opt
4
+ #
5
+ # A single command line switch.
6
+ #
7
+ class Switch
8
+ class << self
9
+ #
10
+ # Parse an object or string into a Set of switches.
11
+ #
12
+ # @example
13
+ # Opt::Switch.parse '-h, --help'
14
+ # #=> Set{<Switch: -h>, <Switch: --help>}
15
+ #
16
+ def parse(object)
17
+ if object.is_a?(self)
18
+ object
19
+ else
20
+ if object.respond_to?(:to_str)
21
+ parse_str object.to_str
22
+ else
23
+ parse_str object.to_s
24
+ end
25
+ end
26
+ end
27
+
28
+ # Create new command line switch.
29
+ #
30
+ # If a Switch object is given it will be returned instead.
31
+ #
32
+ # @see #initialize
33
+ #
34
+ def new(object)
35
+ if object.is_a?(self)
36
+ object
37
+ else
38
+ super object
39
+ end
40
+ end
41
+
42
+ private
43
+
44
+ def parse_str(str)
45
+ Set.new str.split(/\s*,\s*/).map{|s| new s }
46
+ end
47
+ end
48
+
49
+ # Switch name.
50
+ #
51
+ attr_reader :name
52
+
53
+ # Regular expression matching an accepted command line switch
54
+ REGEXP = /\A(?<dash>--?)(?<name>[[:word:]]+([[[:word:]]-]*[[:word:]])?)\z/
55
+
56
+ def initialize(str)
57
+ match = REGEXP.match(str)
58
+ raise "Invalid command line switch: #{str.inspect}" unless match
59
+
60
+ @name = match[:name].freeze
61
+
62
+ case match[:dash].to_s.size
63
+ when 0
64
+ @short = name.size == 1
65
+ when 1
66
+ @short = true
67
+ else
68
+ @short = false
69
+ end
70
+ end
71
+
72
+ def short?
73
+ @short
74
+ end
75
+
76
+ def long?
77
+ !short?
78
+ end
79
+
80
+ def eql?(object)
81
+ if object.is_a?(self.class)
82
+ name == object.name
83
+ else
84
+ super
85
+ end
86
+ end
87
+
88
+ def hash
89
+ name.hash
90
+ end
91
+
92
+ def match?(argv)
93
+ case (arg = argv.first).type
94
+ when :long
95
+ long? && arg.value.split('=')[0] == name
96
+ when :short
97
+ short? && arg.value[0] == name[0]
98
+ else
99
+ false
100
+ end
101
+ end
102
+
103
+ def match!(argv)
104
+ return false unless match?(argv)
105
+
106
+ if short? && argv.first.value.size > 1
107
+ argv.first.value.slice!(0, 1)
108
+ else
109
+ arg = argv.shift
110
+
111
+ if arg.value.include?('=')
112
+ argv.unshift Command::Token.new(:text, arg.value.split('=', 2)[1])
113
+ end
114
+ end
115
+
116
+ true
117
+ end
118
+ end
119
+ end
@@ -0,0 +1,4 @@
1
+ module Opt::Types
2
+ class IO < Base
3
+ end
4
+ end
@@ -0,0 +1,3 @@
1
+ module Opt
2
+ VERSION = "0.1.0"
3
+ end
data/opt.gemspec ADDED
@@ -0,0 +1,22 @@
1
+ # coding: utf-8
2
+ lib = File.expand_path('../lib', __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+ require 'opt/version'
5
+
6
+ Gem::Specification.new do |spec|
7
+ spec.name = 'opt'
8
+ spec.version = Opt::VERSION
9
+ spec.authors = ['Jan Graichen']
10
+ spec.email = ['jg@altimos.de']
11
+ spec.summary = %q(An option parsing library.)
12
+ spec.description = %q(An option parsing library. Optional.)
13
+ spec.homepage = ''
14
+ spec.license = 'LGPLv3'
15
+
16
+ spec.files = Dir['**/*'].grep(%r{^((bin|lib|test|spec|features)/|.*\.gemspec|.*LICENSE.*|.*README.*|.*CHANGELOG.*)})
17
+ spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
18
+ spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
19
+ spec.require_paths = %w(lib)
20
+
21
+ spec.add_development_dependency 'bundler', '~> 1.5'
22
+ end
@@ -0,0 +1,100 @@
1
+ # require 'spec_helper'
2
+
3
+ # describe Opt::Flag do
4
+ # shared_examples 'defined flag' do
5
+ # it 'should match short code' do
6
+ # ret = option.parse %w(-v)
7
+ # expect(ret).to eq [true]
8
+ # end
9
+
10
+ # it 'should match long code' do
11
+ # ret = option.parse %w(--verbose)
12
+ # expect(ret).to eq [true]
13
+ # end
14
+
15
+ # it 'should match negated long code' do
16
+ # ret = option.parse %w(--no-verbose)
17
+ # expect(ret).to eq [false]
18
+ # end
19
+
20
+ # it 'should not match something else' do
21
+ # ret = option.parse %w(--fuu)
22
+ # expect(ret).to be_false
23
+ # end
24
+ # end
25
+
26
+ # context 'as simple flag' do
27
+ # let(:option) { Opt::Flag.new :name, '-v, --verbose' }
28
+
29
+ # it_should_behave_like 'defined flag'
30
+ # end
31
+
32
+ # context 'with undashed flag' do
33
+ # let(:option) { Opt::Flag.new :name, 'v, verbose' }
34
+
35
+ # it_should_behave_like 'defined flag'
36
+ # end
37
+
38
+ # context 'with multiple flags' do
39
+ # let(:option) { Opt::Flag.new :name, 'v, verbose, a, ausführlich' }
40
+
41
+ # it_should_behave_like 'defined flag'
42
+
43
+ # it 'should match second short code' do
44
+ # ret = option.parse %w(-a)
45
+ # expect(ret).to eq [true]
46
+ # end
47
+
48
+ # it 'should match second long code' do
49
+ # ret = option.parse %w(--ausführlich)
50
+ # expect(ret).to eq [true]
51
+ # end
52
+
53
+ # it 'should match second negated long code' do
54
+ # ret = option.parse %w(--no-ausführlich)
55
+ # expect(ret).to eq [false]
56
+ # end
57
+ # end
58
+
59
+ # context 'with single-dash long flag' do
60
+ # let(:option) { Opt::Flag.new :name, '-with-recommended' }
61
+
62
+ # it 'should match code' do
63
+ # ret = option.parse %w(-with-recommended)
64
+ # expect(ret).to eq [true]
65
+ # end
66
+
67
+ # it 'should negated code' do
68
+ # ret = option.parse %w(-without-recommended)
69
+ # expect(ret).to eq [false]
70
+ # end
71
+ # end
72
+
73
+ # context 'with already negated flag' do
74
+ # let(:option) { Opt::Flag.new :name, '--without-recommended' }
75
+
76
+ # it 'should match code' do
77
+ # ret = option.parse %w(--with-recommended)
78
+ # expect(ret).to eq [false]
79
+ # end
80
+
81
+ # it 'should negated code' do
82
+ # ret = option.parse %w(--without-recommended)
83
+ # expect(ret).to eq [true]
84
+ # end
85
+
86
+ # context 'with default: false' do
87
+ # let(:option) { Opt::Flag.new :name, '--without-recommended', default: false }
88
+
89
+ # it 'should match code' do
90
+ # ret = option.parse %w(--with-recommended)
91
+ # expect(ret).to eq [true]
92
+ # end
93
+
94
+ # it 'should negated code' do
95
+ # ret = option.parse %w(--without-recommended)
96
+ # expect(ret).to eq [false]
97
+ # end
98
+ # end
99
+ # end
100
+ # end