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 +11 -5
- data/VERSION +1 -1
- data/clive.gemspec +10 -6
- data/lib/clive.rb +9 -50
- data/lib/clive/bool.rb +64 -0
- data/lib/clive/command.rb +288 -0
- data/lib/clive/exceptions.rb +48 -0
- data/lib/clive/ext.rb +3 -12
- data/lib/clive/flag.rb +71 -0
- data/lib/clive/option.rb +77 -0
- data/lib/clive/switch.rb +40 -0
- data/lib/clive/tokens.rb +4 -4
- data/test/bin_test +20 -4
- data/test/test_boolean.rb +13 -6
- data/test/test_clive.rb +36 -3
- data/test/test_command.rb +44 -0
- data/test/test_flag.rb +40 -1
- data/test/test_token.rb +22 -9
- metadata +12 -8
- data/lib/clive/booleans.rb +0 -37
- data/lib/clive/commands.rb +0 -317
- data/lib/clive/flags.rb +0 -17
- data/lib/clive/switches.rb +0 -39
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
|
-
|
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
|
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
|
-
|
129
|
+
bool(:v, :verbose, "Run verbosely") {|i| opts[:verbose] = i}
|
124
130
|
|
125
|
-
command(:add, "Add a new project")
|
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.
|
1
|
+
0.3.0
|
data/clive.gemspec
CHANGED
@@ -5,11 +5,11 @@
|
|
5
5
|
|
6
6
|
Gem::Specification.new do |s|
|
7
7
|
s.name = %q{clive}
|
8
|
-
s.version = "0.
|
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-
|
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/
|
29
|
-
"lib/clive/
|
28
|
+
"lib/clive/bool.rb",
|
29
|
+
"lib/clive/command.rb",
|
30
|
+
"lib/clive/exceptions.rb",
|
30
31
|
"lib/clive/ext.rb",
|
31
|
-
"lib/clive/
|
32
|
-
"lib/clive/
|
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",
|
data/lib/clive.rb
CHANGED
@@ -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
|
-
|
6
|
-
require 'clive/
|
7
|
-
require 'clive/
|
8
|
-
require 'clive/
|
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
|
90
|
-
@base.
|
49
|
+
def bools
|
50
|
+
@base.bools
|
91
51
|
end
|
92
52
|
|
93
53
|
end
|
94
|
-
|
data/lib/clive/bool.rb
ADDED
@@ -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
|