cli 0.0.4 → 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- data/README.md +151 -0
- data/VERSION +1 -1
- data/cli.gemspec +14 -5
- data/examples/processor +17 -0
- data/examples/sinatra +41 -0
- data/lib/cli.rb +40 -8
- data/lib/cli/arguments.rb +4 -0
- data/lib/cli/dsl.rb +23 -16
- data/lib/cli/options.rb +4 -11
- data/spec/argument_spec.rb +133 -0
- data/spec/cli_spec.rb +17 -557
- data/spec/conflict_reporting_spec.rb +69 -0
- data/spec/option_spec.rb +155 -0
- data/spec/separator_spec.rb +57 -0
- data/spec/spec_helper.rb +54 -0
- data/spec/stdin_spec.rb +74 -0
- data/spec/switch_spec.rb +62 -0
- data/spec/usage_spec.rb +303 -0
- metadata +15 -6
- data/README.rdoc +0 -19
data/README.md
ADDED
@@ -0,0 +1,151 @@
|
|
1
|
+
# CLI
|
2
|
+
|
3
|
+
Command Line Interface gem allows you to quickly specify command argument parser that will automatically handle usage rendering, casting, default values and other stuff for you.
|
4
|
+
|
5
|
+
CLI supports specifying:
|
6
|
+
|
7
|
+
* switches - (`--name` or `-n`) binary operators, by default set to nil and when specified set to true
|
8
|
+
* options - (`--name John` or `-n John`) switches that take value; default value can be given, otherwise default to nil
|
9
|
+
* arguments - (`John`) capture command arguments that are not switches
|
10
|
+
* stdin - if standard input is to be handled it can be mentioned in usage output; also stdin data casting is supported
|
11
|
+
|
12
|
+
Each element can have description that will be visible in the usage output.
|
13
|
+
|
14
|
+
See examples and specs for more info.
|
15
|
+
|
16
|
+
## Installing
|
17
|
+
|
18
|
+
gem install cli
|
19
|
+
|
20
|
+
## Usage
|
21
|
+
|
22
|
+
### Sinatra server example
|
23
|
+
|
24
|
+
```ruby
|
25
|
+
require 'cli'
|
26
|
+
require 'ip'
|
27
|
+
|
28
|
+
options = CLI.new do
|
29
|
+
description 'Example CLI usage for Sinatra server application'
|
30
|
+
version (cli_root + 'VERSION').read
|
31
|
+
switch :no_bind, :description => "Do not bind to TCP socket - useful with -s fastcgi option"
|
32
|
+
switch :no_logging, :description => "Disable logging"
|
33
|
+
switch :debug, :description => "Enable debugging"
|
34
|
+
switch :no_optimization, :description => "Disable size hinting and related optimization (loading, prescaling)"
|
35
|
+
option :bind, :short => :b, :default => '127.0.0.1', :cast => IP, :description => "HTTP server bind address - use 0.0.0.0 to bind to all interfaces"
|
36
|
+
option :port, :short => :p, :default => 3100, :cast => Integer, :description => "HTTP server TCP port"
|
37
|
+
option :server, :short => :s, :default => 'mongrel', :description => "Rack server handler like thin, mongrel, webrick, fastcgi etc."
|
38
|
+
option :limit_memory, :default => 128*1024**2, :cast => Integer, :description => "Image cache heap memory size limit in bytes"
|
39
|
+
option :limit_map, :default => 256*1024**2, :cast => Integer, :description => "Image cache memory mapped file size limit in bytes - used when heap memory limit is used up"
|
40
|
+
option :limit_disk, :default => 0, :cast => Integer, :description => "Image cache temporary file size limit in bytes - used when memory mapped file limit is used up"
|
41
|
+
end.parse!
|
42
|
+
|
43
|
+
# use to set sinatra settings
|
44
|
+
require 'sinatra/base'
|
45
|
+
|
46
|
+
sinatra = Sinatra.new
|
47
|
+
|
48
|
+
sinatra.set :environment, 'production'
|
49
|
+
sinatra.set :server, options.server
|
50
|
+
sinatra.set :lock, true
|
51
|
+
sinatra.set :boundary, "thumnail image data"
|
52
|
+
sinatra.set :logging, (not options.no_logging)
|
53
|
+
sinatra.set :debug, options.debug
|
54
|
+
sinatra.set :optimization, (not options.no_optimization)
|
55
|
+
sinatra.set :limit_memory, options.limit_memory
|
56
|
+
sinatra.set :limit_map, options.limit_map
|
57
|
+
sinatra.set :limit_disk, options.limit_disk
|
58
|
+
|
59
|
+
# set up your application
|
60
|
+
|
61
|
+
sinatra.run!
|
62
|
+
```
|
63
|
+
|
64
|
+
To see help message use `--help` or `-h` anywhere on the command line:
|
65
|
+
|
66
|
+
examples/sinatra --help
|
67
|
+
|
68
|
+
Example help message:
|
69
|
+
|
70
|
+
Usage: sinatra [switches|options]
|
71
|
+
Example CLI usage for Sinatra server application
|
72
|
+
Switches:
|
73
|
+
--no-bind - Do not bind to TCP socket - useful with -s fastcgi option
|
74
|
+
--no-logging - Disable logging
|
75
|
+
--debug - Enable debugging
|
76
|
+
--no-optimization - Disable size hinting and related optimization (loading, prescaling)
|
77
|
+
--help (-h) - display this help message
|
78
|
+
--version - display version string
|
79
|
+
Options:
|
80
|
+
--bind (-b) [127.0.0.1] - HTTP server bind address - use 0.0.0.0 to bind to all interfaces
|
81
|
+
--port (-p) [3100] - HTTP server TCP port
|
82
|
+
--server (-s) [mongrel] - Rack server handler like thin, mongrel, webrick, fastcgi etc.
|
83
|
+
--limit-memory [134217728] - Image cache heap memory size limit in bytes
|
84
|
+
--limit-map [268435456] - Image cache memory mapped file size limit in bytes - used when heap memory limit is used up
|
85
|
+
--limit-disk [0] - Image cache temporary file size limit in bytes - used when memory mapped file limit is used up
|
86
|
+
|
87
|
+
To see version string use `--version`
|
88
|
+
|
89
|
+
examples/sinatra --version
|
90
|
+
|
91
|
+
Example version output:
|
92
|
+
|
93
|
+
sinatra version "0.0.4"
|
94
|
+
|
95
|
+
### Statistic data processor example
|
96
|
+
|
97
|
+
```ruby
|
98
|
+
require 'cli'
|
99
|
+
|
100
|
+
options = CLI.new do
|
101
|
+
description 'Generate blog posts in given Jekyll directory from input statistics'
|
102
|
+
stdin :log_data, :cast => YAML, :description => 'statistic data in YAML format'
|
103
|
+
option :location, :short => :l, :description => 'location name (ex. Dublin, Singapore, Califorina)'
|
104
|
+
option :csv_dir, :short => :c, :cast => Pathname, :default => 'csv', :description => 'directory name where CSV file will be storred (relative to jekyll-dir)'
|
105
|
+
argument :jekyll_dir, :cast => Pathname, :default => '/var/lib/vhs/jekyll', :description => 'directory where site source is located'
|
106
|
+
end.parse!
|
107
|
+
|
108
|
+
# do your stuff
|
109
|
+
```
|
110
|
+
|
111
|
+
Example help message:
|
112
|
+
|
113
|
+
Usage: processor [switches|options] [--] jekyll-dir < log-data
|
114
|
+
Generate blog posts in given Jekyll directory from input statistics
|
115
|
+
Input:
|
116
|
+
log-data - statistic data in YAML format
|
117
|
+
Switches:
|
118
|
+
--help (-h) - display this help message
|
119
|
+
Options:
|
120
|
+
--location (-l) - location name (ex. Dublin, Singapore, Califorina)
|
121
|
+
--csv-dir (-c) [csv] - directory name where CSV file will be storred (relative to jekyll-dir)
|
122
|
+
Arguments:
|
123
|
+
jekyll-dir - directory where site source is located
|
124
|
+
|
125
|
+
With this example usage:
|
126
|
+
|
127
|
+
examples/processor --location Singapore <<EOF
|
128
|
+
:parser:
|
129
|
+
:successes: 41
|
130
|
+
:failures: 0
|
131
|
+
EOF
|
132
|
+
|
133
|
+
The `options` variable will contain:
|
134
|
+
|
135
|
+
#<CLI::Values location="Singapore", stdin={:parser=>{:failures=>0, :successes=>41}}, jekyll_dir=#<Pathname:/var/lib/vhs/jekyll>, csv_dir=#<Pathname:csv>>
|
136
|
+
|
137
|
+
## Contributing to CLI
|
138
|
+
|
139
|
+
* Check out the latest master to make sure the feature hasn't been implemented or the bug hasn't been fixed yet
|
140
|
+
* Check out the issue tracker to make sure someone already hasn't requested it and/or contributed it
|
141
|
+
* Fork the project
|
142
|
+
* Start a feature/bugfix branch
|
143
|
+
* Commit and push until you are happy with your contribution
|
144
|
+
* Make sure to add tests for it. This is important so I don't break it in a future version unintentionally.
|
145
|
+
* 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.
|
146
|
+
|
147
|
+
## Copyright
|
148
|
+
|
149
|
+
Copyright (c) 2011 Jakub Pastuszek. See LICENSE.txt for
|
150
|
+
further details.
|
151
|
+
|
data/VERSION
CHANGED
@@ -1 +1 @@
|
|
1
|
-
0.0
|
1
|
+
0.1.0
|
data/cli.gemspec
CHANGED
@@ -5,16 +5,16 @@
|
|
5
5
|
|
6
6
|
Gem::Specification.new do |s|
|
7
7
|
s.name = "cli"
|
8
|
-
s.version = "0.0
|
8
|
+
s.version = "0.1.0"
|
9
9
|
|
10
10
|
s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
|
11
11
|
s.authors = ["Jakub Pastuszek"]
|
12
|
-
s.date = "2011-12-
|
12
|
+
s.date = "2011-12-16"
|
13
13
|
s.description = "Provides DSL for command-line options, switches and arguments parser and stdin handling with generated usage printer"
|
14
14
|
s.email = "jpastuszek@gmail.com"
|
15
15
|
s.extra_rdoc_files = [
|
16
16
|
"LICENSE.txt",
|
17
|
-
"README.
|
17
|
+
"README.md"
|
18
18
|
]
|
19
19
|
s.files = [
|
20
20
|
".document",
|
@@ -22,10 +22,12 @@ Gem::Specification.new do |s|
|
|
22
22
|
"Gemfile",
|
23
23
|
"Gemfile.lock",
|
24
24
|
"LICENSE.txt",
|
25
|
-
"README.
|
25
|
+
"README.md",
|
26
26
|
"Rakefile",
|
27
27
|
"VERSION",
|
28
28
|
"cli.gemspec",
|
29
|
+
"examples/processor",
|
30
|
+
"examples/sinatra",
|
29
31
|
"features/cli.feature",
|
30
32
|
"features/step_definitions/cli_steps.rb",
|
31
33
|
"features/support/env.rb",
|
@@ -34,8 +36,15 @@ Gem::Specification.new do |s|
|
|
34
36
|
"lib/cli/dsl.rb",
|
35
37
|
"lib/cli/options.rb",
|
36
38
|
"lib/cli/switches.rb",
|
39
|
+
"spec/argument_spec.rb",
|
37
40
|
"spec/cli_spec.rb",
|
38
|
-
"spec/
|
41
|
+
"spec/conflict_reporting_spec.rb",
|
42
|
+
"spec/option_spec.rb",
|
43
|
+
"spec/separator_spec.rb",
|
44
|
+
"spec/spec_helper.rb",
|
45
|
+
"spec/stdin_spec.rb",
|
46
|
+
"spec/switch_spec.rb",
|
47
|
+
"spec/usage_spec.rb"
|
39
48
|
]
|
40
49
|
s.homepage = "http://github.com/jpastuszek/cli"
|
41
50
|
s.licenses = ["MIT"]
|
data/examples/processor
ADDED
@@ -0,0 +1,17 @@
|
|
1
|
+
#!/usr/bin/ruby
|
2
|
+
require 'pathname'
|
3
|
+
cli_root = Pathname.new(__FILE__).dirname + '..'
|
4
|
+
$LOAD_PATH.unshift(cli_root + 'lib')
|
5
|
+
|
6
|
+
require 'cli'
|
7
|
+
|
8
|
+
options = CLI.new do
|
9
|
+
description 'Generate blog posts in given Jekyll directory from input statistics'
|
10
|
+
stdin :log_data, :cast => YAML, :description => 'statistic data in YAML format'
|
11
|
+
option :location, :short => :l, :description => 'location name (ex. Dublin, Singapore, Califorina)'
|
12
|
+
option :csv_dir, :short => :c, :cast => Pathname, :default => 'csv', :description => 'directory name where CSV file will be storred (relative to jekyll-dir)'
|
13
|
+
argument :jekyll_dir, :cast => Pathname, :default => '/var/lib/vhs/jekyll', :description => 'directory where site source is located'
|
14
|
+
end.parse!
|
15
|
+
|
16
|
+
p options
|
17
|
+
|
data/examples/sinatra
ADDED
@@ -0,0 +1,41 @@
|
|
1
|
+
#!/usr/bin/ruby
|
2
|
+
require 'pathname'
|
3
|
+
cli_root = Pathname.new(__FILE__).dirname + '..'
|
4
|
+
$LOAD_PATH.unshift(cli_root + 'lib')
|
5
|
+
|
6
|
+
require 'cli'
|
7
|
+
require 'ip'
|
8
|
+
|
9
|
+
options = CLI.new do
|
10
|
+
description 'Example CLI usage for Sinatra server application'
|
11
|
+
version (cli_root + 'VERSION').read
|
12
|
+
switch :no_bind, :description => "Do not bind to TCP socket - useful with -s fastcgi option"
|
13
|
+
switch :no_logging, :description => "Disable logging"
|
14
|
+
switch :debug, :description => "Enable debugging"
|
15
|
+
switch :no_optimization, :description => "Disable size hinting and related optimization (loading, prescaling)"
|
16
|
+
option :bind, :short => :b, :default => '127.0.0.1', :cast => IP, :description => "HTTP server bind address - use 0.0.0.0 to bind to all interfaces"
|
17
|
+
option :port, :short => :p, :default => 3100, :cast => Integer, :description => "HTTP server TCP port"
|
18
|
+
option :server, :short => :s, :default => 'mongrel', :description => "Rack server handler like thin, mongrel, webrick, fastcgi etc."
|
19
|
+
option :limit_memory, :default => 128*1024**2, :cast => Integer, :description => "Image cache heap memory size limit in bytes"
|
20
|
+
option :limit_map, :default => 256*1024**2, :cast => Integer, :description => "Image cache memory mapped file size limit in bytes - used when heap memory limit is used up"
|
21
|
+
option :limit_disk, :default => 0, :cast => Integer, :description => "Image cache temporary file size limit in bytes - used when memory mapped file limit is used up"
|
22
|
+
end.parse!
|
23
|
+
|
24
|
+
p options
|
25
|
+
|
26
|
+
## use to set sinatra settings
|
27
|
+
#require 'sinatra/base'
|
28
|
+
#
|
29
|
+
#sinatra = Sinatra.new
|
30
|
+
#
|
31
|
+
#sinatra.set :environment, 'production'
|
32
|
+
#sinatra.set :server, options.server
|
33
|
+
#sinatra.set :lock, true
|
34
|
+
#sinatra.set :boundary, "thumnail image data"
|
35
|
+
#sinatra.set :logging, (not options.no_logging)
|
36
|
+
#sinatra.set :debug, options.debug
|
37
|
+
#sinatra.set :optimization, (not options.no_optimization)
|
38
|
+
#sinatra.set :limit_memory, options.limit_memory
|
39
|
+
#sinatra.set :limit_map, options.limit_map
|
40
|
+
#sinatra.set :limit_disk, options.limit_disk
|
41
|
+
|
data/lib/cli.rb
CHANGED
@@ -98,13 +98,21 @@ class CLI
|
|
98
98
|
@arguments = Arguments.new
|
99
99
|
@switches = Switches.new
|
100
100
|
@options = Options.new
|
101
|
+
|
101
102
|
instance_eval(&block) if block_given?
|
103
|
+
|
104
|
+
switch :help, :short => :h, :description => 'display this help message'
|
105
|
+
switch :version, :description => 'display version string' if @version
|
102
106
|
end
|
103
107
|
|
104
108
|
def description(desc)
|
105
109
|
@description = desc
|
106
110
|
end
|
107
111
|
|
112
|
+
def version(version)
|
113
|
+
@version = version.to_s
|
114
|
+
end
|
115
|
+
|
108
116
|
def stdin(name = :data, options = {})
|
109
117
|
@stdin = DSL::Input.new(name, options)
|
110
118
|
end
|
@@ -143,10 +151,19 @@ class CLI
|
|
143
151
|
values = Values.new
|
144
152
|
argv = _argv.dup
|
145
153
|
|
146
|
-
# check help
|
147
|
-
|
148
|
-
|
149
|
-
|
154
|
+
# check help and version
|
155
|
+
argv.each do |arg|
|
156
|
+
break if arg == '--'
|
157
|
+
|
158
|
+
if arg == '-h' or arg == '--help'
|
159
|
+
values.help = usage
|
160
|
+
return values
|
161
|
+
end
|
162
|
+
|
163
|
+
if @version and arg == '--version'
|
164
|
+
values.version = "#{name} version \"#{@version}\"\n"
|
165
|
+
return values
|
166
|
+
end
|
150
167
|
end
|
151
168
|
|
152
169
|
# set defaults
|
@@ -157,7 +174,7 @@ class CLI
|
|
157
174
|
# process switches
|
158
175
|
mandatory_options = @options.mandatory.dup
|
159
176
|
|
160
|
-
while Switches.is_switch?(argv.first)
|
177
|
+
while not argv.first == '--' and Switches.is_switch?(argv.first)
|
161
178
|
arg = argv.shift
|
162
179
|
|
163
180
|
if switch = @switches.find(arg)
|
@@ -171,18 +188,24 @@ class CLI
|
|
171
188
|
end
|
172
189
|
end
|
173
190
|
|
191
|
+
argv.shift if argv.first == '--'
|
192
|
+
|
174
193
|
# check mandatory options
|
175
194
|
raise ParsingError::MandatoryOptionsNotSpecifiedError.new(mandatory_options) unless mandatory_options.empty?
|
176
195
|
|
177
196
|
# process arguments
|
178
197
|
arguments = @arguments.dup
|
198
|
+
mandatory_arguments_left = @arguments.mandatory.length
|
199
|
+
|
179
200
|
while argument = arguments.shift
|
180
|
-
value = if argv.length
|
181
|
-
argument.default #
|
201
|
+
value = if argv.length == mandatory_arguments_left and not argument.mandatory?
|
202
|
+
argument.default # use defaults for optional arguments
|
182
203
|
else
|
183
204
|
argv.shift or raise ParsingError::MandatoryArgumentNotSpecifiedError.new(argument)
|
184
205
|
end
|
185
206
|
|
207
|
+
mandatory_arguments_left -= 1 if argument.mandatory?
|
208
|
+
|
186
209
|
values.value(argument, argument.cast(value))
|
187
210
|
end
|
188
211
|
|
@@ -199,19 +222,28 @@ class CLI
|
|
199
222
|
stdout.write pp.help
|
200
223
|
exit 0
|
201
224
|
end
|
225
|
+
if pp.version
|
226
|
+
stdout.write pp.version
|
227
|
+
exit 0
|
228
|
+
end
|
202
229
|
pp
|
203
230
|
rescue ParsingError => pe
|
204
231
|
usage!(pe, stderr)
|
205
232
|
end
|
206
233
|
end
|
207
234
|
|
235
|
+
def name
|
236
|
+
File.basename $0
|
237
|
+
end
|
238
|
+
|
208
239
|
def usage(msg = nil)
|
209
240
|
out = StringIO.new
|
210
241
|
out.puts msg if msg
|
211
|
-
out.print "Usage: #{
|
242
|
+
out.print "Usage: #{name}"
|
212
243
|
out.print ' [switches|options]' if not @switches.empty? and not @options.empty?
|
213
244
|
out.print ' [switches]' if not @switches.empty? and @options.empty?
|
214
245
|
out.print ' [options]' if @switches.empty? and not @options.empty?
|
246
|
+
out.print ' [--]' if not @arguments.empty? and (not @switches.empty? or not @options.empty?)
|
215
247
|
out.print ' ' + @arguments.map{|a| a.to_s}.join(' ') unless @arguments.empty?
|
216
248
|
out.print " < #{@stdin}" if @stdin
|
217
249
|
|
data/lib/cli/arguments.rb
CHANGED
data/lib/cli/dsl.rb
CHANGED
@@ -15,17 +15,24 @@ class CLI
|
|
15
15
|
module Cast
|
16
16
|
def cast(value)
|
17
17
|
begin
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
18
|
+
cast_to = @options[:cast] or return value
|
19
|
+
|
20
|
+
if cast_to.is_a? Module # all classes are modules
|
21
|
+
if cast_to == Integer
|
22
|
+
value.to_i
|
23
|
+
elsif cast_to == Float
|
24
|
+
value.to_f
|
25
|
+
elsif cast_to == YAML
|
26
|
+
YAML.load(value)
|
27
|
+
else
|
28
|
+
cast_to.new(value)
|
29
|
+
end
|
27
30
|
else
|
28
|
-
|
31
|
+
if cast_to.is_a? Proc
|
32
|
+
cast_to.call(value)
|
33
|
+
else
|
34
|
+
raise ArgumentError, "can't cast to instance of #{cast_to.class.name}"
|
35
|
+
end
|
29
36
|
end
|
30
37
|
rescue => e
|
31
38
|
raise ParsingError::CastError.new(@name, @options[:cast].name, e)
|
@@ -39,21 +46,21 @@ class CLI
|
|
39
46
|
end
|
40
47
|
|
41
48
|
def description
|
42
|
-
@options[:description]
|
49
|
+
@options[:description].to_s
|
43
50
|
end
|
44
51
|
end
|
45
52
|
|
46
53
|
module Value
|
47
54
|
def default
|
48
|
-
@options[:default]
|
55
|
+
@options[:default].to_s
|
49
56
|
end
|
50
57
|
|
51
58
|
def has_default?
|
52
59
|
@options.member? :default
|
53
60
|
end
|
54
61
|
|
55
|
-
def
|
56
|
-
has_default?
|
62
|
+
def mandatory?
|
63
|
+
not has_default?
|
57
64
|
end
|
58
65
|
end
|
59
66
|
|
@@ -112,8 +119,8 @@ class CLI
|
|
112
119
|
include DSL::Value
|
113
120
|
include DSL::Cast
|
114
121
|
|
115
|
-
def
|
116
|
-
has_default?
|
122
|
+
def mandatory?
|
123
|
+
not has_default? and @options[:required]
|
117
124
|
end
|
118
125
|
end
|
119
126
|
end
|