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
@@ -0,0 +1,48 @@
|
|
1
|
+
class Clive
|
2
|
+
|
3
|
+
# general problem
|
4
|
+
class CliveError < StandardError
|
5
|
+
attr_accessor :args
|
6
|
+
|
7
|
+
def initialize(*args)
|
8
|
+
@args = args
|
9
|
+
end
|
10
|
+
|
11
|
+
def self.filter_backtrace(array)
|
12
|
+
unless $DEBUG
|
13
|
+
array = [$0]
|
14
|
+
end
|
15
|
+
array
|
16
|
+
end
|
17
|
+
|
18
|
+
def set_backtrace(array)
|
19
|
+
super(self.class.filter_backtrace(array))
|
20
|
+
end
|
21
|
+
|
22
|
+
def message
|
23
|
+
self.reason + ': ' + args.join(' ')
|
24
|
+
end
|
25
|
+
alias_method :to_s, :message
|
26
|
+
|
27
|
+
end
|
28
|
+
|
29
|
+
# general problem with input
|
30
|
+
class ParseError < CliveError
|
31
|
+
def reason; "parse error"; end
|
32
|
+
end
|
33
|
+
|
34
|
+
# a flag has a missing argument
|
35
|
+
class MissingArgument < ParseError
|
36
|
+
def reason; "missing argument"; end
|
37
|
+
end
|
38
|
+
|
39
|
+
# a option that wasn't defined has been found
|
40
|
+
class InvalidOption < ParseError
|
41
|
+
def reason; "invalid option"; end
|
42
|
+
end
|
43
|
+
|
44
|
+
class MissingLongName < CliveError
|
45
|
+
def reason; "missing long name"; end
|
46
|
+
end
|
47
|
+
|
48
|
+
end
|
data/lib/clive/ext.rb
CHANGED
@@ -2,24 +2,15 @@ class Clive
|
|
2
2
|
|
3
3
|
class Array < ::Array
|
4
4
|
|
5
|
-
# If passed a Symbol or String will get the item with that name
|
6
|
-
#
|
7
|
-
# what you expect of an Array (see Array#[])
|
5
|
+
# If passed a Symbol or String will get the item with that name.
|
6
|
+
# Otherwise does what you expect of an Array (see ::Array#[])
|
8
7
|
#
|
9
8
|
# @param [Symbol, String, Integer, Range] val name or index of item to return
|
10
9
|
# @return the item that has been found
|
11
10
|
def [](val)
|
12
11
|
val = val.to_s if val.is_a? Symbol
|
13
12
|
if val.is_a? String
|
14
|
-
|
15
|
-
self.find_all {|i| i.name == val}[0]
|
16
|
-
elsif self[0].respond_to?(:long)
|
17
|
-
if val.length == 1
|
18
|
-
self.find_all {|i| i.short == val}[0]
|
19
|
-
else
|
20
|
-
self.find_all {|i| i.long == val}[0]
|
21
|
-
end
|
22
|
-
end
|
13
|
+
self.find_all {|i| i.names.include?(val)}[0]
|
23
14
|
else
|
24
15
|
super
|
25
16
|
end
|
data/lib/clive/flag.rb
ADDED
@@ -0,0 +1,71 @@
|
|
1
|
+
class Clive
|
2
|
+
|
3
|
+
# A switch that takes an argument, with or without an equals
|
4
|
+
# eg. wget --tries=10
|
5
|
+
# wget -t 10
|
6
|
+
#
|
7
|
+
class Flag < Option
|
8
|
+
attr_accessor :arg_name, :optional
|
9
|
+
|
10
|
+
# Creates a new Flag instance.
|
11
|
+
#
|
12
|
+
# +short+ _or_ +long+ can be omitted but not both.
|
13
|
+
#
|
14
|
+
# @overload flag(short, long, desc, &block)
|
15
|
+
# Creates a new flag
|
16
|
+
# @param [Symbol] short single character for short flag, eg. +:t+ => +-t 10+
|
17
|
+
# @param [Symbol] long longer switch to be used, eg. +:tries+ => +--tries=10+
|
18
|
+
# @param [String] desc the description for the flag
|
19
|
+
#
|
20
|
+
# @yield [String] A block to be run if switch is triggered
|
21
|
+
#
|
22
|
+
def initialize(*args, &block)
|
23
|
+
@names = []
|
24
|
+
@optional = false
|
25
|
+
@arg_name = "ARG"
|
26
|
+
|
27
|
+
args.each do |i|
|
28
|
+
if i.is_a? String
|
29
|
+
if i =~ /^[\[\]A-Z0-9]+$/
|
30
|
+
@arg_name = i
|
31
|
+
else
|
32
|
+
@desc = i
|
33
|
+
end
|
34
|
+
else
|
35
|
+
@names << i.to_s
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
if @arg_name[0] == "["
|
40
|
+
@arg_name = @arg_name[1..@arg_name.length-2]
|
41
|
+
@optional = true
|
42
|
+
end
|
43
|
+
|
44
|
+
@block = block
|
45
|
+
end
|
46
|
+
|
47
|
+
# Runs the block that was given with an argument
|
48
|
+
#
|
49
|
+
# @param [String] arg argument to pass to the block
|
50
|
+
def run(arg)
|
51
|
+
@block.call(arg)
|
52
|
+
end
|
53
|
+
|
54
|
+
# @return [String] summary for help
|
55
|
+
def summary(width=30, prepend=5)
|
56
|
+
n = names_to_strings.join(', ')
|
57
|
+
if @optional
|
58
|
+
n << " [#{@arg_name}]"
|
59
|
+
else
|
60
|
+
n << " #{@arg_name}"
|
61
|
+
end
|
62
|
+
|
63
|
+
spaces = width-n.length
|
64
|
+
spaces = 1 if spaces < 1
|
65
|
+
s = spaces(spaces)
|
66
|
+
p = spaces(prepend)
|
67
|
+
"#{p}#{n}#{s}#{@desc}"
|
68
|
+
end
|
69
|
+
|
70
|
+
end
|
71
|
+
end
|
data/lib/clive/option.rb
ADDED
@@ -0,0 +1,77 @@
|
|
1
|
+
class Clive
|
2
|
+
|
3
|
+
class Option
|
4
|
+
attr_accessor :names, :desc, :block
|
5
|
+
|
6
|
+
def initialize(desc, *names, &block)
|
7
|
+
@names = names
|
8
|
+
@desc = desc
|
9
|
+
@block = block
|
10
|
+
end
|
11
|
+
|
12
|
+
def run
|
13
|
+
@block.call
|
14
|
+
end
|
15
|
+
|
16
|
+
def summary(width=30, prepend=5)
|
17
|
+
n = names_to_strings.join(', ')
|
18
|
+
spaces = width-n.length
|
19
|
+
spaces = 1 if spaces < 1
|
20
|
+
s = spaces(spaces)
|
21
|
+
p = spaces(prepend)
|
22
|
+
"#{p}#{n}#{s}#{@desc}"
|
23
|
+
end
|
24
|
+
|
25
|
+
# Convert the names to strings, depending on length
|
26
|
+
#
|
27
|
+
# @param [Boolean] bool whether to add [no-] to long
|
28
|
+
#
|
29
|
+
# @example
|
30
|
+
#
|
31
|
+
# @names = ['v', 'verbose']
|
32
|
+
# names_to_strings
|
33
|
+
# #=> ['-v', '--verbose']
|
34
|
+
#
|
35
|
+
def names_to_strings(bool=false)
|
36
|
+
r = []
|
37
|
+
@names.each do |i|
|
38
|
+
next if i.nil?
|
39
|
+
if i.length == 1 # short
|
40
|
+
r << "-#{i}"
|
41
|
+
else # long
|
42
|
+
if bool
|
43
|
+
r << "--[no-]#{i}"
|
44
|
+
else
|
45
|
+
r << "--#{i}"
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
49
|
+
r
|
50
|
+
end
|
51
|
+
|
52
|
+
# Create a string of +n+ spaces
|
53
|
+
def spaces(n)
|
54
|
+
s = ''
|
55
|
+
(0...n).each {s << ' '}
|
56
|
+
s
|
57
|
+
end
|
58
|
+
|
59
|
+
# Tries to get the short name, if not choose lowest alphabetically
|
60
|
+
#
|
61
|
+
# @return [String] name to sort by
|
62
|
+
def sort_name
|
63
|
+
r = @names.sort[0]
|
64
|
+
@names.each do |i|
|
65
|
+
if i.length == 1
|
66
|
+
r = i
|
67
|
+
end
|
68
|
+
end
|
69
|
+
r
|
70
|
+
end
|
71
|
+
|
72
|
+
def <=>(other)
|
73
|
+
self.sort_name <=> other.sort_name
|
74
|
+
end
|
75
|
+
|
76
|
+
end
|
77
|
+
end
|
data/lib/clive/switch.rb
ADDED
@@ -0,0 +1,40 @@
|
|
1
|
+
class Clive
|
2
|
+
|
3
|
+
# A string that takes no argument, beginning with one or two dashes
|
4
|
+
# eg. ruby --version
|
5
|
+
# ruby -v
|
6
|
+
#
|
7
|
+
class Switch < Option
|
8
|
+
|
9
|
+
# Create a new Switch instance.
|
10
|
+
#
|
11
|
+
# +short+ _or_ +long+ may be omitted but not both.
|
12
|
+
#
|
13
|
+
# @overload switch(short, long, desc, &block)
|
14
|
+
# Creates a new switch
|
15
|
+
# @param [Symbol] short single character for short switch, eg. +:v+ => +-v+
|
16
|
+
# @param [Symbol] long longer switch to be used, eg. +:verbose+ => +--verbose+
|
17
|
+
# @param [String] desc the description for the switch
|
18
|
+
#
|
19
|
+
# @yield A block to run if the switch is triggered
|
20
|
+
#
|
21
|
+
def initialize(*args, &block)
|
22
|
+
@names = []
|
23
|
+
args.each do |i|
|
24
|
+
case i
|
25
|
+
when Symbol
|
26
|
+
@names << i.to_s
|
27
|
+
when String
|
28
|
+
@desc = i
|
29
|
+
end
|
30
|
+
end
|
31
|
+
@block = block
|
32
|
+
end
|
33
|
+
|
34
|
+
# Runs the block that was given
|
35
|
+
def run
|
36
|
+
@block.call
|
37
|
+
end
|
38
|
+
|
39
|
+
end
|
40
|
+
end
|
data/lib/clive/tokens.rb
CHANGED
@@ -17,7 +17,7 @@ class Clive
|
|
17
17
|
# Create a new Tokens instance. Pass either an array of tokens
|
18
18
|
# or a plain array, they will be converted correctly.
|
19
19
|
#
|
20
|
-
# @param [Array]
|
20
|
+
# @param [::Array]
|
21
21
|
# @return [Tokens]
|
22
22
|
#
|
23
23
|
def initialize(args=[])
|
@@ -32,7 +32,7 @@ class Clive
|
|
32
32
|
# Turn +@tokens+ into an array, this ensures that shorts are split
|
33
33
|
# as is expected
|
34
34
|
#
|
35
|
-
# @return [Array] array representation of tokens held
|
35
|
+
# @return [::Array] array representation of tokens held
|
36
36
|
def array
|
37
37
|
return [] unless self.tokens
|
38
38
|
arr = []
|
@@ -53,7 +53,7 @@ class Clive
|
|
53
53
|
|
54
54
|
# Creates an array of tokens based on +self+
|
55
55
|
#
|
56
|
-
# @return [Array] the tokens that are held
|
56
|
+
# @return [::Array] the tokens that are held
|
57
57
|
def tokens
|
58
58
|
t = []
|
59
59
|
self.each do |i|
|
@@ -97,7 +97,7 @@ class Clive
|
|
97
97
|
|
98
98
|
# Test whether an array is a token
|
99
99
|
#
|
100
|
-
# @param [Array]
|
100
|
+
# @param [::Array]
|
101
101
|
# @return [Boolean]
|
102
102
|
#
|
103
103
|
# @example
|
data/test/bin_test
CHANGED
@@ -9,15 +9,23 @@ c = Clive.new do
|
|
9
9
|
options[:verbose] = true
|
10
10
|
}
|
11
11
|
|
12
|
-
flag(:t, :type, "
|
13
|
-
|
14
|
-
|
12
|
+
flag(:t, :type, "TYPE", "Change type to TYPE") do |i|
|
13
|
+
options[:type] = i
|
14
|
+
end
|
15
15
|
|
16
16
|
flag(:long_only, "A flag with only a long") {}
|
17
17
|
|
18
|
+
flag(:p, :print, "[TEXT]", "Print TEXT, defaults to 'hey'") do |i|
|
19
|
+
i ||= 'hey'
|
20
|
+
p i
|
21
|
+
end
|
22
|
+
|
23
|
+
flag(:z, :apple, "More applez") {}
|
24
|
+
|
18
25
|
command(:add, "Add something") {
|
19
26
|
|
20
|
-
|
27
|
+
header "Usage: bin_test add [options]\n" +
|
28
|
+
"Use add to add things to add, which in turn can be added."
|
21
29
|
|
22
30
|
switch(:f, :full, "Use full") {
|
23
31
|
options[:full] = true
|
@@ -27,7 +35,15 @@ c = Clive.new do
|
|
27
35
|
switch(:e, :empty, "Use empty") {
|
28
36
|
options[:full] = false
|
29
37
|
}
|
38
|
+
|
39
|
+
footer "If the header was a little confusing, don't worry the footer thought so too."
|
40
|
+
|
41
|
+
}
|
30
42
|
|
43
|
+
command(:remove, :delete, "Get rid of something") {
|
44
|
+
switch(:f, :force, "Force") {
|
45
|
+
p "deleting! now"
|
46
|
+
}
|
31
47
|
}
|
32
48
|
|
33
49
|
end
|
data/test/test_boolean.rb
CHANGED
@@ -6,17 +6,23 @@ class TestBoolean < Test::Unit::TestCase
|
|
6
6
|
|
7
7
|
setup do
|
8
8
|
@c = Clive.new do
|
9
|
-
|
9
|
+
bool(:v, :verbose, "Run verbosely") {|i| puts(i)}
|
10
10
|
end
|
11
11
|
end
|
12
12
|
|
13
13
|
should "create two switches" do
|
14
|
-
assert_equal 2, @c.
|
14
|
+
assert_equal 2, @c.bools.length
|
15
|
+
end
|
16
|
+
|
17
|
+
should "raise error when no long name given" do
|
18
|
+
assert_raise Clive::MissingLongName do
|
19
|
+
Clive::Bool.new(:v, "Run verbosely") {}
|
20
|
+
end
|
15
21
|
end
|
16
22
|
|
17
23
|
context "the true switch" do
|
18
24
|
should "have a short name" do
|
19
|
-
|
25
|
+
assert_contains @c.bools["verbose"].names, "v"
|
20
26
|
end
|
21
27
|
|
22
28
|
should "pass true to the block" do
|
@@ -25,13 +31,13 @@ class TestBoolean < Test::Unit::TestCase
|
|
25
31
|
end
|
26
32
|
|
27
33
|
should "create summary" do
|
28
|
-
assert_equal "-v, --[no-]verbose Run verbosely", @c.
|
34
|
+
assert_equal "-v, --[no-]verbose Run verbosely", @c.bools["verbose"].summary(0, 0)
|
29
35
|
end
|
30
36
|
end
|
31
37
|
|
32
38
|
context "the false switch" do
|
33
39
|
should "not have short name" do
|
34
|
-
|
40
|
+
assert_does_not_contain @c.bools["no-verbose"].names, "v"
|
35
41
|
end
|
36
42
|
|
37
43
|
should "pass false to the block" do
|
@@ -40,9 +46,10 @@ class TestBoolean < Test::Unit::TestCase
|
|
40
46
|
end
|
41
47
|
|
42
48
|
should "not create summary" do
|
43
|
-
assert_equal nil, @c.
|
49
|
+
assert_equal nil, @c.bools["no-verbose"].summary
|
44
50
|
end
|
45
51
|
end
|
46
52
|
|
47
53
|
end
|
54
|
+
|
48
55
|
end
|
data/test/test_clive.rb
CHANGED
@@ -30,8 +30,8 @@ class TestClive < Test::Unit::TestCase
|
|
30
30
|
c = Clive.new do
|
31
31
|
boolean(:verbose) {}
|
32
32
|
end
|
33
|
-
assert_equal
|
34
|
-
assert_instance_of Clive::
|
33
|
+
assert_equal 2, c.bools.length
|
34
|
+
assert_instance_of Clive::Bool, c.bools[0]
|
35
35
|
end
|
36
36
|
|
37
37
|
context "When parsing input" do
|
@@ -216,7 +216,40 @@ class TestClive < Test::Unit::TestCase
|
|
216
216
|
r = {:v => true, :add => {:full => true, :init => {:base => true, :name => 'Works'}} }
|
217
217
|
assert_equal r, opts
|
218
218
|
end
|
219
|
+
|
220
|
+
should "parse the one in the readme" do
|
221
|
+
opts = {}
|
222
|
+
c = Clive.new do
|
223
|
+
bool(:v, :verbose, "Run verbosely") {|i| opts[:verbose] = i}
|
224
|
+
|
225
|
+
command(:add, "Add a new project") do
|
226
|
+
opts[:add] = {}
|
227
|
+
|
228
|
+
switch(:force, "Force overwrite") {opts[:add][:force] = true}
|
229
|
+
flag(:framework, "Add framework") do |i|
|
230
|
+
opts[:add][:framework] ||= []
|
231
|
+
opts[:add][:framework] << i
|
232
|
+
end
|
233
|
+
|
234
|
+
command(:init, "Initialize the project after creating") do
|
235
|
+
switch(:m, :minimum, "Use minimum settings") {opts[:add][:min] = true}
|
236
|
+
flag(:w, :width) {|i| opts[:add][:width] = i.to_i}
|
237
|
+
end
|
238
|
+
|
239
|
+
end
|
240
|
+
|
241
|
+
switch(:version, "Show version") do
|
242
|
+
puts "1.0.0"
|
243
|
+
exit
|
244
|
+
end
|
245
|
+
end
|
246
|
+
argv = %w(-v add --framework=blueprint init -m -w 200 ~/Desktop/new_thing ~/Desktop/another_thing)
|
247
|
+
args = c.parse(argv)
|
248
|
+
|
249
|
+
opts_r = {:add => {:min => true, :width => 200, :framework => ["blueprint"]}, :verbose => true}
|
250
|
+
assert_equal opts_r, opts
|
251
|
+
assert_equal ["~/Desktop/new_thing", "~/Desktop/another_thing"], args
|
252
|
+
end
|
219
253
|
|
220
254
|
end
|
221
|
-
|
222
255
|
end
|