# Copyright: Copyright (c) 2004  Nicolas Pouillard. All rights reserved.
# Author: Nicolas Pouillard  <ertai@lrde.epita.fr>.
# License: Gnu General Public License.

# $LastChangedBy: ertai $
# $Id: temp_path.rb 514 2007-01-14 10:53:46Z ertai $

require 'tempfile'
require 'tmpdir'
require 'thread'

module TempPath
  module_function

  @@initialized = false
  @@mutex = Mutex.new

  # You can use a temporary pathname like that:
  #
  #  - # No argument are mandatory.
  #    tmp = TempPath.new
  #    tmp.open('w') { |f| f << 'foo' }
  #    tmp.clean
  #
  #  - # You can choose the basename and an extension.
  #    TempPath.new('the_base_file_name', 'rb')
  #
  #  - # You can supply a block (recomended).
  #    TempPath.new('foo') do |tmp|
  #      tmp.open('w') { |f| << 'foo' }
  #      tmp.exist? == true
  #      $tmp = tmp
  #    end
  #    $tmp.exist? == false
  #
  #  - # You can make a temporary directory.
  #    TempPath.new('adirectory') do |tmpdir|
  #      tmpdir.mkpath
  #      $file = tmpdir + 'foo'
  #      $file.open('w') { |f| f << 'foo' }
  #    end
  #    $file.exist? == false
  #
  # The file name follow this scheme:
  #
  # TempPath.new('foo', 'rb')
  #   => 'foo.111811.432.rb'
  # TempPath.new('bar')
  #   => 'bar.111811.134'
  #
  # which follow this format:
  #   => 'base.pid.uniq.ext
  #
  def new base=nil, ext='', &block
    tmp = nil
    if base and base.to_s =~ /\//
      raise ArgumentError, "bad basename, you give me a pathname #{base}"
    end
    init
    base ||= @@progname
    ext = ".#{ext}" unless ext.empty? or ext[0] == ?.
    res = nil
    @@mutex.synchronize do
      tmp = Pathname.new ''
      id_tmp = tmp.object_id
      while (res = @@tmpdir + "#{base}.#{id_tmp}#{ext}").exist? \
        and not @@tmps.include? res
        id_tmp += 1
      end
      tmp.instance_eval { @path = res.instance_eval { @path } }
      tmp.extend TempPathname
      @@tmps << self
    end
    return tmp unless block_given?
    begin
      return block[tmp.dup]
    ensure
      tmp.clean
    end
  end

    def init ( tmp_base_dir=nil ) #:nodoc:
      return if @@initialized
      @@mutex.synchronize do
        return if @@initialized
        @@tmps = Set.new
        @@progname = Pathname.new($0).basename
        @@tmpdir = (tmp_base_dir || Dir.tmpdir).to_path + "#{@@progname}.#{$$}"
        @@tmpdir.freeze
        @@clean_planned = false
        @@auto_clean = true
        @@tmpdir.mkpath
        @@initialized = true
        at_exit { clean if @@auto_clean }
      end
    end

    # Call TempPath.fork_init when you want to reset the global TempPath status.
    # For example when you make a fork and you don't want that the child have
    # and destroy the same temporary directory as the father.
    # This method setup a child TempPath environement where the temporary directory
    # of the child is in the father one (unless if you set +sub_dir+ to false)
    def fork_init ( sub_dir=true )
      @@tmpdir ||= nil
      @@initialized = false
      init((sub_dir)? @@tmpdir : nil)
    end

    # By default the autoclean is on.
    # But in some case (if you use your temppaths in at_exit)
    # You must disable the autoclean.
    # And manually call TempPath.clean at the very of the program.
    def clean ( &block )
      @@mutex.synchronize do
        return if @@clean_planned
        @@clean_planned = true
      end
      begin
        block[] if block_given?
      ensure
        if @@tmpdir.exist?
          @@tmpdir.rmtree
          @@initialized = false
        end
      end
    end

    def auto_clean= ( aBool )
      @@auto_clean = aBool
    end

    def tmpdir
      init
      @@tmpdir
    end

end # module TempPath

module TempPathname

  # This method remove your temporary pathname.
  # You do not need to call this method if you provide
  # a block when you create a tempfile.
  def clean
    if exist?
      if directory?
        rmtree
      else
        unlink
      end
    end
  end

  def temp?
    true
  end

end # class TempPathname


class Pathname

  def temp?
    false
  end

end # class Pathname



test_section __FILE__ do

  class TempPathTest < Test::Unit::TestCase

    def setup
      assert_nothing_raised { @foo = TempPath.new('foo') }
      assert_nothing_raised { @foobar = TempPath.new('foo', 'bar') }
      @list = []
    end

    def teardown
      @list.each { |l| l.clean }
      [@foo, @foobar].each { |l| l.clean }
    end

    def test_interface
      assert_match(/\.#{$$}\/foo\.-?\d+$/, @foo.to_s)
      assert_match(/\.#{$$}\/foo\.-?\d+\.bar$/, @foobar.to_s)
      assert_nothing_raised { @foo.open('w') { |f| f.puts 'FooFoo'  }  }
    end

    def test_many
      (0 .. 100).each do |i|
        tmp = TempPath.new('many')
        tmp.open('w') { |f| f.puts "i: #{i}" }
        assert(tmp.exist?)
        assert_equal("i: #{i}\n", tmp.readlines.join)
        @list << tmp
      end
    end

    def test_different
      assert_not_equal TempPath.new, TempPath.new
    end

    def test_temp?
      assert(@foo.temp?, 'not tmp.temp?')
      assert_nothing_raised do
        assert(!Pathname.new(@foo.to_s).temp?)
      end
    end

    def test_block_return
      assert_nothing_raised do
        @x = TempPath.new { |tmp| 42 }
      end
      assert_equal(42, @x)
    end

  end # class MkTempTest

end
