hirobumi-argument_sensei 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/README.rdoc ADDED
@@ -0,0 +1,57 @@
1
+ = ArgumentSensei
2
+
3
+ ArgumentSensei is yet another command-line argument parser with a minimal set of features.
4
+
5
+ == Installation
6
+
7
+ sudo gem install hirobumi-argument_sensei -s http://gems.github.com --no-ri --no-rdoc
8
+
9
+ == Usage
10
+
11
+ require 'rubygems'
12
+ require 'argument_sensei'
13
+
14
+ commands = ArgumentSensei.define do
15
+
16
+ option 'help', :short => 'h' do
17
+ arguments 0
18
+ describe 'show this message'
19
+ action do
20
+ # commands to run when -h or --help is given.
21
+ end # action do
22
+ end # option 'help', :short => 'h' do
23
+
24
+ default 'help'
25
+
26
+ end
27
+
28
+ # ...
29
+
30
+ # in the place you want to run the codes...
31
+ commands.run!
32
+
33
+
34
+ == License
35
+
36
+ Copyright (c) 2009 Hirobumi Hama
37
+
38
+ Permission is hereby granted, free of charge, to any person
39
+ obtaining a copy of this software and associated documentation
40
+ files (the "Software"), to deal in the Software without
41
+ restriction, including without limitation the rights to use,
42
+ copy, modify, merge, publish, distribute, sublicense, and/or sell
43
+ copies of the Software, and to permit persons to whom the
44
+ Software is furnished to do so, subject to the following
45
+ conditions:
46
+
47
+ The above copyright notice and this permission notice shall be
48
+ included in all copies or substantial portions of the Software.
49
+
50
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
51
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
52
+ OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
53
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
54
+ HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
55
+ WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
56
+ FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
57
+ OTHER DEALINGS IN THE SOFTWARE.
data/Rakefile ADDED
@@ -0,0 +1,40 @@
1
+ require 'rubygems'
2
+ require 'rake/gempackagetask'
3
+ require 'rake/testtask'
4
+
5
+ require 'lib/argument_sensei/version'
6
+
7
+ task :default => :test
8
+
9
+ spec = Gem::Specification.new do |s|
10
+ s.name = 'argument_sensei'
11
+ s.version = ArgumentSensei::Version.to_s
12
+ s.has_rdoc = true
13
+ s.extra_rdoc_files = %w(README.rdoc)
14
+ s.rdoc_options = %w(--main README.rdoc)
15
+ s.summary = "ArgumentSensei is yet another command-line argument parser with a minimal set of features."
16
+ s.author = 'Hirobumi Hama'
17
+ s.email = 'hama@yoidore.org'
18
+ s.homepage = 'http://yoidore.org/argument_sensei'
19
+ s.files = %w(README.rdoc Rakefile) + Dir.glob("{lib,test}/**/*")
20
+ # s.executables = ['argument_sensei']
21
+
22
+ # s.add_dependency('gem_name', '~> 0.0.1')
23
+ end
24
+
25
+ Rake::GemPackageTask.new(spec) do |pkg|
26
+ pkg.gem_spec = spec
27
+ end
28
+
29
+ Rake::TestTask.new do |t|
30
+ t.libs << 'test'
31
+ t.test_files = FileList["test/**/*_test.rb"]
32
+ t.verbose = true
33
+ end
34
+
35
+ desc 'Generate the gemspec to serve this Gem from Github'
36
+ task :github do
37
+ file = File.dirname(__FILE__) + "/#{spec.name}.gemspec"
38
+ File.open(file, 'w') {|f| f << spec.to_ruby }
39
+ puts "Created gemspec: #{file}"
40
+ end
@@ -0,0 +1,259 @@
1
+ module ArgumentSensei
2
+
3
+ class Commands
4
+
5
+ include Enumerable
6
+
7
+ def initialize(opts = {})
8
+ @arguments = opts[:arguments] || ARGV || []
9
+ @options = opts[:options] || []
10
+ @queue = {}
11
+ @default = nil
12
+ end # def initialize(opts = {})
13
+
14
+ attr_reader :arguments, :options
15
+
16
+ def method_missing(method_name, *args, &block)
17
+ if @options.respond_to?(method_name)
18
+ @options.send(method_name, *args, &block)
19
+ else
20
+ raise(NameError.new("undefined local variable or method '#{method_name}' for both #{inspect} and its @options"))
21
+ end
22
+ end
23
+
24
+ # parses and runs actions if necessary, returns a hash of command-line options
25
+ def run!
26
+ count = 0
27
+
28
+ h = parse
29
+ keys = h.keys
30
+
31
+ opts = @options.select { |o| o[:priority] == :option && (keys.include?(o[:long]) || keys.include?(o[:short])) } || []
32
+ runs = @options.select { |o| o[:priority] == :runner && (keys.include?(o[:long]) || keys.include?(o[:short])) } || []
33
+
34
+ # TODO: Exception message can be more informative.
35
+ raise(RuntimeError.new(runs.map { |o| "--#{o[:long]}/-#{o[:short]}" }.join(' ') + ' cannot be ran at the same time')) if runs.length > 1
36
+
37
+ (opts + runs).each do |o|
38
+ next unless o[:action]
39
+ if h.keys.include?(o[:short]) || h.keys.include?(o[:long])
40
+ o[:action].call(h[o[:short] || o[:long]])
41
+ count += 1
42
+ end
43
+ end # @options.each do |o|
44
+
45
+ if count == 0 && !@default.nil? # runs default if given
46
+ definition(@default)[:action].call(nil)
47
+ end # if count == 0
48
+
49
+ end # def run!
50
+
51
+ # returns a hash of command-line options
52
+ def parse
53
+ arr = []
54
+ for_arguments = false
55
+ temp = []
56
+
57
+ # splits grouped booelean short options
58
+ @arguments.collect! do |x|
59
+ unless x =~ /^-\w{2,}/
60
+ x
61
+ else
62
+ x.gsub(/^-/, '').split(//).map do |y|
63
+ raise(ArgumentError.new("option #{y} (which takes #{definition(y)[:arguments]}) should not be combined (#{x})")) if definition(y) && definition(y)[:arguments] != 0
64
+ '-' + y
65
+ end # x.gsub(/^-/, '').split(//).map do |y|
66
+ end
67
+ end.flatten! # @arguments.collect! do |x|
68
+
69
+ @arguments.each do |x|
70
+
71
+ if for_arguments && ((argument_length(temp[0]) != :all && temp.length - 1 >= argument_length(temp[0])) || x =~ /^-+/)
72
+ arr << temp
73
+ for_arguments = false
74
+ temp = []
75
+ end # if for_arguments && temp.length - 1 >= argument_length(temp[0])
76
+
77
+ if x =~ /^-+/
78
+ unless option_defined?(x)
79
+ arr << x
80
+ else
81
+ for_arguments = true
82
+ temp << x
83
+ end
84
+ elsif for_arguments # if x =~ /^-+/
85
+ temp << x
86
+ else # elsif for_arguments if x =~ /^-+/
87
+ arr << x
88
+ end # end elsif for_arguments if x =~ /^-+/
89
+
90
+ end # each do |x|
91
+
92
+ arr << temp if temp.length > 0
93
+
94
+ hash = {}
95
+ hash[:orphans] = arr.select { |a| !a.is_a?(Array) }
96
+ (arr - hash[:orphans]).each do |a|
97
+ gsubbed = a[0].gsub(/^-+/, '')
98
+ if argument_length(gsubbed) == 0
99
+ hash[gsubbed] = true
100
+ else
101
+ hash[gsubbed] = a[1..-1]
102
+ end
103
+ end # (arr - hash[:orphans]).each do |a|
104
+
105
+ # make sure both :short and :long are filled if they are provided
106
+ each do |c|
107
+ short, long = [c[:short], c[:long]].map { |n| (n || '').gsub(/^-+/, '') }
108
+ next if short == '' || long == ''
109
+
110
+ if hash[short] || hash[long]
111
+ next if hash[short] == hash[long]
112
+ if hash[short].nil?
113
+ hash[short] = hash[long]
114
+ else
115
+ hash[long] = hash[short]
116
+ end
117
+ end
118
+
119
+ end # each do |c|
120
+
121
+ hash # returns
122
+ end # def parse
123
+
124
+ # finds a detailed option for given name
125
+ def definition(name)
126
+ n = name.gsub(/^-+/, '')
127
+ select { |h| h[:short] == n || h[:long] == n }[0] # can be nil
128
+ end
129
+
130
+ def option_defined?(name)
131
+ !!definition(name)
132
+ end # def defined?(name)
133
+
134
+ def argument_length(name)
135
+ definition(name)[:arguments]
136
+ end # def arguments(name)
137
+
138
+ def value(name)
139
+ definition(name)[:value]
140
+ end # def value(name)
141
+
142
+ def set_value(name, given_value)
143
+ hash = select { |h| h[:short] == name || h[:long] == name }[0]
144
+ raise(ArgumentError.new("option with #{name} could not be found")) if hash.nil?
145
+ @options[index(hash)] = hash.merge(:value => given_value) # [index(hash)]= gives you a syntax error
146
+ end # def set_value(name, given_value)
147
+
148
+ # shows help message to STDOUT
149
+ def help
150
+ STDERR.puts("USAGE: #{$0}")
151
+
152
+ @options.each do |o|
153
+
154
+ temp = ''
155
+
156
+ o[:short] ? temp << "-#{o[:short]}" : temp << "\t"
157
+ temp.length > 0 ? temp << ", " : temp << "\t"
158
+ o[:long] ? temp << "--#{o[:long]}" : temp << "\t"
159
+
160
+ temp << "\t\t"
161
+ temp << o[:description]
162
+
163
+ if o[:description]
164
+ temp << ' '
165
+ end
166
+
167
+ case o[:arguments]
168
+ when :all
169
+ temp << "(multiple arguments)"
170
+ when 0
171
+ temp << "(boolean)"
172
+ when 1
173
+ temp << "(1 argument)"
174
+ else
175
+ temp << "(#{o[:arguments]} arguments)"
176
+ end
177
+ puts temp
178
+
179
+ end # @options.each do |o|
180
+
181
+ nil
182
+ end # def help
183
+
184
+ def option(name, opts = {}, &block)
185
+ @queue = {
186
+ :short => opts[:short], # without hyphens
187
+ :long => opts[:long], # this too
188
+ :description => '',
189
+ :arguments => :all, # Integer, :all
190
+ :action => nil, # a Proc object, rarity should be :arguments, use *args if :all.
191
+ :priority => :option # priority, :option first then :runner. only one :runner should be called at a time.
192
+ } # defaults
193
+
194
+ if name.length > 1
195
+ @queue[:long] = name
196
+ else
197
+ @queue[:short] = name
198
+ end
199
+
200
+ instance_eval(&block) if block_given?
201
+
202
+ flush!
203
+ end
204
+
205
+ private
206
+
207
+ # flushes @queue into @commands array, used to implement DSL.
208
+ def flush!
209
+ return nil if @queue.empty?
210
+
211
+ self << @queue
212
+ @queue = {}
213
+ self[-1]
214
+ end
215
+
216
+ def describe(desc = '', opts = {})
217
+ @queue[:description] = desc
218
+ end
219
+
220
+ # Integer or :all (= default), defaults are set in #option method
221
+ def arguments(n, opts = {})
222
+ @queue[:arguments] = n
223
+ end
224
+
225
+ def action(opts = {}, &block)
226
+ @queue[:action] = block
227
+ end
228
+
229
+ # sets priority for the option, default is :option.
230
+ def priority(type)
231
+ raise(ArgumentError.new("priority must be :option or :runner but :#{type} given.")) unless [:option, :runner].include?(type)
232
+ @queue[:priority] = type
233
+ end # def priority(type)
234
+
235
+ def default(name)
236
+ @default = name
237
+ end
238
+
239
+ class << self
240
+
241
+ # Commands.define do
242
+ # option 'run', :short => 'S' do
243
+ # action do ||
244
+ # end
245
+ # end
246
+ def define(opts = {}, &block)
247
+ raise(ArgumentError.new('no block given')) unless block_given?
248
+
249
+ c = Commands.new(opts)
250
+ c.instance_eval(&block)
251
+
252
+ c
253
+ end
254
+
255
+ end
256
+
257
+ end # class Commands
258
+
259
+ end # module ArgumentSensei
@@ -0,0 +1,13 @@
1
+ module ArgumentSensei
2
+ module Version
3
+
4
+ MAJOR = 0
5
+ MINOR = 1
6
+ TINY = 0
7
+
8
+ def self.to_s # :nodoc:
9
+ [MAJOR, MINOR, TINY].join('.')
10
+ end
11
+
12
+ end
13
+ end
@@ -0,0 +1,7 @@
1
+ $:.unshift File.dirname(__FILE__)
2
+
3
+ module ArgumentSensei
4
+ end # module ArgumentSensei
5
+
6
+ require 'argument_sensei/version'
7
+ require 'argument_sensei/commands'
@@ -0,0 +1,9 @@
1
+ # http://sneaq.net/textmate-wtf
2
+ $:.reject! { |e| e.include? 'TextMate' }
3
+
4
+ require 'rubygems'
5
+ require 'test/unit'
6
+ gem 'thoughtbot-shoulda', '>= 2.10.1'
7
+ require 'shoulda'
8
+
9
+ require File.dirname(__FILE__) + '/../lib/argument_sensei'
@@ -0,0 +1,202 @@
1
+ require File.dirname(__FILE__) + '/../test_helper'
2
+
3
+ class ArgumentSenseiTest < Test::Unit::TestCase
4
+
5
+ Commands = ArgumentSensei::Commands
6
+
7
+ context 'Commands' do
8
+
9
+ should 'parses accordingly with defined options' do
10
+
11
+ c = Commands.define(:arguments => %w(orphan1 -S short1 short2 orphan2 --long1 long1 long2 long3 --long2 long4 long5 orphan3 --boolean)) do
12
+ option 'S' do
13
+ arguments 2
14
+ end
15
+ option 'long1'
16
+ option 'long2', :short => 'L' do
17
+ arguments 2
18
+ describe 'takes long4 and long5 then leaves orphan3'
19
+ end
20
+ option 'boolean' do
21
+ arguments 0
22
+ end
23
+ end # c = Commands.define do
24
+
25
+ assert_equal({
26
+ :orphans => %w(orphan1 orphan2 orphan3),
27
+ 'S' => %w(short1 short2),
28
+ 'long1' => %w(long1 long2 long3),
29
+ 'long2' => %w(long4 long5),
30
+ 'L' => %w(long4 long5),
31
+ 'boolean' => true
32
+ }, c.parse)
33
+
34
+ c = Commands.define(:arguments => %w(--what if there --are nothing defined)) do
35
+ end
36
+
37
+ assert_nothing_raised { c.parse.inspect }
38
+
39
+ end
40
+
41
+ should 'parses combined short boolean switches' do
42
+ # -abcDEF => -a -b -c -D -E -F, as long as they are boolean.
43
+ # if non-booleans included, returns error,
44
+ # unknowns will just be ignored.
45
+ c = Commands.define(:arguments => %w(-aBC -A forA)) do
46
+ option 'a' do
47
+ arguments 0
48
+ end
49
+ option 'B' do
50
+ arguments 0
51
+ end
52
+ option 'C' do
53
+ arguments 0
54
+ end
55
+ option 'A' do
56
+ arguments 1
57
+ end
58
+ end.parse # c = Commands.define(:arguments => %w()) do
59
+ assert(c['a'])
60
+ assert(c['B'])
61
+ assert(c['C'])
62
+ assert_equal(['forA'], c['A'])
63
+
64
+ # non boolean should not be combined.
65
+ assert_raise(ArgumentError) do
66
+ c = Commands.define(:arguments => %w(-aB)) do
67
+ option 'a' do
68
+ arguments 0
69
+ end
70
+ option 'B' do
71
+ arguments :all
72
+ end
73
+ end.parse
74
+ end
75
+ end
76
+
77
+ should 'runs given actions' do
78
+ done = false
79
+ should_not_do = true
80
+ Commands.define(:arguments => %w(--do)) do
81
+ option 'do' do
82
+ action do
83
+ done = true
84
+ end
85
+ end
86
+ option 'do not' do
87
+ action do
88
+ should_not_do = false
89
+ end
90
+ end
91
+ end.run! # c = Commands.define do
92
+ assert(done, 'action were not called')
93
+ assert(should_not_do, 'even other action got run')
94
+ end
95
+
96
+ should 'runs default action' do
97
+ done = false
98
+ Commands.define do
99
+ option 'D' do
100
+ action do
101
+ done = true
102
+ end
103
+ end
104
+ default 'D'
105
+ end.run!
106
+ assert(done, 'default action was not called')
107
+ end
108
+
109
+ should 'should not raise any error for undefined options' do
110
+ assert_nothing_raised(Exception) do
111
+ Commands.define(:arguments => %w(-these -commands --are not defined --yet)) do
112
+ option 't' do
113
+ arguments 0
114
+ end
115
+ option 'o' do
116
+ arguments 0
117
+ end
118
+ end
119
+ end
120
+ end
121
+
122
+ context 'Prioritization' do
123
+
124
+ should 'should be defined as option' do
125
+ assert_equal(:option, Commands.define { option('-t') { priority :option } }.options[0][:priority])
126
+ end
127
+
128
+ should 'should be defined as runner' do
129
+ assert_equal(:runner, Commands.define { option('-t') { priority :runner } }.options[0][:priority])
130
+ end
131
+
132
+ should 'runs options first then a runner' do
133
+ first = false; last = false
134
+ was_first_last = false
135
+ was_last_first = false
136
+
137
+ assert_nothing_raised(Exception) do
138
+ Commands.define(:arguments => %w(-l -f)) do
139
+ option 'l' do
140
+ priority :runner
141
+ action do
142
+
143
+ last = true
144
+ was_last_first = true unless first
145
+
146
+ end
147
+ end
148
+ option 'f' do
149
+ priority :option
150
+ action do
151
+
152
+ first = true
153
+ was_first_last = true if last
154
+
155
+ end
156
+ end
157
+ end.run!
158
+ end
159
+
160
+ assert(!was_last_first, 'Runner ran first.')
161
+ assert(!was_first_last, 'Option ran last.')
162
+
163
+ end
164
+
165
+ should 'should not accept multiple runners at a time' do
166
+ shouldnotrun1 = false
167
+ shouldnotrun2 = false
168
+ assert_raise(RuntimeError) do
169
+ Commands.define(:arguments => %w(-a -b)) do
170
+ option 'a' do
171
+ priority :runner
172
+ action do
173
+ shouldnotrun1 = true
174
+ end
175
+ end
176
+ option 'b' do
177
+ priority :runner
178
+ action do
179
+ shouldnotrun2 = true
180
+ end
181
+ end
182
+ end.run!
183
+ end
184
+ assert(!shouldnotrun1)
185
+ assert(!shouldnotrun2)
186
+ end
187
+
188
+ end # context 'Priority' do
189
+
190
+ end # context 'Commands' do
191
+
192
+ context 'ArgumentSensei' do
193
+
194
+ context 'Class' do
195
+ end # Class
196
+
197
+ context 'Instance' do
198
+ end # Instance
199
+
200
+ end # ArgumentSensei
201
+
202
+ end # class ArgumentSenseiTest < Test::Unit::TestCase
metadata ADDED
@@ -0,0 +1,62 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: hirobumi-argument_sensei
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - Hirobumi Hama
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+
12
+ date: 2009-04-10 00:00:00 -07:00
13
+ default_executable:
14
+ dependencies: []
15
+
16
+ description:
17
+ email: hama@yoidore.org
18
+ executables: []
19
+
20
+ extensions: []
21
+
22
+ extra_rdoc_files:
23
+ - README.rdoc
24
+ files:
25
+ - README.rdoc
26
+ - Rakefile
27
+ - lib/argument_sensei
28
+ - lib/argument_sensei/commands.rb
29
+ - lib/argument_sensei/version.rb
30
+ - lib/argument_sensei.rb
31
+ - test/test_helper.rb
32
+ - test/unit
33
+ - test/unit/argument_sensei_test.rb
34
+ has_rdoc: true
35
+ homepage: http://yoidore.org/argument_sensei
36
+ post_install_message:
37
+ rdoc_options:
38
+ - --main
39
+ - README.rdoc
40
+ require_paths:
41
+ - lib
42
+ required_ruby_version: !ruby/object:Gem::Requirement
43
+ requirements:
44
+ - - ">="
45
+ - !ruby/object:Gem::Version
46
+ version: "0"
47
+ version:
48
+ required_rubygems_version: !ruby/object:Gem::Requirement
49
+ requirements:
50
+ - - ">="
51
+ - !ruby/object:Gem::Version
52
+ version: "0"
53
+ version:
54
+ requirements: []
55
+
56
+ rubyforge_project:
57
+ rubygems_version: 1.2.0
58
+ signing_key:
59
+ specification_version: 2
60
+ summary: ArgumentSensei is yet another command-line argument parser with a minimal set of features.
61
+ test_files: []
62
+