rook 0.0.2 → 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- data/README.txt +124 -37
- data/bin/rook +8 -902
- data/doc-api/classes/BZ2.html +111 -0
- data/doc-api/classes/BZ2/Reader.html +234 -0
- data/doc-api/classes/Digest.html +107 -0
- data/doc-api/classes/Digest/Base.html +254 -0
- data/doc-api/classes/Enumerable.html +430 -0
- data/doc-api/classes/File.html +208 -0
- data/doc-api/classes/FileUtils.html +758 -0
- data/doc-api/classes/Kernel.html +317 -0
- data/doc-api/classes/Rook.html +148 -0
- data/doc-api/classes/Rook/Assertion.html +164 -0
- data/doc-api/classes/Rook/AssertionError.html +111 -0
- data/doc-api/classes/Rook/CommandOptionError.html +113 -0
- data/doc-api/classes/Rook/Commands.html +1158 -0
- data/doc-api/classes/Rook/Cookbook.html +357 -0
- data/doc-api/classes/Rook/CookbookError.html +161 -0
- data/doc-api/classes/Rook/CookbookValidator.html +215 -0
- data/doc-api/classes/Rook/Kitchen.html +875 -0
- data/doc-api/classes/Rook/KitchenHelper.html +386 -0
- data/doc-api/classes/Rook/Main.html +325 -0
- data/doc-api/classes/Rook/Parameters.html +443 -0
- data/doc-api/classes/Rook/Recipe.html +546 -0
- data/doc-api/classes/Rook/RookError.html +111 -0
- data/doc-api/classes/Rook/Util.html +408 -0
- data/doc-api/classes/Rook/Util/UndefinedPropertyError.html +160 -0
- data/doc-api/created.rid +1 -0
- data/doc-api/files/__/README_txt.html +313 -0
- data/doc-api/files/rook/commands_rb.html +115 -0
- data/doc-api/files/rook/cookbook_rb.html +115 -0
- data/doc-api/files/rook/helper/bz2_rb.html +114 -0
- data/doc-api/files/rook/helper/digest_rb.html +114 -0
- data/doc-api/files/rook/helper/enumerable_rb.html +107 -0
- data/doc-api/files/rook/helper/file_rb.html +107 -0
- data/doc-api/files/rook/helper/fileutils_rb.html +124 -0
- data/doc-api/files/rook/helper/kernel_rb.html +107 -0
- data/doc-api/files/rook/kitchen_rb.html +117 -0
- data/doc-api/files/rook/main_rb.html +117 -0
- data/doc-api/files/rook/recipe_rb.html +114 -0
- data/doc-api/files/rook/util_rb.html +115 -0
- data/doc-api/files/rook_rb.html +122 -0
- data/doc-api/fr_class_index.html +50 -0
- data/doc-api/fr_file_index.html +40 -0
- data/doc-api/fr_method_index.html +181 -0
- data/doc-api/index.html +24 -0
- data/doc-api/rdoc-style.css +208 -0
- data/examples/hello_c/Rookbook.rb +37 -0
- data/examples/hello_c/Rookbook.yaml +45 -0
- data/examples/hello_c/hello.c +6 -3
- data/examples/project/README.txt +8 -0
- data/examples/project/Rookbook.props +1 -0
- data/examples/project/Rookbook.rb +120 -0
- data/examples/project/Rookbook.yaml +117 -0
- data/examples/project/bin/example +12 -0
- data/examples/project/example.gemspec +29 -0
- data/examples/project/lib/example.rb +36 -0
- data/examples/{archive → project}/lib/example/bar.rb +0 -0
- data/examples/{archive → project}/lib/example/baz.rb +0 -0
- data/examples/{archive → project}/lib/example/foo.rb +0 -0
- data/examples/project/setup.rb +1331 -0
- data/examples/project/test/test.rb +27 -0
- data/lib/rook.rb +50 -0
- data/lib/rook/commands.rb +426 -0
- data/lib/rook/cookbook.rb +237 -0
- data/lib/rook/helper/bz2.rb +39 -0
- data/lib/rook/helper/digest.rb +76 -0
- data/lib/rook/helper/enumerable.rb +121 -0
- data/lib/rook/helper/file.rb +50 -0
- data/lib/rook/helper/fileutils.rb +340 -0
- data/lib/rook/helper/kernel.rb +108 -0
- data/lib/rook/kitchen.rb +668 -0
- data/lib/rook/main.rb +280 -0
- data/lib/rook/recipe.rb +259 -0
- data/lib/rook/rookbook.schema.yaml +156 -0
- data/lib/rook/util.rb +172 -0
- data/rook.gemspec +56 -0
- metadata +139 -45
- data/examples/archive/COPYING +0 -340
- data/examples/archive/README.txt +0 -1
- data/examples/archive/Rookbook +0 -56
- data/examples/archive/bin/example +0 -0
- data/examples/archive/doc/index.html +0 -0
- data/examples/archive/doc/index.txt +0 -0
- data/examples/archive/lib/example.rb +0 -0
- data/examples/archive/test/test1.rb +0 -0
- data/examples/archive/test/test2.rb +0 -0
- data/examples/hello_c/Rookbook +0 -24
@@ -0,0 +1,237 @@
|
|
1
|
+
##
|
2
|
+
## $Rev: 31 $
|
3
|
+
## $Release: 0.1.0 $
|
4
|
+
## copyright(c) 2006 kuwata-lab.com all rights reserved.
|
5
|
+
##
|
6
|
+
|
7
|
+
|
8
|
+
require 'kwalify'
|
9
|
+
|
10
|
+
require 'rook/util'
|
11
|
+
|
12
|
+
|
13
|
+
|
14
|
+
module Rook
|
15
|
+
|
16
|
+
|
17
|
+
|
18
|
+
class CookbookValidator < Kwalify::Validator
|
19
|
+
|
20
|
+
|
21
|
+
filename = nil
|
22
|
+
path = $:.find { |dir| test(?f, filename = "#{dir}/rook/rookbook.schema.yaml") }
|
23
|
+
path or raise RookError.new("schema file ('rookbook.schema.yaml') is not found.")
|
24
|
+
parser = Kwalify::YamlParser.new(File.read(filename))
|
25
|
+
@@schema = parser.parse()
|
26
|
+
|
27
|
+
|
28
|
+
def initialize()
|
29
|
+
super(@@schema)
|
30
|
+
end
|
31
|
+
|
32
|
+
|
33
|
+
def create_error(path, message)
|
34
|
+
return Kwalify::ValidationError.new(message, path)
|
35
|
+
end
|
36
|
+
|
37
|
+
|
38
|
+
#PROPERTY_NAME_PATTERN = /\A[a-zA-Z_][-\w]*\*?\z/
|
39
|
+
|
40
|
+
|
41
|
+
##
|
42
|
+
def validate_hook(value, rule, path, errors)
|
43
|
+
case rule.name
|
44
|
+
when 'MATERIAL'
|
45
|
+
unless value.is_a?(String) || value.is_a?(Symbol)
|
46
|
+
errors << create_error(path, "String or Symbol is required.")
|
47
|
+
end
|
48
|
+
when 'PROPERTY'
|
49
|
+
unless value.key?('value') || value.key?('expr')
|
50
|
+
errors << create_error(path, "either 'value' or 'expr' is required.")
|
51
|
+
end
|
52
|
+
when 'PROP'
|
53
|
+
value.each do |k, v|
|
54
|
+
if !k.is_a?(String)
|
55
|
+
errors << error("#{path}/#{k}", "property name is not a string.")
|
56
|
+
elsif k !~ /\A[a-zA-Z_][-\w]*\*?\z/ && k != '.desc'
|
57
|
+
errors << error("#{path}/#{k}", "invlaid property name.")
|
58
|
+
elsif k[-1] == ?* && !v.is_a?(String)
|
59
|
+
errors << create_error("#{path}/#{k}", "expr is not a string.")
|
60
|
+
end
|
61
|
+
end
|
62
|
+
end if rule.name
|
63
|
+
end
|
64
|
+
|
65
|
+
|
66
|
+
end
|
67
|
+
|
68
|
+
|
69
|
+
|
70
|
+
class CookbookError < RookError
|
71
|
+
|
72
|
+
|
73
|
+
def initialize(message, validation_errors)
|
74
|
+
@errors = validation_errors
|
75
|
+
end
|
76
|
+
attr_reader :errors
|
77
|
+
|
78
|
+
|
79
|
+
end
|
80
|
+
|
81
|
+
|
82
|
+
|
83
|
+
##
|
84
|
+
## represents cookbook
|
85
|
+
##
|
86
|
+
## ex.
|
87
|
+
## cookbooks, errors = Cookbook.load_yamlfile('Rookbook.yaml')
|
88
|
+
## if errors && !errors.empty?
|
89
|
+
## puts "%d: [%s] %s" % [errors.linenum, errors.path, errors.message]
|
90
|
+
## end
|
91
|
+
##
|
92
|
+
class Cookbook
|
93
|
+
include Assertion
|
94
|
+
#include DslHelper
|
95
|
+
|
96
|
+
|
97
|
+
@@validator = CookbookValidator.new
|
98
|
+
|
99
|
+
|
100
|
+
##
|
101
|
+
def initialize(bookname=nil, load_yaml=true, expand_tab=true)
|
102
|
+
@precookings = []
|
103
|
+
@properties = []
|
104
|
+
@variables = []
|
105
|
+
@materials = []
|
106
|
+
@recipes = []
|
107
|
+
@bookname = bookname
|
108
|
+
load_yamlfile(bookname, expand_tab) if bookname && load_yaml
|
109
|
+
end
|
110
|
+
attr_reader :properties, :variables, :recipes, :materials, :precookings
|
111
|
+
attr_accessor :bookname
|
112
|
+
|
113
|
+
|
114
|
+
def precooking
|
115
|
+
return @precookings.map { |hash| hash['code'] }.join("\n")
|
116
|
+
end
|
117
|
+
|
118
|
+
|
119
|
+
def load_yamlfile(bookname, tab_expand=true)
|
120
|
+
yaml_str = File.read(bookname)
|
121
|
+
yaml_str = Util.untabify(yaml_str) if tab_expand
|
122
|
+
return load_yamlstr(yaml_str)
|
123
|
+
end
|
124
|
+
|
125
|
+
|
126
|
+
def load_yamlstr(yaml_str)
|
127
|
+
parser = Kwalify::YamlParser.new(yaml_str)
|
128
|
+
errors = []
|
129
|
+
while parser.has_next?
|
130
|
+
ydoc = parser.parse()
|
131
|
+
next if ydoc.nil?
|
132
|
+
errs = @@validator.validate(ydoc)
|
133
|
+
if errs && !errs.empty?
|
134
|
+
parser.set_errors_linenum(errs)
|
135
|
+
errors += errs
|
136
|
+
else
|
137
|
+
load_ydoc(ydoc, parser)
|
138
|
+
end
|
139
|
+
end
|
140
|
+
errors.empty? or raise CookbookError.new("invalid cookbook", errors)
|
141
|
+
end
|
142
|
+
|
143
|
+
|
144
|
+
def load_ydoc(ydoc, parser=nil)
|
145
|
+
ydoc = ydoc.dup
|
146
|
+
#
|
147
|
+
code = ydoc['precooking']
|
148
|
+
ydoc['precookings'] = [ { 'code'=>code } ] if code
|
149
|
+
#
|
150
|
+
[ 'properties', 'variables' ].each do |key|
|
151
|
+
next unless ydoc[key]
|
152
|
+
list = []
|
153
|
+
ydoc[key].each_with_index do |prop, i|
|
154
|
+
opts = {}
|
155
|
+
prop.keys.each do |k| opts[k.to_s] = prop[k] if k.is_a?(Symbol) end
|
156
|
+
prop.each do |name, val|
|
157
|
+
next unless name.is_a?(String)
|
158
|
+
vkey = name[-1] == ?* ? 'expr' : 'value'
|
159
|
+
#name[-1,1] = '' if name[-1] == ?* # TypeError: can't modify frozen string
|
160
|
+
name = name[0, name.length-1] if name[-1] == ?*
|
161
|
+
h = { 'name'=>name, vkey=>val }
|
162
|
+
h.update(opts)
|
163
|
+
list << h
|
164
|
+
end
|
165
|
+
end
|
166
|
+
ydoc[key] = list
|
167
|
+
end
|
168
|
+
#
|
169
|
+
@precookings += _load_section(ydoc['precookings'], 'code', parser, 1) { |h, i| "/precooking" }
|
170
|
+
@recipes += _load_section(ydoc['recipes'], 'method', parser, 1) { |h, i| "/recipes/#{i}/method" }
|
171
|
+
@properties += _load_section(ydoc['properties*'], 'expr', parser, 0) { |h, i| "/properties*/#{i}/expr" }
|
172
|
+
@variables += _load_section(ydoc['variables*'], 'expr', parser, 0) { |h, i| "/variables*/#{i}/expr" }
|
173
|
+
@properties += _load_section(ydoc['properties'], 'expr', parser, 0) { |h, i| "/properties/#{i}/#{h['name']}*" }
|
174
|
+
@variables += _load_section(ydoc['variables'], 'expr', parser, 0) { |h, i| "/variables/#{i}/#{h['name']}*" }
|
175
|
+
#
|
176
|
+
list = ydoc['materials']
|
177
|
+
@materials += list if list
|
178
|
+
end
|
179
|
+
|
180
|
+
|
181
|
+
def _load_section(section, attr, parser, delta)
|
182
|
+
list = []
|
183
|
+
section.each_with_index do |hash, i|
|
184
|
+
if parser && hash[attr]
|
185
|
+
path = yield(hash, i)
|
186
|
+
hash['linenum'] = parser.path_linenum(path) + delta
|
187
|
+
end
|
188
|
+
list << hash
|
189
|
+
end if section
|
190
|
+
return list
|
191
|
+
end
|
192
|
+
private :_load_section
|
193
|
+
|
194
|
+
|
195
|
+
#--
|
196
|
+
#def load_dsl(ruby_code)
|
197
|
+
# eval(ruby_code, binding(), @bookname, 1)
|
198
|
+
#end
|
199
|
+
#++
|
200
|
+
|
201
|
+
|
202
|
+
#--
|
203
|
+
#def self.load_yamlfile(bookname, tab_expand=true)
|
204
|
+
# str = File.read(bookname)
|
205
|
+
# str = Util.untabify(str) if tab_expand
|
206
|
+
# return load_yamlstr(str, bookname)
|
207
|
+
#end
|
208
|
+
#
|
209
|
+
#
|
210
|
+
#def self.load_yamlstr(yaml_str, bookname)
|
211
|
+
# parser = Kwalify::YamlParser.new(yaml_str)
|
212
|
+
# ydocs = []
|
213
|
+
# errors = []
|
214
|
+
# books = []
|
215
|
+
# while parser.has_next?
|
216
|
+
# ydoc = parser.parse()
|
217
|
+
# errs = @@validator.validate(ydoc)
|
218
|
+
# if errs && !errs.empty?
|
219
|
+
# errors += errs
|
220
|
+
# else
|
221
|
+
# cookbook = self.new
|
222
|
+
# cookbook.bookname = bookname
|
223
|
+
# cookbook.load_ydoc(ydoc, parser)
|
224
|
+
# books << cookbook
|
225
|
+
# end
|
226
|
+
# end
|
227
|
+
# errors.empty? or raise CookbookError.new("invalid cookbook", errors)
|
228
|
+
# return books
|
229
|
+
#end
|
230
|
+
#++
|
231
|
+
|
232
|
+
|
233
|
+
end
|
234
|
+
|
235
|
+
|
236
|
+
|
237
|
+
end
|
@@ -0,0 +1,39 @@
|
|
1
|
+
##
|
2
|
+
## $Rev: 20 $
|
3
|
+
## $Release: 0.1.0 $
|
4
|
+
## copyright(c) 2006 kuwata-lab.com all rights reserved.
|
5
|
+
##
|
6
|
+
|
7
|
+
## download 'bz2' from http://raa.ruby-lang.org/project/bz2/
|
8
|
+
|
9
|
+
require 'bz2'
|
10
|
+
|
11
|
+
|
12
|
+
module BZ2
|
13
|
+
|
14
|
+
|
15
|
+
class Reader
|
16
|
+
|
17
|
+
alias _initialize initialize
|
18
|
+
|
19
|
+
def initialize(object, small=false)
|
20
|
+
_initialize(object, small)
|
21
|
+
@_io = object
|
22
|
+
end
|
23
|
+
|
24
|
+
def pos
|
25
|
+
@_io.pos
|
26
|
+
end
|
27
|
+
|
28
|
+
def pos=(index)
|
29
|
+
@_io.pos = index
|
30
|
+
end
|
31
|
+
|
32
|
+
def rewind
|
33
|
+
@_io.rewind
|
34
|
+
end
|
35
|
+
|
36
|
+
end
|
37
|
+
|
38
|
+
|
39
|
+
end
|
@@ -0,0 +1,76 @@
|
|
1
|
+
##
|
2
|
+
## $Rev$
|
3
|
+
## $Release: 0.1.0 $
|
4
|
+
## copyright(c) 2006 kuwata-lab.com all rights reserved.
|
5
|
+
##
|
6
|
+
|
7
|
+
|
8
|
+
require 'digest'
|
9
|
+
|
10
|
+
|
11
|
+
|
12
|
+
class Digest::Base
|
13
|
+
|
14
|
+
|
15
|
+
##
|
16
|
+
## create digest object from file content
|
17
|
+
##
|
18
|
+
## ex.
|
19
|
+
## require 'digest/md5'
|
20
|
+
## digest = Digest::MD5.create_from_file('largefile.mov')
|
21
|
+
## puts digest.hexdigest
|
22
|
+
##
|
23
|
+
## this is faster than 'Digest::MD5.new(File.read("largefile.mov"))'
|
24
|
+
## when file size is large.
|
25
|
+
##
|
26
|
+
def self.create_from_file(filename)
|
27
|
+
digest = self.new
|
28
|
+
File.open(filename) do |f|
|
29
|
+
#size = 4 * 1024 # 4KB
|
30
|
+
#size = 8 * 1024 # 8KB
|
31
|
+
size = 16 * 1024 # 16KB
|
32
|
+
#size = 32 * 1024 # 32KB
|
33
|
+
#size = 64 * 1024 # 64KB
|
34
|
+
#size = 128 * 1024 # 128KB
|
35
|
+
#size = 256 * 1024 # 256KB
|
36
|
+
#size = 512 * 1024 # 512KB
|
37
|
+
#size = 1 * 1024 * 1024 # 1MB
|
38
|
+
#size = 4 * 1024 * 1024 # 4MB
|
39
|
+
buf = ''
|
40
|
+
digest.update(buf) while f.read(size, buf)
|
41
|
+
end
|
42
|
+
return digest
|
43
|
+
end
|
44
|
+
|
45
|
+
|
46
|
+
##
|
47
|
+
## return hex-digest value of file content
|
48
|
+
##
|
49
|
+
## ex.
|
50
|
+
## require 'digest/md5'
|
51
|
+
## puts Digest::MD5.hex_digest('largefile.mov')
|
52
|
+
##
|
53
|
+
## this is faster than 'Digest::MD5.hex_digest(File.read(filename))'
|
54
|
+
## when file size is large.
|
55
|
+
##
|
56
|
+
def self.file_hexdigest(filename)
|
57
|
+
return self.create_from_file(filename).hexdigest
|
58
|
+
end
|
59
|
+
|
60
|
+
|
61
|
+
##
|
62
|
+
## return digest value of file content
|
63
|
+
##
|
64
|
+
## ex.
|
65
|
+
## require 'digest/md5'
|
66
|
+
## puts Digest::MD5.digest('largefile.mov')
|
67
|
+
##
|
68
|
+
## this is faster than 'Digest::MD5.digest(File.read(filename))'
|
69
|
+
## when file size is large.
|
70
|
+
##
|
71
|
+
def self.file_digest(filename)
|
72
|
+
return self.create_from_file(filename).digest
|
73
|
+
end
|
74
|
+
|
75
|
+
|
76
|
+
end
|
@@ -0,0 +1,121 @@
|
|
1
|
+
##
|
2
|
+
## $Rev: 20 $
|
3
|
+
## $Release: 0.1.0 $
|
4
|
+
## copyright(c) 2006 kuwata-lab.com all rights reserved.
|
5
|
+
##
|
6
|
+
|
7
|
+
|
8
|
+
module Enumerable
|
9
|
+
|
10
|
+
|
11
|
+
##
|
12
|
+
## apply method with args
|
13
|
+
##
|
14
|
+
## ex.
|
15
|
+
## [1, 2, 3].apply(:'+', 5) #=> [6, 7, 8]
|
16
|
+
##
|
17
|
+
def apply(method_name, *args)
|
18
|
+
return self.collect { |item| item.__send__(method_name, *args) }
|
19
|
+
end
|
20
|
+
|
21
|
+
|
22
|
+
##
|
23
|
+
## add prefix for each item
|
24
|
+
##
|
25
|
+
## ex.
|
26
|
+
## ['a', 'b', 'c'].add_prefix('dir/') #=> ["dir/a", "dir/b", "dir/c"]
|
27
|
+
##
|
28
|
+
def add_prefix(prefix)
|
29
|
+
return self.collect { |item| "#{prefix}#{item}" }
|
30
|
+
end
|
31
|
+
|
32
|
+
|
33
|
+
##
|
34
|
+
## add suffix for each item
|
35
|
+
##
|
36
|
+
## ex.
|
37
|
+
## ['a', 'b', 'c'].add_suffix('.txt') #=> ["a.txt", "b.txt", "c.txt"]
|
38
|
+
##
|
39
|
+
def add_suffix(suffix)
|
40
|
+
return self.collect { |item| "#{item}#{suffix}" }
|
41
|
+
end
|
42
|
+
|
43
|
+
|
44
|
+
##
|
45
|
+
## add prefix and suffix for each item
|
46
|
+
##
|
47
|
+
## ex.
|
48
|
+
## ['a','b','c'].sandwich('p/', '.s') #=> ["p/a.s", "p/b.s", "p/c.s"]
|
49
|
+
##
|
50
|
+
def sandwich(prefix, suffix)
|
51
|
+
return self.collect { |item| "#{prefix}#{item}#{suffix}" }
|
52
|
+
end
|
53
|
+
|
54
|
+
|
55
|
+
##
|
56
|
+
## apply 'sub()' method to each item
|
57
|
+
##
|
58
|
+
## ex.
|
59
|
+
## ['a.txt', 'b.txt'].each_sub(/\.txt$/, '.html') #=> ["a.html", "b.html"]
|
60
|
+
##
|
61
|
+
def each_sub(pattern, replace, &block)
|
62
|
+
if replace
|
63
|
+
return self.collect { |item| item.sub(pattern, replace) }
|
64
|
+
else
|
65
|
+
return self.collect { |item| item.sub(pattern, &block) }
|
66
|
+
end
|
67
|
+
end
|
68
|
+
|
69
|
+
|
70
|
+
##
|
71
|
+
## apply 'gsub()' method to each item
|
72
|
+
##
|
73
|
+
## ex.
|
74
|
+
## ['a.txt', 'b.txt'].each_gsub(/\.txt$/, '.html') #=> ["a.html", "b.html"]
|
75
|
+
##
|
76
|
+
def each_gsub(pattern, replace, &block)
|
77
|
+
if replace
|
78
|
+
return self.collect { |item| item.gsub(pattern, replace) }
|
79
|
+
else
|
80
|
+
return self.collect { |item| item.gsub(pattern, &block) }
|
81
|
+
end
|
82
|
+
end
|
83
|
+
|
84
|
+
|
85
|
+
##
|
86
|
+
## delete suffix for each item
|
87
|
+
##
|
88
|
+
## ex.
|
89
|
+
## ['a.txt', 'b.txt'].delete_suffix() #=> ["a", "b"]
|
90
|
+
##
|
91
|
+
def delete_suffix(suffix=/\.\w+\z/)
|
92
|
+
unless suffix.is_a?(Regexp)
|
93
|
+
suffix = Regexp.compile("#{suffix.to_s}\\z")
|
94
|
+
end
|
95
|
+
return self.collect { |item| item.sub(suffix, '') }
|
96
|
+
end
|
97
|
+
|
98
|
+
|
99
|
+
##
|
100
|
+
## get basename of each item
|
101
|
+
##
|
102
|
+
## ex.
|
103
|
+
## ['dir1/a.txt', 'b.txt'].basenames() #=> ['a.txt', 'b.txt']
|
104
|
+
##
|
105
|
+
def basenames
|
106
|
+
return self.collect { |item| File.basename(item) }
|
107
|
+
end
|
108
|
+
|
109
|
+
|
110
|
+
##
|
111
|
+
## get dirname of each item
|
112
|
+
##
|
113
|
+
## ex.
|
114
|
+
## ['dir1/a.txt', 'b.txt'].dirnames() #=> ['dir1', '.']
|
115
|
+
##
|
116
|
+
def dirnames
|
117
|
+
return self.collect { |item| File.dirname(item) }
|
118
|
+
end
|
119
|
+
|
120
|
+
|
121
|
+
end
|