cli 0.0.2

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,5 @@
1
+ lib/**/*.rb
2
+ bin/*
3
+ -
4
+ features/**/*.feature
5
+ LICENSE.txt
data/.rspec ADDED
@@ -0,0 +1 @@
1
+ --color
data/Gemfile ADDED
@@ -0,0 +1,13 @@
1
+ source "http://rubygems.org"
2
+
3
+ # Add dependencies to develop your gem here.
4
+ # Include everything needed to run rake, tests, features, etc.
5
+ group :development do
6
+ gem "rspec", "~> 2.3.0"
7
+ gem "cucumber", ">= 0"
8
+ gem "bundler", "~> 1.0.0"
9
+ gem "jeweler", "~> 1.6.4"
10
+ gem "rcov", ">= 0"
11
+ gem "rdoc", "~> 3.9"
12
+ gem "ruby-ip", "~> 0.9"
13
+ end
@@ -0,0 +1,45 @@
1
+ GEM
2
+ remote: http://rubygems.org/
3
+ specs:
4
+ builder (3.0.0)
5
+ cucumber (1.1.3)
6
+ builder (>= 2.1.2)
7
+ diff-lcs (>= 1.1.2)
8
+ gherkin (~> 2.6.7)
9
+ json (>= 1.4.6)
10
+ term-ansicolor (>= 1.0.6)
11
+ diff-lcs (1.1.3)
12
+ gherkin (2.6.8)
13
+ json (>= 1.4.6)
14
+ git (1.2.5)
15
+ jeweler (1.6.4)
16
+ bundler (~> 1.0)
17
+ git (>= 1.2.5)
18
+ rake
19
+ json (1.6.2)
20
+ rake (0.9.2.2)
21
+ rcov (0.9.11)
22
+ rdoc (3.11)
23
+ json (~> 1.4)
24
+ rspec (2.3.0)
25
+ rspec-core (~> 2.3.0)
26
+ rspec-expectations (~> 2.3.0)
27
+ rspec-mocks (~> 2.3.0)
28
+ rspec-core (2.3.1)
29
+ rspec-expectations (2.3.0)
30
+ diff-lcs (~> 1.1.2)
31
+ rspec-mocks (2.3.0)
32
+ ruby-ip (0.9.0)
33
+ term-ansicolor (1.0.7)
34
+
35
+ PLATFORMS
36
+ ruby
37
+
38
+ DEPENDENCIES
39
+ bundler (~> 1.0.0)
40
+ cucumber
41
+ jeweler (~> 1.6.4)
42
+ rcov
43
+ rdoc (~> 3.9)
44
+ rspec (~> 2.3.0)
45
+ ruby-ip (~> 0.9)
@@ -0,0 +1,20 @@
1
+ Copyright (c) 2011 Jakub Pastuszek
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining
4
+ a copy of this software and associated documentation files (the
5
+ "Software"), to deal in the Software without restriction, including
6
+ without limitation the rights to use, copy, modify, merge, publish,
7
+ distribute, sublicense, and/or sell copies of the Software, and to
8
+ permit persons to whom the Software is furnished to do so, subject to
9
+ the following conditions:
10
+
11
+ The above copyright notice and this permission notice shall be
12
+ included in all copies or substantial portions of the Software.
13
+
14
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
@@ -0,0 +1,19 @@
1
+ = cli
2
+
3
+ Description goes here.
4
+
5
+ == Contributing to cli
6
+
7
+ * Check out the latest master to make sure the feature hasn't been implemented or the bug hasn't been fixed yet
8
+ * Check out the issue tracker to make sure someone already hasn't requested it and/or contributed it
9
+ * Fork the project
10
+ * Start a feature/bugfix branch
11
+ * Commit and push until you are happy with your contribution
12
+ * Make sure to add tests for it. This is important so I don't break it in a future version unintentionally.
13
+ * Please try not to mess with the Rakefile, version, or history. If you want to have your own version, or is otherwise necessary, that is fine, but please isolate to its own commit so I can cherry-pick around it.
14
+
15
+ == Copyright
16
+
17
+ Copyright (c) 2011 Jakub Pastuszek. See LICENSE.txt for
18
+ further details.
19
+
@@ -0,0 +1,52 @@
1
+ # encoding: utf-8
2
+
3
+ require 'rubygems'
4
+ require 'bundler'
5
+ begin
6
+ Bundler.setup(:default, :development)
7
+ rescue Bundler::BundlerError => e
8
+ $stderr.puts e.message
9
+ $stderr.puts "Run `bundle install` to install missing gems"
10
+ exit e.status_code
11
+ end
12
+ require 'rake'
13
+
14
+ require 'jeweler'
15
+ Jeweler::Tasks.new do |gem|
16
+ # gem is a Gem::Specification... see http://docs.rubygems.org/read/chapter/20 for more options
17
+ gem.name = "cli"
18
+ gem.homepage = "http://github.com/jpastuszek/cli"
19
+ gem.license = "MIT"
20
+ gem.summary = %Q{Helps writing Command-line interface program}
21
+ gem.description = %Q{Provides DSL for command-line options, switches and arguments parser and stdin handling with generated usage printer}
22
+ gem.email = "jpastuszek@gmail.com"
23
+ gem.authors = ["Jakub Pastuszek"]
24
+ # dependencies defined in Gemfile
25
+ end
26
+ Jeweler::RubygemsDotOrgTasks.new
27
+
28
+ require 'rspec/core'
29
+ require 'rspec/core/rake_task'
30
+ RSpec::Core::RakeTask.new(:spec) do |spec|
31
+ spec.pattern = FileList['spec/**/*_spec.rb']
32
+ end
33
+
34
+ RSpec::Core::RakeTask.new(:rcov) do |spec|
35
+ spec.pattern = 'spec/**/*_spec.rb'
36
+ spec.rcov = true
37
+ end
38
+
39
+ require 'cucumber/rake/task'
40
+ Cucumber::Rake::Task.new(:features)
41
+
42
+ task :default => :spec
43
+
44
+ require 'rdoc/task'
45
+ RDoc::Task.new do |rdoc|
46
+ version = File.exist?('VERSION') ? File.read('VERSION') : ""
47
+
48
+ rdoc.rdoc_dir = 'rdoc'
49
+ rdoc.title = "cli #{version}"
50
+ rdoc.rdoc_files.include('README*')
51
+ rdoc.rdoc_files.include('lib/**/*.rb')
52
+ end
data/VERSION ADDED
@@ -0,0 +1 @@
1
+ 0.0.2
@@ -0,0 +1,72 @@
1
+ # Generated by jeweler
2
+ # DO NOT EDIT THIS FILE DIRECTLY
3
+ # Instead, edit Jeweler::Tasks in Rakefile, and run 'rake gemspec'
4
+ # -*- encoding: utf-8 -*-
5
+
6
+ Gem::Specification.new do |s|
7
+ s.name = "cli"
8
+ s.version = "0.0.2"
9
+
10
+ s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
11
+ s.authors = ["Jakub Pastuszek"]
12
+ s.date = "2011-12-01"
13
+ s.description = "Provides DSL for command-line options, switches and arguments parser and stdin handling with generated usage printer"
14
+ s.email = "jpastuszek@gmail.com"
15
+ s.extra_rdoc_files = [
16
+ "LICENSE.txt",
17
+ "README.rdoc"
18
+ ]
19
+ s.files = [
20
+ ".document",
21
+ ".rspec",
22
+ "Gemfile",
23
+ "Gemfile.lock",
24
+ "LICENSE.txt",
25
+ "README.rdoc",
26
+ "Rakefile",
27
+ "VERSION",
28
+ "cli.gemspec",
29
+ "features/cli.feature",
30
+ "features/step_definitions/cli_steps.rb",
31
+ "features/support/env.rb",
32
+ "lib/cli.rb",
33
+ "spec/cli_spec.rb",
34
+ "spec/spec_helper.rb"
35
+ ]
36
+ s.homepage = "http://github.com/jpastuszek/cli"
37
+ s.licenses = ["MIT"]
38
+ s.require_paths = ["lib"]
39
+ s.rubygems_version = "1.8.10"
40
+ s.summary = "Helps writing Command-line interface program"
41
+
42
+ if s.respond_to? :specification_version then
43
+ s.specification_version = 3
44
+
45
+ if Gem::Version.new(Gem::VERSION) >= Gem::Version.new('1.2.0') then
46
+ s.add_development_dependency(%q<rspec>, ["~> 2.3.0"])
47
+ s.add_development_dependency(%q<cucumber>, [">= 0"])
48
+ s.add_development_dependency(%q<bundler>, ["~> 1.0.0"])
49
+ s.add_development_dependency(%q<jeweler>, ["~> 1.6.4"])
50
+ s.add_development_dependency(%q<rcov>, [">= 0"])
51
+ s.add_development_dependency(%q<rdoc>, ["~> 3.9"])
52
+ s.add_development_dependency(%q<ruby-ip>, ["~> 0.9"])
53
+ else
54
+ s.add_dependency(%q<rspec>, ["~> 2.3.0"])
55
+ s.add_dependency(%q<cucumber>, [">= 0"])
56
+ s.add_dependency(%q<bundler>, ["~> 1.0.0"])
57
+ s.add_dependency(%q<jeweler>, ["~> 1.6.4"])
58
+ s.add_dependency(%q<rcov>, [">= 0"])
59
+ s.add_dependency(%q<rdoc>, ["~> 3.9"])
60
+ s.add_dependency(%q<ruby-ip>, ["~> 0.9"])
61
+ end
62
+ else
63
+ s.add_dependency(%q<rspec>, ["~> 2.3.0"])
64
+ s.add_dependency(%q<cucumber>, [">= 0"])
65
+ s.add_dependency(%q<bundler>, ["~> 1.0.0"])
66
+ s.add_dependency(%q<jeweler>, ["~> 1.6.4"])
67
+ s.add_dependency(%q<rcov>, [">= 0"])
68
+ s.add_dependency(%q<rdoc>, ["~> 3.9"])
69
+ s.add_dependency(%q<ruby-ip>, ["~> 0.9"])
70
+ end
71
+ end
72
+
@@ -0,0 +1,9 @@
1
+ Feature: something something
2
+ In order to something something
3
+ A user something something
4
+ something something something
5
+
6
+ Scenario: something something
7
+ Given inspiration
8
+ When I create a sweet new gem
9
+ Then everyone should see how awesome I am
File without changes
@@ -0,0 +1,13 @@
1
+ require 'bundler'
2
+ begin
3
+ Bundler.setup(:default, :development)
4
+ rescue Bundler::BundlerError => e
5
+ $stderr.puts e.message
6
+ $stderr.puts "Run `bundle install` to install missing gems"
7
+ exit e.status_code
8
+ end
9
+
10
+ $LOAD_PATH.unshift(File.dirname(__FILE__) + '/../../lib')
11
+ require 'cli'
12
+
13
+ require 'rspec/expectations'
@@ -0,0 +1,266 @@
1
+ require 'ostruct'
2
+ require 'stringio'
3
+ require 'yaml'
4
+
5
+ class CLI
6
+ class ParsingError < ArgumentError
7
+ end
8
+
9
+ class Parsed < OpenStruct
10
+ def set(argument, value)
11
+ send((argument.name.to_s + '=').to_sym, argument.cast(value))
12
+ end
13
+ end
14
+
15
+ module Options
16
+ module Cast
17
+ def cast(value)
18
+ begin
19
+ cast_class = @options[:cast]
20
+ if cast_class == nil
21
+ value
22
+ elsif cast_class == Integer
23
+ value.to_i
24
+ elsif cast_class == Float
25
+ value.to_f
26
+ elsif cast_class == YAML
27
+ YAML.load(value)
28
+ else
29
+ cast_class.new(value)
30
+ end
31
+ rescue => e
32
+ raise ParsingError, "failed to cast: #{@name} to type: #{@options[:cast].name}: #{e}"
33
+ end
34
+ end
35
+ end
36
+
37
+ module Description
38
+ def description?
39
+ @options.member? :description
40
+ end
41
+
42
+ def description
43
+ @options[:description]
44
+ end
45
+ end
46
+ end
47
+
48
+ class STDINHandling
49
+ def initialize(name, options = {})
50
+ @name = name
51
+ @options = options
52
+ end
53
+
54
+ include Options::Cast
55
+ include Options::Description
56
+
57
+ def to_s
58
+ (@name or @options[:cast] or 'data').to_s.tr('_', '-')
59
+ end
60
+ end
61
+
62
+ class Argument
63
+ def initialize(name, options = {})
64
+ @name = name
65
+ @options = {
66
+ :cast => String
67
+ }.merge(options)
68
+ end
69
+
70
+ attr_reader :name
71
+
72
+ include Options::Cast
73
+ include Options::Description
74
+
75
+ def default
76
+ @options[:default]
77
+ end
78
+
79
+ def has_default?
80
+ @options.member? :default
81
+ end
82
+
83
+ def optional?
84
+ has_default?
85
+ end
86
+
87
+ def to_s
88
+ name.to_s.tr('_', '-')
89
+ end
90
+ end
91
+
92
+ class Option < Argument
93
+ def optional?
94
+ has_default? or not @options[:required]
95
+ end
96
+
97
+ def has_short?
98
+ @options.member? :short
99
+ end
100
+
101
+ def short
102
+ @options[:short]
103
+ end
104
+
105
+ def switch
106
+ '--' + name.to_s.tr('_', '-')
107
+ end
108
+
109
+ def switch_short
110
+ '-' + short.to_s
111
+ end
112
+
113
+ def to_s
114
+ switch
115
+ end
116
+ end
117
+
118
+ def initialize(&block)
119
+ #TODO: optoins should be in own class?
120
+ @options = []
121
+ @optoins_long = {}
122
+ @optoins_short = {}
123
+ @options_default = []
124
+ @options_required = []
125
+ @arguments = []
126
+ instance_eval(&block) if block_given?
127
+ end
128
+
129
+ def description(desc)
130
+ @description = desc
131
+ end
132
+
133
+ def stdin(name = nil, options = {})
134
+ @stdin_handling = STDINHandling.new(name, options)
135
+ end
136
+
137
+ def argument(name, options = {})
138
+ raise ArgumentError, "expected argument options of type Hash, got: #{options.class.name}" unless options.is_a? Hash
139
+ @arguments << Argument.new(name, options)
140
+ end
141
+
142
+ def option(name, options = {})
143
+ o = Option.new(name, options)
144
+ @options << o
145
+ @optoins_long[name] = o
146
+ @optoins_short[o.short] = o if o.has_short?
147
+ @options_default << o if o.has_default?
148
+ @options_required << o unless o.optional?
149
+ end
150
+
151
+ def parse(_argv = ARGV, stdin = STDIN, stderr = STDERR)
152
+ parsed = Parsed.new
153
+
154
+ argv = _argv.dup
155
+
156
+ # check help
157
+ if argv.include? '-h' or argv.include? '--help'
158
+ parsed.help = usage
159
+ return parsed
160
+ end
161
+
162
+ # set defaults
163
+ @options_default.each do |o|
164
+ parsed.set(o, o.default)
165
+ end
166
+
167
+ # process switches
168
+ options_required = @options_required.dup
169
+ while argv.first =~ /^-/
170
+ switch = argv.shift
171
+ option = if switch =~ /^--/
172
+ @optoins_long[switch.sub(/^--/, '').tr('-', '_').to_sym]
173
+ else
174
+ @optoins_short[switch.sub(/^-/, '').tr('-', '_').to_sym]
175
+ end
176
+
177
+ if option
178
+ value = argv.shift or raise ParsingError, "missing option argument: #{option}"
179
+ parsed.set(option, value)
180
+ options_required.delete(option)
181
+ else
182
+ raise ParsingError, "unknonw switch: #{switch}"
183
+ end
184
+ end
185
+
186
+ # check required
187
+ raise ParsingError, "following options are required but were not specified: #{options_required.map{|o| o.switch}.join(', ')}" unless options_required.empty?
188
+
189
+ # process arguments
190
+ arguments = @arguments.dup
191
+ while argument = arguments.shift
192
+ value = if argv.length < arguments.length + 1 and argument.optional?
193
+ argument.default # not enough arguments, try to skip optional if possible
194
+ else
195
+ argv.shift or raise ParsingError, "missing argument: #{argument}"
196
+ end
197
+
198
+ parsed.set(argument, value)
199
+ end
200
+
201
+ # process stdin
202
+ parsed.stdin = @stdin_handling.cast(stdin) if @stdin_handling
203
+
204
+ parsed
205
+ end
206
+
207
+ def parse!(argv = ARGV, stdin = STDIN, stderr = STDERR, stdout = STDOUT)
208
+ begin
209
+ pp = parse(argv, stdin, stderr)
210
+ if pp.help
211
+ stdout.write pp.help
212
+ exit 0
213
+ end
214
+ pp
215
+ rescue ParsingError => pe
216
+ usage!(pe, stderr)
217
+ end
218
+ end
219
+
220
+ def usage(msg = nil)
221
+ out = StringIO.new
222
+ out.puts msg if msg
223
+ out.print "Usage: #{File.basename $0}"
224
+ out.print ' [options]' unless @optoins_long.empty?
225
+ out.print ' ' + @arguments.map{|a| a.to_s}.join(' ') unless @arguments.empty?
226
+ out.print " < #{@stdin_handling}" if @stdin_handling
227
+
228
+ out.puts
229
+ out.puts @description if @description
230
+
231
+ if @stdin_handling and @stdin_handling.description?
232
+ out.puts "Input:"
233
+ out.puts " #{@stdin_handling} - #{@stdin_handling.description}"
234
+ end
235
+
236
+ unless @optoins_long.empty?
237
+ out.puts "Options:"
238
+ @options.each do |o|
239
+ out.print ' '
240
+ out.print o.switch
241
+ out.print " (#{o.switch_short})" if o.has_short?
242
+ out.print " [%s]" % o.default if o.has_default?
243
+ out.print " - #{o.description}" if o.description?
244
+ out.puts
245
+ end
246
+ end
247
+
248
+ described_arguments = @arguments.select{|a| a.description?}
249
+ unless described_arguments.empty?
250
+ out.puts "Arguments:"
251
+ described_arguments.each do |a|
252
+ out.puts " #{a} - #{a.description}"
253
+ end
254
+ end
255
+
256
+ out.rewind
257
+ out.read
258
+ end
259
+
260
+ def usage!(msg = nil, io = STDERR)
261
+ msg = "Error: #{msg}" if msg
262
+ io.write usage(msg)
263
+ exit 42
264
+ end
265
+ end
266
+
@@ -0,0 +1,403 @@
1
+ require File.expand_path(File.dirname(__FILE__) + '/spec_helper')
2
+ require 'cli'
3
+
4
+ def stdin_write(data)
5
+ r, w = IO.pipe
6
+ old_stdin = STDIN.reopen r
7
+ Thread.new do
8
+ w.write data
9
+ w.close
10
+ end
11
+ begin
12
+ yield
13
+ ensure
14
+ STDIN.reopen old_stdin
15
+ end
16
+ end
17
+
18
+ describe CLI do
19
+ describe 'STDIN handling' do
20
+ before :all do
21
+ @yaml = <<EOF
22
+ ---
23
+ :parser:
24
+ :successes: 41
25
+ :failures: 0
26
+ EOF
27
+ end
28
+
29
+ it "should be nil if not specified" do
30
+ ps = CLI.new.parse
31
+ ps.stdin.should be_nil
32
+ end
33
+
34
+ it "should return IO if stdin is defined" do
35
+ ps = CLI.new do
36
+ stdin
37
+ end.parse
38
+ ps.stdin.should be_a IO
39
+ end
40
+
41
+ it "should return YAML document if stdin is casted to YAML" do
42
+ ps = nil
43
+ ss = CLI.new do
44
+ stdin :log_data, :cast => YAML, :description => 'log statistic data in YAML format'
45
+ end
46
+
47
+ stdin_write(@yaml) do
48
+ ps = ss.parse
49
+ end
50
+
51
+ ps.stdin.should == {:parser=>{:successes=>41, :failures=>0}}
52
+ end
53
+ end
54
+
55
+ describe 'argument handling' do
56
+ it "should handle single argument" do
57
+ ps = CLI.new do
58
+ argument :log
59
+ end.parse(['/tmp'])
60
+ ps.log.should be_a String
61
+ ps.log.should == '/tmp'
62
+ end
63
+
64
+ it "non empty, non optional with class casting" do
65
+ ps = CLI.new do
66
+ argument :log, :cast => Pathname
67
+ end.parse(['/tmp'])
68
+ ps.log.should be_a Pathname
69
+ ps.log.to_s.should == '/tmp'
70
+ end
71
+
72
+ it "non empty, non optional with builtin class casting" do
73
+ ps = CLI.new do
74
+ argument :number, :cast => Integer
75
+ end.parse(['123'])
76
+ ps.number.should be_a Integer
77
+ ps.number.should == 123
78
+
79
+ ps = CLI.new do
80
+ argument :number, :cast => Float
81
+ end.parse(['123'])
82
+ ps.number.should be_a Float
83
+ ps.number.should == 123.0
84
+ end
85
+
86
+ it "should handle multiple arguments" do
87
+ ps = CLI.new do
88
+ argument :log, :cast => Pathname
89
+ argument :test
90
+ end.parse(['/tmp', 'hello'])
91
+ ps.log.should be_a Pathname
92
+ ps.log.to_s.should == '/tmp'
93
+ ps.test.should be_a String
94
+ ps.test.should == 'hello'
95
+ end
96
+
97
+ it "should raise error if not given" do
98
+ lambda {
99
+ ps = CLI.new do
100
+ argument :log
101
+ end.parse([])
102
+ }.should raise_error CLI::ParsingError
103
+ end
104
+
105
+ it "should raise error if casting fail" do
106
+ require 'ip'
107
+ lambda {
108
+ ps = CLI.new do
109
+ argument :log, :cast => IP
110
+ end.parse(['abc'])
111
+ }.should raise_error CLI::ParsingError, 'failed to cast: log to type: IP: invalid address'
112
+ end
113
+
114
+ describe "with defaults" do
115
+ it "should use default first argument" do
116
+ ps = CLI.new do
117
+ argument :log, :cast => Pathname, :default => '/tmp'
118
+ argument :test
119
+ end.parse(['hello'])
120
+ ps.log.should be_a Pathname
121
+ ps.log.to_s.should == '/tmp'
122
+ ps.test.should be_a String
123
+ ps.test.should == 'hello'
124
+ end
125
+
126
+ it "should use default second argument" do
127
+ ps = CLI.new do
128
+ argument :log, :cast => Pathname
129
+ argument :test, :default => 'hello'
130
+ end.parse(['/tmp'])
131
+ ps.log.should be_a Pathname
132
+ ps.log.to_s.should == '/tmp'
133
+ ps.test.should be_a String
134
+ ps.test.should == 'hello'
135
+ end
136
+
137
+ it "should use default second argument" do
138
+ ps = CLI.new do
139
+ argument :log, :cast => Pathname
140
+ argument :magick, :default => 'word'
141
+ argument :test
142
+ argument :code, :cast => Integer, :default => '123'
143
+ end.parse(['/tmp', 'hello'])
144
+ ps.log.to_s.should == '/tmp'
145
+ ps.magick.should == 'word'
146
+ ps.test.should == 'hello'
147
+ ps.code.should == 123
148
+ end
149
+ end
150
+ end
151
+
152
+ describe 'option handling' do
153
+ it "should handle long option names" do
154
+ ps = CLI.new do
155
+ option :location
156
+ end.parse(['--location', 'singapore'])
157
+ ps.location.should be_a String
158
+ ps.location.should == 'singapore'
159
+ end
160
+
161
+ it "should handle short option names" do
162
+ ps = CLI.new do
163
+ option :location, :short => :l
164
+ end.parse(['-l', 'singapore'])
165
+ ps.location.should be_a String
166
+ ps.location.should == 'singapore'
167
+ end
168
+
169
+ it "should handle default values" do
170
+ ps = CLI.new do
171
+ option :location, :default => 'singapore'
172
+ option :size, :cast => Integer, :default => 23
173
+ end.parse([])
174
+ ps.location.should be_a String
175
+ ps.location.should == 'singapore'
176
+ ps.size.should be_a Integer
177
+ ps.size.should == 23
178
+ end
179
+
180
+ it "should support casting" do
181
+ ps = CLI.new do
182
+ option :size, :cast => Integer
183
+ end.parse(['--size', '24'])
184
+ ps.size.should be_a Integer
185
+ ps.size.should == 24
186
+ end
187
+
188
+ it "not given and not defined options should be nil" do
189
+ ps = CLI.new do
190
+ option :size, :cast => Integer
191
+ end.parse([])
192
+ ps.size.should be_nil
193
+ ps.gold.should be_nil
194
+ end
195
+
196
+ it "should handle multiple long and short intermixed options" do
197
+ ps = CLI.new do
198
+ option :location, :short => :l
199
+ option :group, :default => 'red'
200
+ option :power_up, :short => :p
201
+ option :speed, :short => :s, :cast => Integer
202
+ option :not_given
203
+ option :size
204
+ end.parse(['-l', 'singapore', '--power-up', 'yes', '-s', '24', '--size', 'XXXL'])
205
+ ps.group.should == 'red'
206
+ ps.power_up.should == 'yes'
207
+ ps.speed.should == 24
208
+ ps.not_given.should be_nil
209
+ ps.size.should == 'XXXL'
210
+ ps.gold.should be_nil
211
+ end
212
+
213
+ it "should raise error on unrecognized switch" do
214
+ ps = CLI.new do
215
+ option :location
216
+ end
217
+
218
+ lambda {
219
+ ps.parse(['--xxx', 'singapore'])
220
+ }.should raise_error CLI::ParsingError
221
+ end
222
+
223
+ it "should raise error on missing option argument" do
224
+ ps = CLI.new do
225
+ option :location
226
+ end
227
+
228
+ lambda {
229
+ ps.parse(['--location'])
230
+ }.should raise_error CLI::ParsingError
231
+ end
232
+
233
+ it "should raise error on missing required option" do
234
+ ps = CLI.new do
235
+ option :location
236
+ option :size, :required => true
237
+ option :group, :default => 'red'
238
+ option :speed, :short => :s, :cast => Integer
239
+ end
240
+
241
+ lambda {
242
+ ps.parse(['--location', 'singapore'])
243
+ }.should raise_error CLI::ParsingError, "following options are required but were not specified: --size"
244
+ end
245
+ end
246
+
247
+ it "should handle options and then arguments" do
248
+ ps = CLI.new do
249
+ option :location, :short => :l
250
+ option :group, :default => 'red'
251
+ option :power_up, :short => :p
252
+ option :speed, :short => :s, :cast => Integer
253
+ option :size
254
+
255
+ argument :log, :cast => Pathname
256
+ argument :magick, :default => 'word'
257
+ argument :test
258
+ argument :code, :cast => Integer, :default => '123'
259
+ end.parse(['-l', 'singapore', '--power-up', 'yes', '-s', '24', '--size', 'XXXL', '/tmp', 'hello'])
260
+
261
+ ps.group.should == 'red'
262
+ ps.power_up.should == 'yes'
263
+ ps.speed.should == 24
264
+ ps.size.should == 'XXXL'
265
+
266
+ ps.log.to_s.should == '/tmp'
267
+ ps.magick.should == 'word'
268
+ ps.test.should == 'hello'
269
+ ps.code.should == 123
270
+ end
271
+
272
+ describe "usage and description" do
273
+ it "parse should set help variable if -h or --help specified in the argument list and not parse the input" do
274
+ ss = CLI.new do
275
+ option :location, :short => :l
276
+ option :group, :default => 'red'
277
+ option :power_up, :short => :p
278
+ option :speed, :short => :s, :cast => Integer
279
+ option :size
280
+
281
+ argument :log, :cast => Pathname
282
+ argument :magick, :default => 'word'
283
+ argument :test
284
+ argument :code, :cast => Integer, :default => '123'
285
+ end
286
+
287
+ ps = ss.parse(['-l', 'singapore', '--power-up', 'yes', '-s', '24', '--size', 'XXXL', '/tmp', 'hello'])
288
+ ps.help.should be_nil
289
+ ps.location.should == 'singapore'
290
+
291
+ ps = ss.parse(['-h', '-l', 'singapore', '--power-up'])
292
+ ps.help.should be_a String
293
+ ps.location.should be_nil
294
+
295
+ ps = ss.parse(['-l', 'singapore', '--power-up', '-h', 'yes', '-s', '24', '--size', 'XXXL', '/tmp', 'hello'])
296
+ ps.help.should be_a String
297
+ ps.location.should be_nil
298
+
299
+ ps = ss.parse(['-l', 'singapore', '--power-up', '--help'])
300
+ ps.help.should be_a String
301
+ ps.location.should be_nil
302
+
303
+ ps = ss.parse(['--help', '-l', 'singapore', '--power-up', 'yes', '-s', '24', '--size', 'XXXL', '/tmp', 'hello'])
304
+ ps.help.should be_a String
305
+ ps.location.should be_nil
306
+
307
+ ps = ss.parse(['-l', 'singapore', '--power-up', 'yes', '-s', '24', '--size', 'XXXL', '/tmp', 'hello'])
308
+ ps.help.should be_nil
309
+ ps.location.should == 'singapore'
310
+ end
311
+
312
+ it "should allow describing options" do
313
+ ss = CLI.new do
314
+ option :location, :short => :l, :description => "place where server is located"
315
+ option :group, :default => 'red'
316
+ end
317
+
318
+ ss.usage.should include("place where server is located")
319
+ end
320
+
321
+ it "should allow describing arguments" do
322
+ ss = CLI.new do
323
+ option :group, :default => 'red'
324
+ argument :log, :cast => Pathname, :description => "log file to process"
325
+ end
326
+
327
+ ss.usage.should include("log file to process")
328
+ end
329
+
330
+ it "should allow describing whole script" do
331
+ ss = CLI.new do
332
+ description 'Log file processor'
333
+ option :group, :default => 'red'
334
+ argument :log, :cast => Pathname
335
+ end
336
+
337
+ ss.usage.should include("Log file processor")
338
+ end
339
+
340
+ it "should provide stdin usage information" do
341
+ CLI.new do
342
+ stdin
343
+ end.usage.should include(" < data")
344
+
345
+ CLI.new do
346
+ stdin :log_file
347
+ end.usage.should include(" < log-file")
348
+
349
+ u = CLI.new do
350
+ stdin :log_file, :description => 'log file to process'
351
+ end.usage
352
+ u.should include(" < log-file")
353
+ u.should include("log file to process")
354
+
355
+ u = CLI.new do
356
+ stdin :log_data, :cast => YAML, :description => 'log data to process'
357
+ end.usage
358
+ u.should include(" < log-data")
359
+ u.should include("log data to process")
360
+ end
361
+
362
+ it "should provide formated usage with optional message" do
363
+ u = CLI.new do
364
+ description 'Log file processor'
365
+ stdin :log_data, :cast => YAML, :description => "YAML formatted log data"
366
+ option :location, :short => :l, :description => "place where server is located"
367
+ option :group, :default => 'red'
368
+ option :power_up, :short => :p
369
+ option :speed, :short => :s, :cast => Integer
370
+ option :the_number_of_the_beast, :short => :b, :cast => Integer, :default => 666, :description => "The number of the beast"
371
+ option :size
372
+
373
+ argument :log, :cast => Pathname, :description => "log file to process"
374
+ argument :magick, :default => 'word'
375
+ argument :string
376
+ argument :number, :cast => Integer
377
+ argument :code, :cast => Integer, :default => '123', :description => "secret code"
378
+ argument :illegal_prime, :cast => Integer, :description => "prime number that represents information that it is forbidden to possess or distribute"
379
+ end.usage
380
+
381
+ #puts u
382
+
383
+ u.should == <<EOS
384
+ Usage: rspec [options] log magick string number code illegal-prime < log-data
385
+ Log file processor
386
+ Input:
387
+ log-data - YAML formatted log data
388
+ Options:
389
+ --location (-l) - place where server is located
390
+ --group [red]
391
+ --power-up (-p)
392
+ --speed (-s)
393
+ --the-number-of-the-beast (-b) [666] - The number of the beast
394
+ --size
395
+ Arguments:
396
+ log - log file to process
397
+ code - secret code
398
+ illegal-prime - prime number that represents information that it is forbidden to possess or distribute
399
+ EOS
400
+ end
401
+ end
402
+ end
403
+
@@ -0,0 +1,11 @@
1
+ $LOAD_PATH.unshift(File.join(File.dirname(__FILE__), '..', 'lib'))
2
+ $LOAD_PATH.unshift(File.dirname(__FILE__))
3
+ require 'rspec'
4
+
5
+ # Requires supporting files with custom matchers and macros, etc,
6
+ # in ./support/ and its subdirectories.
7
+ Dir["#{File.dirname(__FILE__)}/support/**/*.rb"].each {|f| require f}
8
+
9
+ RSpec.configure do |config|
10
+
11
+ end
metadata ADDED
@@ -0,0 +1,185 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: cli
3
+ version: !ruby/object:Gem::Version
4
+ hash: 27
5
+ prerelease:
6
+ segments:
7
+ - 0
8
+ - 0
9
+ - 2
10
+ version: 0.0.2
11
+ platform: ruby
12
+ authors:
13
+ - Jakub Pastuszek
14
+ autorequire:
15
+ bindir: bin
16
+ cert_chain: []
17
+
18
+ date: 2011-12-01 00:00:00 Z
19
+ dependencies:
20
+ - !ruby/object:Gem::Dependency
21
+ type: :development
22
+ requirement: &id001 !ruby/object:Gem::Requirement
23
+ none: false
24
+ requirements:
25
+ - - ~>
26
+ - !ruby/object:Gem::Version
27
+ hash: 3
28
+ segments:
29
+ - 2
30
+ - 3
31
+ - 0
32
+ version: 2.3.0
33
+ prerelease: false
34
+ name: rspec
35
+ version_requirements: *id001
36
+ - !ruby/object:Gem::Dependency
37
+ type: :development
38
+ requirement: &id002 !ruby/object:Gem::Requirement
39
+ none: false
40
+ requirements:
41
+ - - ">="
42
+ - !ruby/object:Gem::Version
43
+ hash: 3
44
+ segments:
45
+ - 0
46
+ version: "0"
47
+ prerelease: false
48
+ name: cucumber
49
+ version_requirements: *id002
50
+ - !ruby/object:Gem::Dependency
51
+ type: :development
52
+ requirement: &id003 !ruby/object:Gem::Requirement
53
+ none: false
54
+ requirements:
55
+ - - ~>
56
+ - !ruby/object:Gem::Version
57
+ hash: 23
58
+ segments:
59
+ - 1
60
+ - 0
61
+ - 0
62
+ version: 1.0.0
63
+ prerelease: false
64
+ name: bundler
65
+ version_requirements: *id003
66
+ - !ruby/object:Gem::Dependency
67
+ type: :development
68
+ requirement: &id004 !ruby/object:Gem::Requirement
69
+ none: false
70
+ requirements:
71
+ - - ~>
72
+ - !ruby/object:Gem::Version
73
+ hash: 7
74
+ segments:
75
+ - 1
76
+ - 6
77
+ - 4
78
+ version: 1.6.4
79
+ prerelease: false
80
+ name: jeweler
81
+ version_requirements: *id004
82
+ - !ruby/object:Gem::Dependency
83
+ type: :development
84
+ requirement: &id005 !ruby/object:Gem::Requirement
85
+ none: false
86
+ requirements:
87
+ - - ">="
88
+ - !ruby/object:Gem::Version
89
+ hash: 3
90
+ segments:
91
+ - 0
92
+ version: "0"
93
+ prerelease: false
94
+ name: rcov
95
+ version_requirements: *id005
96
+ - !ruby/object:Gem::Dependency
97
+ type: :development
98
+ requirement: &id006 !ruby/object:Gem::Requirement
99
+ none: false
100
+ requirements:
101
+ - - ~>
102
+ - !ruby/object:Gem::Version
103
+ hash: 21
104
+ segments:
105
+ - 3
106
+ - 9
107
+ version: "3.9"
108
+ prerelease: false
109
+ name: rdoc
110
+ version_requirements: *id006
111
+ - !ruby/object:Gem::Dependency
112
+ type: :development
113
+ requirement: &id007 !ruby/object:Gem::Requirement
114
+ none: false
115
+ requirements:
116
+ - - ~>
117
+ - !ruby/object:Gem::Version
118
+ hash: 25
119
+ segments:
120
+ - 0
121
+ - 9
122
+ version: "0.9"
123
+ prerelease: false
124
+ name: ruby-ip
125
+ version_requirements: *id007
126
+ description: Provides DSL for command-line options, switches and arguments parser and stdin handling with generated usage printer
127
+ email: jpastuszek@gmail.com
128
+ executables: []
129
+
130
+ extensions: []
131
+
132
+ extra_rdoc_files:
133
+ - LICENSE.txt
134
+ - README.rdoc
135
+ files:
136
+ - .document
137
+ - .rspec
138
+ - Gemfile
139
+ - Gemfile.lock
140
+ - LICENSE.txt
141
+ - README.rdoc
142
+ - Rakefile
143
+ - VERSION
144
+ - cli.gemspec
145
+ - features/cli.feature
146
+ - features/step_definitions/cli_steps.rb
147
+ - features/support/env.rb
148
+ - lib/cli.rb
149
+ - spec/cli_spec.rb
150
+ - spec/spec_helper.rb
151
+ homepage: http://github.com/jpastuszek/cli
152
+ licenses:
153
+ - MIT
154
+ post_install_message:
155
+ rdoc_options: []
156
+
157
+ require_paths:
158
+ - lib
159
+ required_ruby_version: !ruby/object:Gem::Requirement
160
+ none: false
161
+ requirements:
162
+ - - ">="
163
+ - !ruby/object:Gem::Version
164
+ hash: 3
165
+ segments:
166
+ - 0
167
+ version: "0"
168
+ required_rubygems_version: !ruby/object:Gem::Requirement
169
+ none: false
170
+ requirements:
171
+ - - ">="
172
+ - !ruby/object:Gem::Version
173
+ hash: 3
174
+ segments:
175
+ - 0
176
+ version: "0"
177
+ requirements: []
178
+
179
+ rubyforge_project:
180
+ rubygems_version: 1.8.10
181
+ signing_key:
182
+ specification_version: 3
183
+ summary: Helps writing Command-line interface program
184
+ test_files: []
185
+