rubypath 0.2.1 → 0.3.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 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