clive 0.2.3 → 0.3.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.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