clive 0.2.3 → 0.3.0

Sign up to get free protection for your applications and to get access to all the features.
data/README.md CHANGED
@@ -39,7 +39,7 @@ As we've seen above switches are created using #switch. You can provide as littl
39
39
  Boolean switches allow you to accept arguments like `--no-verbose` and `--verbose`, and deal with both situations in the same block.
40
40
 
41
41
  c = Clive.new do
42
- boolean(:v, :verbose) {|i| p i}
42
+ bool(:v, :verbose) {|i| p i}
43
43
  end
44
44
  c.parse(ARGV)
45
45
 
@@ -59,7 +59,7 @@ As you can see the true case can be triggered with the short or long form, the f
59
59
  Flags are like switches but also take an argument:
60
60
 
61
61
  c = Clive.new do
62
- flag(:p, :print, "Print the argument") do |i|
62
+ flag(:p, :print, "ARG", "Print ARG") do |i|
63
63
  p i
64
64
  end
65
65
  end
@@ -75,6 +75,12 @@ Flags are like switches but also take an argument:
75
75
  #=> "short"
76
76
 
77
77
  The argument is then passed into the block. As you can see you can use short, long, equals, or no equals to call flags. As with switches you can call `flag(:p) {|i| ...}` which responds to `-p ...`, `flag(:print) {|i| ...}` which responds to `--print ...` or `--print=...`.
78
+ Flags can have default values, for that situation put square brackets round the argument name.
79
+
80
+ flag(:p, :print, "[ARG]", "Print ARG or "hey" by default) do |i|
81
+ i ||= "hey"
82
+ p i
83
+ end
78
84
 
79
85
  ### Commands
80
86
 
@@ -120,9 +126,9 @@ Anything that isn't a command, switch or flag is taken as an argument. These are
120
126
 
121
127
  opts = {}
122
128
  c = Clive.new do
123
- switch(:v, :verbose, "Run verbosely") {opts[:verbose] = true}
129
+ bool(:v, :verbose, "Run verbosely") {|i| opts[:verbose] = i}
124
130
 
125
- command(:add, "Add a new project")) do
131
+ command(:add, "Add a new project") do
126
132
  opts[:add] = {}
127
133
 
128
134
  switch(:force, "Force overwrite") {opts[:add][:force] = true}
@@ -133,7 +139,7 @@ Anything that isn't a command, switch or flag is taken as an argument. These are
133
139
 
134
140
  command(:init, "Initialize the project after creating") do
135
141
  switch(:m, :minimum, "Use minimum settings") {opts[:add][:min] = true}
136
- flag(:width) {|i| opts[:add][:width] = i.to_i}
142
+ flag(:w, :width) {|i| opts[:add][:width] = i.to_i}
137
143
  end
138
144
 
139
145
  end
data/VERSION CHANGED
@@ -1 +1 @@
1
- 0.2.3
1
+ 0.3.0
@@ -5,11 +5,11 @@
5
5
 
6
6
  Gem::Specification.new do |s|
7
7
  s.name = %q{clive}
8
- s.version = "0.2.3"
8
+ s.version = "0.3.0"
9
9
 
10
10
  s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
11
11
  s.authors = ["Joshua Hawxwell"]
12
- s.date = %q{2010-08-17}
12
+ s.date = %q{2010-08-21}
13
13
  s.description = %q{Clive is a DSL for creating a command line interface. It is for people who, like me, love OptionParser's syntax and love GLI's commands.}
14
14
  s.email = %q{m@hawx.me}
