diff --git a/spec/std/dir_spec.cr b/spec/std/dir_spec.cr index a358d2c7433a..f87049f36e52 100644 --- a/spec/std/dir_spec.cr +++ b/spec/std/dir_spec.cr @@ -90,6 +90,82 @@ describe "Dir" do end end + describe "mktmpdir" do + it "creates the temporary directory" do + tmp_dir = Dir.mktmpdir + + Dir.exists?(tmp_dir).should be_true + end + + it "accepts a prefix when creating the temporary directory" do + prefix = "foo" + path = Dir.mktmpdir(prefix) + + Dir.exists?(path).should be_true + (path =~ /#{prefix}/).should_not be_nil + end + + describe "with a block" do + it "creates the dir" do + Dir.mktmpdir do |dir| + Dir.exists?(dir).should be_true + end + end + + it "removes the dir" do + tmp_dir = "/" + Dir.mktmpdir do |dir| + tmp_dir = dir + end + + Dir.exists?(tmp_dir).should be_false + end + end + end + + describe "tmpdir" do + it "returns default tmp directory" do + original_tmpdir = ENV["TMPDIR"]? + ENV.delete("TMPDIR") + Dir.tmpdir.should eq("/tmp") + ENV["TMPDIR"] = original_tmpdir if original_tmpdir + end + + it "returns configured tmp directory" do + original_tmpdir = ENV["TMPDIR"]? + ENV["TMPDIR"] = "/my/tmp" + Dir.tmpdir.should eq("/my/tmp") + + if original_tmpdir + ENV["TMPDIR"] = original_tmpdir + else + ENV.delete("TMPDIR") + end + end + end + + describe "rm_r" do + it "deletes a directory recursively" do + data_path = File.join(__DIR__, "data") + path = File.join(data_path, "rm_r_test") + + begin + Dir.mkdir(path) + File.write(File.join(path, "a"), "") + Dir.mkdir(File.join(path, "b")) + File.write(File.join(path, "b/c"), "") + + Dir.rm_r(path) + Dir.exists?(path).should be_false + ensure + File.delete(File.join(path, "b/c")) if File.exists?(File.join(path, "b/c")) + File.delete(File.join(path, "a")) if File.exists?(File.join(path, "a")) + Dir.rmdir(File.join(path, "b")) if Dir.exists?(File.join(path, "b")) + Dir.rmdir(path) if Dir.exists?(path) + end + end + end + describe "glob" do it "tests glob with a single pattern" do assert_dir_glob [ diff --git a/spec/std/file_utils_spec.cr b/spec/std/file_utils_spec.cr index 862388492776..29c598e4489f 100644 --- a/spec/std/file_utils_spec.cr +++ b/spec/std/file_utils_spec.cr @@ -156,289 +156,4 @@ describe "FileUtils" do end end end - - describe "rm_r" do - it "deletes a directory recursively" do - data_path = File.join(__DIR__, "data") - path = File.join(data_path, "rm_r_test") - - begin - Dir.mkdir(path) - File.write(File.join(path, "a"), "") - Dir.mkdir(File.join(path, "b")) - File.write(File.join(path, "b/c"), "") - - FileUtils.rm_r(path) - Dir.exists?(path).should be_false - ensure - File.delete(File.join(path, "b/c")) if File.exists?(File.join(path, "b/c")) - File.delete(File.join(path, "a")) if File.exists?(File.join(path, "a")) - Dir.rmdir(File.join(path, "b")) if Dir.exists?(File.join(path, "b")) - Dir.rmdir(path) if Dir.exists?(path) - end - end - - it "doesn't follow symlinks" do - data_path = File.join(__DIR__, "data") - removed_path = File.join(data_path, "rm_r_test_removed") - linked_path = File.join(data_path, "rm_r_test_linked") - link_path = File.join(removed_path, "link") - file_path = File.join(linked_path, "file") - - begin - Dir.mkdir(removed_path) - Dir.mkdir(linked_path) - File.symlink(linked_path, link_path) - File.write(file_path, "") - - FileUtils.rm_r(removed_path) - Dir.exists?(removed_path).should be_false - Dir.exists?(linked_path).should be_true - File.exists?(file_path).should be_true - ensure - File.delete(file_path) if File.exists?(file_path) - File.delete(link_path) if File.exists?(link_path) - Dir.rmdir(linked_path) if Dir.exists?(linked_path) - Dir.rmdir(removed_path) if Dir.exists?(removed_path) - end - end - end - - describe "rm_rf" do - it "delete recursively a directory" do - path = "/tmp/crystal_rm_rftest_#{Process.pid}/" - FileUtils.mkdir(path) - File.write(File.join(path, "a"), "") - FileUtils.mkdir(File.join(path, "b")) - FileUtils.rm_rf(path).should be_nil - Dir.exists?(path).should be_false - end - - it "delete recursively multiple directory" do - path1 = "/tmp/crystal_rm_rftest_#{Process.pid}/" - path2 = "/tmp/crystal_rm_rftest_#{Process.pid + 1}/" - FileUtils.mkdir(path1) - FileUtils.mkdir(path2) - File.write(File.join(path1, "a"), "") - File.write(File.join(path2, "a"), "") - FileUtils.mkdir(File.join(path1, "b")) - FileUtils.mkdir(File.join(path2, "b")) - FileUtils.rm_rf([path1, path2]).should be_nil - Dir.exists?(path1).should be_false - Dir.exists?(path2).should be_false - end - - it "doesn't return error on non existing file" do - path1 = "/tmp/crystal_rm_rftest_#{Process.pid}/" - path2 = File.join(path1, "a") - FileUtils.mkdir(path1) - FileUtils.rm_rf([path1, path2]).should be_nil - end - end - - describe "mv" do - it "moves a file from one place to another" do - begin - path1 = "/tmp/crystal_rm_rftest_#{Process.pid}/" - path2 = "/tmp/crystal_rm_rftest_#{Process.pid + 1}/" - FileUtils.mkdir([path1, path2]) - path1 = File.join(path1, "a") - path2 = File.join(path2, "b") - File.write(path1, "") - FileUtils.mv(path1, path2).should be_nil - File.exists?(path1).should be_false - File.exists?(path2).should be_true - ensure - path1 = "/tmp/crystal_rm_rftest_#{Process.pid}/" - path2 = "/tmp/crystal_rm_rftest_#{Process.pid + 1}/" - FileUtils.rm_rf([path1, path2]) - end - end - - it "raises an error if non correct arguments" do - expect_raises Errno do - FileUtils.mv("/tmp/crystal_mv_test/a", "/tmp/crystal_mv_test/b") - end - end - - it "moves multiple files to one place" do - begin - path1 = "/tmp/crystal_rm_rftest_#{Process.pid}/" - path2 = "/tmp/crystal_rm_rftest_#{Process.pid + 1}/" - path3 = "/tmp/crystal_rm_rftest_#{Process.pid + 2}/" - FileUtils.mkdir([path1, path2, path3]) - path1 = File.join(path1, "a") - path2 = File.join(path2, "b") - File.write(path1, "") - File.write(path2, "") - FileUtils.mv([path1, path2], path3).should be_nil - File.exists?(path1).should be_false - File.exists?(path2).should be_false - File.exists?(File.join(path3, "a")).should be_true - File.exists?(File.join(path3, "b")).should be_true - ensure - path1 = "/tmp/crystal_rm_rftest_#{Process.pid}/" - path2 = "/tmp/crystal_rm_rftest_#{Process.pid + 1}/" - path3 = "/tmp/crystal_rm_rftest_#{Process.pid + 2}/" - FileUtils.rm_rf([path1, path2, path3]) - end - end - - it "raises an error if dest is non correct" do - expect_raises ArgumentError do - FileUtils.mv(["/tmp/crystal_mv_test/a", "/tmp/crystal_mv_test/b"], "/tmp/crystal_not_here") - end - end - - it "moves all existing files to destination" do - begin - path1 = "/tmp/crystal_rm_rftest_#{Process.pid}/" - path2 = "/tmp/crystal_rm_rftest_#{Process.pid + 1}/" - path3 = "/tmp/crystal_rm_rftest_#{Process.pid + 2}/" - path4 = "/tmp/crystal_rm_rftest_#{Process.pid + 3}/a" - FileUtils.mkdir([path1, path2, path3]) - path1 = File.join(path1, "a") - path2 = File.join(path2, "b") - File.write(path1, "") - File.write(path2, "") - FileUtils.mv([path1, path2, path4], path3).should be_nil - File.exists?(path1).should be_false - File.exists?(path2).should be_false - File.exists?(File.join(path3, "a")).should be_true - File.exists?(File.join(path3, "b")).should be_true - ensure - path1 = "/tmp/crystal_rm_rftest_#{Process.pid}/" - path2 = "/tmp/crystal_rm_rftest_#{Process.pid + 1}/" - path3 = "/tmp/crystal_rm_rftest_#{Process.pid + 2}/" - FileUtils.rm_rf([path1, path2, path3]) - end - end - end - - it "tests mkdir and rmdir with a new path" do - path = "/tmp/crystal_mkdir_test_#{Process.pid}/" - FileUtils.mkdir(path, 0o700).should be_nil - Dir.exists?(path).should be_true - FileUtils.rmdir(path).should be_nil - Dir.exists?(path).should be_false - end - - it "tests mkdir and rmdir with multiple new paths" do - path1 = "/tmp/crystal_mkdir_test_#{Process.pid}/" - path2 = "/tmp/crystal_mkdir_test_#{Process.pid + 1}/" - FileUtils.mkdir([path1, path2], 0o700).should be_nil - Dir.exists?(path1).should be_true - Dir.exists?(path2).should be_true - FileUtils.rmdir([path1, path2]).should be_nil - Dir.exists?(path1).should be_false - Dir.exists?(path2).should be_false - end - - it "tests mkdir with an existing path" do - expect_raises Errno do - Dir.mkdir(__DIR__, 0o700) - end - end - - it "tests mkdir with multiples existing paths" do - expect_raises Errno do - FileUtils.mkdir([__DIR__, __DIR__], 0o700) - end - expect_raises Errno do - FileUtils.mkdir(["/tmp/crystal_mkdir_test_#{Process.pid}/", __DIR__], 0o700) - end - end - - it "tests mkdir_p with a new path" do - path = "/tmp/crystal_mkdir_ptest_#{Process.pid}/" - FileUtils.mkdir_p(path).should be_nil - Dir.exists?(path).should be_true - path = File.join({path, "a", "b", "c"}) - FileUtils.mkdir_p(path).should be_nil - Dir.exists?(path).should be_true - end - - it "tests mkdir_p with multiples new path" do - path1 = "/tmp/crystal_mkdir_ptest_#{Process.pid}/" - path2 = "/tmp/crystal_mkdir_ptest_#{Process.pid + 1}" - FileUtils.mkdir_p([path1, path2]).should be_nil - Dir.exists?(path1).should be_true - Dir.exists?(path2).should be_true - path1 = File.join({path1, "a", "b", "c"}) - path2 = File.join({path2, "a", "b", "c"}) - FileUtils.mkdir_p([path1, path2]).should be_nil - Dir.exists?(path1).should be_true - Dir.exists?(path2).should be_true - end - - it "tests mkdir_p with an existing path" do - FileUtils.mkdir_p(__DIR__).should be_nil - expect_raises Errno do - FileUtils.mkdir_p(__FILE__) - end - end - - it "tests mkdir_p with multiple existing path" do - FileUtils.mkdir_p([__DIR__, __DIR__]).should be_nil - expect_raises Errno do - FileUtils.mkdir_p([__FILE__, "/tmp/crystal_mkdir_ptest_#{Process.pid}/"]) - end - end - - it "tests rmdir with an non existing path" do - expect_raises Errno do - FileUtils.rmdir("/tmp/crystal_mkdir_test_#{Process.pid}/tmp/") - end - end - - it "tests rmdir with multiple non existing path" do - expect_raises Errno do - FileUtils.rmdir(["/tmp/crystal_mkdir_test_#{Process.pid}/tmp/", "/tmp/crystal_mkdir_test_#{Process.pid + 1}/tmp/"]) - end - end - - it "tests rmdir with a path that cannot be removed" do - expect_raises Errno do - FileUtils.rmdir(__DIR__) - end - end - - it "tests rmdir with multiple path that cannot be removed" do - expect_raises Errno do - FileUtils.rmdir([__DIR__, __DIR__]) - end - end - - it "tests rm with an existing path" do - path = "/tmp/crystal_rm_test_#{Process.pid}" - File.write(path, "") - FileUtils.rm(path).should be_nil - File.exists?(path).should be_false - end - - it "tests rm with non existing path" do - expect_raises Errno do - FileUtils.rm("/tmp/crystal_rm_test_#{Process.pid}") - end - end - - it "tests rm with multiple existing paths" do - path1 = "/tmp/crystal_rm_test_#{Process.pid}" - path2 = "/tmp/crystal_rm_test_#{Process.pid + 1}" - File.write(path1, "") - File.write(path2, "") - FileUtils.rm([path1, path2]).should be_nil - File.exists?(path1).should be_false - File.exists?(path2).should be_false - end - - it "tests rm with some non existing paths" do - expect_raises Errno do - path1 = "/tmp/crystal_rm_test_#{Process.pid}" - path2 = "/tmp/crystal_rm_test_#{Process.pid + 1}" - File.write(path1, "") - File.write(path2, "") - FileUtils.rm([path1, path2, path2]) - end - end end diff --git a/spec/std/tempfile_spec.cr b/spec/std/tempfile_spec.cr index 7e3cb6b61e08..c3afa36d7add 100644 --- a/spec/std/tempfile_spec.cr +++ b/spec/std/tempfile_spec.cr @@ -53,18 +53,4 @@ describe Tempfile do tempfile.gets(chomp: false).should eq("Hello!\n") tempfile.close end - - it "returns default directory for tempfiles" do - old_tmpdir = ENV["TMPDIR"]? - ENV.delete("TMPDIR") - Tempfile.dirname.should eq("/tmp") - ENV["TMPDIR"] = old_tmpdir if old_tmpdir - end - - it "returns configure directory for tempfiles" do - old_tmpdir = ENV["TMPDIR"]? - ENV["TMPDIR"] = "/my/tmp" - Tempfile.dirname.should eq("/my/tmp") - ENV["TMPDIR"] = old_tmpdir if old_tmpdir - end end diff --git a/src/dir.cr b/src/dir.cr index 15d66767e45b..cf2ef0f819c1 100644 --- a/src/dir.cr +++ b/src/dir.cr @@ -215,6 +215,54 @@ class Dir 0 end + # Creates a new temporary directory + # + # ``` + # Dir.mktmpdir # => "/tmp/c.a56b2F" + # ``` + def self.mktmpdir(prefix = "c") + tmp_dir = File.join(Dir.tmpdir, "#{prefix}.XXXXXX") + + fileno = LibC.mkdtemp(tmp_dir) + if fileno == nil + raise Errno.new("mkdtemp") + end + + tmp_dir + end + + # Creates a new temporary directory within the lifecycle + # of the given block and destroys it, and its content, when the block returns. + # + # ``` + # Dir.mktmpdir do |dir| + # puts dir + # => "/tmp/c.a56b2F" + # end + # ``` + def self.mktmpdir(prefix = "c", &block) + tmp_dir = Dir.mktmpdir(prefix) + begin + yield tmp_dir + ensure + Dir.rm_r(tmp_dir) + end + + tmp_dir + end + + # Returns the tmp dir + # + # ``` + # Dir.tmpdir # => "/tmp" + # ``` + def self.tmpdir + tmpdir = ENV["TMPDIR"]? || "/tmp" + tmpdir = tmpdir + File::SEPARATOR unless tmpdir.ends_with? File::SEPARATOR + + File.dirname(tmpdir) + end + # Removes the directory at the given path. def self.rmdir(path) if LibC.rmdir(path.check_no_null_byte) == -1 @@ -223,6 +271,28 @@ class Dir 0 end + # Deletes a file or directory *path* + # If *path* is a directory, this method removes all its contents recursively + # ``` + # Dir.rm_r("dir") + # Dir.rm_r("file.cr") + # ``` + def self.rm_r(path : String) + if Dir.exists?(path) + Dir.open(path) do |dir| + dir.each do |entry| + if entry != "." && entry != ".." + src = File.join(path, entry) + rm_r(src) + end + end + end + Dir.rmdir(path) + else + File.delete(path) + end + end + def to_s(io) io << "#" end diff --git a/src/file_utils.cr b/src/file_utils.cr index 98391998c6ca..edb40ac5af1e 100644 --- a/src/file_utils.cr +++ b/src/file_utils.cr @@ -238,20 +238,8 @@ module FileUtils # FileUtils.rm_r("dir") # FileUtils.rm_r("file.cr") # ``` - def rm_r(path : String) : Nil - if Dir.exists?(path) && !File.symlink?(path) - Dir.open(path) do |dir| - dir.each do |entry| - if entry != "." && entry != ".." - src = File.join(path, entry) - rm_r(src) - end - end - end - Dir.rmdir(path) - else - File.delete(path) - end + def rm_r(path : String) + Dir.rm_r(path) end # Deletes a list of files or directories *paths*. diff --git a/src/lib_c/i686-linux-gnu/c/stdlib.cr b/src/lib_c/i686-linux-gnu/c/stdlib.cr index 6b86df8aefa2..5de88284bee7 100644 --- a/src/lib_c/i686-linux-gnu/c/stdlib.cr +++ b/src/lib_c/i686-linux-gnu/c/stdlib.cr @@ -14,6 +14,7 @@ lib LibC fun getenv(name : Char*) : Char* fun malloc(size : SizeT) : Void* fun mkstemp(template : Char*) : Int + fun mkdtemp(template : Char*) : Char* fun putenv(string : Char*) : Int fun realloc(ptr : Void*, size : SizeT) : Void* fun realpath(name : Char*, resolved : Char*) : Char* diff --git a/src/lib_c/i686-linux-musl/c/stdlib.cr b/src/lib_c/i686-linux-musl/c/stdlib.cr index 5729203618e5..1fbbd71bd54f 100644 --- a/src/lib_c/i686-linux-musl/c/stdlib.cr +++ b/src/lib_c/i686-linux-musl/c/stdlib.cr @@ -14,6 +14,7 @@ lib LibC fun getenv(x0 : Char*) : Char* fun malloc(x0 : SizeT) : Void* fun mkstemp(x0 : Char*) : Int + fun mkdtemp(template : Char*) : Char* fun putenv(x0 : Char*) : Int fun realloc(x0 : Void*, x1 : SizeT) : Void* fun realpath(x0 : Char*, x1 : Char*) : Char* diff --git a/src/lib_c/x86_64-linux-gnu/c/stdlib.cr b/src/lib_c/x86_64-linux-gnu/c/stdlib.cr index 6b86df8aefa2..5de88284bee7 100644 --- a/src/lib_c/x86_64-linux-gnu/c/stdlib.cr +++ b/src/lib_c/x86_64-linux-gnu/c/stdlib.cr @@ -14,6 +14,7 @@ lib LibC fun getenv(name : Char*) : Char* fun malloc(size : SizeT) : Void* fun mkstemp(template : Char*) : Int + fun mkdtemp(template : Char*) : Char* fun putenv(string : Char*) : Int fun realloc(ptr : Void*, size : SizeT) : Void* fun realpath(name : Char*, resolved : Char*) : Char* diff --git a/src/lib_c/x86_64-linux-musl/c/stdlib.cr b/src/lib_c/x86_64-linux-musl/c/stdlib.cr index 5729203618e5..1fbbd71bd54f 100644 --- a/src/lib_c/x86_64-linux-musl/c/stdlib.cr +++ b/src/lib_c/x86_64-linux-musl/c/stdlib.cr @@ -14,6 +14,7 @@ lib LibC fun getenv(x0 : Char*) : Char* fun malloc(x0 : SizeT) : Void* fun mkstemp(x0 : Char*) : Int + fun mkdtemp(template : Char*) : Char* fun putenv(x0 : Char*) : Int fun realloc(x0 : Void*, x1 : SizeT) : Void* fun realpath(x0 : Char*, x1 : Char*) : Char* diff --git a/src/lib_c/x86_64-macosx-darwin/c/stdlib.cr b/src/lib_c/x86_64-macosx-darwin/c/stdlib.cr index 5729203618e5..1fbbd71bd54f 100644 --- a/src/lib_c/x86_64-macosx-darwin/c/stdlib.cr +++ b/src/lib_c/x86_64-macosx-darwin/c/stdlib.cr @@ -14,6 +14,7 @@ lib LibC fun getenv(x0 : Char*) : Char* fun malloc(x0 : SizeT) : Void* fun mkstemp(x0 : Char*) : Int + fun mkdtemp(template : Char*) : Char* fun putenv(x0 : Char*) : Int fun realloc(x0 : Void*, x1 : SizeT) : Void* fun realpath(x0 : Char*, x1 : Char*) : Char* diff --git a/src/lib_c/x86_64-portbld-freebsd/c/stdlib.cr b/src/lib_c/x86_64-portbld-freebsd/c/stdlib.cr index 5729203618e5..1fbbd71bd54f 100644 --- a/src/lib_c/x86_64-portbld-freebsd/c/stdlib.cr +++ b/src/lib_c/x86_64-portbld-freebsd/c/stdlib.cr @@ -14,6 +14,7 @@ lib LibC fun getenv(x0 : Char*) : Char* fun malloc(x0 : SizeT) : Void* fun mkstemp(x0 : Char*) : Int + fun mkdtemp(template : Char*) : Char* fun putenv(x0 : Char*) : Int fun realloc(x0 : Void*, x1 : SizeT) : Void* fun realpath(x0 : Char*, x1 : Char*) : Char* diff --git a/src/tempfile.cr b/src/tempfile.cr index d95199f83101..2b676383bf44 100644 --- a/src/tempfile.cr +++ b/src/tempfile.cr @@ -34,8 +34,7 @@ require "c/stdlib" class Tempfile < IO::FileDescriptor # Creates a `Tempfile` with the given filename. def initialize(name) - tmpdir = self.class.dirname + File::SEPARATOR - @path = "#{tmpdir}#{name}.XXXXXX" + @path = File.join(Dir.tmpdir, "#{name}.XXXXXX") fileno = LibC.mkstemp(@path) if fileno == -1 raise Errno.new("mkstemp")