rspec-command 1.0.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.
- checksums.yaml +7 -0
- data/.gitignore +4 -0
- data/.travis.yml +12 -0
- data/.yardopts +5 -0
- data/Gemfile +19 -0
- data/LICENSE +202 -0
- data/README.md +181 -0
- data/Rakefile +32 -0
- data/lib/rspec-command.rb +17 -0
- data/lib/rspec_command.rb +361 -0
- data/lib/rspec_command/match_fixture.rb +165 -0
- data/lib/rspec_command/rake.rb +120 -0
- data/lib/rspec_command/version.rb +21 -0
- data/rspec-command.gemspec +48 -0
- data/spec/command_spec.rb +301 -0
- data/spec/file_list_spec.rb +106 -0
- data/spec/fixtures/data.txt +1 -0
- data/spec/fixtures/sub/sub1.txt +1 -0
- data/spec/fixtures/sub/sub2.txt +1 -0
- data/spec/fixtures/sub_nested/sub_inner/sub_inner1.txt +1 -0
- data/spec/fixtures/sub_nested/sub_inner/sub_inner2.txt +1 -0
- data/spec/fixtures/sub_nested/sub_nested.txt +1 -0
- data/spec/match_fixture_spec.rb +186 -0
- data/spec/rake_spec.rb +117 -0
- data/spec/spec_helper.rb +58 -0
- metadata +240 -0
@@ -0,0 +1,17 @@
|
|
1
|
+
#
|
2
|
+
# Copyright 2015, Noah Kantrowitz
|
3
|
+
#
|
4
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
5
|
+
# you may not use this file except in compliance with the License.
|
6
|
+
# You may obtain a copy of the License at
|
7
|
+
#
|
8
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
9
|
+
#
|
10
|
+
# Unless required by applicable law or agreed to in writing, software
|
11
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
12
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
13
|
+
# See the License for the specific language governing permissions and
|
14
|
+
# limitations under the License.
|
15
|
+
#
|
16
|
+
|
17
|
+
require 'rspec_command'
|
@@ -0,0 +1,361 @@
|
|
1
|
+
#
|
2
|
+
# Copyright 2015, Noah Kantrowitz
|
3
|
+
#
|
4
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
5
|
+
# you may not use this file except in compliance with the License.
|
6
|
+
# You may obtain a copy of the License at
|
7
|
+
#
|
8
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
9
|
+
#
|
10
|
+
# Unless required by applicable law or agreed to in writing, software
|
11
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
12
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
13
|
+
# See the License for the specific language governing permissions and
|
14
|
+
# limitations under the License.
|
15
|
+
#
|
16
|
+
|
17
|
+
require 'fileutils'
|
18
|
+
require 'tempfile'
|
19
|
+
|
20
|
+
require 'rspec'
|
21
|
+
require 'rspec/its'
|
22
|
+
require 'mixlib/shellout'
|
23
|
+
|
24
|
+
|
25
|
+
# An RSpec helper module for testing command-line tools.
|
26
|
+
#
|
27
|
+
# @api public
|
28
|
+
# @since 1.0.0
|
29
|
+
# @example Enable globally
|
30
|
+
# RSpec.configure do |config|
|
31
|
+
# config.include RSpecCommand
|
32
|
+
# end
|
33
|
+
# @example Enable for a single example group
|
34
|
+
# describe 'myapp' do
|
35
|
+
# command 'myapp --version'
|
36
|
+
# its(:stdout) { it_expected.to include('1.0.0') }
|
37
|
+
# end
|
38
|
+
module RSpecCommand
|
39
|
+
extend RSpec::SharedContext
|
40
|
+
|
41
|
+
autoload :MatchFixture, 'rspec_command/match_fixture'
|
42
|
+
autoload :Rake, 'rspec_command/rake'
|
43
|
+
|
44
|
+
around do |example|
|
45
|
+
Dir.mktmpdir('rspec_command') do |path|
|
46
|
+
example.metadata[:rspec_command_temp_path] = path
|
47
|
+
example.run
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
# @!attribute [r] temp_path
|
52
|
+
# Path to the temporary directory created for the current example.
|
53
|
+
# @return [String]
|
54
|
+
let(:temp_path) do |example|
|
55
|
+
example.metadata[:rspec_command_temp_path]
|
56
|
+
end
|
57
|
+
|
58
|
+
# @!attribute [r] fixture_root
|
59
|
+
# Base path for the fixtures directory. Default value is 'fixtures'.
|
60
|
+
# @return [String]
|
61
|
+
# @example
|
62
|
+
# let(:fixture_root) { 'data' }
|
63
|
+
let(:fixture_root) { 'fixtures' }
|
64
|
+
|
65
|
+
# @!attribute [r] _environment
|
66
|
+
# @!visibility private
|
67
|
+
# @api private
|
68
|
+
# Accumulator for environment variables.
|
69
|
+
# @see RSpecCommand.environment
|
70
|
+
let(:_environment) { Hash.new }
|
71
|
+
|
72
|
+
# Run a command.
|
73
|
+
#
|
74
|
+
# @see .command
|
75
|
+
# @param cmd [String, Array] Command to run. If passed as an array, no shell
|
76
|
+
# expansion will be performed.
|
77
|
+
# @param options [Hash<Symbol, Object>] Options to pass to
|
78
|
+
# Mixlib::ShellOut.new.
|
79
|
+
# @option options [Boolean] allow_error If true, don't raise an error on
|
80
|
+
# failed commands.
|
81
|
+
# @return [Mixlib::ShellOut]
|
82
|
+
# @example
|
83
|
+
# before do
|
84
|
+
# command('git init')
|
85
|
+
# end
|
86
|
+
def command(cmd, options={})
|
87
|
+
# Try to find a Gemfile
|
88
|
+
gemfile_path = find_file(self.class.file_path, 'Gemfile')
|
89
|
+
gemfile_environment = gemfile_path ? {'BUNDLE_GEMFILE' => gemfile_path} : {}
|
90
|
+
# Create the command
|
91
|
+
allow_error = options.delete(:allow_error)
|
92
|
+
full_cmd = if gemfile_path
|
93
|
+
if cmd.is_a?(Array)
|
94
|
+
%w{bundle exec} + cmd
|
95
|
+
else
|
96
|
+
"bundle exec #{cmd}"
|
97
|
+
end
|
98
|
+
else
|
99
|
+
cmd
|
100
|
+
end
|
101
|
+
Mixlib::ShellOut.new(
|
102
|
+
full_cmd,
|
103
|
+
{
|
104
|
+
cwd: temp_path,
|
105
|
+
environment: gemfile_environment.merge(_environment),
|
106
|
+
}.merge(options),
|
107
|
+
).tap do |cmd|
|
108
|
+
# Run the command
|
109
|
+
cmd.run_command
|
110
|
+
cmd.error! unless allow_error
|
111
|
+
end
|
112
|
+
end
|
113
|
+
|
114
|
+
# Matcher to compare files or folders from the temporary directory to a
|
115
|
+
# fixture.
|
116
|
+
#
|
117
|
+
# @example
|
118
|
+
# describe 'myapp' do
|
119
|
+
# command 'myapp write'
|
120
|
+
# it { is_expected.to match_fixture('write_data') }
|
121
|
+
# end
|
122
|
+
def match_fixture(fixture_path, local_path=nil)
|
123
|
+
MatchFixture.new(find_fixture(self.class.file_path), temp_path, fixture_path, local_path)
|
124
|
+
end
|
125
|
+
|
126
|
+
# Run a local block with $stdout and $stderr redirected to a strings. Useful
|
127
|
+
# for running CLI code in unit tests. The returned string has `#stdout`,
|
128
|
+
# `#stderr` and `#exitstatus` attributes to emulate the output from {.command}.
|
129
|
+
#
|
130
|
+
# @param block [Proc] Code to run.
|
131
|
+
# @return [String]
|
132
|
+
# @example
|
133
|
+
# describe 'my rake task' do
|
134
|
+
# subject do
|
135
|
+
# capture_output do
|
136
|
+
# Rake::Task['mytask'].invoke
|
137
|
+
# end
|
138
|
+
# end
|
139
|
+
# end
|
140
|
+
def capture_output(&block)
|
141
|
+
old_stdout = $stdout.dup
|
142
|
+
old_stderr = $stderr.dup
|
143
|
+
# Potential future improvement is to use IO.pipe instead of temp files, but
|
144
|
+
# that would require threads or something to read contiuously since the
|
145
|
+
# buffer is only 64k on the kernel side.
|
146
|
+
Tempfile.open('capture_stdout') do |tmp_stdout|
|
147
|
+
Tempfile.open('capture_stderr') do |tmp_stderr|
|
148
|
+
$stdout.reopen(tmp_stdout)
|
149
|
+
$stdout.sync = true
|
150
|
+
$stderr.reopen(tmp_stderr)
|
151
|
+
$stderr.sync = true
|
152
|
+
output = nil
|
153
|
+
begin
|
154
|
+
# Inner block to make sure the ensure happens first.
|
155
|
+
begin
|
156
|
+
block.call
|
157
|
+
ensure
|
158
|
+
# Rewind.
|
159
|
+
tmp_stdout.seek(0, 0)
|
160
|
+
tmp_stderr.seek(0, 0)
|
161
|
+
# Read in the output.
|
162
|
+
output = OutputString.new(tmp_stdout.read, tmp_stderr.read)
|
163
|
+
end
|
164
|
+
rescue Exception => e
|
165
|
+
if output
|
166
|
+
# Try to add the output so far as an attribute on the exception via
|
167
|
+
# a closure.
|
168
|
+
e.define_singleton_method(:output_so_far) do
|
169
|
+
output
|
170
|
+
end
|
171
|
+
end
|
172
|
+
raise
|
173
|
+
else
|
174
|
+
output
|
175
|
+
end
|
176
|
+
end
|
177
|
+
end
|
178
|
+
ensure
|
179
|
+
$stdout.reopen(old_stdout)
|
180
|
+
$stderr.reopen(old_stderr)
|
181
|
+
end
|
182
|
+
|
183
|
+
# String subclass to make string output look kind of like Mixlib::ShellOut.
|
184
|
+
#
|
185
|
+
# @api private
|
186
|
+
# @see capture_stdout
|
187
|
+
class OutputString < String
|
188
|
+
def initialize(stdout, stderr)
|
189
|
+
super(stdout)
|
190
|
+
@stderr = stderr
|
191
|
+
end
|
192
|
+
|
193
|
+
def stdout
|
194
|
+
self
|
195
|
+
end
|
196
|
+
|
197
|
+
def stderr
|
198
|
+
@stderr
|
199
|
+
end
|
200
|
+
|
201
|
+
def exitstatus
|
202
|
+
0
|
203
|
+
end
|
204
|
+
end
|
205
|
+
|
206
|
+
private
|
207
|
+
|
208
|
+
# Search backwards along the working directory looking for a file, a la .git.
|
209
|
+
# Either file or block must be given.
|
210
|
+
#
|
211
|
+
# @param example_path [String] Path of the current example file. Find via
|
212
|
+
# example.file_path.
|
213
|
+
# @param file [String] Relative path to search for.
|
214
|
+
# @param backstop [String] Path to not search past.
|
215
|
+
# @param block [Proc] Block to use as a filter.
|
216
|
+
# @return [String, nil]
|
217
|
+
def find_file(example_path, file=nil, backstop=nil, &block)
|
218
|
+
path = File.dirname(File.expand_path(example_path))
|
219
|
+
last_path = nil
|
220
|
+
while path != last_path && path != backstop
|
221
|
+
if block
|
222
|
+
block_val = block.call(path)
|
223
|
+
return block_val if block_val
|
224
|
+
else
|
225
|
+
file_path = File.join(path, file)
|
226
|
+
return file_path if File.exists?(file_path)
|
227
|
+
end
|
228
|
+
last_path = path
|
229
|
+
path = File.dirname(path)
|
230
|
+
end
|
231
|
+
nil
|
232
|
+
end
|
233
|
+
|
234
|
+
# Find the base folder of the current gem.
|
235
|
+
def find_gem_base(example_path)
|
236
|
+
@gem_base ||= begin
|
237
|
+
paths = []
|
238
|
+
paths << find_file(example_path) do |path|
|
239
|
+
spec_path = Dir.entries(path).find do |ent|
|
240
|
+
ent.end_with?('.gemspec')
|
241
|
+
end
|
242
|
+
spec_path = File.join(path, spec_path) if spec_path
|
243
|
+
spec_path
|
244
|
+
end
|
245
|
+
paths << find_file(example_path, 'Gemfile')
|
246
|
+
File.dirname(paths.find {|v| v })
|
247
|
+
end
|
248
|
+
end
|
249
|
+
|
250
|
+
# Find a fixture file or the fixture base folder.
|
251
|
+
def find_fixture(example_path, path=nil)
|
252
|
+
@fixture_base ||= find_file(example_path, fixture_root, find_gem_base(example_path))
|
253
|
+
path ? File.join(@fixture_base, path) : @fixture_base
|
254
|
+
end
|
255
|
+
|
256
|
+
# @!classmethods
|
257
|
+
module ClassMethods
|
258
|
+
# Run a command as the subject of this example. The command can be passed in
|
259
|
+
# as a string, array, or block. The subject will be a Mixlib::ShellOut
|
260
|
+
# object, all attributes from there will work with rspec-its.
|
261
|
+
#
|
262
|
+
# @see #command
|
263
|
+
# @param cmd [String, Array] Command to run. If passed as an array, no shell
|
264
|
+
# expansion will be performed.
|
265
|
+
# @param options [Hash<Symbol, Object>] Options to pass to
|
266
|
+
# Mixlib::ShellOut.new.
|
267
|
+
# @param block [Proc] Optional block to return a command to run.
|
268
|
+
# @option options [Boolean] allow_error If true, don't raise an error on
|
269
|
+
# failed commands.
|
270
|
+
# @example
|
271
|
+
# describe 'myapp' do
|
272
|
+
# command 'myapp show'
|
273
|
+
# its(:stdout) { is_expected.to match(/a thing/) }
|
274
|
+
# end
|
275
|
+
def command(cmd=nil, options={}, &block)
|
276
|
+
metadata[:command] = true
|
277
|
+
subject do |example|
|
278
|
+
# If a block is given, use it to get the command.
|
279
|
+
cmd = instance_eval(&block) if block
|
280
|
+
command(cmd, options)
|
281
|
+
end
|
282
|
+
end
|
283
|
+
|
284
|
+
# Create a file in the temporary directory for this example.
|
285
|
+
#
|
286
|
+
# @param path [String] Path within the temporary directory to write to.
|
287
|
+
# @param content [String] File data to write.
|
288
|
+
# @param block [Proc] Optional block to return file data to write.
|
289
|
+
# @example
|
290
|
+
# describe 'myapp' do
|
291
|
+
# command 'myapp read data.txt'
|
292
|
+
# file 'data.txt', <<-EOH
|
293
|
+
# a thing
|
294
|
+
# EOH
|
295
|
+
# its(:exitstatus) { is_expected.to eq 0 }
|
296
|
+
# end
|
297
|
+
def file(path, content=nil, &block)
|
298
|
+
raise "file path should be relative the the temporary directory." if path == File.expand_path(path)
|
299
|
+
before do
|
300
|
+
content = instance_eval(&block) if block
|
301
|
+
dest_path = File.join(temp_path, path)
|
302
|
+
FileUtils.mkdir_p(File.dirname(dest_path))
|
303
|
+
IO.write(dest_path, content)
|
304
|
+
end
|
305
|
+
end
|
306
|
+
|
307
|
+
# Copy fixture data from the spec folder to the temporary directory for this
|
308
|
+
# example.
|
309
|
+
#
|
310
|
+
# @param path [String] Path of the fixture to copy.
|
311
|
+
# @param dest [String] Optional destination path. By default the destination
|
312
|
+
# is the same as path.
|
313
|
+
# @example
|
314
|
+
# describe 'myapp' do
|
315
|
+
# command 'myapp run test/'
|
316
|
+
# fixture_file 'test'
|
317
|
+
# its(:exitstatus) { is_expected.to eq 0 }
|
318
|
+
# end
|
319
|
+
def fixture_file(path, dest=nil)
|
320
|
+
raise "file path should be relative the the temporary directory." if path == File.expand_path(path)
|
321
|
+
before do |example|
|
322
|
+
fixture_path = find_fixture(example.file_path, path)
|
323
|
+
dest_path = dest ? File.join(temp_path, dest) : temp_path
|
324
|
+
FileUtils.mkdir_p(dest_path)
|
325
|
+
file_list = MatchFixture::FileList.new(fixture_path)
|
326
|
+
file_list.files.each do |file|
|
327
|
+
abs = file_list.absolute(file)
|
328
|
+
if File.directory?(abs)
|
329
|
+
FileUtils.mkdir_p(File.join(dest_path, file))
|
330
|
+
else
|
331
|
+
FileUtils.copy(abs , File.join(dest_path, file), preserve: true)
|
332
|
+
end
|
333
|
+
end
|
334
|
+
end
|
335
|
+
end
|
336
|
+
|
337
|
+
# Set an environment variable for this example.
|
338
|
+
#
|
339
|
+
# @param variables [Hash] Key/value pairs to set.
|
340
|
+
# @example
|
341
|
+
# describe 'myapp' do
|
342
|
+
# command 'myapp show'
|
343
|
+
# environment DEBUG: true
|
344
|
+
# its(:stderr) { is_expected.to include('[debug]') }
|
345
|
+
# end
|
346
|
+
def environment(variables)
|
347
|
+
before do
|
348
|
+
variables.each do |key, value|
|
349
|
+
_environment[key.to_s] = value.to_s
|
350
|
+
end
|
351
|
+
end
|
352
|
+
end
|
353
|
+
|
354
|
+
def included(klass)
|
355
|
+
super
|
356
|
+
klass.extend ClassMethods
|
357
|
+
end
|
358
|
+
end
|
359
|
+
|
360
|
+
extend ClassMethods
|
361
|
+
end
|
@@ -0,0 +1,165 @@
|
|
1
|
+
#
|
2
|
+
# Copyright 2015, Noah Kantrowitz
|
3
|
+
#
|
4
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
5
|
+
# you may not use this file except in compliance with the License.
|
6
|
+
# You may obtain a copy of the License at
|
7
|
+
#
|
8
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
9
|
+
#
|
10
|
+
# Unless required by applicable law or agreed to in writing, software
|
11
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
12
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
13
|
+
# See the License for the specific language governing permissions and
|
14
|
+
# limitations under the License.
|
15
|
+
#
|
16
|
+
|
17
|
+
|
18
|
+
module RSpecCommand
|
19
|
+
# @api private
|
20
|
+
# @since 1.0.0
|
21
|
+
class MatchFixture
|
22
|
+
# Create a new matcher for a fixture.
|
23
|
+
#
|
24
|
+
# @param fixture_root [String] Absolute path to the fixture folder.
|
25
|
+
# @param local_root [String] Absolute path to test folder to compare against.
|
26
|
+
# @param fixture_path [String] Relative path to the fixture to compare against.
|
27
|
+
# @param local_path [String] Optional relative path to the test data to compare against.
|
28
|
+
def initialize(fixture_root, local_root, fixture_path, local_path=nil)
|
29
|
+
@fixture = FileList.new(fixture_root, fixture_path)
|
30
|
+
@local = FileList.new(local_root, local_path)
|
31
|
+
end
|
32
|
+
|
33
|
+
# Primary callback for RSpec matcher API.
|
34
|
+
#
|
35
|
+
# @param cmd Ignored.
|
36
|
+
# @return [Boolean]
|
37
|
+
def matches?(cmd)
|
38
|
+
files_match? && file_content_match?
|
39
|
+
end
|
40
|
+
|
41
|
+
# Callback for RSpec. Returns a human-readable description for the matcher.
|
42
|
+
#
|
43
|
+
# @return [String]
|
44
|
+
def description
|
45
|
+
"match fixture #{@fixture.path}"
|
46
|
+
end
|
47
|
+
|
48
|
+
# Callback fro RSpec. Returns a human-readable failure message.
|
49
|
+
#
|
50
|
+
# @return [String]
|
51
|
+
def failure_message
|
52
|
+
matching_files = @fixture.files & @local.files
|
53
|
+
fixture_only_files = @fixture.files - @local.files
|
54
|
+
local_only_files = @local.files - @fixture.files
|
55
|
+
buf = "expected fixture #{@fixture.path} to match files:\n"
|
56
|
+
(@fixture.files | @local.files).sort.each do |file|
|
57
|
+
if matching_files.include?(file)
|
58
|
+
local_file = @local.absolute(file)
|
59
|
+
fixture_file = @fixture.absolute(file)
|
60
|
+
if File.directory?(local_file) && File.directory?(fixture_file)
|
61
|
+
# Do nothing
|
62
|
+
elsif File.directory?(fixture_file)
|
63
|
+
buf << " #{file} should be a directory\n"
|
64
|
+
elsif File.directory?(local_file)
|
65
|
+
buf << " #{file} should not be a directory"
|
66
|
+
else
|
67
|
+
actual = IO.read(local_file)
|
68
|
+
expected = IO.read(fixture_file)
|
69
|
+
if actual != expected
|
70
|
+
# Show a diff
|
71
|
+
buf << " #{file} does not match fixture:"
|
72
|
+
buf << differ.diff(actual, expected).split(/\n/).map {|line| ' '+line }.join("\n")
|
73
|
+
end
|
74
|
+
end
|
75
|
+
elsif fixture_only_files.include?(file)
|
76
|
+
buf << " #{file} is not found\n"
|
77
|
+
elsif local_only_files.include?(file)
|
78
|
+
buf << " #{file} should not exist\n"
|
79
|
+
end
|
80
|
+
end
|
81
|
+
buf
|
82
|
+
end
|
83
|
+
|
84
|
+
private
|
85
|
+
|
86
|
+
# Do the file entries match? Doesn't check content.
|
87
|
+
#
|
88
|
+
# @return [Boolean]
|
89
|
+
def files_match?
|
90
|
+
@fixture.files == @local.files
|
91
|
+
end
|
92
|
+
|
93
|
+
# Do the file contents match?
|
94
|
+
#
|
95
|
+
# @return [Boolean]
|
96
|
+
def file_content_match?
|
97
|
+
@fixture.full_files.zip(@local.full_files).all? do |fixture_file, local_file|
|
98
|
+
if File.directory?(fixture_file)
|
99
|
+
File.directory?(local_file)
|
100
|
+
else
|
101
|
+
!File.directory?(local_file) && IO.read(fixture_file) == IO.read(local_file)
|
102
|
+
end
|
103
|
+
end
|
104
|
+
end
|
105
|
+
|
106
|
+
# Return a Differ object to make diffs.
|
107
|
+
#
|
108
|
+
# @note This is using a nominally private API. It could break in the future.
|
109
|
+
# @return [RSpec::Support::Differ]
|
110
|
+
# @example
|
111
|
+
# differ.diff(actual, expected)
|
112
|
+
def differ
|
113
|
+
RSpec::Expectations.differ
|
114
|
+
end
|
115
|
+
|
116
|
+
class FileList
|
117
|
+
attr_reader :root, :path
|
118
|
+
|
119
|
+
# @param root [String] Absolute path to the root of the files.
|
120
|
+
# @param path [String] Relative path to the specific files.
|
121
|
+
def initialize(root, path=nil)
|
122
|
+
@root = root
|
123
|
+
@path = path
|
124
|
+
end
|
125
|
+
|
126
|
+
# Absolute path to the target.
|
127
|
+
def full_path
|
128
|
+
@full_path ||= path ? File.join(root, path) : root
|
129
|
+
end
|
130
|
+
|
131
|
+
# Absolute paths to target files that exist.
|
132
|
+
def full_files
|
133
|
+
@full_files ||= if File.directory?(full_path)
|
134
|
+
Dir[File.join(full_path, '**', '*')].sort
|
135
|
+
else
|
136
|
+
[full_path].select {|path| File.exist?(path) }
|
137
|
+
end
|
138
|
+
end
|
139
|
+
|
140
|
+
# Relative paths to the target files that exist.
|
141
|
+
def files
|
142
|
+
@files ||= full_files.map {|file| relative(file) }
|
143
|
+
end
|
144
|
+
|
145
|
+
# Convert an absolute path to a relative one
|
146
|
+
def relative(file)
|
147
|
+
if File.directory?(full_path)
|
148
|
+
file[full_path.length+1..-1]
|
149
|
+
else
|
150
|
+
File.basename(file)
|
151
|
+
end
|
152
|
+
end
|
153
|
+
|
154
|
+
# Convert a relative path to an absolute one.
|
155
|
+
def absolute(file)
|
156
|
+
if File.directory?(full_path)
|
157
|
+
File.join(full_path, file)
|
158
|
+
else
|
159
|
+
full_path
|
160
|
+
end
|
161
|
+
end
|
162
|
+
end
|
163
|
+
|
164
|
+
end
|
165
|
+
end
|