facets 2.0.2 → 2.0.3
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/AUTHORS +3 -3
- data/README +6 -7
- data/lib/core/facets.rb +1 -46
- data/lib/core/facets/array.rb +3 -0
- data/lib/core/facets/array/indexable.rb +6 -1
- data/lib/core/facets/array/only.rb +20 -0
- data/lib/core/facets/dir/multiglob.rb +12 -1
- data/lib/core/facets/enumerable.rb +0 -1
- data/lib/core/facets/enumerable/collect.rb +1 -0
- data/lib/core/facets/enumerable/combination.rb +44 -90
- data/lib/core/facets/facets.rb +46 -0
- data/lib/core/facets/file/write.rb +46 -58
- data/lib/core/facets/hash.rb +2 -0
- data/lib/core/facets/hash/select.rb +14 -0
- data/lib/core/facets/integer/multiples.rb +12 -55
- data/lib/core/facets/kernel/val.rb +14 -0
- data/lib/core/facets/module/alias.rb +28 -9
- data/lib/core/facets/module/methods.rb +18 -0
- data/lib/core/facets/module/traits.rb +65 -70
- data/lib/core/facets/proc/compose.rb +15 -12
- data/lib/core/facets/stackable.rb +3 -2
- data/lib/core/facets/string/format.rb +4 -6
- data/lib/core/facets/string/tabs.rb +34 -0
- data/lib/core/facets/symbol.rb +1 -0
- data/lib/core/facets/symbol/succ.rb +1 -42
- data/lib/core/facets/symbol/to_proc.rb +34 -0
- data/lib/methods/facets/array/contains.rb +1 -0
- data/lib/methods/facets/facets/require_core.rb +1 -0
- data/lib/methods/facets/file/writelines.rb +1 -0
- data/lib/methods/facets/io/writelines.rb +1 -0
- data/lib/methods/facets/kernel/not_nil.rb +1 -0
- data/lib/methods/facets/module/conflict.rb +1 -0
- data/lib/methods/facets/module/instance_method_defined.rb +1 -0
- data/lib/methods/facets/module/module_method_defined.rb +1 -0
- data/lib/methods/facets/module/private_conflict.rb +1 -0
- data/lib/methods/facets/module/protected_conflict.rb +1 -0
- data/lib/methods/facets/module/public_conflict.rb +1 -0
- data/lib/methods/facets/string/expand_tabs.rb +1 -0
- data/lib/methods/facets/string/outdent.rb +1 -0
- data/lib/methods/facets/string/taballto.rb +1 -0
- data/lib/more/facets/arguments.rb +1 -1
- data/lib/more/facets/association.rb +0 -46
- data/lib/more/facets/autoarray.rb +0 -28
- data/lib/more/facets/command.rb +341 -8
- data/lib/more/facets/dictionary.rb +25 -131
- data/lib/more/facets/downloader.rb +1 -1
- data/lib/more/facets/infinity.rb +3 -3
- data/lib/more/facets/interval.rb +0 -161
- data/lib/more/facets/multiton.rb +16 -12
- data/lib/more/facets/namespace.rb +1 -1
- data/lib/more/facets/ostruct.rb +46 -10
- data/lib/more/facets/overload.rb +0 -51
- data/lib/more/facets/paramix.rb +0 -97
- data/lib/more/facets/pp_s.rb +30 -0
- data/lib/more/facets/progressbar.rb +18 -10
- data/lib/more/facets/prototype.rb +1 -40
- data/lib/more/facets/random.rb +1 -0
- data/lib/more/facets/rbsystem.rb +4 -1
- data/lib/more/facets/snapshot.rb +8 -1
- data/lib/more/facets/stylize.rb +2 -0
- data/meta/{project.yaml → facets-2.0.3.roll} +22 -14
- data/meta/manifest.txt +38 -8
- data/task/{config.yaml → config/general.yaml} +7 -2
- data/task/{rdoc.yaml → config/rdoc.yaml} +1 -1
- data/task/crosstest +309 -0
- data/task/isotest +293 -0
- data/task/loadtest +28 -0
- data/task/methods +4 -4
- data/task/prepare +5 -0
- data/task/publish +2 -2
- data/task/rdoc +1 -0
- data/task/syntax +29 -0
- data/task/test +0 -1
- data/task/testeach +42 -0
- data/task/testpairs +50 -0
- data/test/lib/rq.rb +15 -0
- data/test/unit/array/test_merge.rb +21 -43
- data/test/unit/array/test_only.rb +21 -0
- data/test/unit/enumerable/test_collect.rb +1 -21
- data/test/unit/enumerable/test_combination.rb +50 -44
- data/test/unit/file/test_topath.rb +48 -57
- data/test/unit/file/test_write.rb +82 -0
- data/test/unit/hash/test_select.rb +43 -0
- data/test/unit/integer/test_multiples.rb +28 -32
- data/test/unit/kernel/test_report.rb +9 -12
- data/test/unit/kernel/test_val.rb +50 -45
- data/test/unit/module/test_include.rb +56 -57
- data/test/unit/module/test_name.rb +42 -55
- data/test/unit/module/test_traits.rb +46 -47
- data/test/unit/string/test_filter.rb +19 -34
- data/test/unit/string/test_format.rb +87 -96
- data/test/unit/string/test_regesc.rb +18 -26
- data/test/unit/string/test_tabs.rb +226 -119
- data/test/unit/symbol/test_succ.rb +14 -23
- data/test/unit/symbol/test_to_proc.rb +41 -0
- data/test/unit/test_association.rb +38 -47
- data/test/unit/test_attributes.rb +24 -33
- data/test/unit/test_autoarray.rb +23 -32
- data/test/unit/test_command.rb +26 -0
- data/test/unit/test_dictionary.rb +123 -117
- data/test/unit/test_infinity.rb +41 -47
- data/test/unit/test_inheritor.rb +133 -142
- data/test/unit/test_interval.rb +129 -93
- data/test/unit/test_ostruct.rb +148 -101
- data/test/unit/test_overload.rb +8 -15
- data/test/unit/test_paramix.rb +67 -73
- data/test/unit/test_pp_s.rb +23 -0
- data/test/unit/test_prototype.rb +28 -38
- metadata +47 -11
- data/lib/core/facets/enumerable/instance_map.rb +0 -0
- data/lib/more/facets/command_options.rb +0 -328
- data/meta/version.txt +0 -1
- data/task/load +0 -39
- data/test/unit/test_command_options.rb +0 -29
@@ -27,7 +27,7 @@ module Stackable
|
|
27
27
|
slice(0)
|
28
28
|
end
|
29
29
|
|
30
|
-
|
30
|
+
alias_method :shift, :pull
|
31
31
|
|
32
32
|
#
|
33
33
|
|
@@ -35,7 +35,7 @@ module Stackable
|
|
35
35
|
insert(0,x)
|
36
36
|
end
|
37
37
|
|
38
|
-
|
38
|
+
alias_method :unshift, :poke
|
39
39
|
|
40
40
|
#
|
41
41
|
|
@@ -44,3 +44,4 @@ module Stackable
|
|
44
44
|
end
|
45
45
|
|
46
46
|
end
|
47
|
+
|
@@ -17,13 +17,12 @@
|
|
17
17
|
#
|
18
18
|
class String
|
19
19
|
|
20
|
-
# CREDIT George Moschovitis
|
21
|
-
|
22
20
|
# Returns short abstract of long strings (first 'count'
|
23
21
|
# characters, chopped at the nearest word, appended by '...')
|
24
22
|
# force_cutoff: break forcibly at 'count' chars. Does not accept
|
25
23
|
# count < 2
|
26
|
-
|
24
|
+
#
|
25
|
+
# CREDIT George Moschovitis
|
27
26
|
def brief(count = 128, force_cutoff = false, ellipsis="...")
|
28
27
|
return nil if count < 2
|
29
28
|
|
@@ -148,9 +147,6 @@ class String
|
|
148
147
|
# return wrapped_string
|
149
148
|
# end
|
150
149
|
|
151
|
-
# CREDIT Gavin Kistner
|
152
|
-
# CREDIT Dayne Broderson
|
153
|
-
|
154
150
|
# Word wrap a string not exceeding max width.
|
155
151
|
#
|
156
152
|
# puts "this is a test".word_wrap(4)
|
@@ -161,6 +157,8 @@ class String
|
|
161
157
|
# is a
|
162
158
|
# test
|
163
159
|
#
|
160
|
+
# CREDIT Gavin Kistner
|
161
|
+
# CREDIT Dayne Broderson
|
164
162
|
def word_wrap( col_width=80 )
|
165
163
|
self.dup.word_wrap!( col_width )
|
166
164
|
end
|
@@ -20,6 +20,32 @@ class String
|
|
20
20
|
gsub(/^ */, ' ' * n)
|
21
21
|
end
|
22
22
|
|
23
|
+
alias_method :taballto, :tab
|
24
|
+
|
25
|
+
# Expands tabs to +n+ spaces. Non-destructive. If +n+ is 0, then tabs are
|
26
|
+
# simply removed. Raises an exception if +n+ is negative.
|
27
|
+
#
|
28
|
+
# Thanks to GGaramuno for a more efficient algorithm. Very nice.
|
29
|
+
#
|
30
|
+
# CREDIT Noah Gibbs
|
31
|
+
# CREDIT Gavin Sinclair
|
32
|
+
# CREDIT GGaramuno
|
33
|
+
|
34
|
+
def expand_tabs(n=8)
|
35
|
+
n = n.to_int
|
36
|
+
raise ArgumentError, "n must be >= 0" if n < 0
|
37
|
+
return gsub(/\t/, "") if n == 0
|
38
|
+
return gsub(/\t/, " ") if n == 1
|
39
|
+
str = self.dup
|
40
|
+
while
|
41
|
+
str.gsub!(/^([^\t\n]*)(\t+)/) { |f|
|
42
|
+
val = ( n * $2.size - ($1.size % n) )
|
43
|
+
$1 << (' ' * val)
|
44
|
+
}
|
45
|
+
end
|
46
|
+
str
|
47
|
+
end
|
48
|
+
|
23
49
|
# Preserves relative tabbing.
|
24
50
|
# The first non-empty line ends up with n spaces before nonspace.
|
25
51
|
|
@@ -42,6 +68,14 @@ class String
|
|
42
68
|
end
|
43
69
|
end
|
44
70
|
|
71
|
+
# Outdent just indents a negative number of spaces.
|
72
|
+
#
|
73
|
+
# CREDIT Noah Gibbs
|
74
|
+
|
75
|
+
def outdent(n)
|
76
|
+
indent(-n)
|
77
|
+
end
|
78
|
+
|
45
79
|
# Provides a margin controlled string.
|
46
80
|
#
|
47
81
|
# x = %Q{
|
data/lib/core/facets/symbol.rb
CHANGED
@@ -1,21 +1,3 @@
|
|
1
|
-
# TITLE:
|
2
|
-
#
|
3
|
-
# Symbol Generation
|
4
|
-
#
|
5
|
-
# DESCRIPTION:
|
6
|
-
#
|
7
|
-
# Symbol generation extensions.
|
8
|
-
#
|
9
|
-
# AUTHORS:
|
10
|
-
#
|
11
|
-
# CREDIT Thomas Sawyer
|
12
|
-
#
|
13
|
-
# NOTES:
|
14
|
-
#
|
15
|
-
# TODO Is Symbol#chomp worth having? Are the any other
|
16
|
-
# String methods that Symbols really should have too?
|
17
|
-
|
18
|
-
#
|
19
1
|
class Symbol
|
20
2
|
|
21
3
|
# Successor method for symobol. This simply converts
|
@@ -26,7 +8,7 @@ class Symbol
|
|
26
8
|
#
|
27
9
|
#--
|
28
10
|
# In the future I would like this to work more like
|
29
|
-
# a simple
|
11
|
+
# a simple character dial.
|
30
12
|
#++
|
31
13
|
|
32
14
|
def succ
|
@@ -35,26 +17,3 @@ class Symbol
|
|
35
17
|
|
36
18
|
end
|
37
19
|
|
38
|
-
|
39
|
-
# _____ _
|
40
|
-
# |_ _|__ ___| |_
|
41
|
-
# | |/ _ \/ __| __|
|
42
|
-
# | | __/\__ \ |_
|
43
|
-
# |_|\___||___/\__|
|
44
|
-
#
|
45
|
-
=begin test
|
46
|
-
|
47
|
-
require 'test/unit'
|
48
|
-
|
49
|
-
class TestSymbol < Test::Unit::TestCase
|
50
|
-
|
51
|
-
def test_succ
|
52
|
-
assert_equal( :b, :a.succ )
|
53
|
-
assert_equal( :aab, :aaa.succ )
|
54
|
-
assert_equal( :"2", :"1".succ )
|
55
|
-
end
|
56
|
-
|
57
|
-
end
|
58
|
-
|
59
|
-
=end
|
60
|
-
|
@@ -0,0 +1,34 @@
|
|
1
|
+
class Symbol
|
2
|
+
|
3
|
+
# Turn a symbol into a proc calling the method to
|
4
|
+
# which it refers.
|
5
|
+
#
|
6
|
+
# up = :upcase.to_proc
|
7
|
+
# up.call("hello") #=> HELLO
|
8
|
+
#
|
9
|
+
# More useful is the fact that this allows <tt>&</tt>
|
10
|
+
# to be used to coerce Symbol into Proc.
|
11
|
+
#
|
12
|
+
# %w{foo bar qux}.map(&:upcase) #=> ["FOO","BAR","QUX"]
|
13
|
+
# [1, 2, 3].inject(&:+) #=> 6
|
14
|
+
#
|
15
|
+
# And other conveniences such as:
|
16
|
+
#
|
17
|
+
# %{john terry fiona}.map(&:capitalize) # -> %{John Terry Fiona}
|
18
|
+
# sum = numbers.inject(&:+)
|
19
|
+
#
|
20
|
+
# TODO This will be deprecated as of Ruby 1.9, since it will become standard Ruby.
|
21
|
+
#
|
22
|
+
# CREDIT Florian Gross (orignal)
|
23
|
+
# CREDIT Nobuhiro Imai (current)
|
24
|
+
|
25
|
+
def to_proc
|
26
|
+
Proc.new{|*args| args.shift.__send__(self, *args)}
|
27
|
+
end
|
28
|
+
|
29
|
+
#def to_proc
|
30
|
+
# proc { |obj, *args| obj.send(self, *args) }
|
31
|
+
#end
|
32
|
+
|
33
|
+
end
|
34
|
+
|
@@ -0,0 +1 @@
|
|
1
|
+
require 'facets/array/indexable.rb'
|
@@ -0,0 +1 @@
|
|
1
|
+
require 'facets/facets.rb'
|
@@ -0,0 +1 @@
|
|
1
|
+
require 'facets/file/write.rb'
|
@@ -0,0 +1 @@
|
|
1
|
+
require 'facets/io/write.rb'
|
@@ -0,0 +1 @@
|
|
1
|
+
require 'facets/kernel/val.rb'
|
@@ -0,0 +1 @@
|
|
1
|
+
require 'facets/module/traits.rb'
|
@@ -0,0 +1 @@
|
|
1
|
+
require 'facets/module/methods.rb'
|
@@ -0,0 +1 @@
|
|
1
|
+
require 'facets/module/methods.rb'
|
@@ -0,0 +1 @@
|
|
1
|
+
require 'facets/module/traits.rb'
|
@@ -0,0 +1 @@
|
|
1
|
+
require 'facets/module/traits.rb'
|
@@ -0,0 +1 @@
|
|
1
|
+
require 'facets/module/traits.rb'
|
@@ -0,0 +1 @@
|
|
1
|
+
require 'facets/string/tabs.rb'
|
@@ -0,0 +1 @@
|
|
1
|
+
require 'facets/string/tabs.rb'
|
@@ -0,0 +1 @@
|
|
1
|
+
require 'facets/string/tabs.rb'
|
@@ -155,49 +155,3 @@ end
|
|
155
155
|
# remove_method :>>
|
156
156
|
# end
|
157
157
|
#++
|
158
|
-
|
159
|
-
|
160
|
-
# _____ _
|
161
|
-
# |_ _|__ ___| |_
|
162
|
-
# | |/ _ \/ __| __|
|
163
|
-
# | | __/\__ \ |_
|
164
|
-
# |_|\___||___/\__|
|
165
|
-
#
|
166
|
-
=begin test
|
167
|
-
|
168
|
-
require 'test/unit'
|
169
|
-
|
170
|
-
class TC01 < Test::Unit::TestCase
|
171
|
-
|
172
|
-
def setup
|
173
|
-
@complex_hierarchy = [
|
174
|
-
'parent' >> 'child',
|
175
|
-
'childless',
|
176
|
-
'another_parent' >> [
|
177
|
-
'subchildless',
|
178
|
-
'subparent' >> 'subchild'
|
179
|
-
]
|
180
|
-
]
|
181
|
-
end
|
182
|
-
|
183
|
-
def test_ohash
|
184
|
-
k,v = [],[]
|
185
|
-
ohash = [ 'A' >> '3', 'B' >> '2', 'C' >> '1' ]
|
186
|
-
ohash.each { |e1,e2| k << e1 ; v << e2 }
|
187
|
-
assert_equal( ['A','B','C'], k )
|
188
|
-
assert_equal( ['3','2','1'], v )
|
189
|
-
end
|
190
|
-
|
191
|
-
def test_complex
|
192
|
-
complex = [ 'Drop Menu' >> [ 'Button 1', 'Button 2', 'Button 3' ], 'Help' ]
|
193
|
-
assert_equal( 'Drop Menu', complex[0].index )
|
194
|
-
end
|
195
|
-
|
196
|
-
def test_associations
|
197
|
-
complex = [ :a >> :b, :a >> :c ]
|
198
|
-
assert_equal( [ :b, :c ], :a.associations )
|
199
|
-
end
|
200
|
-
|
201
|
-
end
|
202
|
-
|
203
|
-
=end
|
@@ -57,31 +57,3 @@ class Autoarray < Array
|
|
57
57
|
end
|
58
58
|
|
59
59
|
end
|
60
|
-
|
61
|
-
|
62
|
-
# _____ _
|
63
|
-
# |_ _|__ ___| |_
|
64
|
-
# | |/ _ \/ __| __|
|
65
|
-
# | | __/\__ \ |_
|
66
|
-
# |_|\___||___/\__|
|
67
|
-
#
|
68
|
-
|
69
|
-
=begin test
|
70
|
-
|
71
|
-
require 'test/unit'
|
72
|
-
|
73
|
-
class TC_Autoarray
|
74
|
-
|
75
|
-
def test_001
|
76
|
-
a = Autoarray.new
|
77
|
-
assert_equal( 12, a[1][2][3] = 12 )
|
78
|
-
assert_equal( [nil, [nil, nil, [nil, nil, nil, 12]]], a )
|
79
|
-
assert_equal( [], a[2][3][4] )
|
80
|
-
assert_equal( [nil, [nil, nil, [nil, nil, nil, 12]]], a )
|
81
|
-
assert_equal( "Negative", a[1][-2][1] = "Negative" )
|
82
|
-
assert_equal( [nil, [nil, [nil, "Negative"], [nil, nil, nil, 12]]], a )
|
83
|
-
end
|
84
|
-
|
85
|
-
end
|
86
|
-
|
87
|
-
=end
|
data/lib/more/facets/command.rb
CHANGED
@@ -19,15 +19,21 @@
|
|
19
19
|
# PURPOSE.
|
20
20
|
#
|
21
21
|
# AUTHORS:
|
22
|
-
#
|
22
|
+
#
|
23
|
+
# - 7rans
|
23
24
|
# - Tyler Rick
|
24
25
|
#
|
25
26
|
# TODOs:
|
27
|
+
#
|
26
28
|
# - Add help/documentation features.
|
27
|
-
# - Problem
|
29
|
+
# - Problem with exit -1 when testing. See IMPORTANT!!! remark below.
|
30
|
+
#
|
31
|
+
# LOG:
|
32
|
+
#
|
33
|
+
# - 2007.10.31 TRANS
|
34
|
+
# Re-added support for __option notation.
|
28
35
|
|
29
36
|
require 'shellwords'
|
30
|
-
require 'facets/command_options'
|
31
37
|
#require 'facets/annotations' # for help ?
|
32
38
|
|
33
39
|
module Console
|
@@ -72,10 +78,10 @@ module Console
|
|
72
78
|
module Syntax
|
73
79
|
|
74
80
|
# Starts the command execution.
|
75
|
-
def execute(
|
81
|
+
def execute(*args)
|
76
82
|
cmd = new()
|
77
83
|
#cmd.instance_variable_set("@global_options",global_options)
|
78
|
-
cmd.execute(
|
84
|
+
cmd.execute(*args)
|
79
85
|
end
|
80
86
|
alias_method :start, :execute
|
81
87
|
|
@@ -90,7 +96,7 @@ module Console
|
|
90
96
|
def options(name, klass=nil, &block)
|
91
97
|
raise ArgumentError if klass && block
|
92
98
|
if block
|
93
|
-
command_options[name.to_sym] = Class.new(
|
99
|
+
command_options[name.to_sym] = Class.new(Options, &block)
|
94
100
|
else
|
95
101
|
command_options[name.to_sym] = klass
|
96
102
|
end
|
@@ -119,10 +125,10 @@ module Console
|
|
119
125
|
|
120
126
|
#
|
121
127
|
|
122
|
-
def execute(line)
|
128
|
+
def execute(line=ARGV)
|
123
129
|
argv = line
|
124
130
|
|
125
|
-
g_opts =
|
131
|
+
g_opts = Command::Options.new(self)
|
126
132
|
g_keys = self.class.global_options
|
127
133
|
|
128
134
|
# Deal with global options.
|
@@ -259,4 +265,331 @@ module Console
|
|
259
265
|
extend Help::ClassMethods
|
260
266
|
=end
|
261
267
|
|
268
|
+
|
269
|
+
# = Command::Options
|
270
|
+
#
|
271
|
+
# CommandOptions provides the basis for Command to Object Mapping (COM).
|
272
|
+
# It is an commandline options parser that uses method definitions
|
273
|
+
# as means of interprting command arguments.
|
274
|
+
#
|
275
|
+
# == Synopsis
|
276
|
+
#
|
277
|
+
# Let's make an executable called 'mycmd'.
|
278
|
+
#
|
279
|
+
# #!/usr/bin/env ruby
|
280
|
+
#
|
281
|
+
# require 'facets/command_options'
|
282
|
+
#
|
283
|
+
# class MyOptions < CommandOptions
|
284
|
+
# attr_accessor :file
|
285
|
+
#
|
286
|
+
# def v!
|
287
|
+
# @verbose = true
|
288
|
+
# end
|
289
|
+
# end
|
290
|
+
#
|
291
|
+
# opts = MyOptions.parse("-v --file hello.rb")
|
292
|
+
#
|
293
|
+
# opts.verbose #=> true
|
294
|
+
# opts.file #=> "hello.rb"
|
295
|
+
#
|
296
|
+
#--
|
297
|
+
# == Global Options
|
298
|
+
#
|
299
|
+
# You can define <i>global options</i> which are options that will be
|
300
|
+
# processed no matter where they occur in the command line. In the above
|
301
|
+
# examples only the options occuring before the subcommand are processed
|
302
|
+
# globally. Anything occuring after the subcommand belonds strictly to
|
303
|
+
# the subcommand. For instance, if we had added the following to the above
|
304
|
+
# example:
|
305
|
+
#
|
306
|
+
# global_option :_v
|
307
|
+
#
|
308
|
+
# Then -v could appear anywhere in the command line, even on the end,
|
309
|
+
# and still work as expected.
|
310
|
+
#
|
311
|
+
# % mycmd jump -h 3 -v
|
312
|
+
#++
|
313
|
+
#
|
314
|
+
# == Missing Options
|
315
|
+
#
|
316
|
+
# You can use #option_missing to catch any options that are not explicility
|
317
|
+
# defined.
|
318
|
+
#
|
319
|
+
# The method signature should look like:
|
320
|
+
#
|
321
|
+
# option_missing(option_name, args)
|
322
|
+
#
|
323
|
+
# Example:
|
324
|
+
# def option_missing(option_name, args)
|
325
|
+
# p args if $debug
|
326
|
+
# case option_name
|
327
|
+
# when 'p'
|
328
|
+
# @a = args[0].to_i
|
329
|
+
# @b = args[1].to_i
|
330
|
+
# 2
|
331
|
+
# else
|
332
|
+
# raise InvalidOptionError(option_name, args)
|
333
|
+
# end
|
334
|
+
# end
|
335
|
+
#
|
336
|
+
# Its return value should be the effective "arity" of that options -- that is,
|
337
|
+
# how many arguments it consumed ("-p a b", for example, would consume 2 args:
|
338
|
+
# "a" and "b"). An arity of 1 is assumed if nil or false is returned.
|
339
|
+
|
340
|
+
class Command::Options
|
341
|
+
|
342
|
+
def self.parse(*line_and_options)
|
343
|
+
o = new
|
344
|
+
o.parse(*line_and_options)
|
345
|
+
o
|
346
|
+
end
|
347
|
+
|
348
|
+
def initialize(delegate=nil)
|
349
|
+
@__self__ = delegate || self
|
350
|
+
end
|
351
|
+
|
352
|
+
# Parse line for options in the context self.
|
353
|
+
#
|
354
|
+
# Options:
|
355
|
+
#
|
356
|
+
# :pass => true || false
|
357
|
+
#
|
358
|
+
# Setting this to true prevents the parse_missing routine from running.
|
359
|
+
#
|
360
|
+
# :only => [ global options, ... ]
|
361
|
+
#
|
362
|
+
# When processing global options, we only want to parse selected options.
|
363
|
+
# This also set +pass+ to true.
|
364
|
+
#
|
365
|
+
# :stop => true || false
|
366
|
+
#
|
367
|
+
# If we are parsing options for the *main* command and we are allowing
|
368
|
+
# subcommands, then we want to stop as soon as we get to the first non-option,
|
369
|
+
# because that non-option will be the name of our subcommand and all options that
|
370
|
+
# follow should be parsed later when we handle the subcommand.
|
371
|
+
# This also set +pass+ to true.
|
372
|
+
|
373
|
+
def parse(*line_and_options)
|
374
|
+
__self__ = @__self__
|
375
|
+
|
376
|
+
if Hash === line_and_options.last
|
377
|
+
options = line_and_options.pop
|
378
|
+
line = line_and_options.first
|
379
|
+
else
|
380
|
+
options = {}
|
381
|
+
line = line_and_options.first
|
382
|
+
end
|
383
|
+
|
384
|
+
case line
|
385
|
+
when String
|
386
|
+
argv = Shellwords.shellwords(line)
|
387
|
+
when Array
|
388
|
+
argv = line.dup
|
389
|
+
else
|
390
|
+
argv = ARGV.dup
|
391
|
+
end
|
392
|
+
|
393
|
+
only = options[:only] # only parse these options
|
394
|
+
stop = options[:stop] # stop at first non-option
|
395
|
+
pass = options[:pass] || only || stop # don't run options_missing
|
396
|
+
|
397
|
+
if $debug
|
398
|
+
puts(only ? "\nGlobal parsing..." : "\nParsing...")
|
399
|
+
end
|
400
|
+
|
401
|
+
puts "# line: #{argv.inspect}" if $debug
|
402
|
+
|
403
|
+
# Split single letter option groupings into separate options.
|
404
|
+
# ie. -xyz => -x -y -z
|
405
|
+
argv = argv.collect { |arg|
|
406
|
+
if md = /^-(\w{2,})/.match( arg )
|
407
|
+
md[1].split(//).collect { |c| "-#{c}" }
|
408
|
+
else
|
409
|
+
arg
|
410
|
+
end
|
411
|
+
}.flatten
|
412
|
+
|
413
|
+
index = 0
|
414
|
+
|
415
|
+
until index >= argv.size
|
416
|
+
arg = argv.at(index)
|
417
|
+
break if arg == '--' # POSIX compliance
|
418
|
+
if arg[0,1] == '-'
|
419
|
+
puts "# option: #{arg}" if $debug
|
420
|
+
cnt = (arg[0,2] == '--' ? 2 : 1)
|
421
|
+
#opt = Option.new(arg)
|
422
|
+
#name = opt.methodize
|
423
|
+
name = arg.sub(/^-{1,2}/,'')
|
424
|
+
skip = only && only.include?(name)
|
425
|
+
unam = ('__'*cnt)+name
|
426
|
+
if __self__.respond_to?(unam)
|
427
|
+
puts "# method: #{uname}" if $debug
|
428
|
+
meth = method(unam)
|
429
|
+
arity = meth.arity
|
430
|
+
if arity < 0
|
431
|
+
meth.call(*argv.slice(index+1..-1)) unless skip
|
432
|
+
arity[index..-1] = nil # Get rid of the *name* and values
|
433
|
+
elsif arity == 0
|
434
|
+
meth.call unless skip
|
435
|
+
argv.delete_at(index) # Get rid of the *name* of the option
|
436
|
+
else
|
437
|
+
meth.call(*argv.slice(index+1, arity)) unless skip
|
438
|
+
#argv.delete_at(index) # Get rid of the *name* of the option
|
439
|
+
#arity.times{ argv.delete_at(index) } # Get rid of the *value* of the option
|
440
|
+
arity[index,arity] = nil
|
441
|
+
end
|
442
|
+
elsif __self__.respond_to?(name+'=')
|
443
|
+
puts "# method: #{name}=" if $debug
|
444
|
+
__self__.send(name+'=', *argv.slice(index+1, 1)) unless skip
|
445
|
+
argv.delete_at(index) # Get rid of the *name* of the option
|
446
|
+
argv.delete_at(index) # Get rid of the *value* of the option
|
447
|
+
elsif __self__.respond_to?(name+'!')
|
448
|
+
puts "# method: #{name}!" if $debug
|
449
|
+
__self__.send(name+'!') unless skip
|
450
|
+
argv.delete_at(index) # Get rid of the *name* of the option
|
451
|
+
else
|
452
|
+
index += 1
|
453
|
+
end
|
454
|
+
else
|
455
|
+
index += 1
|
456
|
+
break if stop
|
457
|
+
end
|
458
|
+
end
|
459
|
+
# parse missing ?
|
460
|
+
argv = parse_missing(argv) unless pass
|
461
|
+
# return the remaining argv
|
462
|
+
puts "# return: #{argv.inspect}" if $debug
|
463
|
+
return argv
|
464
|
+
end
|
465
|
+
|
466
|
+
#
|
467
|
+
|
468
|
+
def parse_missing(argv)
|
469
|
+
argv.each_with_index do |a,i|
|
470
|
+
if a =~ /^-/
|
471
|
+
#raise InvalidOptionError.new(a) unless @__self__.respond_to?(:option_missing)
|
472
|
+
kept = @__self__.option_missing(a, *argv[i+1,1])
|
473
|
+
argv.delete_at(i) if kept # delete if value kept
|
474
|
+
argv.delete_at(i) # delete option
|
475
|
+
end
|
476
|
+
end
|
477
|
+
return argv
|
478
|
+
end
|
479
|
+
|
480
|
+
#
|
481
|
+
|
482
|
+
def option_missing(opt, arg=nil)
|
483
|
+
raise InvalidOptionError.new(opt)
|
484
|
+
# #$stderr << "Unknown option '#{arg}'.\n"
|
485
|
+
# #exit -1
|
486
|
+
end
|
487
|
+
|
488
|
+
#
|
489
|
+
|
490
|
+
def to_h
|
491
|
+
#writers = public_methods.sellect{ |m| m =~ /=$/ }
|
492
|
+
instance_variables.inject({}) do |h, v|
|
493
|
+
h[v[1,-1]] = instance_variable_get(v); h
|
494
|
+
end
|
495
|
+
end
|
496
|
+
|
497
|
+
# Provides a very basic usage help string.
|
498
|
+
#
|
499
|
+
# TODO Add support for __options.
|
500
|
+
def usage
|
501
|
+
str = []
|
502
|
+
public_methods(false).sort.each do |meth|
|
503
|
+
meth = meth.to_s
|
504
|
+
case meth
|
505
|
+
when /^_/
|
506
|
+
opt = meth.sub(/^_+/, '')
|
507
|
+
meth = method(meth)
|
508
|
+
if meth.arity == 0
|
509
|
+
str << (opt.size > 1 ? "[--#{opt}]" : "[-#{opt}]")
|
510
|
+
elsif meth.arity == 1
|
511
|
+
str << (opt.size > 1 ? "[--#{opt} value]" : "[-#{opt} value]")
|
512
|
+
elsif meth.arity > 0
|
513
|
+
v = []; meth.arity.times{ |i| v << 'value' + (i + 1).to_s }
|
514
|
+
str << (opt.size > 1 ? "[--#{opt} #{v.join(' ')}]" : "[-#{opt} #{v.join(' ')}]")
|
515
|
+
else
|
516
|
+
str << (opt.size > 1 ? "[--#{opt} *values]" : "[-#{opt} *values]")
|
517
|
+
end
|
518
|
+
when /=$/
|
519
|
+
opt = meth.chomp('=')
|
520
|
+
str << (opt.size > 1 ? "[--#{opt} value]" : "[-#{opt} value]")
|
521
|
+
when /!$/
|
522
|
+
opt = meth.chomp('!')
|
523
|
+
str << (opt.size > 1 ? "[--#{opt}]" : "[-#{opt}]")
|
524
|
+
end
|
525
|
+
end
|
526
|
+
return str.join(" ")
|
527
|
+
end
|
528
|
+
|
529
|
+
#
|
530
|
+
|
531
|
+
def self.usage_class(usage)
|
532
|
+
c = Class.new(self)
|
533
|
+
argv = Shellwords.shellwords(usage)
|
534
|
+
argv.each_with_index do |name, i|
|
535
|
+
if name =~ /^-/
|
536
|
+
if argv[i+1] =~ /^[(.*?)]/
|
537
|
+
c.class_eval %{
|
538
|
+
attr_accessor :#{name}
|
539
|
+
}
|
540
|
+
else
|
541
|
+
c.class_eval %{
|
542
|
+
attr_reader :#{name}
|
543
|
+
def #{name}! ; @#{name} = true ; end
|
544
|
+
}
|
545
|
+
end
|
546
|
+
end
|
547
|
+
end
|
548
|
+
return c
|
549
|
+
end
|
550
|
+
|
551
|
+
# # Single Option
|
552
|
+
#
|
553
|
+
# class Option < String
|
554
|
+
#
|
555
|
+
# def initialize(option)
|
556
|
+
# @flag = option
|
557
|
+
# @long = (/^--/ =~ option)
|
558
|
+
# super(option.sub(/^-{1,2}/,''))
|
559
|
+
# end
|
560
|
+
#
|
561
|
+
# def long?
|
562
|
+
# @long
|
563
|
+
# end
|
564
|
+
#
|
565
|
+
# def short?
|
566
|
+
# !@long
|
567
|
+
# end
|
568
|
+
#
|
569
|
+
# #def demethodize
|
570
|
+
# # sub('__','--').sub('_','-')
|
571
|
+
# #end
|
572
|
+
#
|
573
|
+
# def methodize
|
574
|
+
# @flag.sub(/^-{1,2}/,'')
|
575
|
+
# end
|
576
|
+
#
|
577
|
+
# end
|
578
|
+
|
579
|
+
end
|
580
|
+
|
581
|
+
# For CommandOptions, but defined external to it, so
|
582
|
+
# that it is easy to access from user defined commands.
|
583
|
+
# (This lookup issue should be fixed in Ruby 1.9+, and then
|
584
|
+
# the class can be moved back into Command namespace.)
|
585
|
+
|
586
|
+
class InvalidOptionError < StandardError
|
587
|
+
def initialize(option_name)
|
588
|
+
@option_name = option_name
|
589
|
+
end
|
590
|
+
def message
|
591
|
+
"Unknown option '#{@option_name}'."
|
592
|
+
end
|
593
|
+
end
|
594
|
+
|
262
595
|
end
|