diff --git a/spec/compiler/codegen/private_spec.cr b/spec/compiler/codegen/private_spec.cr index eee389178232..89fe3de809a5 100644 --- a/spec/compiler/codegen/private_spec.cr +++ b/spec/compiler/codegen/private_spec.cr @@ -1,5 +1,4 @@ require "../../spec_helper" -require "tempfile" describe "Codegen: private" do it "codegens private def in same file" do @@ -15,13 +14,11 @@ describe "Codegen: private" do ] compiler.prelude = "empty" - tempfile = Tempfile.new("crystal-spec-output") - output_filename = tempfile.path - tempfile.close + output_filename = File.tempname("crystal-spec-output") compiler.compile sources, output_filename ensure - File.delete(tempfile.path) if tempfile + File.delete(output_filename) if output_filename end it "codegens overloaded private def in same file" do @@ -42,13 +39,11 @@ describe "Codegen: private" do ] compiler.prelude = "empty" - tempfile = Tempfile.new("crystal-spec-output") - output_filename = tempfile.path - tempfile.close + output_filename = File.tempname("crystal-spec-output") compiler.compile sources, output_filename ensure - File.delete(tempfile.path) if tempfile + File.delete(output_filename) if output_filename end it "doesn't include filename for private types" do diff --git a/spec/spec_helper.cr b/spec/spec_helper.cr index 5ea235ae0cc6..75582e194aec 100644 --- a/spec/spec_helper.cr +++ b/spec/spec_helper.cr @@ -186,25 +186,23 @@ def run(code, filename = nil, inject_primitives = true, debug = Crystal::Debug:: end def build_and_run(code) - code_file = Tempfile.new("build_and_run_code") - code_file.close + code_file = File.tempname("build_and_run_code") # write code to the temp file - File.write(code_file.path, code) + File.write(code_file, code) - binary_file = Tempfile.new("build_and_run_bin") - binary_file.close + binary_file = File.tempname("build_and_run_bin") `bin/crystal build #{code_file.path.inspect} -o #{binary_file.path.inspect}` - File.exists?(binary_file.path).should be_true + File.exists?(binary_file).should be_true out_io, err_io = IO::Memory.new, IO::Memory.new - status = Process.run(binary_file.path, output: out_io, error: err_io) + status = Process.run(binary_file, output: out_io, error: err_io) {status, out_io.to_s, err_io.to_s} ensure - File.delete(code_file.path) if code_file - File.delete(binary_file.path) if binary_file + File.delete(code_file) if code_file + File.delete(binary_file) if binary_file end def test_c(c_code, crystal_code) diff --git a/spec/std/dir_spec.cr b/spec/std/dir_spec.cr index 1b18f0ba883a..583f42a23cd1 100644 --- a/spec/std/dir_spec.cr +++ b/spec/std/dir_spec.cr @@ -342,6 +342,24 @@ describe "Dir" do end end + describe ".tempdir" do + it "returns default directory for tempfiles" do + old_tmpdir = ENV["TMPDIR"]? + ENV.delete("TMPDIR") + Dir.tempdir.should eq("/tmp") + ensure + ENV["TMPDIR"] = old_tmpdir + end + + it "returns configure directory for tempfiles" do + old_tmpdir = ENV["TMPDIR"]? + ENV["TMPDIR"] = "/my/tmp" + Dir.tempdir.should eq("/my/tmp") + ensure + ENV["TMPDIR"] = old_tmpdir + end + end + it "opens with new" do filenames = [] of String diff --git a/spec/std/file/tempfile_spec.cr b/spec/std/file/tempfile_spec.cr new file mode 100644 index 000000000000..26360c154a62 --- /dev/null +++ b/spec/std/file/tempfile_spec.cr @@ -0,0 +1,144 @@ +require "../spec_helper" + +describe File do + describe ".tempname" do + it "creates a path without creating the file" do + path = File.tempname + + File.exists?(path).should be_false + File.dirname(path).should eq Dir.tempdir + end + + it "accepts single suffix argument" do + path = File.tempname ".bar" + + File.exists?(path).should be_false + File.dirname(path).should eq Dir.tempdir + File.extname(path).should eq(".bar") + end + + it "accepts prefix and suffix arguments" do + path = File.tempname "foo", ".bar" + + File.exists?(path).should be_false + File.dirname(path).should eq Dir.tempdir + File.extname(path).should eq(".bar") + File.basename(path).starts_with?("foo").should be_true + end + + it "accepts prefix with separator" do + path = File.tempname "foo/", nil + File.dirname(path).should eq File.join(Dir.tempdir, "foo") + File.basename(path).starts_with?("foo").should be_false + end + + it "accepts dir argument" do + path = File.tempname(dir: "foo") + File.dirname(path).should eq("foo") + end + end + + describe ".tempfile" do + it "creates and writes" do + tempfile = File.tempfile + tempfile.print "Hello!" + tempfile.close + + File.exists?(tempfile.path).should be_true + File.read(tempfile.path).should eq("Hello!") + ensure + tempfile.try &.delete + end + + it "accepts single suffix argument" do + tempfile = File.tempfile ".bar" + tempfile.print "Hello!" + tempfile.close + + File.extname(tempfile.path).should eq(".bar") + + File.exists?(tempfile.path).should be_true + File.read(tempfile.path).should eq("Hello!") + ensure + tempfile.try &.delete + end + + it "accepts prefix and suffix arguments" do + tempfile = File.tempfile "foo", ".bar" + tempfile.print "Hello!" + tempfile.close + + File.extname(tempfile.path).should eq(".bar") + File.basename(tempfile.path).starts_with?("foo").should be_true + + File.exists?(tempfile.path).should be_true + File.read(tempfile.path).should eq("Hello!") + ensure + tempfile.try &.delete + end + + it "accepts dir argument" do + file = File.tempfile(dir: datapath) + File.dirname(file.path).should eq(datapath) + end + + it "fails in unwritable folder" do + expect_raises(Errno, /mkstemp: ".*\/non-existing-folder\/.*": No such file or directory/) do + File.tempfile dir: datapath("non-existing-folder") + end + end + + describe "with block" do + it "closes file" do + filepath = nil + tempfile = File.tempfile do |tempfile| + filepath = tempfile.path + end + tempfile.path.should eq filepath + tempfile.closed?.should be_true + + filepath = filepath.not_nil! + File.exists?(filepath).should be_true + ensure + File.delete(filepath) if filepath + end + + it "accepts single suffix argument" do + tempfile = File.tempfile(".bar") do |tempfile| + File.exists?(tempfile.path).should be_true + tempfile.closed?.should be_false + end + tempfile.closed?.should be_true + + File.extname(tempfile.path).should eq(".bar") + + File.exists?(tempfile.path).should be_true + ensure + File.delete(tempfile.path) if tempfile + end + + it "accepts prefix and suffix arguments" do + tempfile = File.tempfile("foo", ".bar") do |tempfile| + File.exists?(tempfile.path).should be_true + tempfile.closed?.should be_false + end + tempfile.closed?.should be_true + + File.extname(tempfile.path).should eq(".bar") + File.basename(tempfile.path).starts_with?("foo").should be_true + + File.exists?(tempfile.path).should be_true + ensure + File.delete(tempfile.path) if tempfile + end + + it "accepts dir argument" do + tempfile = File.tempfile(dir: datapath) do |tempfile| + end + File.dirname(tempfile.path).should eq(datapath) + ensure + File.delete(tempfile.path) if tempfile + end + end + end +end diff --git a/spec/std/file_spec.cr b/spec/std/file_spec.cr index b97779cea242..c1f5e871ca90 100644 --- a/spec/std/file_spec.cr +++ b/spec/std/file_spec.cr @@ -1032,6 +1032,22 @@ describe "File" do end end + describe "#delete" do + it "deletes" do + path = datapath("file-to-be-deleted") + File.touch(path) + + file = File.new path + file.close + + File.exists?(path).should be_true + file.delete + File.exists?(path).should be_false + ensure + File.delete(path) if path && File.exists?(path) + end + end + # TODO: these specs don't compile on win32 because iconv isn't implemented {% unless flag?(:win32) %} describe "encoding" do diff --git a/spec/std/http/server/server_spec.cr b/spec/std/http/server/server_spec.cr index 4dba0dff164a..baff008955bc 100644 --- a/spec/std/http/server/server_spec.cr +++ b/spec/std/http/server/server_spec.cr @@ -1,7 +1,6 @@ require "spec" require "http/server" require "http/client/response" -require "tempfile" require "../../../support/ssl" private class RaiseErrno < IO @@ -354,8 +353,8 @@ module HTTP {% if flag?(:unix) %} describe "#bind_unix" do it "binds to different unix sockets" do - path1 = Tempfile.tempname - path2 = Tempfile.tempname + path1 = File.tempname + path2 = File.tempname begin server = Server.new do |context| diff --git a/spec/std/tempfile_spec.cr b/spec/std/tempfile_spec.cr deleted file mode 100644 index 7f01d835015a..000000000000 --- a/spec/std/tempfile_spec.cr +++ /dev/null @@ -1,105 +0,0 @@ -require "spec" -require "tempfile" - -describe Tempfile do - describe "tempname" do - it "creates a path without creating the file" do - path = Tempfile.tempname - - File.exists?(path).should be_false - end - - it "has the given extension" do - path = Tempfile.tempname ".sock" - - File.extname(path).should eq(".sock") - end - end - - it "creates and writes" do - tempfile = Tempfile.new "foo" - tempfile.print "Hello!" - tempfile.close - - File.exists?(tempfile.path).should be_true - File.read(tempfile.path).should eq("Hello!") - ensure - File.delete(tempfile.path) if tempfile - end - - it "has given extension if passed to constructor" do - tempfile = Tempfile.new "foo", ".pdf" - File.extname(tempfile.path).should eq(".pdf") - end - - it "creates and deletes" do - tempfile = Tempfile.new "foo" - tempfile.close - tempfile.delete - - File.exists?(tempfile.path).should be_false - ensure - File.delete(tempfile.path) if tempfile && File.exists?(tempfile.path) - end - - it "doesn't delete on open with block" do - tempfile = Tempfile.open("foo") do |f| - f.print "Hello!" - end - File.exists?(tempfile.path).should be_true - ensure - File.delete(tempfile.path) if tempfile - end - - it "has given extension if passed to open" do - tempfile = Tempfile.open("foo", ".pdf") { |f| } - File.extname(tempfile.path).should eq(".pdf") - ensure - File.delete(tempfile.path) if tempfile - end - - it "creates and writes with TMPDIR environment variable" do - old_tmpdir = ENV["TMPDIR"]? - ENV["TMPDIR"] = "/tmp" - - tempfile = Tempfile.new "foo" - tempfile.print "Hello!" - tempfile.close - - File.exists?(tempfile.path).should be_true - File.read(tempfile.path).should eq("Hello!") - ensure - ENV["TMPDIR"] = old_tmpdir if old_tmpdir - File.delete(tempfile.path) if tempfile - end - - it "is seekable" do - tempfile = Tempfile.new "foo" - tempfile.puts "Hello!" - tempfile.seek(0, IO::Seek::Set) - tempfile.tell.should eq(0) - tempfile.pos.should eq(0) - tempfile.gets(chomp: false).should eq("Hello!\n") - tempfile.pos = 0 - tempfile.gets(chomp: false).should eq("Hello!\n") - tempfile.close - ensure - File.delete(tempfile.path) if tempfile - end - - it "returns default directory for tempfiles" do - old_tmpdir = ENV["TMPDIR"]? - ENV.delete("TMPDIR") - Tempfile.dirname.should eq("/tmp") - ensure - 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") - ensure - ENV["TMPDIR"] = old_tmpdir if old_tmpdir - end -end diff --git a/spec/support/tempfile.cr b/spec/support/tempfile.cr index bc465dd85dff..4dd15f511c79 100644 --- a/spec/support/tempfile.cr +++ b/spec/support/tempfile.cr @@ -1,10 +1,9 @@ -require "tempfile" require "file_utils" {% if flag?(:win32) %} - SPEC_TEMPFILE_PATH = File.join(Tempfile.dirname, "cr-spec-#{Random.new.hex(4)}").gsub("C:\\", '/').gsub('\\', '/') + SPEC_TEMPFILE_PATH = File.join(Dir.tempdir, "cr-spec-#{Random.new.hex(4)}").gsub("C:\\", '/').gsub('\\', '/') {% else %} - SPEC_TEMPFILE_PATH = File.join(Tempfile.dirname, "cr-spec-#{Random.new.hex(4)}") + SPEC_TEMPFILE_PATH = File.join(Dir.tempdir, "cr-spec-#{Random.new.hex(4)}") {% end %} SPEC_TEMPFILE_CLEANUP = ENV["SPEC_TEMPFILE_CLEANUP"]? != "0" @@ -17,7 +16,7 @@ SPEC_TEMPFILE_CLEANUP = ENV["SPEC_TEMPFILE_CLEANUP"]? != "0" # The constructed path is yielded to the block and cleaned up afterwards. # # Paths should still be uniquely chosen inside a spec file. This helper -# ensures they're placed in the temporary location (`Tempfile.dirname`), +# ensures they're placed in the temporary location (`Dir.tempdir`), # avoids name clashes between parallel spec runs and cleans up afterwards. # # The unique directory for the spec run is removed `at_exit`. diff --git a/src/compiler/crystal/tools/playground/server.cr b/src/compiler/crystal/tools/playground/server.cr index 946daa6dfcbe..460c1ec19ed5 100644 --- a/src/compiler/crystal/tools/playground/server.cr +++ b/src/compiler/crystal/tools/playground/server.cr @@ -1,5 +1,4 @@ require "http/server" -require "tempfile" require "logger" require "ecr/macros" require "markdown" diff --git a/src/crystal/system/unix/dir.cr b/src/crystal/system/unix/dir.cr index 1a3fe5b41b67..8dc5e950f844 100644 --- a/src/crystal/system/unix/dir.cr +++ b/src/crystal/system/unix/dir.cr @@ -48,6 +48,11 @@ module Crystal::System::Dir path end + def self.tempdir + tmpdir = ENV["TMPDIR"]? || "/tmp" + tmpdir.rchop(::File::SEPARATOR) + end + def self.create(path : String, mode : Int32) : Nil if LibC.mkdir(path.check_no_null_byte, mode) == -1 raise Errno.new("Unable to create directory '#{path}'") diff --git a/src/crystal/system/unix/file.cr b/src/crystal/system/unix/file.cr index b927d2bb9b0c..4599d1bdad0e 100644 --- a/src/crystal/system/unix/file.cr +++ b/src/crystal/system/unix/file.cr @@ -12,25 +12,20 @@ module Crystal::System::File fd end - def self.mktemp(name, extension) - tmpdir = tempdir + ::File::SEPARATOR - path = "#{tmpdir}#{name}.XXXXXX#{extension}" + def self.mktemp(prefix, suffix, dir) : {LibC::Int, String} + dir = dir + ::File::SEPARATOR + path = "#{dir}#{prefix}.XXXXXX#{suffix}" - if extension - fd = LibC.mkstemps(path, extension.bytesize) + if suffix + fd = LibC.mkstemps(path, suffix.bytesize) else fd = LibC.mkstemp(path) end - raise Errno.new("mkstemp") if fd == -1 + raise Errno.new("mkstemp: #{path.inspect}") if fd == -1 {fd, path} end - def self.tempdir - tmpdir = ENV["TMPDIR"]? || "/tmp" - tmpdir.rchop(::File::SEPARATOR) - end - def self.info?(path : String, follow_symlinks : Bool) : ::File::Info? stat = uninitialized LibC::Stat if follow_symlinks diff --git a/src/crystal/system/win32/dir.cr b/src/crystal/system/win32/dir.cr index 87171e21b941..2c9fe3dec367 100644 --- a/src/crystal/system/win32/dir.cr +++ b/src/crystal/system/win32/dir.cr @@ -84,6 +84,21 @@ module Crystal::System::Dir path end + def self.tempdir : String + tmpdir = System.retry_wstr_buffer do |buffer, small_buf| + len = LibC.GetTempPathW(buffer.size, buffer) + if 0 < len < buffer.size + break String.from_utf16(buffer[0, len]) + elsif small_buf && len > 0 + next len + else + raise WinError.new("Error while getting current directory") + end + end + + tmpdir.rchop("\\") + end + def self.create(path : String, mode : Int32) : Nil if LibC._wmkdir(to_windows_path(path)) == -1 raise Errno.new("Unable to create directory '#{path}'") diff --git a/src/crystal/system/win32/file.cr b/src/crystal/system/win32/file.cr index 664d15a71575..3618e4fe5bcb 100644 --- a/src/crystal/system/win32/file.cr +++ b/src/crystal/system/win32/file.cr @@ -25,8 +25,8 @@ module Crystal::System::File fd end - def self.mktemp(name : String, extension : String?) : {LibC::Int, String} - path = "#{tempdir}\\#{name}.#{::Random::Secure.hex}#{extension}" + def self.mktemp(prefix : String, suffix : String?, dir : String) : {LibC::Int, String} + path = "#{tempdir}\\#{prefix}.#{::Random::Secure.hex}#{suffix}" fd = LibC._wopen(to_windows_path(path), LibC::O_RDWR | LibC::O_CREAT | LibC::O_EXCL | LibC::O_BINARY, ::File::DEFAULT_CREATE_PERMISSIONS) if fd == -1 @@ -36,21 +36,6 @@ module Crystal::System::File {fd, path} end - def self.tempdir : String - tmpdir = System.retry_wstr_buffer do |buffer, small_buf| - len = LibC.GetTempPathW(buffer.size, buffer) - if 0 < len < buffer.size - break String.from_utf16(buffer[0, len]) - elsif small_buf && len > 0 - next len - else - raise WinError.new("Error while getting current directory") - end - end - - tmpdir.rchop("\\") - end - NOT_FOUND_ERRORS = { WinError::ERROR_FILE_NOT_FOUND, WinError::ERROR_PATH_NOT_FOUND, diff --git a/src/dir.cr b/src/dir.cr index 79f283720f4f..239f20c603a8 100644 --- a/src/dir.cr +++ b/src/dir.cr @@ -159,6 +159,15 @@ class Dir end end + # Returns the tmp dir used for tempfile. + # + # ``` + # Dir.tempdir # => "/tmp" + # ``` + def self.tempdir : String + Crystal::System::Dir.tempdir + end + # See `#each`. def self.each(dirname) Dir.open(dirname) do |dir| diff --git a/src/docs_main.cr b/src/docs_main.cr index f36e9cbf8d1a..c44c68fcdee1 100644 --- a/src/docs_main.cr +++ b/src/docs_main.cr @@ -54,7 +54,6 @@ require "./readline" require "./signal" require "./string_pool" require "./string_scanner" -require "./tempfile" require "./uri" require "./uuid" require "./uuid/json" diff --git a/src/file.cr b/src/file.cr index 9a904f4d7735..696e2bd6b505 100644 --- a/src/file.cr +++ b/src/file.cr @@ -1,5 +1,48 @@ require "crystal/system/file" +# A `File` instance represents a file entry in the local file system and allows using it as an `IO`. +# +# ``` +# file = File.new("path/to/file") +# content = file.gets_to_end +# file.close +# +# # Implicit close with `open` +# content = File.open("path/to/file") do |file| +# file.gets_to_end +# end +# +# # Shortcut: +# content = File.read("path/to/file") +# ``` +# +# ## Temporary Files +# +# Every tempfile is operated as a `File`, including initializing, reading and writing. +# +# ``` +# tempfile = File.tempfile("foo") +# +# File.size(tempfile.path) # => 6 +# File.info(tempfile.path).modification_time # => 2015-10-20 13:11:12 UTC +# File.exists?(tempfile.path) # => true +# File.read_lines(tempfile.path) # => ["foobar"] +# ``` +# +# Files created from `tempfile` are stored in a directory that handles +# temporary files (`Dir.tempdir`): +# +# ``` +# File.tempfile("foo").path # => "/tmp/foo.ulBCPS" +# ``` +# +# It is encouraged to delete a tempfile after using it, which +# ensures they are not left behind in your filesystem until garbage collected. +# +# ``` +# tempfile = File.tempfile("foo") +# tempfile.delete +# ``` class File < IO::FileDescriptor # The file/directory separator character. `'/'` on all platforms. SEPARATOR = '/' @@ -860,6 +903,11 @@ class File < IO::FileDescriptor def flock_unlock system_flock_unlock end + + # Deletes this file. + def delete + File.delete(@path) + end end require "./file/*" diff --git a/src/file/tempfile.cr b/src/file/tempfile.cr new file mode 100644 index 000000000000..7ba9e6b66d48 --- /dev/null +++ b/src/file/tempfile.cr @@ -0,0 +1,138 @@ +class File + # Returns a fully-qualified path to a temporary file. + # The file is not actually created on the file system. + # + # ``` + # File.tempname("foo", ".sock") # => "/tmp/foo20171206-1234-449386.sock" + # ``` + # + # *prefix* and *suffix* are appended to the front and end of the file name, respectively. + # These values may contain directory separators. + # + # The path will be placed in *dir* which defaults to the standard temporary directory `Dir.tempdir`. + def self.tempname(prefix : String?, suffix : String?, *, dir : String = Dir.tempdir) + name = String.build do |io| + if prefix + io << prefix + io << '-' + end + + io << Time.now.to_s("%Y%m%d") + io << '-' + + {% unless flag?(:win32) %} + # TODO: Remove this once Process is implemented + io << Process.pid + io << '-' + {% end %} + + io << Random.rand(0x100000000).to_s(36) + + io << suffix + end + + File.join(dir, name) + end + + # Returns a fully-qualified path to a temporary file. + # The optional *suffix* is appended to the file name. + # + # ``` + # File.tempname # => "/tmp/20171206-1234-449386" + # File.tempname(".sock") # => "/tmp/20171206-1234-449386.sock" + # ``` + def self.tempname(suffix : String? = nil, *, dir : String = Dir.tempdir) + tempname(prefix: nil, suffix: suffix, dir: dir) + end + + # Creates a temporary file. + # + # ``` + # tempfile = File.tempfile("foo", ".bar") + # tempfile.delete + # ``` + # + # *prefix* and *suffix* are appended to the front and end of the file name, respectively. + # These values may contain directory separators. + # + # The file will be placed in *dir* which defaults to the standard temporary directory `Dir.tempdir`. + # + # *encoding* and *invalid* are passed to `IO#set_encoding`. + # + # It is the caller's responsibility to remove the file when no longer needed. + def self.tempfile(prefix : String?, suffix : String?, *, dir : String = Dir.tempdir, encoding = nil, invalid = nil) + fileno, path = Crystal::System::File.mktemp(prefix, suffix, dir) + new(path, fileno, blocking: true, encoding: encoding, invalid: invalid) + end + + # Creates a temporary file. + # + # ``` + # tempfile = File.tempfile(".bar") + # tempfile.delete + # ``` + # + # *prefix* and *suffix* are appended to the front and end of the file name, respectively. + # These values may contain directory separators. + # + # The file will be placed in *dir* which defaults to the standard temporary directory `Dir.tempdir`. + # + # *encoding* and *invalid* are passed to `IO#set_encoding`. + # + # It is the caller's responsibility to remove the file when no longer needed. + def self.tempfile(suffix : String? = nil, *, dir : String = Dir.tempdir, encoding = nil, invalid = nil) + tempfile(prefix: nil, suffix: suffix, dir: dir, encoding: encoding, invalid: invalid) + end + + # Creates a temporary file and yields it to the given block. It is closed and returned at the end of this method call. + # + # ``` + # tempfile = File.tempfile("foo", ".bar") do |file| + # file.print("bar") + # end + # File.read(tempfile.path) # => "bar" + # tempfile.delete + # ``` + # + # *prefix* and *suffix* are appended to the front and end of the file name, respectively. + # These values may contain directory separators. + # + # The file will be placed in *dir* which defaults to the standard temporary directory `Dir.tempdir`. + # + # *encoding* and *invalid* are passed to `IO#set_encoding`. + # + # It is the caller's responsibility to remove the file when no longer needed. + def self.tempfile(prefix : String?, suffix : String?, *, dir : String = Dir.tempdir, encoding = nil, invalid = nil) + tempfile = tempfile(prefix: prefix, suffix: suffix, dir: dir, encoding: encoding, invalid: invalid) + begin + yield tempfile + ensure + tempfile.close + end + tempfile + end + + # Creates a temporary file and yields it to the given block. It is closed and returned at the end of this method call. + # + # ``` + # tempfile = File.tempfile(".bar") do |file| + # file.print("bar") + # end + # File.read(tempfile.path) # => "bar" + # tempfile.delete + # ``` + # + # *prefix* and *suffix* are appended to the front and end of the file name, respectively. + # These values may contain directory separators. + # + # The file will be placed in *dir* which defaults to the standard temporary directory `Dir.tempdir`. + # + # *encoding* and *invalid* are passed to `IO#set_encoding`. + # + # It is the caller's responsibility to remove the file when no longer needed. + def self.tempfile(suffix : String? = nil, *, dir : String = Dir.tempdir, encoding = nil, invalid = nil) + tempfile(prefix: nil, suffix: suffix, dir: dir, encoding: encoding, invalid: invalid) do |tempfile| + yield tempfile + end + end +end diff --git a/src/http/formdata.cr b/src/http/formdata.cr index fafdc35315a9..f7d63526f836 100644 --- a/src/http/formdata.cr +++ b/src/http/formdata.cr @@ -20,7 +20,7 @@ require "./formdata/**" # when "name" # name = part.body.gets_to_end # when "file" -# file = Tempfile.open("upload") do |file| +# file = File.tempfile("upload") do |file| # IO.copy(part.body, file) # end # end diff --git a/src/tempfile.cr b/src/tempfile.cr deleted file mode 100644 index cd2926db6b43..000000000000 --- a/src/tempfile.cr +++ /dev/null @@ -1,113 +0,0 @@ -require "c/stdlib" - -# The `Tempfile` class is for managing temporary files. -# Every tempfile is operated as a `File`, including -# initializing, reading and writing. -# -# ``` -# tempfile = Tempfile.new("foo") -# # or -# tempfile = Tempfile.open("foo") do |file| -# file.print("foobar") -# end -# -# File.size(tempfile.path) # => 6 -# File.info(tempfile.path).modification_time # => 2015-10-20 13:11:12 UTC -# File.exists?(tempfile.path) # => true -# File.read_lines(tempfile.path) # => ["foobar"] -# ``` -# -# Files created from this class are stored in a directory that handles -# temporary files. -# -# ``` -# Tempfile.new("foo").path # => "/tmp/foo.ulBCPS" -# ``` -# -# Also, it is encouraged to delete a tempfile after using it, which -# ensures they are not left behind in your filesystem until garbage collected. -# -# ``` -# tempfile = Tempfile.new("foo") -# tempfile.delete -# ``` -# -# The optional `extension` argument can be used to force the resulting filename -# to end with the given extension. -# -# ``` -# Tempfile.new("foo", ".png").path # => "/tmp/foo.ulBCPS.png" -# ``` -class Tempfile < File - # Creates a `Tempfile` with the given filename and extension. - # - # *encoding* and *invalid* are passed to `IO#set_encoding`. - def initialize(name, extension = nil, encoding = nil, invalid = nil) - fileno, path = Crystal::System::File.mktemp(name, extension) - super(path, fileno, blocking: true, encoding: encoding, invalid: invalid) - end - - # Retrieves the full path of a this tempfile. - # - # ``` - # Tempfile.new("foo").path # => "/tmp/foo.ulBCPS" - # ``` - getter path : String - - # Returns a fully-qualified path to a temporary file without actually - # creating the file. - # - # ``` - # Tempfile.tempname # => "/tmp/20171206-1234-449386" - # ``` - # - # The optional `extension` argument can be used to make the resulting - # filename to end with the given extension. - # - # ``` - # Tempfile.tempname(".sock") # => "/tmp/20171206-1234-449386.sock" - # ``` - def self.tempname(extension = nil) - time = Time.now.to_s("%Y%m%d") - rand = Random.rand(0x100000000).to_s(36) - {% if flag?(:win32) %} - # TODO: Remove this once Process is implemented - File.join(dirname, "#{time}-#{rand}#{extension}") - {% else %} - File.join(dirname, "#{time}-#{Process.pid}-#{rand}#{extension}") - {% end %} - end - - # Creates a file with *filename* and *extension*, and yields it to the given - # block. It is closed and returned at the end of this method call. - # - # ``` - # tempfile = Tempfile.open("foo") do |file| - # file.print("bar") - # end - # File.read(tempfile.path) # => "bar" - # ``` - def self.open(filename, extension = nil) - tempfile = Tempfile.new(filename, extension) - begin - yield tempfile - ensure - tempfile.close - end - tempfile - end - - # Returns the tmp dir used for tempfile. - # - # ``` - # Tempfile.dirname # => "/tmp" - # ``` - def self.dirname : String - Crystal::System::File.tempdir - end - - # Deletes this tempfile. - def delete - File.delete(@path) - end -end