15
15
  s.extra_rdoc_files = [
@@ -25,16 +25,19 @@ Gem::Specification.new do |s|
25
25
  "VERSION",
26
26
  "clive.gemspec",
27
27
  "lib/clive.rb",
28
- "lib/clive/booleans.rb",
29
- "lib/clive/commands.rb",
28
+ "lib/clive/bool.rb",
29
+ "lib/clive/command.rb",
30
+ "lib/clive/exceptions.rb",
30
31
  "lib/clive/ext.rb",
31
- "lib/clive/flags.rb",
32
- "lib/clive/switches.rb",
32
+ "lib/clive/flag.rb",
33
+ "lib/clive/option.rb",
34
+ "lib/clive/switch.rb",
33
35
  "lib/clive/tokens.rb",
34
36
  "test/bin_test",
35
37
  "test/helper.rb",
36
38
  "test/test_boolean.rb",
37
39
  "test/test_clive.rb",
40
+ "test/test_command.rb",
38
41
  "test/test_exceptions.rb",
39
42
  "test/test_flag.rb",
40
43
  "test/test_switch.rb",
@@ -49,6 +52,7 @@ Gem::Specification.new do |s|
49
52
  "test/helper.rb",
50
53
  "test/test_boolean.rb",
51
54
  "test/test_clive.rb",
55
+ "test/test_command.rb",
52
56
  "test/test_exceptions.rb",
53
57
  "test/test_flag.rb",
54
58
  "test/test_switch.rb",
@@ -1,11 +1,14 @@
1
1
  $LOAD_PATH.unshift(File.dirname(__FILE__))
2
2
 
3
+ require 'clive/exceptions'
3
4
  require 'clive/tokens'
4
5
  require 'clive/ext'
5
- require 'clive/switches'
6
- require 'clive/flags'
7
- require 'clive/commands'
8
- require 'clive/booleans'
6
+
7
+ require 'clive/option'
8
+ require 'clive/command'
9
+ require 'clive/switch'
10
+ require 'clive/flag'
11
+ require 'clive/bool'
9
12
 
10
13
  # Clive is a simple dsl for creating command line interfaces
11
14
  #
@@ -21,49 +24,6 @@ require 'clive/booleans'
21
24
  #
22
25
  class Clive
23
26
 
24
- # general problem with input
25
- class ParseError < StandardError
26
- attr_accessor :args, :reason
27
-
28
- def initialize(*args)
29
- @args = args
30
- @reason = "parse error"
31
- end
32
-
33
- def self.filter_backtrace(array)
34
- unless $DEBUG
35
- array = [$0]
36
- end
37
- array
38
- end
39
-
40
- def set_backtrace(array)
41
- super(self.class.filter_backtrace(array))
42
- end
43
-
44
- def message
45
- @reason + ': ' + args.join(' ')
46
- end
47
- alias_method :to_s, :message
48
-
49
- end
50
-
51
- # a flag has a missing argument
52
- class MissingArgument < ParseError
53
- def initialize(*args)
54
- @args = args
55
- @reason = "missing argument"
56
- end
57
- end
58
-
59
- # a option that wasn't defined has been found
60
- class InvalidOption < ParseError
61
- def initialize(*args)
62
- @args = args
63
- @reason = "invalid option"
64
- end
65
- end
66
-
67
27
  attr_accessor :base
68
28
 
69
29
  def initialize(&block)
@@ -86,9 +46,8 @@ class Clive
86
46
  @base.flags
87
47
  end
88
48
 
89
- def booleans
90
- @base.booleans
49
+ def bools
50
+ @base.bools
91
51
  end
92
52
 
93
53
  end
94
-
@@ -0,0 +1,64 @@
1
+ class Clive
2
+
3
+ # A switch which can be triggered with either --no-verbose or --verbose
4
+ # for example.
5
+ class Bool < Option
6
+ attr_accessor :truth
7
+
8
+ # Creates a new Bool switch instance. A boolean switch has a truth,
9
+ # this determines what is passed to the block. They should be created
10
+ # in pairs so one can be +--something+ the other +--no-something+.
11
+ #
12
+ # +short+ and/or +desc+ can be omitted when creating a Boolean, all
13
+ # other arguments must be present.
14
+ #
15
+ # @overload initialize(short, long, desc, truth, &block)
16
+ # Creates a new boolean switch
17
+ # @param [Symbol] short single character to use
18
+ # @param [Symbol] long word/longer name for boolean switch
19
+ # @param [String] desc description of use/purpose
20
+ #
21
+ # @yield [Boolean] A block to be run when the switch is triggered
22
+ #
23
+ def initialize(*args, truth, &block)
24
+ @names = []
25
+ args.each do |i|
26
+ case i
27
+ when Symbol
28
+ if truth
29
+ @names << i.to_s
30
+ else
31
+ @names << "no-#{i.to_s}" if i.length > 1
32
+ end
33
+ when String
34
+ @desc = i
35
+ end
36
+ end
37
+
38
+ unless @names.find_all {|i| i.length > 1}.length > 0
39
+ raise MissingLongName, @names[0]
40
+ end
41
+
42
+ @truth = truth
43
+ @block = block
44
+ end
45
+
46
+ # Run the block with +@truth+
47
+ def run
48
+ @block.call(@truth)
49
+ end
50
+
51
+ # @return [String] summary for help or nil if +@truth = false+
52
+ def summary(width=30, prepend=5)
53
+ return nil unless @truth
54
+
55
+ n = names_to_strings(true).join(', ')
56
+ spaces = width-n.length
57
+ spaces = 1 if spaces < 1
58
+ s = spaces(spaces)
59
+ p = spaces(prepend)
60
+ "#{p}#{n}#{s}#{@desc}"
61
+ end
62
+
63
+ end
64
+ end
@@ -0,0 +1,288 @@
1
+ class Clive
2
+
3
+ # A string which describes the command to execute
4
+ # eg. git add
5
+ # git pull
6
+ #
7
+ class Command < Option
8
+
9
+ attr_accessor :options, :commands
10
+ attr_accessor :argv, :base
11
+ attr_accessor :header, :footer
12
+
13
+ # Create a new Command instance
14
+ #
15
+ # @overload initialize(base, &block)
16
+ # Creates a new base Command to house everything else
17
+ # @param [Boolean] base whether the command is the base
18
+ #
19
+ # @overload initialize(name, desc, &block)
20
+ # Creates a new Command as part of the base Command
21
+ # @param [Symbol] name the name of the command
22
+ # @param [String] desc the description of the command
23
+ #
24
+ # @yield A block to run, containing switches, flags and commands
25
+ #
26
+ def initialize(*args, &block)
27
+ @argv = []
28
+ @names = []
29
+ @base = false
30
+ @commands = Clive::Array.new
31
+ @options = Clive::Array.new
32
+
33
+ if args.length == 1 && args[0] == true
34
+ @base = true
35
+ self.instance_eval(&block)
36
+ else
37
+ args.each do |i|
38
+ case i
39
+ when Symbol
40
+ @names << i.to_s
41
+ when String
42
+ @desc = i
43
+ end
44
+ end
45
+ @block = block
46
+ end
47
+
48
+ @header = "Usage: #{File.basename($0, '.*')} "
49
+ @header << (@base ? "[commands]" : @names.join(', '))
50
+ @header << " [options]"
51
+ @footer = nil
52
+
53
+ self.build_help
54
+ end
55
+
56
+ # @return [Clive::Array] all bools in this command
57
+ def bools
58
+ Clive::Array.new(@options.find_all {|i| i.class == Bool})
59
+ end
60
+
61
+ # @return [Clive::Array] all switches in this command
62
+ def switches
63
+ Clive::Array.new(@options.find_all {|i| i.class == Switch})
64
+ end
65
+
66
+ # @return [Clive::Array] all flags in this command
67
+ def flags
68
+ Clive::Array.new(@options.find_all {|i| i.class == Flag})
69
+ end
70
+
71
+ # Run the block that was passed to find switches, flags, etc.
72
+ #
73
+ # This should only be called if the command has been called
74
+ # as the block could contain other actions to perform only
75
+ # when called.
76
+ #
77
+ def find
78
+ return nil if @base || @block.nil?
79
+ self.instance_eval(&@block)
80
+ @block = nil
81
+ end
82
+
83
+ # Parse the ARGV passed from the command line, and run
84
+ #
85
+ # @param [::Array] argv the command line input, usually just +ARGV+
86
+ # @return [::Array] any arguments that were present in the input but not used
87
+ #
88
+ def run(argv=[])
89
+ tokens = argv
90
+ tokens = tokenize(argv) if @base
91
+
92
+ r = []
93
+ tokens.each do |i|
94
+ k, v = i[0], i[1]
95
+ case k
96
+ when :command
97
+ r << v.run(i[2])
98
+ when :switch
99
+ v.run
100
+ when :flag
101
+ raise MissingArgument.new(v.sort_name) unless i[2] || v.optional
102
+ v.run(i[2])
103
+ when :argument
104
+ r << v
105
+ end
106
+ end
107
+ r.flatten
108
+ end
109
+
110
+ # Turns the command line input into a series of tokens.
111
+ # It will only raise errors if this is the base command instance.
112
+ #
113
+ # @param [::Array] argv the command line input
114
+ # @return [::Array] a series of tokens
115
+ #
116
+ # @example
117
+ #
118
+ # c.tokenize(["add", "-al", "--verbose"])
119
+ # #=> [[:command, #<Clive::Command>, ...args...], [:switch, "a",
120
+ # #<Clive::Switch>], [:switch, "l", #<Clive::Switch>], [:switch,
121
+ # "verbose", #<Clive::Switch>]]
122
+ #
123
+ def tokenize(argv)
124
+ self.find
125
+ r = []
126
+ tokens = Tokens.new(argv)
127
+
128
+ pre_command = Tokens.new
129
+ command = nil
130
+ tokens.tokens.each do |i|
131
+ k, v = i[0], i[1]
132
+ # check if a command
133
+ if k == :word && commands[v]
134
+ command = v
135
+ break
136
+ else
137
+ pre_command << i
138
+ end
139
+ end
140
+
141
+ post_command = Tokens.new(tokens.array - pre_command - [command])
142
+ pre_command_tokens = parse(pre_command)
143
+ r = pre_command_tokens
144
+
145
+ if command
146
+ t = commands[command].tokenize(post_command)
147
+ r << [:command, commands[command], t]
148
+ end
149
+
150
+ r
151
+ end
152
+
153
+ # This runs through the tokens from Tokens#to_tokens (or similar)
154
+ # and creates a new array with the type of object and the object
155
+ # itself, possibly with an argument in the case of Flag.
156
+ #
157
+ # @param [Tokens] tokens the tokens to run through
158
+ # @return [::Array] of the form
159
+ # [[:flag, #<Clive::Flag...>, "word"], [:switch, #<Clive::Switch....
160
+ # @raise [InvalidOption] raised if option given can't be found
161
+ #
162
+ def parse(tokens)
163
+ r = []
164
+ tokens.tokens.each do |i|
165
+ k, v = i[0], i[1]
166
+ if switch = switches[v] || switch = bools[v]
167
+ r << [:switch, switch]
168
+ elsif flag = flags[v]
169
+ r << [:flag, flag]
170
+ else
171
+ if k == :word
172
+ if r.last
173
+ case r.last[0]
174
+ when :flag
175
+ if r.last[2]
176
+ r << [:argument, v]
177
+ else
178
+ r.last[2] = v
179
+ end
180
+ else
181
+ r << [:argument, v]
182
+ end
183
+ end
184
+ else
185
+ raise InvalidOption.new(v)
186
+ end
187
+ end
188
+ end
189
+ r
190
+ end
191
+
192
+
193
+ #### CREATION HELPERS ####
194
+
195
+ # Add a new command to +@commands+
196
+ #
197
+ # @overload command(name, ..., desc, &block)
198
+ # Creates a new command
199
+ # @param [Symbol] name the name(s) of the command, eg. +:add+ for +git add+
200
+ # @param [String] desc description of the command
201
+ #
202
+ # @yield A block to run when the command is called, can contain switches
203
+ # and flags
204
+ #
205
+ def command(*args, &block)
206
+ @commands << Command.new(*args, &block)
207
+ end
208
+
209
+ # Add a new switch to +@switches+
210
+ # @see Switch#initialize
211
+ def switch(*args, &block)
212
+ @options << Switch.new(*args, &block)
213
+ end
214
+
215
+ # Adds a new flag to +@flags+
216
+ # @see Flag#initialize
217
+ def flag(*args, &block)
218
+ @options << Flag.new(*args, &block)
219
+ end
220
+
221
+ # Creates a boolean switch. This is done by adding two switches of
222
+ # Bool type to +@switches+, one is created normally the other has
223
+ # "no-" appended to the long name and has no short name.
224
+ #
225
+ # @see Bool#initialize
226
+ def bool(*args, &block)
227
+ @options << Bool.new(*args, true, &block)
228
+ @options << Bool.new(*args, false, &block)
229
+ end
230
+ alias_method :boolean, :bool
231
+
232
+ #### HELP STUFF ####
233
+
234
+ # This actually creates a switch with "-h" and "--help" that controls
235
+ # the help on this command.
236
+ def build_help
237
+ @options << Switch.new(:h, :help, "Display help") do
238
+ puts self.help
239
+ exit 0
240
+ end
241
+ end
242
+
243
+ # Set the header
244
+ def header(val)
245
+ @header = val
246
+ end
247
+
248
+ # Set the footer
249
+ def footer(val)
250
+ @footer = val
251
+ end
252
+
253
+ def summary(width=30, prepend=5)
254
+ a = @names.sort.join(', ')
255
+ b = @desc
256
+ s = spaces(width-a.length)
257
+ p = spaces(prepend)
258
+ "#{p}#{a}#{s}#{b}"
259
+ end
260
+
261
+ # Generate the summary for help, show all flags and switches, but do not
262
+ # show the flags and switches within each command. Should also prepend the
263
+ # header and append the footer if set.
264
+ def help(width=30, prepend=5)
265
+ summary = "#{@header}\n"
266
+
267
+ if @options.length > 0
268
+ summary << "\n Options:\n"
269
+ @options.sort.each do |i|
270
+ summary << i.summary(width, prepend) << "\n" if i.summary
271
+ end
272
+ end
273
+
274
+ if @commands.length > 0
275
+ summary << "\n Commands:\n"
276
+ @commands.sort.each do |i|
277
+ summary << i.summary(width, prepend) << "\n"
278
+ end
279
+ end
280
+
281
+ summary << "\n#{@footer}\n" if @footer
282
+
283
+ summary
284
+ end
285
+
286
+
287
+ end
288
+ end