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 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.4
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.4"
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-15"
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.rdoc"
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.rdoc",
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/spec_helper.rb"
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"]
@@ -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
- if argv.include? '-h' or argv.include? '--help'
148
- values.help = usage
149
- return values
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 < arguments.length + 1 and argument.optional?
181
- argument.default # not enough arguments, try to skip optional if possible
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: #{File.basename $0}"
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
@@ -2,5 +2,9 @@ class CLI::Arguments < Array
2
2
  def has?(argument_dsl)
3
3
  self.find{|a| a.name == argument_dsl.name}
4
4
  end
5
+
6
+ def mandatory
7
+ select{|a| a.mandatory?}
8
+ end
5
9
  end
6
10
 
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
- cast_class = @options[:cast]
19
- if cast_class == nil
20
- value
21
- elsif cast_class == Integer
22
- value.to_i
23
- elsif cast_class == Float
24
- value.to_f
25
- elsif cast_class == YAML
26
- YAML.load(value)
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
- cast_class.new(value)
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 optional?
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 optional?
116
- has_default? or not @options[:required]
122
+ def mandatory?
123
+ not has_default? and @options[:required]
117
124
  end
118
125
  end
119
126
  end