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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: f4d517017d2e21a8fc5e59485e285f19cede540d
4
- data.tar.gz: d978363e7ce770913f83b02c13ab41799f04e6b3
3
+ metadata.gz: b6a50c0dd1eea579ab534a5b321a38265bd9c4f6
4
+ data.tar.gz: ad371190f1cc868eeba76d652302445f515dddfb
5
5
  SHA512:
6
- metadata.gz: e9b2defbdf58415d76e6d2cb8d4902eca1845ba71d1ffb237ebbd11699eb11bead4317735286dadb53c31d07cb72165d4afecc2d3de0ccf142ec586d2f609d1a
7
- data.tar.gz: 7f54b7a30c7a56af8ecd10943672b7ba550cd526d8b51874772b683bc3118ef9aadcca16fc7487288447f35f1f83b4c41a072e751cbbd81c46e5d52f2beb10e9
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 while file system for fast and reliable testing.
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. Commit your changes (`git commit -am 'Add some feature'`)
83
- 4. Push to the branch (`git push origin my-new-feature`)
84
- 5. Create new Pull Request
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><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 while file system for fast and reliable testing.</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>TODO</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'>&#39;</span><span class='tstring_content'>/path/to/base</span><span class='tstring_end'>&#39;</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'>&#39;</span><span class='tstring_content'>project/src</span><span class='tstring_end'>&#39;</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'>&#39;</span><span class='tstring_content'>Rakefile</span><span class='tstring_end'>&#39;</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'>&#39;</span><span class='tstring_content'>lib</span><span class='tstring_end'>&#39;</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'>&#39;</span><span class='tstring_content'>mylib</span><span class='tstring_end'>&#39;</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'>&#39;</span><span class='tstring_content'>version.rb</span><span class='tstring_end'>&#39;</span></span><span class='rparen'>)</span>
86
+ <span class='comment'>#=&gt; &lt;Path &#39;/path/to/base/project/src/lib/mylib/version.rb&#39;
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'>&quot;</span><span class='tstring_content'>module Mylib\n VERSION = &#39;0.1.0&#39;\nend</span><span class='tstring_end'>&quot;</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'>&#39;</span><span class='tstring_content'>project.yml</span><span class='tstring_end'>&#39;</span></span><span class='rparen'>)</span><span class='period'>.</span><span class='id identifier rubyid_read'>read</span>
94
+ <span class='comment'>#=&gt; &quot;...&quot;
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'>&amp;</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 &quot;chroot&quot; 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'>&#39;</span><span class='tstring_content'>/root</span><span class='tstring_end'>&#39;</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'>&#39;</span><span class='tstring_content'>test</span><span class='tstring_end'>&#39;</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'>&#39;</span><span class='tstring_content'>test</span><span class='tstring_end'>&#39;</span></span> <span class='op'>=&gt;</span> <span class='tstring'><span class='tstring_beg'>&#39;</span><span class='tstring_content'>/home/test</span><span class='tstring_end'>&#39;</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'>&#39;</span><span class='tstring_content'>/home/test</span><span class='tstring_end'>&#39;</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'>&#39;</span><span class='tstring_content'>src/test.txt</span><span class='tstring_end'>&#39;</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'>&#39;</span><span class='tstring_content'>CONTENT</span><span class='tstring_end'>&#39;</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'>&#39;</span><span class='tstring_content'>src/test.html</span><span class='tstring_end'>&#39;</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'>&#39;</span><span class='tstring_content'>&lt;html&gt;&lt;head&gt;&lt;title&gt;&lt;/title&gt;...</span><span class='tstring_end'>&#39;</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'>&#39;</span><span class='tstring_content'>should mock all FS</span><span class='tstring_end'>&#39;</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'>&#39;</span><span class='tstring_content'>~test</span><span class='tstring_end'>&#39;</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'>&#39;</span><span class='tstring_content'>CONTENT</span><span class='tstring_end'>&#39;</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'>&#39;</span><span class='tstring_content'>**/*</span><span class='tstring_end'>&#39;</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 &#39;Add specs for feature&#39;</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 &#39;Add some feature&#39;</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 Wed Apr 16 20:25:34 2014 by
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.
@@ -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
@@ -64,6 +64,11 @@ class Path
64
64
  delegate :set_umask
65
65
  delegate :mode
66
66
  delegate :chmod
67
+ delegate :unlink
68
+ delegate :rmtree
69
+ delegate :rmtree!
70
+ delegate :safe_rmtree
71
+ delegate :safe_rmtree!
67
72
  end
68
73
 
69
74
  private
@@ -9,13 +9,10 @@ class Path
9
9
  # @return [Boolean] True if object represents same path.
10
10
  #
11
11
  def eql?(other)
12
- case other
13
- when String
14
- internal_path.eql? other
15
- when Path
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
- protected
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
- Path Pathname.new(self).cleanpath
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
@@ -1,8 +1,8 @@
1
1
  class Path
2
2
  module VERSION
3
3
  MAJOR = 0
4
- MINOR = 2
5
- PATCH = 1
4
+ MINOR = 3
5
+ PATCH = 0
6
6
  STAGE = nil
7
7
  STRING = [MAJOR, MINOR, PATCH, STAGE].reject(&:nil?).join('.').freeze
8
8
 
@@ -11,10 +11,20 @@ describe Path do
11
11
  expect(res).to be true
12
12
  end
13
13
 
14
- it 'should compare paths (1)' do
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 *args }
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 described_method, *join_args}
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 ([''] + %w(path to templates dir) + [''])
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 {|b|
92
+ expect do |b|
64
93
  path.send described_method, &b
65
- }.to yield_successive_args(*%w(path to templates dir))
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)).to eq "#{base}path/to"
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, :absolute, :absolute_path] do
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 &example }
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 described_method, &b }.to yield_successive_args *expected_paths
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 described_method, &b }.to yield_successive_args *expected_paths.map{ Path }
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 &b }.to yield_successive_args *expected_paths
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 &b }.to yield_successive_args *expected_paths.map{ Path }
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
- when :mock
7
- lambda { |ex| Path::Backend.mock &ex }
8
- when :sys
9
- lambda { |ex| Path::Backend.mock(root: :tmp, &ex) }
10
- else
11
- raise ArgumentError.new 'Unknown backend.'
12
- end
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 &block
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 { pending "Pending on #{backend_type} backend." if args.include? backend_type }
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.2.1
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-24 00:00:00.000000000 Z
11
+ date: 2014-04-27 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: bundler