rubypath 0.2.1 → 0.3.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/README.md +12 -4
- data/doc/file.README.html +74 -3
- data/lib/rubypath/backend/mock.rb +41 -0
- data/lib/rubypath/backend/sys.rb +20 -0
- data/lib/rubypath/backend.rb +5 -0
- data/lib/rubypath/comparison.rb +4 -7
- data/lib/rubypath/dir_operations.rb +72 -0
- data/lib/rubypath/file_operations.rb +26 -0
- data/lib/rubypath/path_operations.rb +20 -3
- data/lib/rubypath/version.rb +2 -2
- data/spec/rubypath/comparison_spec.rb +32 -1
- data/spec/rubypath/dir_operations_spec.rb +78 -1
- data/spec/rubypath/file_operations_spec.rb +39 -0
- data/spec/rubypath/identity_spec.rb +1 -1
- data/spec/rubypath/path_operations_spec.rb +49 -14
- data/spec/support/with_backend.rb +17 -11
- metadata +2 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: b6a50c0dd1eea579ab534a5b321a38265bd9c4f6
|
4
|
+
data.tar.gz: ad371190f1cc868eeba76d652302445f515dddfb
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 97edfdceb6a0bea7103f0f1923a36b9c1fdba7a604672910c7162886c701475b6bdb1975875d3d4bd42f75663e7f46ec34114d9a22208fa07b9528b33f40e82b
|
7
|
+
data.tar.gz: c4dbb1749cbc28cb73411cf387f39b10bf78a114f1b3d883b32d8ef13514e17878fc66f4a75b32a947443dbfb639e2a576fade52fb41481bc1e0248ec40581c3
|
data/README.md
CHANGED
@@ -6,7 +6,7 @@
|
|
6
6
|
[![Dependency Status](http://img.shields.io/gemnasium/jgraichen/rubypath.svg)](https://gemnasium.com/jgraichen/rubypath)
|
7
7
|
[![RubyDoc Documentation](http://img.shields.io/badge/rubydoc-here-blue.svg)](http://rubydoc.info/github/jgraichen/rubypath/master/frames)
|
8
8
|
|
9
|
-
*Ruby Path* introduces a global `Path` class unifying most `File`, `Dir`, `FileUtils`, `Pathname` and `IO` operations with a flexible and powerful Object-Interface and still adding new useful methods and functions like mocking a
|
9
|
+
*Ruby Path* introduces a global `Path` class unifying most `File`, `Dir`, `FileUtils`, `Pathname` and `IO` operations with a flexible and powerful Object-Interface and still adding new useful methods and functions like mocking a whole file system for fast and reliable testing.
|
10
10
|
|
11
11
|
## Installation
|
12
12
|
|
@@ -79,9 +79,17 @@ See full API documentation here: http://rubydoc.info/gems/rubypath/Path
|
|
79
79
|
|
80
80
|
1. Fork it
|
81
81
|
2. Create your feature branch (`git checkout -b my-new-feature`)
|
82
|
-
3.
|
83
|
-
4.
|
84
|
-
5.
|
82
|
+
3. Add specs testing SYS *and* MOCK file system
|
83
|
+
4. Commit your specs (`git commit -am 'Add specs for feature'`)
|
84
|
+
5. Add our changes for SYS *and* MOCK file system
|
85
|
+
6. Commit your changes (`git commit -am 'Add some feature'`)
|
86
|
+
7. Push to the branch (`git push origin my-new-feature`)
|
87
|
+
8. Create new Pull Request
|
88
|
+
|
89
|
+
### ToDos
|
90
|
+
|
91
|
+
* Add missing methods
|
92
|
+
* Improve MOCK FS implementation
|
85
93
|
|
86
94
|
## License
|
87
95
|
|
data/doc/file.README.html
CHANGED
@@ -63,7 +63,13 @@
|
|
63
63
|
|
64
64
|
<div id="content"><div id='filecontents'><h1>Ruby Path</h1>
|
65
65
|
|
66
|
-
<p><
|
66
|
+
<p><a href="http://badge.fury.io/rb/rubypath"><img src="https://badge.fury.io/rb/rubypath.svg" alt="Gem Version"></a>
|
67
|
+
<a href="https://travis-ci.org/jgraichen/rubypath"><img src="http://img.shields.io/travis/jgraichen/rubypath/master.svg" alt="Build Status"></a>
|
68
|
+
<a href="https://coveralls.io/r/jgraichen/rubypath"><img src="http://img.shields.io/coveralls/jgraichen/rubypath/master.svg" alt="Coverage Status"></a>
|
69
|
+
<a href="https://gemnasium.com/jgraichen/rubypath"><img src="http://img.shields.io/gemnasium/jgraichen/rubypath.svg" alt="Dependency Status"></a>
|
70
|
+
<a href="http://rubydoc.info/github/jgraichen/rubypath/master/frames"><img src="http://img.shields.io/badge/rubydoc-here-blue.svg" alt="RubyDoc Documentation"></a></p>
|
71
|
+
|
72
|
+
<p><em>Ruby Path</em> introduces a global <code>Path</code> class unifying most <code>File</code>, <code>Dir</code>, <code>FileUtils</code>, <code>Pathname</code> and <code>IO</code> operations with a flexible and powerful Object-Interface and still adding new useful methods and functions like mocking a whole file system for fast and reliable testing.</p>
|
67
73
|
|
68
74
|
<h2>Installation</h2>
|
69
75
|
|
@@ -71,18 +77,83 @@
|
|
71
77
|
|
72
78
|
<h2>Usage</h2>
|
73
79
|
|
74
|
-
<p>
|
80
|
+
<p>Using <code>Path</code> with file and directory methods:</p>
|
81
|
+
|
82
|
+
<pre class="code ruby"><code class="ruby"><span class='id identifier rubyid_base'>base</span> <span class='op'>=</span> <span class='const'>Path</span> <span class='tstring'><span class='tstring_beg'>'</span><span class='tstring_content'>/path/to/base</span><span class='tstring_end'>'</span></span>
|
83
|
+
<span class='id identifier rubyid_src'>src</span> <span class='op'>=</span> <span class='id identifier rubyid_base'>base</span><span class='period'>.</span><span class='id identifier rubyid_mkpath'>mkpath</span> <span class='tstring'><span class='tstring_beg'>'</span><span class='tstring_content'>project/src</span><span class='tstring_end'>'</span></span>
|
84
|
+
<span class='id identifier rubyid_src'>src</span><span class='period'>.</span><span class='id identifier rubyid_touch'>touch</span> <span class='tstring'><span class='tstring_beg'>'</span><span class='tstring_content'>Rakefile</span><span class='tstring_end'>'</span></span>
|
85
|
+
<span class='id identifier rubyid_src'>src</span><span class='period'>.</span><span class='id identifier rubyid_mkdir'>mkdir</span><span class='lparen'>(</span><span class='tstring'><span class='tstring_beg'>'</span><span class='tstring_content'>lib</span><span class='tstring_end'>'</span></span><span class='rparen'>)</span><span class='period'>.</span><span class='id identifier rubyid_mkdir'>mkdir</span><span class='lparen'>(</span><span class='tstring'><span class='tstring_beg'>'</span><span class='tstring_content'>mylib</span><span class='tstring_end'>'</span></span><span class='rparen'>)</span><span class='period'>.</span><span class='id identifier rubyid_touch'>touch</span><span class='lparen'>(</span><span class='tstring'><span class='tstring_beg'>'</span><span class='tstring_content'>version.rb</span><span class='tstring_end'>'</span></span><span class='rparen'>)</span>
|
86
|
+
<span class='comment'>#=> <Path '/path/to/base/project/src/lib/mylib/version.rb'
|
87
|
+
</span></code></pre>
|
88
|
+
|
89
|
+
<p>Using IO:</p>
|
90
|
+
|
91
|
+
<pre class="code ruby"><code class="ruby"><span class='id identifier rubyid_src'>src</span><span class='period'>.</span><span class='id identifier rubyid_write'>write</span> <span class='tstring'><span class='tstring_beg'>"</span><span class='tstring_content'>module Mylib\n VERSION = '0.1.0'\nend</span><span class='tstring_end'>"</span></span>
|
92
|
+
|
93
|
+
<span class='id identifier rubyid_src'>src</span><span class='period'>.</span><span class='id identifier rubyid_lookup'>lookup</span><span class='lparen'>(</span><span class='tstring'><span class='tstring_beg'>'</span><span class='tstring_content'>project.yml</span><span class='tstring_end'>'</span></span><span class='rparen'>)</span><span class='period'>.</span><span class='id identifier rubyid_read'>read</span>
|
94
|
+
<span class='comment'>#=> "..."
|
95
|
+
</span></code></pre>
|
96
|
+
|
97
|
+
<h3>Mock FS in tests</h3>
|
98
|
+
|
99
|
+
<p>Wrap specific or just all specs in a virtual filesystem:</p>
|
100
|
+
|
101
|
+
<pre class="code ruby"><code class="ruby"><span class='comment'># spec_helper.rb
|
102
|
+
</span>
|
103
|
+
<span class='id identifier rubyid_config'>config</span><span class='period'>.</span><span class='id identifier rubyid_around'>around</span><span class='lparen'>(</span><span class='symbol'>:each</span><span class='rparen'>)</span> <span class='kw'>do</span> <span class='op'>|</span><span class='id identifier rubyid_example'>example</span><span class='op'>|</span>
|
104
|
+
<span class='const'>Path</span><span class='op'>::</span><span class='const'>Backend</span><span class='period'>.</span><span class='id identifier rubyid_mock'>mock</span> <span class='label'>root:</span> <span class='symbol'>:tmp</span><span class='comma'>,</span> <span class='op'>&</span><span class='id identifier rubyid_example'>example</span>
|
105
|
+
<span class='kw'>end</span>
|
106
|
+
</code></pre>
|
107
|
+
|
108
|
+
<p>Supported options for <code>:root</code> are <code>:tmp</code> using the real filesystem but scoping all actions into a temporary directory similar to chroot or a custom defined path to use as "chroot" directory. This mode does not allow to stub users, home directories and some attributes.</p>
|
109
|
+
|
110
|
+
<p>If not <code>:root</code> is specified a completely virtual in-memory filesystem will be used. This backend allows to even specify available users and home directories, the current user etc.</p>
|
111
|
+
|
112
|
+
<p>You can then define a specific scenario in your specs:</p>
|
113
|
+
|
114
|
+
<pre class="code ruby"><code class="ruby"> <span class='id identifier rubyid_before'>before</span> <span class='kw'>do</span>
|
115
|
+
<span class='const'>Path</span><span class='period'>.</span><span class='id identifier rubyid_mock'>mock</span> <span class='kw'>do</span> <span class='op'>|</span><span class='id identifier rubyid_root'>root</span><span class='comma'>,</span> <span class='id identifier rubyid_backend'>backend</span><span class='op'>|</span>
|
116
|
+
<span class='id identifier rubyid_backend'>backend</span><span class='period'>.</span><span class='id identifier rubyid_cwd'>cwd</span> <span class='op'>=</span> <span class='tstring'><span class='tstring_beg'>'</span><span class='tstring_content'>/root</span><span class='tstring_end'>'</span></span>
|
117
|
+
<span class='id identifier rubyid_backend'>backend</span><span class='period'>.</span><span class='id identifier rubyid_current_user'>current_user</span> <span class='op'>=</span> <span class='tstring'><span class='tstring_beg'>'</span><span class='tstring_content'>test</span><span class='tstring_end'>'</span></span>
|
118
|
+
<span class='id identifier rubyid_backend'>backend</span><span class='period'>.</span><span class='id identifier rubyid_homes'>homes</span> <span class='op'>=</span> <span class='lbrace'>{</span><span class='tstring'><span class='tstring_beg'>'</span><span class='tstring_content'>test</span><span class='tstring_end'>'</span></span> <span class='op'>=></span> <span class='tstring'><span class='tstring_beg'>'</span><span class='tstring_content'>/home/test</span><span class='tstring_end'>'</span></span><span class='rbrace'>}</span>
|
119
|
+
|
120
|
+
<span class='id identifier rubyid_home'>home</span> <span class='op'>=</span> <span class='id identifier rubyid_root'>root</span><span class='period'>.</span><span class='id identifier rubyid_mkpath'>mkpath</span><span class='lparen'>(</span><span class='tstring'><span class='tstring_beg'>'</span><span class='tstring_content'>/home/test</span><span class='tstring_end'>'</span></span><span class='rparen'>)</span>
|
121
|
+
<span class='id identifier rubyid_home'>home</span><span class='period'>.</span><span class='id identifier rubyid_mkfile'>mkfile</span><span class='lparen'>(</span><span class='tstring'><span class='tstring_beg'>'</span><span class='tstring_content'>src/test.txt</span><span class='tstring_end'>'</span></span><span class='rparen'>)</span><span class='period'>.</span><span class='id identifier rubyid_write'>write</span> <span class='tstring'><span class='tstring_beg'>'</span><span class='tstring_content'>CONTENT</span><span class='tstring_end'>'</span></span>
|
122
|
+
<span class='id identifier rubyid_home'>home</span><span class='period'>.</span><span class='id identifier rubyid_mkfile'>mkfile</span><span class='lparen'>(</span><span class='tstring'><span class='tstring_beg'>'</span><span class='tstring_content'>src/test.html</span><span class='tstring_end'>'</span></span><span class='rparen'>)</span><span class='period'>.</span><span class='id identifier rubyid_write'>write</span> <span class='tstring'><span class='tstring_beg'>'</span><span class='tstring_content'><html><head><title></title>...</span><span class='tstring_end'>'</span></span>
|
123
|
+
<span class='kw'>end</span>
|
124
|
+
<span class='kw'>end</span>
|
125
|
+
|
126
|
+
<span class='id identifier rubyid_it'>it</span> <span class='tstring'><span class='tstring_beg'>'</span><span class='tstring_content'>should mock all FS</span><span class='tstring_end'>'</span></span> <span class='kw'>do</span>
|
127
|
+
<span class='id identifier rubyid_base'>base</span> <span class='op'>=</span> <span class='const'>Path</span><span class='lparen'>(</span><span class='tstring'><span class='tstring_beg'>'</span><span class='tstring_content'>~test</span><span class='tstring_end'>'</span></span><span class='rparen'>)</span><span class='period'>.</span><span class='id identifier rubyid_expand'>expand</span>
|
128
|
+
<span class='id identifier rubyid_expect'>expect</span><span class='lparen'>(</span><span class='id identifier rubyid_base'>base</span><span class='period'>.</span><span class='id identifier rubyid_join'>join</span><span class='lparen'>(</span><span class='qwords_beg'>%w(</span><span class='tstring_content'>src</span><span class='words_sep'> </span><span class='tstring_content'>test.txt</span><span class='words_sep'>)</span><span class='rparen'>)</span><span class='period'>.</span><span class='id identifier rubyid_read'>read</span><span class='rparen'>)</span><span class='period'>.</span><span class='id identifier rubyid_to'>to</span> <span class='id identifier rubyid_eq'>eq</span> <span class='tstring'><span class='tstring_beg'>'</span><span class='tstring_content'>CONTENT</span><span class='tstring_end'>'</span></span>
|
129
|
+
|
130
|
+
<span class='id identifier rubyid_files'>files</span> <span class='op'>=</span> <span class='id identifier rubyid_base'>base</span><span class='period'>.</span><span class='id identifier rubyid_glob'>glob</span><span class='lparen'>(</span><span class='tstring'><span class='tstring_beg'>'</span><span class='tstring_content'>**/*</span><span class='tstring_end'>'</span></span><span class='rparen'>)</span><span class='period'>.</span><span class='id identifier rubyid_select'>select</span><span class='lbrace'>{</span><span class='op'>|</span><span class='id identifier rubyid_p'>p</span><span class='op'>|</span> <span class='id identifier rubyid_p'>p</span><span class='period'>.</span><span class='id identifier rubyid_file?'>file?</span> <span class='rbrace'>}</span>
|
131
|
+
<span class='id identifier rubyid_expect'>expect</span><span class='lparen'>(</span><span class='id identifier rubyid_files'>files</span><span class='period'>.</span><span class='id identifier rubyid_size'>size</span><span class='rparen'>)</span><span class='period'>.</span><span class='id identifier rubyid_to'>to</span> <span class='id identifier rubyid_eq'>eq</span> <span class='int'>2</span>
|
132
|
+
<span class='kw'>end</span>
|
133
|
+
</code></pre>
|
134
|
+
|
135
|
+
<p>See full API documentation here: <a href="http://rubydoc.info/gems/rubypath/Path">http://rubydoc.info/gems/rubypath/Path</a></p>
|
75
136
|
|
76
137
|
<h2>Contributing</h2>
|
77
138
|
|
78
139
|
<ol>
|
79
140
|
<li>Fork it</li>
|
80
141
|
<li>Create your feature branch (<code>git checkout -b my-new-feature</code>)</li>
|
142
|
+
<li>Add specs testing SYS <em>and</em> MOCK file system</li>
|
143
|
+
<li>Commit your specs (<code>git commit -am 'Add specs for feature'</code>)</li>
|
144
|
+
<li>Add our changes for SYS <em>and</em> MOCK file system</li>
|
81
145
|
<li>Commit your changes (<code>git commit -am 'Add some feature'</code>)</li>
|
82
146
|
<li>Push to the branch (<code>git push origin my-new-feature</code>)</li>
|
83
147
|
<li>Create new Pull Request</li>
|
84
148
|
</ol>
|
85
149
|
|
150
|
+
<h3>ToDos</h3>
|
151
|
+
|
152
|
+
<ul>
|
153
|
+
<li>Add missing methods</li>
|
154
|
+
<li>Improve MOCK FS implementation</li>
|
155
|
+
</ul>
|
156
|
+
|
86
157
|
<h2>License</h2>
|
87
158
|
|
88
159
|
<p>Copyright (C) 2014 Jan Graichen</p>
|
@@ -95,7 +166,7 @@
|
|
95
166
|
</div></div>
|
96
167
|
|
97
168
|
<div id="footer">
|
98
|
-
Generated on
|
169
|
+
Generated on Sun Apr 27 13:05:43 2014 by
|
99
170
|
<a href="http://yardoc.org" title="Yay! A Ruby Documentation Tool" target="_parent">yard</a>
|
100
171
|
0.8.7.3 (ruby-2.1.1).
|
101
172
|
</div>
|
@@ -164,6 +164,47 @@ class Path::Backend
|
|
164
164
|
lookup!(path).mode
|
165
165
|
end
|
166
166
|
|
167
|
+
def unlink(path)
|
168
|
+
node = lookup_parent!(path)
|
169
|
+
file = node.lookup ::File.basename path
|
170
|
+
case file
|
171
|
+
when Dir
|
172
|
+
raise Errno::EISDIR.new path
|
173
|
+
when File
|
174
|
+
node.children.delete(file)
|
175
|
+
when nil
|
176
|
+
raise Errno::ENOENT.new path
|
177
|
+
else
|
178
|
+
raise ArgumentError.new "Unknown node #{node.inspect} for #unlink."
|
179
|
+
end
|
180
|
+
end
|
181
|
+
|
182
|
+
def rmtree(path)
|
183
|
+
node = lookup path
|
184
|
+
case node
|
185
|
+
when Dir, File
|
186
|
+
lookup_parent!(path).children.delete(node)
|
187
|
+
when nil
|
188
|
+
nil
|
189
|
+
else
|
190
|
+
raise ArgumentError.new "Unknown node #{node.inspect} for #rmtree."
|
191
|
+
end
|
192
|
+
end
|
193
|
+
alias_method :safe_rmtree, :rmtree
|
194
|
+
|
195
|
+
def rmtree!(path)
|
196
|
+
node = lookup path
|
197
|
+
case node
|
198
|
+
when Dir, File
|
199
|
+
lookup_parent!(path).children.delete(node)
|
200
|
+
when nil
|
201
|
+
raise Errno::ENOENT.new path
|
202
|
+
else
|
203
|
+
raise ArgumentError.new "Unknown node #{node.inspect} for #rmtree."
|
204
|
+
end
|
205
|
+
end
|
206
|
+
alias_method :safe_rmtree!, :rmtree!
|
207
|
+
|
167
208
|
# @!group Internal Virtual File System
|
168
209
|
|
169
210
|
# Return root node.
|
data/lib/rubypath/backend/sys.rb
CHANGED
@@ -137,5 +137,25 @@ class Path::Backend
|
|
137
137
|
def chmod(path, mode)
|
138
138
|
fs path, ::File, :chmod, mode, r(path)
|
139
139
|
end
|
140
|
+
|
141
|
+
def unlink(path)
|
142
|
+
fs path, ::File, :unlink, r(path)
|
143
|
+
end
|
144
|
+
|
145
|
+
def rmtree(path)
|
146
|
+
fs path, ::FileUtils, :rm_r, r(path), force: true
|
147
|
+
end
|
148
|
+
|
149
|
+
def rmtree!(path)
|
150
|
+
fs path, ::FileUtils, :rm_r, r(path)
|
151
|
+
end
|
152
|
+
|
153
|
+
def safe_rmtree(path)
|
154
|
+
fs path, ::FileUtils, :rm_r, r(path), force: true, secure: true
|
155
|
+
end
|
156
|
+
|
157
|
+
def safe_rmtree!(path)
|
158
|
+
fs path, ::FileUtils, :rm_r, r(path), secure: true
|
159
|
+
end
|
140
160
|
end
|
141
161
|
end
|
data/lib/rubypath/backend.rb
CHANGED
data/lib/rubypath/comparison.rb
CHANGED
@@ -9,13 +9,10 @@ class Path
|
|
9
9
|
# @return [Boolean] True if object represents same path.
|
10
10
|
#
|
11
11
|
def eql?(other)
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
internal_path.eql? other.path
|
17
|
-
else
|
18
|
-
Path.new(other).eql?(self) if Path.like? other
|
12
|
+
if other.is_a?(Path)
|
13
|
+
cleanpath.internal_path == other.cleanpath.internal_path
|
14
|
+
else
|
15
|
+
Path.new(other).eql?(self) if Path.like?(other)
|
19
16
|
end
|
20
17
|
end
|
21
18
|
alias_method :==, :eql?
|
@@ -31,6 +31,8 @@ class Path
|
|
31
31
|
end
|
32
32
|
end
|
33
33
|
|
34
|
+
# @!group Directory Operations
|
35
|
+
|
34
36
|
# Create directory.
|
35
37
|
#
|
36
38
|
# Given arguments will be joined with current path before directory is
|
@@ -80,6 +82,76 @@ class Path
|
|
80
82
|
Path.glob(::File.join(escaped_glob_path, pattern), flags, &block)
|
81
83
|
end
|
82
84
|
|
85
|
+
# Removes file or directory. If it's a directory it will be removed
|
86
|
+
# recursively.
|
87
|
+
#
|
88
|
+
# WARNING: This method causes local vulnerability if one of parent
|
89
|
+
# directories or removing directory tree are world writable (including
|
90
|
+
# `/tmp`, whose permission is 1777), and the current process has strong
|
91
|
+
# privilege such as Unix super user (root), and the system has symbolic link.
|
92
|
+
# For secure removing see {#safe_rmtree}.
|
93
|
+
#
|
94
|
+
# @return [Path] Path to removed file or directory.
|
95
|
+
#
|
96
|
+
def rmtree(*args)
|
97
|
+
with_path(*args) do |path|
|
98
|
+
invoke_backend :rmtree, internal_path
|
99
|
+
Path path
|
100
|
+
end
|
101
|
+
end
|
102
|
+
alias_method :rm_rf, :rmtree
|
103
|
+
|
104
|
+
# Removes file or directory. If it's a directory it will be removed
|
105
|
+
# recursively.
|
106
|
+
#
|
107
|
+
# This method uses #{FileUtils#remove_entry_secure} to avoid TOCTTOU
|
108
|
+
# (time-of-check-to-time-of-use) local security vulnerability of {#rmtree}.
|
109
|
+
# {#rmtree} causes security hole when:
|
110
|
+
#
|
111
|
+
# * Parent directory is world writable (including `/tmp`).
|
112
|
+
# * Removing directory tree includes world writable directory.
|
113
|
+
# * The system has symbolic link.
|
114
|
+
#
|
115
|
+
# @return [Path] Path to removed file or directory.
|
116
|
+
#
|
117
|
+
def safe_rmtree(*args)
|
118
|
+
with_path(*args) do |path|
|
119
|
+
invoke_backend :safe_rmtree, internal_path
|
120
|
+
Path path
|
121
|
+
end
|
122
|
+
end
|
123
|
+
|
124
|
+
# Removes file or directory. If it's a directory it will be removed
|
125
|
+
# recursively.
|
126
|
+
#
|
127
|
+
# This method behaves exactly like {#rmtree} but will raise exceptions
|
128
|
+
# e.g. when file does not exist.
|
129
|
+
#
|
130
|
+
# @return [Path] Path to removed file or directory.
|
131
|
+
#
|
132
|
+
def rmtree!(*args)
|
133
|
+
with_path(*args) do |path|
|
134
|
+
invoke_backend :rmtree!, internal_path
|
135
|
+
Path path
|
136
|
+
end
|
137
|
+
end
|
138
|
+
alias_method :rm_r, :rmtree!
|
139
|
+
|
140
|
+
# Removes file or directory. If it's a directory it will be removed
|
141
|
+
# recursively.
|
142
|
+
#
|
143
|
+
# This method behaves exactly like {#safe_rmtree} but will raise exceptions
|
144
|
+
# e.g. when file does not exist.
|
145
|
+
#
|
146
|
+
# @return [Path] Path to removed file or directory.
|
147
|
+
#
|
148
|
+
def safe_rmtree!(*args)
|
149
|
+
with_path(*args) do |path|
|
150
|
+
invoke_backend :safe_rmtree!, internal_path
|
151
|
+
Path path
|
152
|
+
end
|
153
|
+
end
|
154
|
+
|
83
155
|
private
|
84
156
|
|
85
157
|
def escaped_glob_path
|
@@ -17,6 +17,10 @@ class Path
|
|
17
17
|
# Path('/path/to/file.txt').touch
|
18
18
|
# #=> <Path:"/path/to/file.txt">
|
19
19
|
#
|
20
|
+
# @example
|
21
|
+
# Path('/path/to').touch('file.txt')
|
22
|
+
# #=> <Path:"/path/to/file.txt">
|
23
|
+
#
|
20
24
|
# @return [Path] Path to touched file.
|
21
25
|
#
|
22
26
|
def touch(*args)
|
@@ -26,6 +30,28 @@ class Path
|
|
26
30
|
end
|
27
31
|
end
|
28
32
|
|
33
|
+
# Removes file at current path.
|
34
|
+
#
|
35
|
+
# Raise an error if file does not exists or is a directory.
|
36
|
+
#
|
37
|
+
# @example
|
38
|
+
# Path('/file.txt').touch.unlink
|
39
|
+
# #=> <Path /file.txt>
|
40
|
+
#
|
41
|
+
# @example
|
42
|
+
# Path('/file.txt').touch
|
43
|
+
# Path('/').unlink('file.txt')
|
44
|
+
# #=> <Path /file.txt>
|
45
|
+
#
|
46
|
+
# @return [Path] Unlinked path.
|
47
|
+
#
|
48
|
+
def unlink(*args)
|
49
|
+
with_path(*args) do |path|
|
50
|
+
invoke_backend :unlink, path
|
51
|
+
Path path
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
29
55
|
# Create a file at pointed location and all missing parent directories.
|
30
56
|
#
|
31
57
|
# Given arguments will be joined with current path before directories and
|
@@ -280,9 +280,26 @@ class Path
|
|
280
280
|
end
|
281
281
|
alias_method :relative_path_from, :relative_from
|
282
282
|
|
283
|
-
|
284
|
-
|
283
|
+
# Return cleaned path with all dot components removed.
|
284
|
+
#
|
285
|
+
# No file system will accessed and not symlinks will be resolved.
|
286
|
+
#
|
287
|
+
# @example
|
288
|
+
# Path('./file.txt').cleanpath
|
289
|
+
# #=> <Path file.txt>
|
290
|
+
#
|
291
|
+
# @example
|
292
|
+
# Path('path/to/another/../file/../../txt').cleanpath
|
293
|
+
# #=> <Path path/txt>
|
294
|
+
#
|
295
|
+
# @return [Path] Cleaned path.
|
296
|
+
#
|
285
297
|
def cleanpath
|
286
|
-
|
298
|
+
path = Pathname.new(self).cleanpath
|
299
|
+
if path == internal_path
|
300
|
+
self
|
301
|
+
else
|
302
|
+
Path path
|
303
|
+
end
|
287
304
|
end
|
288
305
|
end
|
data/lib/rubypath/version.rb
CHANGED
@@ -11,10 +11,20 @@ describe Path do
|
|
11
11
|
expect(res).to be true
|
12
12
|
end
|
13
13
|
|
14
|
-
it 'should compare paths (
|
14
|
+
it 'should compare paths (2)' do
|
15
15
|
res = path.send described_method, Path('/path/to/another/file')
|
16
16
|
expect(res).to be false
|
17
17
|
end
|
18
|
+
|
19
|
+
it 'should compare clean paths (1)' do
|
20
|
+
res = path.send described_method, Path('/path/to/./file')
|
21
|
+
expect(res).to be true
|
22
|
+
end
|
23
|
+
|
24
|
+
it 'should compare clean paths (2)' do
|
25
|
+
res = path.send described_method, Path('/path/to/another/../file')
|
26
|
+
expect(res).to be true
|
27
|
+
end
|
18
28
|
end
|
19
29
|
|
20
30
|
context 'with String object' do
|
@@ -27,6 +37,16 @@ describe Path do
|
|
27
37
|
res = path.send described_method, '/path/to/another/file'
|
28
38
|
expect(res).to be false
|
29
39
|
end
|
40
|
+
|
41
|
+
it 'should compare clean paths (1)' do
|
42
|
+
res = path.send described_method, '/path/to/./file'
|
43
|
+
expect(res).to be true
|
44
|
+
end
|
45
|
+
|
46
|
+
it 'should compare clean paths (2)' do
|
47
|
+
res = path.send described_method, '/path/to/another/../file'
|
48
|
+
expect(res).to be true
|
49
|
+
end
|
30
50
|
end
|
31
51
|
|
32
52
|
context 'with Pathname object' do
|
@@ -40,6 +60,17 @@ describe Path do
|
|
40
60
|
Pathname.new('/path/to/another/file')
|
41
61
|
expect(res).to be false
|
42
62
|
end
|
63
|
+
|
64
|
+
it 'should compare clean paths (1)' do
|
65
|
+
res = path.send described_method, Pathname.new('/path/to/./file')
|
66
|
+
expect(res).to be true
|
67
|
+
end
|
68
|
+
|
69
|
+
it 'should compare clean paths (2)' do
|
70
|
+
res = path.send described_method,
|
71
|
+
Pathname.new('/path/to/another/../file')
|
72
|
+
expect(res).to be true
|
73
|
+
end
|
43
74
|
end
|
44
75
|
end
|
45
76
|
end
|
@@ -39,6 +39,83 @@ describe Path do
|
|
39
39
|
end
|
40
40
|
end
|
41
41
|
|
42
|
+
shared_examples '#remove_recursive' do
|
43
|
+
context 'on existent file' do
|
44
|
+
before { path.mkfile }
|
45
|
+
it{ expect{ subject }.to change(path, :exist?).from(true).to(false) }
|
46
|
+
end
|
47
|
+
|
48
|
+
context 'on existent directory' do
|
49
|
+
before { path.mkpath }
|
50
|
+
it{ expect{ subject }.to change(path, :exist?).from(true).to(false) }
|
51
|
+
end
|
52
|
+
|
53
|
+
context 'on existent directory with children' do
|
54
|
+
before { path.mkfile('subdir/file') }
|
55
|
+
it{ expect{ subject }.to change(path, :exist?).from(true).to(false) }
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
describe_method :rmtree, aliases: [:rm_rf] do
|
60
|
+
let(:path) { Path '/path' }
|
61
|
+
subject { path.send(described_method) }
|
62
|
+
|
63
|
+
context 'on non-existent file' do
|
64
|
+
it { expect{ subject }.to_not raise_error }
|
65
|
+
end
|
66
|
+
|
67
|
+
it_behaves_like '#remove_recursive'
|
68
|
+
end
|
69
|
+
|
70
|
+
describe_method :safe_rmtree do
|
71
|
+
let(:path) { Path '/path' }
|
72
|
+
subject { path.send(described_method) }
|
73
|
+
|
74
|
+
it 'should use #remove_entry_secure' do
|
75
|
+
if backend_type == :sys
|
76
|
+
path.mkfile
|
77
|
+
expect(FileUtils).to receive(:remove_entry_secure).and_call_original
|
78
|
+
subject
|
79
|
+
end
|
80
|
+
end
|
81
|
+
|
82
|
+
context 'on non-existent file' do
|
83
|
+
it { expect{ subject }.to_not raise_error }
|
84
|
+
end
|
85
|
+
|
86
|
+
it_behaves_like '#remove_recursive'
|
87
|
+
end
|
88
|
+
|
89
|
+
describe_method :rmtree!, aliases: [:rm_r] do
|
90
|
+
let(:path) { Path '/path' }
|
91
|
+
subject { path.send(described_method) }
|
92
|
+
|
93
|
+
context 'on non-existent file' do
|
94
|
+
it { expect{ subject }.to raise_error Errno::ENOENT }
|
95
|
+
end
|
96
|
+
|
97
|
+
it_behaves_like '#remove_recursive'
|
98
|
+
end
|
99
|
+
|
100
|
+
describe_method :safe_rmtree! do
|
101
|
+
let(:path) { Path '/path' }
|
102
|
+
subject { path.send(described_method) }
|
103
|
+
|
104
|
+
it 'should use #remove_entry_secure' do
|
105
|
+
if backend_type == :sys
|
106
|
+
path.mkfile
|
107
|
+
expect(FileUtils).to receive(:remove_entry_secure).and_call_original
|
108
|
+
subject
|
109
|
+
end
|
110
|
+
end
|
111
|
+
|
112
|
+
context 'on non-existent file' do
|
113
|
+
it { expect{ subject }.to raise_error Errno::ENOENT }
|
114
|
+
end
|
115
|
+
|
116
|
+
it_behaves_like '#remove_recursive'
|
117
|
+
end
|
118
|
+
|
42
119
|
describe '#glob' do
|
43
120
|
it 'should delegate to class#glob' do
|
44
121
|
expect(Path).to receive(:glob)
|
@@ -130,7 +207,7 @@ describe Path do
|
|
130
207
|
end
|
131
208
|
|
132
209
|
it 'should return list of Path objects' do
|
133
|
-
subject.each{|e| expect(e).to be_a Path }
|
210
|
+
subject.each{ |e| expect(e).to be_a Path }
|
134
211
|
end
|
135
212
|
end
|
136
213
|
|
@@ -15,6 +15,45 @@ describe Path do
|
|
15
15
|
end
|
16
16
|
end
|
17
17
|
|
18
|
+
describe_method :unlink do
|
19
|
+
subject { path.send described_method }
|
20
|
+
|
21
|
+
context 'on non-existent file' do
|
22
|
+
it { expect{ subject }.to raise_error Errno::ENOENT }
|
23
|
+
end
|
24
|
+
|
25
|
+
context 'on existent file' do
|
26
|
+
before { path.mkfile }
|
27
|
+
|
28
|
+
it 'should unlink file' do
|
29
|
+
expect{ subject }.to change(path, :exist?).from(true).to(false)
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
context 'on existent directory' do
|
34
|
+
before { path.mkpath }
|
35
|
+
|
36
|
+
it { expect{ subject }.to raise_error Errno::EISDIR }
|
37
|
+
end
|
38
|
+
|
39
|
+
context 'with args' do
|
40
|
+
subject { path.send(described_method, 'file') }
|
41
|
+
|
42
|
+
context 'on non-existent file' do
|
43
|
+
it { expect{ subject }.to raise_error Errno::ENOENT }
|
44
|
+
end
|
45
|
+
|
46
|
+
context 'on existent file' do
|
47
|
+
before { path.mkfile('file') }
|
48
|
+
|
49
|
+
it 'should unlink file' do
|
50
|
+
expect{ subject }
|
51
|
+
.to change(path.join('file'), :exist?).from(true).to(false)
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
18
57
|
describe_method :touch do
|
19
58
|
let(:path) { Path '/rubypath' }
|
20
59
|
let(:args) { Array.new }
|
@@ -4,7 +4,7 @@ describe Path do
|
|
4
4
|
describe 'Identity' do
|
5
5
|
let(:str) { '/path/to/file' }
|
6
6
|
let(:args) { [str] }
|
7
|
-
let(:path) { described_class.new
|
7
|
+
let(:path) { described_class.new(*args) }
|
8
8
|
subject { path }
|
9
9
|
|
10
10
|
describe_method :path, aliases: [:to_path, :to_str, :to_s] do
|
@@ -8,7 +8,7 @@ describe Path do
|
|
8
8
|
subject { path }
|
9
9
|
|
10
10
|
describe_method :join do
|
11
|
-
subject { path.send
|
11
|
+
subject { path.send(described_method, *join_args) }
|
12
12
|
|
13
13
|
context 'with single string' do
|
14
14
|
let(:join_args) { ['to/file.txt'] }
|
@@ -36,6 +36,35 @@ describe Path do
|
|
36
36
|
end
|
37
37
|
end
|
38
38
|
|
39
|
+
describe_method :cleanpath do
|
40
|
+
subject { path.send(described_method) }
|
41
|
+
|
42
|
+
context 'with out dot components' do
|
43
|
+
let(:path) { Path 'path/to/file.txt' }
|
44
|
+
it { should eq 'path/to/file.txt' }
|
45
|
+
end
|
46
|
+
|
47
|
+
context 'with leading dot' do
|
48
|
+
let(:path) { Path './file.txt' }
|
49
|
+
it { should eq 'file.txt' }
|
50
|
+
end
|
51
|
+
|
52
|
+
context 'with including dot' do
|
53
|
+
let(:path) { Path 'path/to/./file.txt' }
|
54
|
+
it { should eq 'path/to/file.txt' }
|
55
|
+
end
|
56
|
+
|
57
|
+
context 'with double dot' do
|
58
|
+
let(:path) { Path 'path/to/../file.txt' }
|
59
|
+
it { should eq 'path/file.txt' }
|
60
|
+
end
|
61
|
+
|
62
|
+
context 'with multiple dots' do
|
63
|
+
let(:path) { Path 'path/to/../../opath/to/./../file.txt' }
|
64
|
+
it { should eq 'opath/file.txt' }
|
65
|
+
end
|
66
|
+
end
|
67
|
+
|
39
68
|
describe_method :each_component do
|
40
69
|
let(:block) { nil }
|
41
70
|
let(:opts) { Hash.new }
|
@@ -52,17 +81,17 @@ describe Path do
|
|
52
81
|
let(:opts) { {empty: true} }
|
53
82
|
|
54
83
|
it 'should also return empty path components' do
|
55
|
-
expect(subject.to_a).to eq
|
84
|
+
expect(subject.to_a).to eq([''] + %w(path to templates dir) + [''])
|
56
85
|
end
|
57
86
|
end
|
58
87
|
|
59
88
|
context 'with block' do
|
60
|
-
let(:block) { proc{|fn| fn} }
|
89
|
+
let(:block) { proc{|fn| fn } }
|
61
90
|
|
62
91
|
it 'should yield components' do
|
63
|
-
expect
|
92
|
+
expect do |b|
|
64
93
|
path.send described_method, &b
|
65
|
-
|
94
|
+
end.to yield_successive_args(*%w(path to templates dir))
|
66
95
|
end
|
67
96
|
|
68
97
|
it { should eq path }
|
@@ -80,7 +109,8 @@ describe Path do
|
|
80
109
|
describe_method :dirname, aliases: [:parent] do
|
81
110
|
shared_examples 'dirname' do
|
82
111
|
it 'should return parent directory' do
|
83
|
-
expect(Path(base, 'path/to/file').send(described_method))
|
112
|
+
expect(Path(base, 'path/to/file').send(described_method))
|
113
|
+
.to eq "#{base}path/to"
|
84
114
|
end
|
85
115
|
|
86
116
|
context 'when hitting root' do
|
@@ -108,7 +138,8 @@ describe Path do
|
|
108
138
|
end
|
109
139
|
|
110
140
|
with_backends :mock, :sys do
|
111
|
-
describe_method :expand, aliases: [:expand_path,
|
141
|
+
describe_method :expand, aliases: [:expand_path,
|
142
|
+
:absolute, :absolute_path] do
|
112
143
|
let(:cwd) { '/working/dir' }
|
113
144
|
let(:base) { cwd }
|
114
145
|
let(:args) { Array.new }
|
@@ -120,7 +151,7 @@ describe Path do
|
|
120
151
|
end
|
121
152
|
end
|
122
153
|
|
123
|
-
around{|example| Path::Backend.mock
|
154
|
+
around{|example| Path::Backend.mock(&example) }
|
124
155
|
|
125
156
|
shared_examples '#expand' do
|
126
157
|
subject { Path(path).send(described_method, *args) }
|
@@ -311,11 +342,13 @@ describe Path do
|
|
311
342
|
it { should eq path }
|
312
343
|
|
313
344
|
it 'should yield part paths' do
|
314
|
-
expect{|b| path.send
|
345
|
+
expect{|b| path.send(described_method, &b) }
|
346
|
+
.to yield_successive_args(*expected_paths)
|
315
347
|
end
|
316
348
|
|
317
349
|
it 'should yield Path objects' do
|
318
|
-
expect{|b| path.send
|
350
|
+
expect{|b| path.send(described_method, &b) }
|
351
|
+
.to yield_successive_args(*expected_paths.map{ Path })
|
319
352
|
end
|
320
353
|
end
|
321
354
|
|
@@ -325,24 +358,26 @@ describe Path do
|
|
325
358
|
it { should be_a Enumerator }
|
326
359
|
|
327
360
|
it 'should yield part paths' do
|
328
|
-
expect{|b| subject.each
|
361
|
+
expect{|b| subject.each(&b) }
|
362
|
+
.to yield_successive_args(*expected_paths)
|
329
363
|
end
|
330
364
|
|
331
365
|
it 'should yield path objects' do
|
332
|
-
expect{|b| subject.each
|
366
|
+
expect{|b| subject.each(&b) }
|
367
|
+
.to yield_successive_args(*expected_paths.map{ Path })
|
333
368
|
end
|
334
369
|
end
|
335
370
|
end
|
336
371
|
|
337
372
|
context 'with absolute path' do
|
338
373
|
let(:path) { Path '/path/to/file.txt' }
|
339
|
-
let(:expected_paths) { %w(/path/to/file.txt /path/to /path /)}
|
374
|
+
let(:expected_paths) { %w(/path/to/file.txt /path/to /path /) }
|
340
375
|
it_behaves_like 'ascend'
|
341
376
|
end
|
342
377
|
|
343
378
|
context 'with relative path' do
|
344
379
|
let(:path) { Path 'path/to/file.txt' }
|
345
|
-
let(:expected_paths) { %w(path/to/file.txt path/to path .)}
|
380
|
+
let(:expected_paths) { %w(path/to/file.txt path/to path .) }
|
346
381
|
it_behaves_like 'ascend'
|
347
382
|
end
|
348
383
|
end
|
@@ -1,15 +1,17 @@
|
|
1
|
+
#
|
1
2
|
module WithBackend
|
2
|
-
|
3
3
|
def with_backends(*args, &block)
|
4
4
|
args.each do |backend|
|
5
5
|
be = case backend
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
6
|
+
when :mock
|
7
|
+
->(ex){ Path::Backend.mock(&ex) }
|
8
|
+
when :sys
|
9
|
+
->(ex){ Path::Backend.mock(root: :tmp, &ex) }
|
10
|
+
else
|
11
|
+
raise ArgumentError.new 'Unknown backend.'
|
12
|
+
end
|
13
|
+
|
14
|
+
next if ENV["FS_#{backend.upcase}"] == '0'
|
13
15
|
|
14
16
|
describe "with #{backend.upcase} FS" do
|
15
17
|
let(:backend_type) { backend }
|
@@ -17,15 +19,19 @@ module WithBackend
|
|
17
19
|
be.call(example)
|
18
20
|
end
|
19
21
|
|
20
|
-
module_eval
|
22
|
+
module_eval(&block)
|
21
23
|
end
|
22
24
|
end
|
23
25
|
end
|
24
26
|
alias_method :with_backend, :with_backends
|
25
27
|
|
26
28
|
def pending_backend(*args)
|
27
|
-
before
|
29
|
+
before do
|
30
|
+
if args.include? backend_type
|
31
|
+
pending "Pending on #{backend_type} backend."
|
32
|
+
end
|
33
|
+
end
|
28
34
|
end
|
29
35
|
|
30
|
-
RSpec.configure{|c| c.extend WithBackend }
|
36
|
+
RSpec.configure{ |c| c.extend WithBackend }
|
31
37
|
end
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: rubypath
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.3.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Jan Graichen
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2014-04-
|
11
|
+
date: 2014-04-27 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: bundler
|