Class: Tempster

Inherits:
Object show all
Defined in:
lib/tempster.rb

Overview

Tempster

Tempster is a pure-Ruby library for creating temporary files and directories. Unlike other tools, it can create both temporary files and directories, and is designed to be secure, thread-safe, easy-to-use, powerful thanks to user-configurable options, and user-friendly thanks to good defaults so you don’t need to provide any arguments.

Why write another library for this? The Tempfile standard library provides no way to create temporary directories and always deletes the files it creates, even if you want to keep them. The MkTemp gem is insecure and fails on collisions. Linux “mktemp” works fine but is platform-specific. Therefore, I had to write something.

WARNING: Using ‘cd’ and :noop together can be dangerous!

Tempster will only pretend to make directories in :noop (no-operation) mode. In :noop mode, it will also only pretend to change into the directory when using :cd or mktempdircd.

This can be disastrous if you’re executing non-AutomateIt commands (e.g. system) that use relative paths and expect to be run inside the newly-created temporary directory because the chdir didn’t actually happen.

Read previews.txt for instructions on how to write code that can be safely previewed.

Credits

Defined Under Namespace

Classes: Messager

Constant Summary collapse

DEFAULT_NAME =
"tempster"
DEFAULT_FILE_MODE =
0600
DEFAULT_DIRECTORY_MODE =
0700
DEFAULT_ARMOR_LENGTH =
10
ARMOR_CHARACTERS =
["A".."Z","a".."z","0".."9"].collect{|r| r.to_a}.join

Class Method Summary collapse

Class Method Details

._armor_string(length = DEFAULT_ARMOR_LENGTH) ⇒ Object

Returns a string of random characters.



139
140
141
# File 'lib/tempster.rb', line 139

def self._armor_string(length=DEFAULT_ARMOR_LENGTH)
  (1..length).collect{ARMOR_CHARACTERS[rand(ARMOR_CHARACTERS.size)]}.pack("C*")
end

._tempster(opts = {}, &block) ⇒ Object

Options:

  • :name – Name prefix to usse, defaults to “tempster”.

  • :kind – Create a :file or :directory, required.

  • :dir – Base directory to create temporary entries in, uses system-wide temporary directory (e.g., /tmp) by default.

  • :cd – Change into the newly directory created using ch within the block, and then switch back to the previous directory. Only used when a block is given and the :kind is :directory. Default is false. See WARNING at the top of this class’s documentation!

  • :noop – no-operation mode, pretends to do actions without actually creating or deleting temporary entries. Default is false. WARNING: See WARNING at the top of this class’s documentation!

  • :verbose – Print status messages about creating and deleting the temporary entries. Default is false.

  • :delete – Delete the temporary entries when exiting block. Default is true when given a block, false otherwise. If you don’t use a block, you’re responsible for deleting the entries yourself.

  • :tries – Number of tries to create a temporary entry, usually it’ll succeed on the first try. Default is 10.

  • :armor – Length of armor to add to the name. These are random characters padding out the temporary entry names to prevent them from using existing files. If you have a very short armor, you’re likely to get a collision and the algorithm will have to try again for the specified number of tries.

  • :message_callback – lambda called when there’s a message, e.g., lambda{|message| puts message}, regardless of :verbose state. By default :messaging is nil and messages are printed to STDOUT only when :verbose is true.

  • :message_prefix – String to put in front of messages, e.g., “# ”

Raises:

  • (ArgumentError)


53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
# File 'lib/tempster.rb', line 53

def self._tempster(opts={}, &block)
  name = opts.delete(:name) || DEFAULT_NAME
  kind = opts.delete(:kind) or raise ArgumentError.new("'kind' option not specified")
  dir = opts.delete(:dir) || Dir.tmpdir
  cd = opts.delete(:cd) || false
  noop =  opts.delete(:noop) || false
  verbose = opts.delete(:verbose) || false
  delete = opts.delete(:delete) || block ? true : false
  tries = opts.delete(:tries) || 10
  armor = opts.delete(:armor) || DEFAULT_ARMOR_LENGTH
  message_callback = opts.delete(:message_callback) || nil
  message_prefix = opts.delete(:message_prefix) || ""
  mode = opts.delete(:mode) || \
    case kind
    when :file
      DEFAULT_FILE_MODE
    when :directory
      DEFAULT_DIRECTORY_MODE
    else
      raise ArgumentError.new("unknown kind: #{kind}")
    end

  raise ArgumentError.new("can only use 'delete' option with block") if delete and not block
  raise ArgumentError.new("can only use 'cd' with directories and blocks") if cd and (not dir or not block)
  raise ArgumentError.new("unknown extra options: #{opts.inspect}") unless opts.empty?

  messager = Messager.new(verbose, message_callback, message_prefix)

  path = nil
  success = false
  for i in 1..tries
    begin
      path = File.join(dir, name+"_"+_armor_string(armor))
      unless noop
        case kind
        when :file
          File.open(path, File::RDWR|File::CREAT|File::EXCL).close
          File.chmod(mode, path)
        when :directory
          Dir.mkdir(path, mode)
        else
          raise ArgumentError.new("unknown kind: #{kind}")
        end
      end
      # XXX Should we pretend that it's mktemp? Or give users something more useful?
      # messager.puts("mktemp -m 0%o%s -p %s %s # => %s" % [mode, kind == :directory ? ' -d' : '', dir, name, path])
      if block
        messager.puts("mktempster --mode=0%o --kind=%s --dir=%s --name=%s" % [mode, kind, dir, name])
      else
        messager.puts("mktempster --mode=0%o --kind=%s --dir=%s --name=%s # => %s" % [mode, kind, dir, name, path])
      end
      success = true
      break
    rescue Errno::EEXIST
      # Try again
    end
  end
  raise IOError.new("couldn't create temporary #{kind}, ") unless success
  if block
    previous = Dir.pwd if cd
    begin
      if cd
        Dir.chdir(path) unless noop
        messager.puts("pushd #{path}")
      end
      block.call(path)
    rescue Exception => e
      # Re-throw exception after cleaning up
      raise e
    ensure
      if cd
        Dir.chdir(previous) unless noop
        messager.puts("popd # => #{previous}")
      end
      if delete
        FileUtils.rm_rf(path) unless noop
        messager.puts("rm -rf #{path}")
      end
    end
    return true
  else
    return path
  end
end

.mktemp(opts = {}, &block) ⇒ Object

Creates a temporary file.



144
145
146
# File 'lib/tempster.rb', line 144

def self.mktemp(opts={}, &block)
  _tempster({:kind => :file}.merge(opts), &block)
end

.mktempdir(opts = {}, &block) ⇒ Object

Creates a temporary directory.

WARNING: See WARNING text at the top of this class’s documentation!



151
152
153
154
# File 'lib/tempster.rb', line 151

def self.mktempdir(opts={}, &block)
  _tempster({:kind => :directory}.merge(opts), &block)

end

.mktempdircd(opts = {}, &block) ⇒ Object

Creates a temporary directory and changes into it using chdir. This is a shortcut for using mktempdir with the :cd => true option.

WARNING: See WARNING text at the top of this class’s documentation!



160
161
162
# File 'lib/tempster.rb', line 160

def self.mktempdircd(opts={}, &block)
  _tempster({:kind => :directory, :cd => true}.merge(opts), &block)
end