rook 0.0.2 → 0.1.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.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
|