tmptation 1.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
data/.gitignore ADDED
@@ -0,0 +1 @@
1
+ *.gem
data/LICENSE ADDED
@@ -0,0 +1,19 @@
1
+ Copyright © 2009 Martin Aumont (mynyml)
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining a copy of
4
+ this software and associated documentation files (the "Software"), to deal in
5
+ the Software without restriction, including without limitation the rights to
6
+ use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
7
+ of the Software, and to permit persons to whom the Software is furnished to do
8
+ so, subject to the following conditions:
9
+
10
+ The above copyright notice and this permission notice shall be included in all
11
+ copies or substantial portions of the Software.
12
+
13
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
16
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
17
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
18
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
19
+ SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,54 @@
1
+ Summary
2
+ -------
3
+ Tmptation provides classes that help safely manipulate temporary files and
4
+ directories. Especially useful for use in tests.
5
+
6
+ Features
7
+ --------
8
+ * garbage collection of all created tmp files and dirs
9
+ * safe deletion - will refuse to delete non-tmp paths (where tmp within `Dir.tmpdir`)
10
+
11
+ Examples
12
+ --------
13
+
14
+ # TmpFile is a subclass of Tempfile, with a few additions
15
+
16
+ file = TmpFile.new('name', 'contents')
17
+
18
+ file.path.exist? #=> true
19
+ file.closed? #=> false
20
+ file.read #=> "contents"
21
+
22
+ TmpFile.delete_all
23
+
24
+ file.path.exist? #=> false
25
+ file.closed? #=> true
26
+
27
+
28
+ # TmpDir is a subclass of Pathname, with a few additions
29
+
30
+ path = TmpDir.new
31
+ path.exist? #=> true
32
+
33
+ TmpDir.delete_all
34
+ path.exist? #=> false
35
+
36
+ Mixins
37
+ ------
38
+
39
+ Tmptation also contains two mixins, `SafeDeletable` and `SubclassTracking`.
40
+ They might be useful on their own. See the inline docs for more details.
41
+
42
+ Protip
43
+ ------
44
+
45
+ If you use Tmptation in specs, add `TmpFile.delete_all` and `TmpDir.delete_all`
46
+ to your global teardown method:
47
+
48
+ class MiniTest::Unit::TestCase
49
+ def teardown
50
+ TmpFile.delete_all
51
+ TmpDir.delete_all
52
+ end
53
+ end
54
+
data/Rakefile ADDED
@@ -0,0 +1,7 @@
1
+ task :default => :test
2
+
3
+ desc "Run tests"
4
+ task :test do
5
+ system "ruby -rubygems -I.:lib:test -e'%w( #{Dir['test/**/*_test.rb'].join(' ')} ).each {|p| require p }'"
6
+ end
7
+
data/lib/tmptation.rb ADDED
@@ -0,0 +1,171 @@
1
+ require 'pathname'
2
+ require 'tempfile'
3
+ require 'tmpdir'
4
+ require 'fileutils'
5
+
6
+ module Tmptation
7
+ VERSION = 1.1
8
+
9
+ # Adds a #safe_delete method that will delete the object's associated path
10
+ # (either #path or #to_s, if it exists) only if it lives within the system's
11
+ # temporary directory (as defined by Dir.tmpdir)
12
+ #
13
+ # @example
14
+ #
15
+ # path = Pathname.new('~/Documents')
16
+ # path.extend(SafeDeletable)
17
+ #
18
+ # path.to_s #=> '~/Documents'
19
+ # path.safe_delete #=> raises UnsafeDelete
20
+ #
21
+ # # however:
22
+ #
23
+ # path = Pathname(Dir.mktmpdir).expand_path
24
+ #
25
+ # Dir.tmpdir #=> /var/folders/l8/l8EJIxZoHGGj+y1RvV0r6U+++TM/-Tmp-/
26
+ # path.to_s #=> /var/folders/l8/l8EJIxZoHGGj+y1RvV0r6U+++TM/-Tmp-/20101103-94996-1iywsjo
27
+ #
28
+ # path.exist? #=> true
29
+ # path.safe_delete
30
+ # path.exist? #=> false
31
+ #
32
+ module SafeDeletable
33
+ UnsafeDelete = Class.new(RuntimeError)
34
+
35
+ # Delete `#path` or `#to_s` if it exists, and only if it lives within
36
+ # `Dir.tmpdir`. If the path is a directory, it is deleted recursively.
37
+ #
38
+ # @raises SafeDeletable::UnsafeDelete if directory isn't within `Dir.tmpdir`
39
+ #
40
+ def safe_delete
41
+ path = self.respond_to?(:path) ? self.path : self.to_s
42
+ path = Pathname(path).expand_path
43
+
44
+ unless path.to_s.match(/^#{Regexp.escape(Dir.tmpdir)}/)
45
+ raise UnsafeDelete.new("refusing to remove non-tmp directory '#{path}'")
46
+ end
47
+ FileUtils.remove_entry_secure(path.to_s)
48
+ end
49
+ end
50
+
51
+ # Keep track of a class's instances
52
+ #
53
+ # @example
54
+ #
55
+ # class Foo
56
+ # include InstanceTracking
57
+ # end
58
+ #
59
+ # a, b, c = Foo.new, Foo.new, Foo.new
60
+ #
61
+ # [a,b,c] == Foo.instances #=> true
62
+ #
63
+ module InstanceTracking
64
+ def self.included(base)
65
+ base.class_eval do
66
+ extend ClassMethods
67
+ include InstanceMethods
68
+ end
69
+ end
70
+
71
+ module ClassMethods
72
+ def instances
73
+ @instances ||= []
74
+ end
75
+ end
76
+
77
+ module InstanceMethods
78
+ def initialize(*args)
79
+ super
80
+ self.class.instances << self
81
+ end
82
+ end
83
+ end
84
+
85
+ # Subclass of core lib's Tempfile that allows safely deleting all of its
86
+ # instances. It also provides a convenient way to add content to the file.
87
+ #
88
+ # @example
89
+ #
90
+ # file = TmpFile.new('name', 'contents')
91
+ #
92
+ # file.path.class #=> Pathname
93
+ # file.path.exist? #=> true
94
+ # file.closed? #=> false
95
+ # file.read #=> "contents"
96
+ #
97
+ # TmpFile.delete_all
98
+ #
99
+ # file.path.exist? #=> false
100
+ # file.closed? #=> true
101
+ #
102
+ class TmpFile < Tempfile
103
+ include SafeDeletable
104
+ include InstanceTracking
105
+
106
+ class << self
107
+
108
+ # Safe deletes and closes all instances
109
+ def delete_all
110
+ instances.each do |instance|
111
+ instance.safe_delete
112
+ instance.close
113
+ end
114
+ end
115
+ alias -@ delete_all
116
+ end
117
+
118
+ # @param name<String> optional
119
+ # prefix name of file
120
+ #
121
+ # @param body<String> optional
122
+ # contents of file
123
+ #
124
+ def initialize(name='anon', body='')
125
+ super(name)
126
+ self << body
127
+ self.rewind
128
+ end
129
+
130
+ # File's path as a Pathname
131
+ #
132
+ # @return path<Pathname>
133
+ #
134
+ def path
135
+ Pathname(super)
136
+ end
137
+ end
138
+
139
+ # Subclass of core lib's Pathname that allows safely deleting all of its
140
+ # instances.
141
+ #
142
+ # @example
143
+ #
144
+ # path = TmpDir.new
145
+ # path.exist? #=> true
146
+ #
147
+ # TmpDir.delete_all
148
+ # path.exist? #=> false
149
+ #
150
+ class TmpDir < Pathname
151
+ include SafeDeletable
152
+ include InstanceTracking
153
+
154
+ class << self
155
+
156
+ # Safe deletes and closes all instances
157
+ def delete_all
158
+ instances.each {|instance| instance.safe_delete }
159
+ end
160
+ alias -@ delete_all
161
+ end
162
+
163
+ # @param prefix<String> optional
164
+ # prefix of directory name
165
+ #
166
+ def initialize(prefix='TmpDir-')
167
+ super(Pathname(Dir.mktmpdir(prefix)).expand_path)
168
+ end
169
+ end
170
+ end
171
+
data/specs.watchr ADDED
@@ -0,0 +1,35 @@
1
+ #!/usr/bin/env watchr
2
+
3
+ # --------------------------------------------------
4
+ # Rules
5
+ # --------------------------------------------------
6
+ watch( '^test.*_test\.rb' ) {|m| ruby m[0] }
7
+ watch( '^lib/(.*)\.rb' ) {|m| ruby "test/#{m[1]}_test.rb" }
8
+
9
+ # --------------------------------------------------
10
+ # Signal Handling
11
+ # --------------------------------------------------
12
+ Signal.trap('QUIT') { ruby tests } # Ctrl-\
13
+ Signal.trap('INT' ) { abort("\n") } # Ctrl-C
14
+
15
+ # --------------------------------------------------
16
+ # Helpers
17
+ # --------------------------------------------------
18
+ def ruby(*paths)
19
+ paths = paths.flatten.select {|p| File.exist?(p) && !File.directory?(p) }.join(' ')
20
+ run "ruby #{gem_opt} -I.:lib:test -e'%w( #{paths} ).each {|p| require p }'" unless paths.empty?
21
+ end
22
+
23
+ def tests
24
+ Dir['test/**/*_test.rb']
25
+ end
26
+
27
+ def run( cmd )
28
+ puts cmd
29
+ system cmd
30
+ end
31
+
32
+ def gem_opt
33
+ defined?(Gem) ? "-rubygems" : ""
34
+ end
35
+
@@ -0,0 +1,256 @@
1
+ require 'minitest/autorun'
2
+ require 'minitest/spec'
3
+ require 'rr'
4
+
5
+ begin require 'ruby-debug'; rescue LoadError; end
6
+ begin require 'redgreen' ; rescue LoadError; end
7
+ begin require 'phocus' ; rescue LoadError; end
8
+
9
+ class MiniTest::Unit::TestCase
10
+ include RR::Adapters::TestUnit
11
+ end
12
+
13
+ require 'tmptation'
14
+ include Tmptation
15
+
16
+ describe Tmptation do
17
+ it "should have a version" do
18
+ assert_kind_of Float, Tmptation::VERSION
19
+ end
20
+ end
21
+
22
+ describe Tmptation::SafeDeletable do
23
+
24
+ it "should delete a tmp directory" do
25
+ begin
26
+ dir = Pathname(Dir.mktmpdir('SafeDeletable-')).expand_path
27
+ dir.extend(SafeDeletable)
28
+
29
+ assert dir.exist?
30
+ assert_match /^#{Regexp.quote(Dir.tmpdir)}/, dir.to_s
31
+
32
+ dir.safe_delete
33
+
34
+ refute dir.exist?
35
+ ensure
36
+ dir.rmdir if dir.directory?
37
+ end
38
+ end
39
+
40
+ it "should refuse to delete a non-tmp directory" do
41
+ begin
42
+ dir = Pathname(Dir.mktmpdir('SafeDeletable-')).expand_path
43
+ dir.extend(SafeDeletable)
44
+
45
+ stub(dir).to_s { '/not/a/tmp/dir' }
46
+ refute_match /^#{Regexp.quote(Dir.tmpdir)}/, dir.to_s
47
+
48
+ assert_raises(SafeDeletable::UnsafeDelete) { dir.safe_delete }
49
+ ensure
50
+ dir.rmdir if dir.directory?
51
+ end
52
+ end
53
+
54
+ it "should hanled relative paths" do
55
+ begin
56
+ dir = Pathname(Dir.mktmpdir('SafeDeletable-')).relative_path_from(Pathname(Dir.pwd))
57
+ dir.extend(SafeDeletable)
58
+
59
+ assert_match /^#{Regexp.quote(Dir.tmpdir)}/, dir.expand_path.to_s
60
+ assert dir.relative?
61
+
62
+ dir.safe_delete
63
+
64
+ refute dir.exist?
65
+ ensure
66
+ dir.rmdir if dir.directory?
67
+ end
68
+ end
69
+
70
+ it "should use an object's #path if it exists" do
71
+ begin
72
+ file = Tempfile.new('safe_deletable')
73
+ file.extend(SafeDeletable)
74
+
75
+ assert File.exist?(file.path)
76
+ assert_match /^#{Regexp.quote(Dir.tmpdir)}/, file.path.to_s
77
+
78
+ file.safe_delete
79
+
80
+ refute File.exist?(file.path)
81
+ ensure
82
+ file.delete if File.exist?(file)
83
+ end
84
+ end
85
+ end
86
+
87
+ describe Tmptation::InstanceTracking do
88
+
89
+ before do
90
+ TmpFile.instance_variable_set(:@instances, nil)
91
+ end
92
+
93
+ it "should keep track of class instances" do
94
+ klass = Class.new
95
+ klass.class_eval { include InstanceTracking }
96
+
97
+ assert_empty klass.instances
98
+
99
+ foo, bar = klass.new, klass.new
100
+ assert_equal [foo,bar], klass.instances
101
+ end
102
+ end
103
+
104
+ describe Tmptation::TmpFile do
105
+
106
+ before do
107
+ TmpFile.instance_variable_set(:@instances, nil)
108
+ end
109
+
110
+ it "should implement SafeDeletable" do
111
+ assert_includes TmpFile.included_modules, SafeDeletable
112
+ end
113
+
114
+ it "should implement InstanceTracking" do
115
+ assert_includes TmpFile.included_modules, InstanceTracking
116
+ end
117
+
118
+ it "should create a new temporary file on init" do
119
+ begin
120
+ foo = TmpFile.new
121
+
122
+ assert File.exist?(foo.path)
123
+ assert_match /^#{Regexp.quote(Dir.tmpdir)}/, foo.path.to_s
124
+ ensure
125
+ foo.delete if foo.path.exist?
126
+ end
127
+ end
128
+
129
+ it "should provide a path as Pathname" do
130
+ begin
131
+ foo = TmpFile.new
132
+ assert_kind_of Pathname, foo.path
133
+ ensure
134
+ foo.delete if foo.path.exist?
135
+ end
136
+ end
137
+
138
+ it "should allow setting a name and body on init" do
139
+ begin
140
+ foo = TmpFile.new('name', 'body')
141
+
142
+ assert_match /^name/, foo.path.basename.to_s
143
+ assert_equal 'body', foo.read
144
+ ensure
145
+ foo.delete if foo.path.exist?
146
+ end
147
+ end
148
+
149
+ it "should delete all instances" do
150
+ begin
151
+ foo, bar = TmpFile.new, TmpFile.new
152
+
153
+ assert foo.path.exist?
154
+ assert bar.path.exist?
155
+
156
+ TmpFile.delete_all
157
+
158
+ refute foo.path.exist?
159
+ refute bar.path.exist?
160
+ ensure
161
+ foo.delete if foo.path.exist?
162
+ bar.delete if bar.path.exist?
163
+ end
164
+ end
165
+
166
+ it "should use #safe_delete" do
167
+ begin
168
+ foo = TmpFile.new
169
+
170
+ mock(foo).safe_delete
171
+ TmpFile.delete_all
172
+
173
+ RR.verify
174
+ ensure
175
+ foo.delete if foo.path.exist?
176
+ end
177
+ end
178
+
179
+ it "should close files when deleting all instances" do
180
+ begin
181
+ foo = TmpFile.new
182
+ refute foo.closed?
183
+
184
+ TmpFile.delete_all
185
+ assert foo.closed?
186
+ ensure
187
+ foo.delete if foo.path.exist?
188
+ end
189
+ end
190
+ end
191
+
192
+ describe Tmptation::TmpDir do
193
+
194
+ before do
195
+ TmpDir.instance_variable_set(:@instances, nil)
196
+ end
197
+
198
+ it "should implement SafeDeletable" do
199
+ assert_includes TmpDir.included_modules, SafeDeletable
200
+ end
201
+
202
+ it "should implement InstanceTracking" do
203
+ assert_includes TmpFile.included_modules, InstanceTracking
204
+ end
205
+
206
+ it "should create a temporary directory on init" do
207
+ begin
208
+ foo = TmpDir.new
209
+
210
+ assert foo.exist?
211
+ assert_match /^#{Regexp.quote(Dir.tmpdir)}/, foo.to_s
212
+ ensure
213
+ foo.rmdir if foo.exist?
214
+ end
215
+ end
216
+
217
+ it "should allow setting a prefix on init" do
218
+ begin
219
+ foo = TmpDir.new('prefix-')
220
+ assert_match /^prefix/, foo.to_s.split('/').last
221
+ ensure
222
+ foo.rmdir if foo.exist?
223
+ end
224
+ end
225
+
226
+ it "should delete all instances" do
227
+ begin
228
+ foo, bar = TmpDir.new, TmpDir.new
229
+
230
+ assert foo.exist?
231
+ assert bar.exist?
232
+
233
+ TmpDir.delete_all
234
+
235
+ refute foo.exist?
236
+ refute bar.exist?
237
+ ensure
238
+ foo.rmdir if foo.exist?
239
+ bar.rmdir if foo.exist?
240
+ end
241
+ end
242
+
243
+ it "should use #safe_delete" do
244
+ begin
245
+ foo = TmpDir.new
246
+
247
+ mock(foo).safe_delete
248
+ TmpDir.delete_all
249
+
250
+ RR.verify
251
+ ensure
252
+ foo.rmdir if foo.exist?
253
+ end
254
+ end
255
+ end
256
+
data/tmptation.gemspec ADDED
@@ -0,0 +1,17 @@
1
+ require 'lib/tmptation'
2
+
3
+ Gem::Specification.new do |s|
4
+ s.name = "tmptation"
5
+ s.version = Tmptation::VERSION
6
+ s.summary = "Classes that help safely manipulate temporary files and directories"
7
+ s.description = "Classes that help safely manipulate temporary files and directories."
8
+ s.author = "Martin Aumont"
9
+ s.email = "mynyml@gmail.com"
10
+ s.homepage = "http://github.com/mynyml/tmptation"
11
+ s.rubyforge_project = ""
12
+ s.require_path = "lib"
13
+ s.files = `git ls-files`.strip.split("\n")
14
+
15
+ s.add_development_dependency 'minitest'
16
+ s.add_development_dependency 'rr'
17
+ end
metadata ADDED
@@ -0,0 +1,91 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: tmptation
3
+ version: !ruby/object:Gem::Version
4
+ prerelease: false
5
+ segments:
6
+ - 1
7
+ - 1
8
+ version: "1.1"
9
+ platform: ruby
10
+ authors:
11
+ - Martin Aumont
12
+ autorequire:
13
+ bindir: bin
14
+ cert_chain: []
15
+
16
+ date: 2010-11-04 00:00:00 -07:00
17
+ default_executable:
18
+ dependencies:
19
+ - !ruby/object:Gem::Dependency
20
+ name: minitest
21
+ prerelease: false
22
+ requirement: &id001 !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - ">="
25
+ - !ruby/object:Gem::Version
26
+ segments:
27
+ - 0
28
+ version: "0"
29
+ type: :development
30
+ version_requirements: *id001
31
+ - !ruby/object:Gem::Dependency
32
+ name: rr
33
+ prerelease: false
34
+ requirement: &id002 !ruby/object:Gem::Requirement
35
+ requirements:
36
+ - - ">="
37
+ - !ruby/object:Gem::Version
38
+ segments:
39
+ - 0
40
+ version: "0"
41
+ type: :development
42
+ version_requirements: *id002
43
+ description: Classes that help safely manipulate temporary files and directories.
44
+ email: mynyml@gmail.com
45
+ executables: []
46
+
47
+ extensions: []
48
+
49
+ extra_rdoc_files: []
50
+
51
+ files:
52
+ - .gitignore
53
+ - LICENSE
54
+ - README.md
55
+ - Rakefile
56
+ - lib/tmptation.rb
57
+ - specs.watchr
58
+ - test/tmptation_test.rb
59
+ - tmptation.gemspec
60
+ has_rdoc: true
61
+ homepage: http://github.com/mynyml/tmptation
62
+ licenses: []
63
+
64
+ post_install_message:
65
+ rdoc_options: []
66
+
67
+ require_paths:
68
+ - lib
69
+ required_ruby_version: !ruby/object:Gem::Requirement
70
+ requirements:
71
+ - - ">="
72
+ - !ruby/object:Gem::Version
73
+ segments:
74
+ - 0
75
+ version: "0"
76
+ required_rubygems_version: !ruby/object:Gem::Requirement
77
+ requirements:
78
+ - - ">="
79
+ - !ruby/object:Gem::Version
80
+ segments:
81
+ - 0
82
+ version: "0"
83
+ requirements: []
84
+
85
+ rubyforge_project: ""
86
+ rubygems_version: 1.3.6
87
+ signing_key:
88
+ specification_version: 3
89
+ summary: Classes that help safely manipulate temporary files and directories
90
+ test_files: []
91
+