[DRE-commits] [ruby-stud] 01/05: Imported Upstream version 0.0.17

Tim Potter tpot-guest at moszumanska.debian.org
Tue Aug 5 05:08:58 UTC 2014


This is an automated email from the git hooks/post-receive script.

tpot-guest pushed a commit to branch master
in repository ruby-stud.

commit 40464319f83b2bd71c4fafdc77096fb38cc62780
Author: Tim Potter <tpot at hp.com>
Date:   Fri Jul 25 15:45:39 2014 +1000

    Imported Upstream version 0.0.17
---
 CHANGELIST                   |    0
 LICENSE                      |   14 +
 README.md                    |   27 +
 lib/stud/benchmark.rb        |  132 +++++
 lib/stud/benchmark/rusage.rb |   54 ++
 lib/stud/buffer.rb           |  260 +++++++++
 lib/stud/interval.rb         |   27 +
 lib/stud/pool.rb             |  215 +++++++
 lib/stud/secret.rb           |   35 ++
 lib/stud/task.rb             |   32 +
 lib/stud/temporary.rb        |   62 ++
 lib/stud/time.rb             |   37 ++
 lib/stud/time/format.rb      | 1323 ++++++++++++++++++++++++++++++++++++++++++
 lib/stud/time/format.tt      |  236 ++++++++
 lib/stud/trap.rb             |   74 +++
 lib/stud/try.rb              |  123 ++++
 lib/stud/with.rb             |   22 +
 metadata.yml                 |  127 ++++
 18 files changed, 2800 insertions(+)

diff --git a/CHANGELIST b/CHANGELIST
new file mode 100644
index 0000000..e69de29
diff --git a/LICENSE b/LICENSE
new file mode 100644
index 0000000..927bb87
--- /dev/null
+++ b/LICENSE
@@ -0,0 +1,14 @@
+Copyright 2012-2013 Jordan Sissel and contributors.
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+
diff --git a/README.md b/README.md
new file mode 100644
index 0000000..71cb6a4
--- /dev/null
+++ b/README.md
@@ -0,0 +1,27 @@
+# Stud.
+
+Ruby's stdlib is missing many things I use to solve most of my software
+problems. Things like like retrying on a failure, supervising workers, resource
+pools, etc.
+
+In general, I started exploring solutions to these things in code over in my
+[software-patterns](https://github.com/jordansissel/software-patterns) repo.
+This library (stud) aims to be a well-tested, production-quality implementation
+of the patterns described in that repo.
+
+For now, these all exist in a single repo because, so far, implementations of
+each 'pattern' are quite small by code size.
+
+## Features
+
+* {Stud::Try} (and {Stud.try}) - retry on failure, with back-off, where failure is any exception.
+* {Stud::Pool} - generic resource pools
+* {Stud::Task} - tasks (threads that can return values, exceptions, etc)
+* {Stud.interval} - interval execution (do X every N seconds)
+* {Stud::Buffer} - batch & flush behavior.
+
+## TODO:
+
+* Make sure all things are documented. rubydoc.info should be able to clearly
+  show folks how to use features of this library.
+* Add tests to cover all supported features.
diff --git a/lib/stud/benchmark.rb b/lib/stud/benchmark.rb
new file mode 100644
index 0000000..b808000
--- /dev/null
+++ b/lib/stud/benchmark.rb
@@ -0,0 +1,132 @@
+# encoding: UTF-8
+# Benchmark Use Cases
+#   * Compare performance of different implementations.
+#     * run each implementation N times, compare runtimes (histogram, etc)
+
+require "metriks"
+require "stud/benchmark/rusage"
+
+module Stud
+  module Benchmark
+    def self.run(iterations=1, &block)
+      timer = Metriks::Timer.new
+      start = Time.now
+      iterations.times { timer.time(&block) }
+      duration = Time.now - start
+      return Results.new(timer, duration)
+    end # def run
+
+    def self.runtimed(seconds=10, &block)
+      timer = Metriks::Timer.new
+      expiration = Time.now + seconds
+
+      start = Time.now
+      timer.time(&block) while Time.now < expiration
+      duration = Time.now - start
+      return Results.new(timer, duration)
+    end # def runtimed
+
+    def self.cputimed(seconds=10, &block)
+      timer = Metriks::Timer.new
+      expiration = Time.now + seconds
+      start_usage = Stud::Benchmark::RUsage.get
+      while Time.now < expiration
+        start = Stud::Benchmark::RUsage.get
+        block.call
+        finish = Stud::Benchmark::RUsage.get
+        cputime = (finish.user + finish.system) - (start.user + start.system)
+        timer.update(cputime)
+      end # while not expired
+      finish_usage = Stud::Benchmark::RUsage.get
+      duration = (finish_usage.user + finish_usage.system) \
+        - (start_usage.user + start_usage.system)
+      return Results.new(timer, duration)
+    end # self.cpu
+
+    class Results
+      include Enumerable
+      # Stolen from https://github.com/holman/spark/blob/master/spark
+      # TICKS = %w{▁ ▂ ▃ ▄ ▅ ▆ ▇ █}
+
+      TICKS = ["\x1b[38;5;#{232 + 8}m_\x1b[0m"] + %w{▁ ▂ ▃ ▄ ▅ ▆ ▇ █}
+
+      #.collect do |tick|
+        # 256 color support, use grayscale
+        #1.times.collect do |shade|
+          # '38' is foreground
+          # '48' is background
+          # Grey colors start at 232, but let's use the brighter half.
+          # escape [ 38 ; 5 ; <color>
+          #"\x1b[38;5;#{232 + 12 + 2 * shade}m#{tick}\x1b[0m"
+        #end
+        #tick
+      #end.flatten
+
+      def initialize(data, duration)
+        @data = data
+        @duration = duration
+      end # def initialize
+
+      def environment
+        # Older rubies don't have the RUBY_ENGINE defiend
+        engine = (RUBY_ENGINE rescue "ruby")
+        # Include jruby version in the engine
+        engine += (JRUBY_VERSION rescue "")
+        version = RUBY_VERSION
+
+        return "#{engine} #{version}"
+      end # def environment
+
+      def each(&block)
+        @data.snapshot.each(&block)
+      end # def each
+
+      def min
+        return @data.min
+      end
+
+      def max
+        return @data.max
+      end
+
+      def rate
+        return @data.count / @duration
+      end
+
+      def mean
+        return @data.mean
+      end # def mean
+
+      def stddev
+        # work around (Timer#stddev reports the variance)
+        # https://github.com/eric/metriks/pull/29
+        return @data.stddev ** 0.5
+      end # def stddev
+
+      def sum
+        return @data.instance_eval { @histogram.sum }
+      end # def sum
+
+      def pretty_print
+        puts self
+      end # def pretty_print
+
+      def to_s(scale=min .. max, ticks=10)
+        snapshot = @data.snapshot
+        values = snapshot.instance_eval { @values }
+        scale_distance = scale.end - scale.begin
+        tick = scale_distance / ticks
+        dist = ticks.to_i.times.collect do |i|
+          range = (scale.begin + tick * i) ... (scale.begin + tick * (i+1))
+          hits = values.select { |v| range.include?(v) }.count
+          percent = hits / values.size.to_f
+          next TICKS[(TICKS.count * percent).ceil] || TICKS.last
+        end
+
+        return sprintf("%20s %s (%.4f ... %.4f, mean: %0.4f, stddev: %0.4f)",
+                       environment, dist.join(""), scale.begin, scale.end,
+                       mean, stddev)
+      end # def to_s
+    end # class Stud::Benchmark::Result
+  end # module Benchmark
+end # module Stud
diff --git a/lib/stud/benchmark/rusage.rb b/lib/stud/benchmark/rusage.rb
new file mode 100644
index 0000000..b9aabcd
--- /dev/null
+++ b/lib/stud/benchmark/rusage.rb
@@ -0,0 +1,54 @@
+require "ffi"
+
+module Stud
+  module Benchmark
+    module LibC
+      extend FFI::Library
+      ffi_lib "libc.so.6"
+
+      attach_function :getrusage, [:int, :pointer], :int
+    end
+
+    class TimeVal < FFI::Struct
+      layout :tv_sec, :long,
+             :tv_usec, :int32
+
+      def to_f
+        return self[:tv_sec] + (self[:tv_usec] / 1_000_000.0)
+      end
+    end
+
+    class RUsage < FFI::Struct
+      layout :utime, TimeVal,
+             :stime, TimeVal,
+             :maxrss, :long,
+             :ixrss, :long,
+             :idrss, :long,
+             :isrss, :long,
+             :minflt, :long,
+             :majflt, :long,
+             :nswap, :long,
+             :inblock, :long,
+             :oublock, :long,
+             :msgsnd, :long,
+             :msgrcv, :long,
+             :nsignals, :long,
+             :nvcsw, :long,
+             :nivcsw, :long
+
+      def self.get
+        usage = RUsage.new
+        LibC.getrusage(0, usage)
+        return usage
+      end
+
+      def user
+        return self[:utime].to_f
+      end
+
+      def system
+        return self[:stime].to_f
+      end
+    end # class RUsage
+  end # module Benchmark
+end # module Stud
diff --git a/lib/stud/buffer.rb b/lib/stud/buffer.rb
new file mode 100644
index 0000000..1e950cc
--- /dev/null
+++ b/lib/stud/buffer.rb
@@ -0,0 +1,260 @@
+module Stud
+
+  # @author {Alex Dean}[http://github.com/alexdean]
+  #
+  # Implements a generic framework for accepting events which are later flushed
+  # in batches. Flushing occurs whenever +:max_items+ or +:max_interval+ (seconds)
+  # has been reached.
+  #
+  # Including class must implement +flush+, which will be called with all
+  # accumulated items either when the output buffer fills (+:max_items+) or
+  # when a fixed amount of time (+:max_interval+) passes.
+  #
+  # == batch_receive and flush
+  # General receive/flush can be implemented in one of two ways.
+  #
+  # === batch_receive(event) / flush(events)
+  # +flush+ will receive an array of events which were passed to +buffer_receive+.
+  #
+  #   batch_receive('one')
+  #   batch_receive('two')
+  #
+  # will cause a flush invocation like
+  #
+  #   flush(['one', 'two'])
+  #
+  # === batch_receive(event, group) / flush(events, group)
+  # flush() will receive an array of events, plus a grouping key.
+  #
+  #   batch_receive('one',   :server => 'a')
+  #   batch_receive('two',   :server => 'b')
+  #   batch_receive('three', :server => 'a')
+  #   batch_receive('four',  :server => 'b')
+  #
+  # will result in the following flush calls
+  #
+  #   flush(['one', 'three'], {:server => 'a'})
+  #   flush(['two', 'four'],  {:server => 'b'})
+  #
+  # Grouping keys can be anything which are valid Hash keys. (They don't have to
+  # be hashes themselves.) Strings or Fixnums work fine. Use anything which you'd
+  # like to receive in your +flush+ method to help enable different handling for
+  # various groups of events.
+  #
+  # == on_flush_error
+  # Including class may implement +on_flush_error+, which will be called with an
+  # Exception instance whenever buffer_flush encounters an error.
+  #
+  # * +buffer_flush+ will automatically re-try failed flushes, so +on_flush_error+
+  #   should not try to implement retry behavior.
+  # * Exceptions occurring within +on_flush_error+ are not handled by
+  #   +buffer_flush+.
+  #
+  # == on_full_buffer_receive
+  # Including class may implement +on_full_buffer_receive+, which will be called
+  # whenever +buffer_receive+ is called while the buffer is full.
+  #
+  # +on_full_buffer_receive+ will receive a Hash like <code>{:pending => 30,
+  # :outgoing => 20}</code> which describes the internal state of the module at
+  # the moment.
+  #
+  # == final flush
+  # Including class should call <code>buffer_flush(:final => true)</code>
+  # during a teardown/shutdown routine (after the last call to buffer_receive)
+  # to ensure that all accumulated messages are flushed.
+  module Buffer
+
+    public
+    # Initialize the buffer.
+    #
+    # Call directly from your constructor if you wish to set some non-default
+    # options. Otherwise buffer_initialize will be called automatically during the
+    # first buffer_receive call.
+    #
+    # Options:
+    # * :max_items, Max number of items to buffer before flushing. Default 50.
+    # * :max_interval, Max number of seconds to wait between flushes. Default 5.
+    # * :logger, A logger to write log messages to. No default. Optional.
+    #
+    # @param [Hash] options
+    def buffer_initialize(options={})
+      if ! self.class.method_defined?(:flush)
+        raise ArgumentError, "Any class including Stud::Buffer must define a flush() method."
+      end
+
+      @buffer_config = {
+        :max_items => options[:max_items] || 50,
+        :max_interval => options[:max_interval] || 5,
+        :logger => options[:logger] || nil,
+        :has_on_flush_error => self.class.method_defined?(:on_flush_error),
+        :has_on_full_buffer_receive => self.class.method_defined?(:on_full_buffer_receive)
+      }
+      @buffer_state = {
+        # items accepted from including class
+        :pending_items => {},
+        :pending_count => 0,
+
+        # guard access to pending_items & pending_count
+        :pending_mutex => Mutex.new,
+
+        # items which are currently being flushed
+        :outgoing_items => {},
+        :outgoing_count => 0,
+
+        # ensure only 1 flush is operating at once
+        :flush_mutex => Mutex.new,
+
+        # data for timed flushes
+        :last_flush => Time.now,
+        :timer => Thread.new do
+          loop do
+            sleep(@buffer_config[:max_interval])
+            buffer_flush(:force => true)
+          end
+        end
+      }
+
+      # events we've accumulated
+      buffer_clear_pending
+    end
+
+    # Determine if +:max_items+ has been reached.
+    #
+    # buffer_receive calls will block while <code>buffer_full? == true</code>.
+    #
+    # @return [bool] Is the buffer full?
+    def buffer_full?
+      @buffer_state[:pending_count] + @buffer_state[:outgoing_count] >= @buffer_config[:max_items]
+    end
+
+    # Save an event for later delivery
+    #
+    # Events are grouped by the (optional) group parameter you provide.
+    # Groups of events, plus the group name, are later passed to +flush+.
+    #
+    # This call will block if +:max_items+ has been reached.
+    #
+    # @see Stud::Buffer The overview has more information on grouping and flushing.
+    #
+    # @param event An item to buffer for flushing later.
+    # @param group Optional grouping key. All events with the same key will be
+    #              passed to +flush+ together, along with the grouping key itself.
+    def buffer_receive(event, group=nil)
+      buffer_initialize if ! @buffer_state
+
+      # block if we've accumulated too many events
+      while buffer_full? do
+        on_full_buffer_receive(
+          :pending => @buffer_state[:pending_count],
+          :outgoing => @buffer_state[:outgoing_count]
+        ) if @buffer_config[:has_on_full_buffer_receive]
+        sleep 0.1
+      end
+
+      @buffer_state[:pending_mutex].synchronize do
+        @buffer_state[:pending_items][group] << event
+        @buffer_state[:pending_count] += 1
+      end
+
+      buffer_flush
+    end
+
+    # Try to flush events.
+    #
+    # Returns immediately if flushing is not necessary/possible at the moment:
+    # * :max_items have not been accumulated
+    # * :max_interval seconds have not elapased since the last flush
+    # * another flush is in progress
+    #
+    # <code>buffer_flush(:force => true)</code> will cause a flush to occur even
+    # if +:max_items+ or +:max_interval+ have not been reached. A forced flush
+    # will still return immediately (without flushing) if another flush is
+    # currently in progress.
+    #
+    # <code>buffer_flush(:final => true)</code> is identical to <code>buffer_flush(:force => true)</code>,
+    # except that if another flush is already in progress, <code>buffer_flush(:final => true)</code>
+    # will block/wait for the other flush to finish before proceeding.
+    #
+    # @param [Hash] options Optional. May be <code>{:force => true}</code> or <code>{:final => true}</code>.
+    # @return [Fixnum] The number of items successfully passed to +flush+.
+    def buffer_flush(options={})
+      force = options[:force] || options[:final]
+      final = options[:final]
+
+      # final flush will wait for lock, so we are sure to flush out all buffered events
+      if options[:final]
+        @buffer_state[:flush_mutex].lock
+      elsif ! @buffer_state[:flush_mutex].try_lock # failed to get lock, another flush already in progress
+        return 0
+      end
+
+      items_flushed = 0
+
+      begin
+        time_since_last_flush = (Time.now - @buffer_state[:last_flush])
+
+        return 0 if @buffer_state[:pending_count] == 0
+        return 0 if (!force) &&
+           (@buffer_state[:pending_count] < @buffer_config[:max_items]) &&
+           (time_since_last_flush < @buffer_config[:max_interval])
+
+        @buffer_state[:pending_mutex].synchronize do
+          @buffer_state[:outgoing_items] = @buffer_state[:pending_items]
+          @buffer_state[:outgoing_count] = @buffer_state[:pending_count]
+          buffer_clear_pending
+        end
+
+        @buffer_config[:logger].debug("Flushing output",
+          :outgoing_count => @buffer_state[:outgoing_count],
+          :time_since_last_flush => time_since_last_flush,
+          :outgoing_events => @buffer_state[:outgoing_items],
+          :batch_timeout => @buffer_config[:max_interval],
+          :force => force,
+          :final => final
+        ) if @buffer_config[:logger]
+
+        @buffer_state[:outgoing_items].each do |group, events|
+          begin
+            if group.nil?
+              flush(events,final)
+            else
+              flush(events, group, final)
+            end
+
+            @buffer_state[:outgoing_items].delete(group)
+            events_size = events.size
+            @buffer_state[:outgoing_count] -= events_size
+            items_flushed += events_size
+
+          rescue => e
+
+            @buffer_config[:logger].warn("Failed to flush outgoing items",
+              :outgoing_count => @buffer_state[:outgoing_count],
+              :exception => e,
+              :backtrace => e.backtrace
+            ) if @buffer_config[:logger]
+
+            if @buffer_config[:has_on_flush_error]
+              on_flush_error e
+            end
+
+            sleep 1
+            retry
+          end
+          @buffer_state[:last_flush] = Time.now
+        end
+
+      ensure
+        @buffer_state[:flush_mutex].unlock
+      end
+
+      return items_flushed
+    end
+
+    private
+    def buffer_clear_pending
+      @buffer_state[:pending_items] = Hash.new { |h, k| h[k] = [] }
+      @buffer_state[:pending_count] = 0
+    end
+  end
+end
diff --git a/lib/stud/interval.rb b/lib/stud/interval.rb
new file mode 100644
index 0000000..5734cb7
--- /dev/null
+++ b/lib/stud/interval.rb
@@ -0,0 +1,27 @@
+module Stud
+  # This implementation tries to keep clock more accurately.
+  # Prior implementations still permitted skew, where as this one
+  # will attempt to correct for skew.
+  #
+  # The execution patterns of this method should be that 
+  # the start time of 'block.call' should always be at time T*interval
+  def self.interval(time, &block)
+    start = Time.now
+    while true
+      block.call
+      duration = Time.now - start
+      # Sleep only if the duration was less than the time interval
+      if duration < time
+        sleep(time - duration)
+        start += time
+      else
+        # Duration exceeded interval time, reset the clock and do not sleep.
+        start = Time.now
+      end
+    end # loop forever
+  end # def interval
+  
+  def interval(time, &block)
+    return Stud.interval(time, &block)
+  end # def interval
+end # module Stud
diff --git a/lib/stud/pool.rb b/lib/stud/pool.rb
new file mode 100644
index 0000000..5be39a6
--- /dev/null
+++ b/lib/stud/pool.rb
@@ -0,0 +1,215 @@
+require "thread"
+
+module Stud
+  # Public: A thread-safe, generic resource pool.
+  #
+  # This class is agnostic as to the resources in the pool. You can put
+  # database connections, sockets, threads, etc. It's up to you!
+  #
+  # Examples:
+  #
+  #     pool = Pool.new
+  #     pool.add(Sequel.connect("postgres://pg-readonly-1/prod"))
+  #     pool.add(Sequel.connect("postgres://pg-readonly-2/prod"))
+  #     pool.add(Sequel.connect("postgres://pg-readonly-3/prod"))
+  #
+  #     pool.fetch # =>  Returns one of the Sequel::Database values from the pool
+  class Pool
+
+    class Error < StandardError; end
+
+    # An error indicating a given resource is busy.
+    class ResourceBusy < Error; end
+
+    # An error indicating a given resource is not found.
+    class NotFound < Error; end
+
+    # You performed an invalid action.
+    class InvalidAction < Error; end
+
+    # Default all methods to private. See the bottom of the class definition
+    # for public method declarations.
+    private
+
+    # Public: initialize a new pool.
+    #
+    # max_size - if specified, limits the number of resources allowed in the pool.
+    def initialize(max_size=nil)
+      # Available resources
+      @available = Hash.new
+      # Busy resources
+      @busy = Hash.new
+
+      # The pool lock
+      @lock = Mutex.new
+
+      # Locks for blocking {#fetch} calls if the pool is full.
+      @full_lock = Mutex.new
+      @full_cv = ConditionVariable.new
+
+      # Maximum size of this pool.
+      @max_size = max_size
+    end # def initialize
+
+    # Private: Is this pool size-limited?
+    #
+    # Returns true if this pool was created with a max_size. False, otherwise.
+    def sized?
+      return !@max_size.nil?
+    end # def sized?
+
+    # Private: Is this pool full?
+    #
+    # Returns true if the pool is sized and the count of resources is at maximum.
+    def full?
+      return sized? && (count == @max_size)
+    end # def full?
+
+    # Public: the count of resources in the pool
+    #
+    # Returns the count of resources in the pool.
+    def count
+      return (@busy.size + @available.size)
+    end # def count
+
+    # Public: Add a new resource to this pool.
+    #
+    # The resource, once added, is assumed to be available for use.
+    # That means once you add it, you must not use it unless you receive it from
+    # {Pool#fetch}
+    #
+    # resource - the object resource to add to the pool.
+    #
+    # Returns nothing
+    def add(resource)
+      @lock.synchronize do
+        @available[resource.object_id] = resource
+      end
+      return nil
+    end # def add
+
+    # Public: Fetch an available resource.
+    #
+    # If no resource is available, and the pool is not full, the
+    # default_value_block will be called and the return value of it used as the
+    # resource.
+    #
+    # If no resource is availabe, and the pool is full, this call will block
+    # until a resource is available.
+    #
+    # Returns a resource ready to be used.
+    def fetch(&default_value_block)
+      @lock.synchronize do
+        object_id, resource = @available.shift
+        if !resource.nil?
+          @busy[resource.object_id] = resource
+          return resource
+        end
+      end
+
+      @full_lock.synchronize do
+        if full?
+          # This should really use a logger.
+          puts "=> Pool is full and nothing available. Waiting for a release..."
+          @full_cv.wait(@full_lock)
+          return fetch(&default_value_block)
+        end
+      end
+
+      # TODO(sissel): If no block is given, we should block until a resource is
+      # available.
+
+      # If we get here, no resource is available and the pool is not full.
+      resource = default_value_block.call
+      # Only add the resource if the default_value_block returned one.
+      if !resource.nil?
+        add(resource)
+        return fetch
+      end
+    end # def fetch
+
+    # Public: Remove a resource from the pool.
+    #
+    # This is useful if the resource is no longer useful. For example, if it is
+    # a database connection and that connection has failed.
+    #
+    # This resource *MUST* be available and not busy.
+    #
+    # Raises Pool::NotFound if no such resource is found.
+    # Raises Pool::ResourceBusy if the resource is found but in use.
+    def remove(resource)
+      # Find the object by object_id
+      #p [:internal, :busy => @busy, :available => @available]
+      @lock.synchronize do
+        if available?(resource)
+          raise InvalidAction, "This resource must be busy for you to remove " \
+            "it (ie; it must be fetched from the pool)"
+        end
+        @busy.delete(resource.object_id)
+      end
+    end # def remove
+
+    # Private: Verify this resource is in the pool.
+    #
+    # You *MUST* call this method only when you are holding @lock.
+    #
+    # Returns :available if it is available, :busy if busy, false if not in the pool.
+    def include?(resource)
+      if @available.include?(resource.object_id)
+        return :available
+      elsif @busy.include?(resource.object_id)
+        return :busy
+      else
+        return false
+      end
+    end # def include?
+
+    # Private: Is this resource available?
+    # You *MUST* call this method only when you are holding @lock.
+    #
+    # Returns true if this resource is available in the pool.
+    # Raises NotFound if the resource given is not in the pool at all.
+    def available?(resource)
+      case include?(resource)
+        when :available; return true
+        when :busy; return false
+        else; raise NotFound, "No resource, #{resource.inspect}, found in pool"
+      end
+    end # def avilable?
+
+    # Private: Is this resource busy?
+    #
+    # You *MUST* call this method only when you are holding @lock.
+    #
+    # Returns true if this resource is busy.
+    # Raises NotFound if the resource given is not in the pool at all.
+    def busy?(resource)
+      return !available?(resource)
+    end # def busy?
+
+    # Public: Release this resource back to the pool.
+    #
+    # After you finish using a resource you received with {#fetch}, you must
+    # release it back to the pool using this method.
+    #
+    # Alternately, you can {#remove} it if you want to remove it from the pool
+    # instead of releasing it.
+    def release(resource)
+      @lock.synchronize do
+        if !include?(resource)
+          raise NotFound, "No resource, #{resource.inspect}, found in pool"
+        end
+
+        # Release is a no-op if this resource is already available.
+        #return if available?(resource)
+        @busy.delete(resource.object_id)
+        @available[resource.object_id] = resource
+
+        # Notify any threads waiting on a resource from the pool.
+        @full_lock.synchronize { @full_cv.signal }
+      end
+    end # def release
+
+    public(:add, :remove, :fetch, :release, :sized?, :count, :initialize)
+  end # class Pool
+end # module Stud
diff --git a/lib/stud/secret.rb b/lib/stud/secret.rb
new file mode 100644
index 0000000..d282b16
--- /dev/null
+++ b/lib/stud/secret.rb
@@ -0,0 +1,35 @@
+
+# A class for holding a secret. The main goal is to prevent the common mistake
+# of accidentally logging or printing passwords or other secrets.
+#
+# See
+# <https://github.com/jordansissel/software-patterns/blob/master/dont-log-secrets/ruby/>
+# for a discussion of why this implementation is useful.
+module Stud
+  class Secret
+    # Initialize a new secret with a given value.
+    #
+    # value - anything you want to keep secret from loggers, etc.
+    def initialize(secret_value)
+      # Redefine the 'value' method on this instance. This exposes no instance
+      # variables to be accidentally leaked by things like awesome_print, etc.
+      # This makes any #value call return the secret value.
+      (class << self; self; end).class_eval do
+        define_method(:value) { secret_value }
+      end
+    end # def initialize
+
+    # Emit simply "<secret>" when printed or logged.
+    def to_s
+      return "<secret>"
+    end # def to_s
+
+    alias_method :inspect, :to_s
+
+    # Get the secret value.
+    def value
+      # Nothing, this will be filled in by Secret.new
+      # But we'll still document this so rdoc/yard know the method exists.
+    end # def value
+  end # class Secret
+end # class Stud
diff --git a/lib/stud/task.rb b/lib/stud/task.rb
new file mode 100644
index 0000000..62c9b6d
--- /dev/null
+++ b/lib/stud/task.rb
@@ -0,0 +1,32 @@
+require "thread"
+
+module Stud
+  class Task
+    def initialize(*args, &block)
+      # A queue to receive the result of the block
+      # TODO(sissel): Don't use a queue, just store it in an instance variable.
+      @queue = Queue.new
+
+      @thread = Thread.new(@queue, *args) do |queue, *args|
+        begin
+          result = block.call(*args)
+          queue << [:return, result]
+        rescue => e
+          queue << [:exception, e]
+        end
+      end # thread
+    end # def initialize
+
+    def wait
+      @thread.join
+      reason, result = @queue.pop
+
+      if reason == :exception
+        #raise StandardError.new(result)
+        raise result
+      else
+        return result
+      end
+    end # def wait
+  end # class Task
+end # module Stud
diff --git a/lib/stud/temporary.rb b/lib/stud/temporary.rb
new file mode 100644
index 0000000..f34cd06
--- /dev/null
+++ b/lib/stud/temporary.rb
@@ -0,0 +1,62 @@
+require "securerandom" # for uuid generation
+require "fileutils"
+
+module Stud
+  module Temporary
+    DEFAULT_PREFIX = "studtmp"
+
+    # Returns a string for a randomly-generated temporary path.
+    #
+    # This does not create any files.
+    def pathname(prefix=DEFAULT_PREFIX)
+
+      root = ENV["TMP"] || ENV["TMPDIR"] || ENV["TEMP"] || "/tmp"
+      return File.join(root, "#{prefix}-#{SecureRandom.uuid}")
+    end
+
+    # Return a File handle to a randomly-generated path.
+    #
+    # Any arguments beyond the first (prefix) argument will be
+    # given to File.new.
+    #
+    # If no file args are given, the default file mode is "w+"
+    def file(prefix=DEFAULT_PREFIX, *args, &block)
+      args << "w+" if args.empty?
+      file = File.new(pathname(prefix), *args)
+      if block_given?
+        begin
+          block.call(file)
+        ensure
+          File.unlink(file.path)
+        end
+      else
+        return file
+      end
+    end
+
+    # Make a temporary directory.
+    #
+    # If given a block, the directory path is given to the block.  WHen the
+    # block finishes, the directory and all its contents will be deleted.
+    #
+    # If no block given, it will return the path to a newly created directory.
+    # You are responsible for then cleaning up.
+    def directory(prefix=DEFAULT_PREFIX, &block)
+      path = pathname(prefix)
+      Dir.mkdir(path)
+
+      if block_given?
+        begin
+          block.call(path)
+        ensure
+          FileUtils.rm_r(path)
+        end
+      else
+        return path
+      end
+    end
+    extend self
+  end # module Temporary
+
+end # module Stud
+
diff --git a/lib/stud/time.rb b/lib/stud/time.rb
new file mode 100644
index 0000000..3afc2a0
--- /dev/null
+++ b/lib/stud/time.rb
@@ -0,0 +1,37 @@
+module Stud
+  module Time
+    # The following table is copied from joda-time's docs
+    # http://joda-time.sourceforge.net/apidocs/org/joda/time/format/DateTimeFormat.html
+    # Symbol  Meaning                      Presentation  Examples
+    # ------  -------                      ------------  -------
+    # G       era                          text          AD
+    # C       century of era (>=0)         number        20
+    # Y       year of era (>=0)            year          1996
+
+    # x       weekyear                     year          1996
+    # w       week of weekyear             number        27
+    # e       day of week                  number        2
+    # E       day of week                  text          Tuesday; Tue
+
+    # y       year                         year          1996
+    # D       day of year                  number        189
+    # M       month of year                month         July; Jul; 07
+    # d       day of month                 number        10
+
+    # a       halfday of day               text          PM
+    # K       hour of halfday (0~11)       number        0
+    # h       clockhour of halfday (1~12)  number        12
+
+    # H       hour of day (0~23)           number        0
+    # k       clockhour of day (1~24)      number        24
+    # m       minute of hour               number        30
+    # s       second of minute             number        55
+    # S       fraction of second           number        978
+
+    # z       time zone                    text          Pacific Standard Time; PST
+    # Z       time zone offset/id          zone          -0800; -08:00; America/Los_Angeles
+
+    # '       escape for text              delimiter
+    # ''      single quote                 literal       '
+  end
+end
diff --git a/lib/stud/time/format.rb b/lib/stud/time/format.rb
new file mode 100644
index 0000000..cd4514c
--- /dev/null
+++ b/lib/stud/time/format.rb
@@ -0,0 +1,1323 @@
+# Autogenerated from a Treetop grammar. Edits may be lost.
+
+
+module StudTimeFormat
+  include Treetop::Runtime
+
+  def root
+    @root ||= :format
+  end
+
+  module Format0
+    def compile
+      return %Q<lambda do |t|
+        return [ 
+          #{elements.collect(&:compile).join(",\n          ")} 
+        ].join("")
+      end>
+    end
+  end
+
+  def _nt_format
+    start_index = index
+    if node_cache[:format].has_key?(index)
+      cached = node_cache[:format][index]
+      if cached
+        cached = SyntaxNode.new(input, index...(index + 1)) if cached == true
+        @index = cached.interval.end
+      end
+      return cached
+    end
+
+    s0, i0 = [], index
+    loop do
+      i1 = index
+      r2 = _nt_time
+      if r2
+        r1 = r2
+      else
+        r3 = _nt_text
+        if r3
+          r1 = r3
+        else
+          @index = i1
+          r1 = nil
+        end
+      end
+      if r1
+        s0 << r1
+      else
+        break
+      end
+    end
+    r0 = instantiate_node(SyntaxNode,input, i0...index, s0)
+    r0.extend(Format0)
+
+    node_cache[:format][start_index] = r0
+
+    r0
+  end
+
+  def _nt_time
+    start_index = index
+    if node_cache[:time].has_key?(index)
+      cached = node_cache[:time][index]
+      if cached
+        cached = SyntaxNode.new(input, index...(index + 1)) if cached == true
+        @index = cached.interval.end
+      end
+      return cached
+    end
+
+    i0 = index
+    r1 = _nt_era
+    if r1
+      r0 = r1
+    else
+      r2 = _nt_century_of_era
+      if r2
+        r0 = r2
+      else
+        r3 = _nt_year_of_era
+        if r3
+          r0 = r3
+        else
+          r4 = _nt_weekyear
+          if r4
+            r0 = r4
+          else
+            r5 = _nt_week_of_weekyear
+            if r5
+              r0 = r5
+            else
+              r6 = _nt_day_of_week_number
+              if r6
+                r0 = r6
+              else
+                r7 = _nt_day_of_week_text
+                if r7
+                  r0 = r7
+                else
+                  r8 = _nt_year
+                  if r8
+                    r0 = r8
+                  else
+                    r9 = _nt_day_of_year
+                    if r9
+                      r0 = r9
+                    else
+                      r10 = _nt_month_of_year
+                      if r10
+                        r0 = r10
+                      else
+                        r11 = _nt_day_of_month
+                        if r11
+                          r0 = r11
+                        else
+                          r12 = _nt_halfday_of_day
+                          if r12
+                            r0 = r12
+                          else
+                            r13 = _nt_hour_of_halfday
+                            if r13
+                              r0 = r13
+                            else
+                              r14 = _nt_clockhour_of_halfday
+                              if r14
+                                r0 = r14
+                              else
+                                r15 = _nt_hour_of_day
+                                if r15
+                                  r0 = r15
+                                else
+                                  r16 = _nt_clockhour_of_day
+                                  if r16
+                                    r0 = r16
+                                  else
+                                    r17 = _nt_minute_of_hour
+                                    if r17
+                                      r0 = r17
+                                    else
+                                      r18 = _nt_second_of_minute
+                                      if r18
+                                        r0 = r18
+                                      else
+                                        r19 = _nt_fraction_of_second
+                                        if r19
+                                          r0 = r19
+                                        else
+                                          r20 = _nt_time_zone_text
+                                          if r20
+                                            r0 = r20
+                                          else
+                                            r21 = _nt_time_zone_offset_or_id
+                                            if r21
+                                              r0 = r21
+                                            else
+                                              @index = i0
+                                              r0 = nil
+                                            end
+                                          end
+                                        end
+                                      end
+                                    end
+                                  end
+                                end
+                              end
+                            end
+                          end
+                        end
+                      end
+                    end
+                  end
+                end
+              end
+            end
+          end
+        end
+      end
+    end
+
+    node_cache[:time][start_index] = r0
+
+    r0
+  end
+
+  def _nt_text
+    start_index = index
+    if node_cache[:text].has_key?(index)
+      cached = node_cache[:text][index]
+      if cached
+        cached = SyntaxNode.new(input, index...(index + 1)) if cached == true
+        @index = cached.interval.end
+      end
+      return cached
+    end
+
+    i0 = index
+    r1 = _nt_string
+    if r1
+      r0 = r1
+    else
+      r2 = _nt_symbol
+      if r2
+        r0 = r2
+      else
+        @index = i0
+        r0 = nil
+      end
+    end
+
+    node_cache[:text][start_index] = r0
+
+    r0
+  end
+
+  module String0
+    def content
+      elements[1]
+    end
+
+  end
+
+  module String1
+    def compile
+      if content.text_value.length == 0
+        return %Q<"'">
+      else
+        return %Q<"#{content.text_value}">
+      end
+    end
+  end
+
+  def _nt_string
+    start_index = index
+    if node_cache[:string].has_key?(index)
+      cached = node_cache[:string][index]
+      if cached
+        cached = SyntaxNode.new(input, index...(index + 1)) if cached == true
+        @index = cached.interval.end
+      end
+      return cached
+    end
+
+    i0, s0 = index, []
+    if has_terminal?("'", false, index)
+      r1 = instantiate_node(SyntaxNode,input, index...(index + 1))
+      @index += 1
+    else
+      terminal_parse_failure("'")
+      r1 = nil
+    end
+    s0 << r1
+    if r1
+      s2, i2 = [], index
+      loop do
+        if has_terminal?('\G[^\']', true, index)
+          r3 = true
+          @index += 1
+        else
+          r3 = nil
+        end
+        if r3
+          s2 << r3
+        else
+          break
+        end
+      end
+      r2 = instantiate_node(SyntaxNode,input, i2...index, s2)
+      s0 << r2
+      if r2
+        if has_terminal?("'", false, index)
+          r4 = instantiate_node(SyntaxNode,input, index...(index + 1))
+          @index += 1
+        else
+          terminal_parse_failure("'")
+          r4 = nil
+        end
+        s0 << r4
+      end
+    end
+    if s0.last
+      r0 = instantiate_node(SyntaxNode,input, i0...index, s0)
+      r0.extend(String0)
+      r0.extend(String1)
+    else
+      @index = i0
+      r0 = nil
+    end
+
+    node_cache[:string][start_index] = r0
+
+    r0
+  end
+
+  module Symbol0
+    def compile
+      return %<"#{text_value}">
+    end
+  end
+
+  def _nt_symbol
+    start_index = index
+    if node_cache[:symbol].has_key?(index)
+      cached = node_cache[:symbol][index]
+      if cached
+        cached = SyntaxNode.new(input, index...(index + 1)) if cached == true
+        @index = cached.interval.end
+      end
+      return cached
+    end
+
+    s0, i0 = [], index
+    loop do
+      if has_terminal?('\G[^A-Za-z\']', true, index)
+        r1 = true
+        @index += 1
+      else
+        r1 = nil
+      end
+      if r1
+        s0 << r1
+      else
+        break
+      end
+    end
+    if s0.empty?
+      @index = i0
+      r0 = nil
+    else
+      r0 = instantiate_node(SyntaxNode,input, i0...index, s0)
+      r0.extend(Symbol0)
+    end
+
+    node_cache[:symbol][start_index] = r0
+
+    r0
+  end
+
+  module QuoteChar0
+    def compile
+      return %Q("'")
+    end
+  end
+
+  def _nt_quote_char
+    start_index = index
+    if node_cache[:quote_char].has_key?(index)
+      cached = node_cache[:quote_char][index]
+      if cached
+        cached = SyntaxNode.new(input, index...(index + 1)) if cached == true
+        @index = cached.interval.end
+      end
+      return cached
+    end
+
+    if has_terminal?("''", false, index)
+      r0 = instantiate_node(SyntaxNode,input, index...(index + 2))
+      r0.extend(QuoteChar0)
+      @index += 2
+    else
+      terminal_parse_failure("''")
+      r0 = nil
+    end
+
+    node_cache[:quote_char][start_index] = r0
+
+    r0
+  end
+
+  def _nt_era
+    start_index = index
+    if node_cache[:era].has_key?(index)
+      cached = node_cache[:era][index]
+      if cached
+        cached = SyntaxNode.new(input, index...(index + 1)) if cached == true
+        @index = cached.interval.end
+      end
+      return cached
+    end
+
+    s0, i0 = [], index
+    loop do
+      if has_terminal?("G", false, index)
+        r1 = instantiate_node(SyntaxNode,input, index...(index + 1))
+        @index += 1
+      else
+        terminal_parse_failure("G")
+        r1 = nil
+      end
+      if r1
+        s0 << r1
+      else
+        break
+      end
+    end
+    if s0.empty?
+      @index = i0
+      r0 = nil
+    else
+      r0 = instantiate_node(SyntaxNode,input, i0...index, s0)
+    end
+
+    node_cache[:era][start_index] = r0
+
+    r0
+  end
+
+  module CenturyOfEra0
+    def compile
+      # TODO(sissel): support eras? I don't really care myself.
+      return "AD"
+    end
+  end
+
+  def _nt_century_of_era
+    start_index = index
+    if node_cache[:century_of_era].has_key?(index)
+      cached = node_cache[:century_of_era][index]
+      if cached
+        cached = SyntaxNode.new(input, index...(index + 1)) if cached == true
+        @index = cached.interval.end
+      end
+      return cached
+    end
+
+    s0, i0 = [], index
+    loop do
+      if has_terminal?("C", false, index)
+        r1 = instantiate_node(SyntaxNode,input, index...(index + 1))
+        @index += 1
+      else
+        terminal_parse_failure("C")
+        r1 = nil
+      end
+      if r1
+        s0 << r1
+      else
+        break
+      end
+    end
+    if s0.empty?
+      @index = i0
+      r0 = nil
+    else
+      r0 = instantiate_node(SyntaxNode,input, i0...index, s0)
+      r0.extend(CenturyOfEra0)
+    end
+
+    node_cache[:century_of_era][start_index] = r0
+
+    r0
+  end
+
+  module YearOfEra0
+    def compile
+      # No 'era' support, so year is just year.
+      return case text_value.length
+        when 2 then "t.year % 100"
+        else "t.year"
+      end
+    end
+  end
+
+  def _nt_year_of_era
+    start_index = index
+    if node_cache[:year_of_era].has_key?(index)
+      cached = node_cache[:year_of_era][index]
+      if cached
+        cached = SyntaxNode.new(input, index...(index + 1)) if cached == true
+        @index = cached.interval.end
+      end
+      return cached
+    end
+
+    i0 = index
+    s1, i1 = [], index
+    loop do
+      if has_terminal?("Y", false, index)
+        r2 = instantiate_node(SyntaxNode,input, index...(index + 1))
+        @index += 1
+      else
+        terminal_parse_failure("Y")
+        r2 = nil
+      end
+      if r2
+        s1 << r2
+      else
+        break
+      end
+    end
+    if s1.empty?
+      @index = i1
+      r1 = nil
+    else
+      r1 = instantiate_node(SyntaxNode,input, i1...index, s1)
+    end
+    if r1
+      r0 = r1
+      r0.extend(YearOfEra0)
+    else
+      r3 = _nt_weekyear
+      if r3
+        r0 = r3
+        r0.extend(YearOfEra0)
+      else
+        r4 = _nt_year
+        if r4
+          r0 = r4
+          r0.extend(YearOfEra0)
+        else
+          @index = i0
+          r0 = nil
+        end
+      end
+    end
+
+    node_cache[:year_of_era][start_index] = r0
+
+    r0
+  end
+
+  def _nt_weekyear
+    start_index = index
+    if node_cache[:weekyear].has_key?(index)
+      cached = node_cache[:weekyear][index]
+      if cached
+        cached = SyntaxNode.new(input, index...(index + 1)) if cached == true
+        @index = cached.interval.end
+      end
+      return cached
+    end
+
+    s0, i0 = [], index
+    loop do
+      if has_terminal?("x", false, index)
+        r1 = instantiate_node(SyntaxNode,input, index...(index + 1))
+        @index += 1
+      else
+        terminal_parse_failure("x")
+        r1 = nil
+      end
+      if r1
+        s0 << r1
+      else
+        break
+      end
+    end
+    if s0.empty?
+      @index = i0
+      r0 = nil
+    else
+      r0 = instantiate_node(SyntaxNode,input, i0...index, s0)
+    end
+
+    node_cache[:weekyear][start_index] = r0
+
+    r0
+  end
+
+  module WeekOfWeekyear0
+    def compile
+      return "(t.yday / 7) + 1"
+    end
+  end
+
+  def _nt_week_of_weekyear
+    start_index = index
+    if node_cache[:week_of_weekyear].has_key?(index)
+      cached = node_cache[:week_of_weekyear][index]
+      if cached
+        cached = SyntaxNode.new(input, index...(index + 1)) if cached == true
+        @index = cached.interval.end
+      end
+      return cached
+    end
+
+    s0, i0 = [], index
+    loop do
+      if has_terminal?("w", false, index)
+        r1 = instantiate_node(SyntaxNode,input, index...(index + 1))
+        @index += 1
+      else
+        terminal_parse_failure("w")
+        r1 = nil
+      end
+      if r1
+        s0 << r1
+      else
+        break
+      end
+    end
+    if s0.empty?
+      @index = i0
+      r0 = nil
+    else
+      r0 = instantiate_node(SyntaxNode,input, i0...index, s0)
+      r0.extend(WeekOfWeekyear0)
+    end
+
+    node_cache[:week_of_weekyear][start_index] = r0
+
+    r0
+  end
+
+  module DayOfWeekNumber0
+    def compile
+      return "t.wday"
+    end
+  end
+
+  def _nt_day_of_week_number
+    start_index = index
+    if node_cache[:day_of_week_number].has_key?(index)
+      cached = node_cache[:day_of_week_number][index]
+      if cached
+        cached = SyntaxNode.new(input, index...(index + 1)) if cached == true
+        @index = cached.interval.end
+      end
+      return cached
+    end
+
+    s0, i0 = [], index
+    loop do
+      if has_terminal?("e", false, index)
+        r1 = instantiate_node(SyntaxNode,input, index...(index + 1))
+        @index += 1
+      else
+        terminal_parse_failure("e")
+        r1 = nil
+      end
+      if r1
+        s0 << r1
+      else
+        break
+      end
+    end
+    if s0.empty?
+      @index = i0
+      r0 = nil
+    else
+      r0 = instantiate_node(SyntaxNode,input, i0...index, s0)
+      r0.extend(DayOfWeekNumber0)
+    end
+
+    node_cache[:day_of_week_number][start_index] = r0
+
+    r0
+  end
+
+  module DayOfWeekText0
+    def compile
+      if text_value.length < 4
+        return %Q<t.strftime("%a")> 
+      end
+      return %Q<t.strftime("%A")>
+    end
+  end
+
+  def _nt_day_of_week_text
+    start_index = index
+    if node_cache[:day_of_week_text].has_key?(index)
+      cached = node_cache[:day_of_week_text][index]
+      if cached
+        cached = SyntaxNode.new(input, index...(index + 1)) if cached == true
+        @index = cached.interval.end
+      end
+      return cached
+    end
+
+    s0, i0 = [], index
+    loop do
+      if has_terminal?("E", false, index)
+        r1 = instantiate_node(SyntaxNode,input, index...(index + 1))
+        @index += 1
+      else
+        terminal_parse_failure("E")
+        r1 = nil
+      end
+      if r1
+        s0 << r1
+      else
+        break
+      end
+    end
+    if s0.empty?
+      @index = i0
+      r0 = nil
+    else
+      r0 = instantiate_node(SyntaxNode,input, i0...index, s0)
+      r0.extend(DayOfWeekText0)
+    end
+
+    node_cache[:day_of_week_text][start_index] = r0
+
+    r0
+  end
+
+  def _nt_year
+    start_index = index
+    if node_cache[:year].has_key?(index)
+      cached = node_cache[:year][index]
+      if cached
+        cached = SyntaxNode.new(input, index...(index + 1)) if cached == true
+        @index = cached.interval.end
+      end
+      return cached
+    end
+
+    s0, i0 = [], index
+    loop do
+      if has_terminal?("y", false, index)
+        r1 = instantiate_node(SyntaxNode,input, index...(index + 1))
+        @index += 1
+      else
+        terminal_parse_failure("y")
+        r1 = nil
+      end
+      if r1
+        s0 << r1
+      else
+        break
+      end
+    end
+    if s0.empty?
+      @index = i0
+      r0 = nil
+    else
+      r0 = instantiate_node(SyntaxNode,input, i0...index, s0)
+    end
+
+    node_cache[:year][start_index] = r0
+
+    r0
+  end
+
+  module DayOfYear0
+    def compile
+      return %Q<t.yday>
+    end
+  end
+
+  def _nt_day_of_year
+    start_index = index
+    if node_cache[:day_of_year].has_key?(index)
+      cached = node_cache[:day_of_year][index]
+      if cached
+        cached = SyntaxNode.new(input, index...(index + 1)) if cached == true
+        @index = cached.interval.end
+      end
+      return cached
+    end
+
+    s0, i0 = [], index
+    loop do
+      if has_terminal?("D", false, index)
+        r1 = instantiate_node(SyntaxNode,input, index...(index + 1))
+        @index += 1
+      else
+        terminal_parse_failure("D")
+        r1 = nil
+      end
+      if r1
+        s0 << r1
+      else
+        break
+      end
+    end
+    if s0.empty?
+      @index = i0
+      r0 = nil
+    else
+      r0 = instantiate_node(SyntaxNode,input, i0...index, s0)
+      r0.extend(DayOfYear0)
+    end
+
+    node_cache[:day_of_year][start_index] = r0
+
+    r0
+  end
+
+  module MonthOfYear0
+    def compile
+      return case text_value.length
+        when 1..2 then %Q<sprintf("%0#{text_value.length}d", t.month)>
+        when 3 then %Q<t.strftime("%b")>
+        else %Q<t.strftime("%B")>
+      end
+    end
+  end
+
+  def _nt_month_of_year
+    start_index = index
+    if node_cache[:month_of_year].has_key?(index)
+      cached = node_cache[:month_of_year][index]
+      if cached
+        cached = SyntaxNode.new(input, index...(index + 1)) if cached == true
+        @index = cached.interval.end
+      end
+      return cached
+    end
+
+    s0, i0 = [], index
+    loop do
+      if has_terminal?("M", false, index)
+        r1 = instantiate_node(SyntaxNode,input, index...(index + 1))
+        @index += 1
+      else
+        terminal_parse_failure("M")
+        r1 = nil
+      end
+      if r1
+        s0 << r1
+      else
+        break
+      end
+    end
+    if s0.empty?
+      @index = i0
+      r0 = nil
+    else
+      r0 = instantiate_node(SyntaxNode,input, i0...index, s0)
+      r0.extend(MonthOfYear0)
+    end
+
+    node_cache[:month_of_year][start_index] = r0
+
+    r0
+  end
+
+  module DayOfMonth0
+    def compile
+      return %Q<t.strftime("%0#{text_value.length}d")>
+    end
+  end
+
+  def _nt_day_of_month
+    start_index = index
+    if node_cache[:day_of_month].has_key?(index)
+      cached = node_cache[:day_of_month][index]
+      if cached
+        cached = SyntaxNode.new(input, index...(index + 1)) if cached == true
+        @index = cached.interval.end
+      end
+      return cached
+    end
+
+    s0, i0 = [], index
+    loop do
+      if has_terminal?("d", false, index)
+        r1 = instantiate_node(SyntaxNode,input, index...(index + 1))
+        @index += 1
+      else
+        terminal_parse_failure("d")
+        r1 = nil
+      end
+      if r1
+        s0 << r1
+      else
+        break
+      end
+    end
+    if s0.empty?
+      @index = i0
+      r0 = nil
+    else
+      r0 = instantiate_node(SyntaxNode,input, i0...index, s0)
+      r0.extend(DayOfMonth0)
+    end
+
+    node_cache[:day_of_month][start_index] = r0
+
+    r0
+  end
+
+  module HalfdayOfDay0
+    def compile
+      return %Q<t.strftime("%p")>
+    end
+  end
+
+  def _nt_halfday_of_day
+    start_index = index
+    if node_cache[:halfday_of_day].has_key?(index)
+      cached = node_cache[:halfday_of_day][index]
+      if cached
+        cached = SyntaxNode.new(input, index...(index + 1)) if cached == true
+        @index = cached.interval.end
+      end
+      return cached
+    end
+
+    s0, i0 = [], index
+    loop do
+      if has_terminal?("a", false, index)
+        r1 = instantiate_node(SyntaxNode,input, index...(index + 1))
+        @index += 1
+      else
+        terminal_parse_failure("a")
+        r1 = nil
+      end
+      if r1
+        s0 << r1
+      else
+        break
+      end
+    end
+    if s0.empty?
+      @index = i0
+      r0 = nil
+    else
+      r0 = instantiate_node(SyntaxNode,input, i0...index, s0)
+      r0.extend(HalfdayOfDay0)
+    end
+
+    node_cache[:halfday_of_day][start_index] = r0
+
+    r0
+  end
+
+  module HourOfHalfday0
+    def compile
+      return %Q<t.strftime("%I")>
+    end
+  end
+
+  def _nt_hour_of_halfday
+    start_index = index
+    if node_cache[:hour_of_halfday].has_key?(index)
+      cached = node_cache[:hour_of_halfday][index]
+      if cached
+        cached = SyntaxNode.new(input, index...(index + 1)) if cached == true
+        @index = cached.interval.end
+      end
+      return cached
+    end
+
+    s0, i0 = [], index
+    loop do
+      if has_terminal?("K", false, index)
+        r1 = instantiate_node(SyntaxNode,input, index...(index + 1))
+        @index += 1
+      else
+        terminal_parse_failure("K")
+        r1 = nil
+      end
+      if r1
+        s0 << r1
+      else
+        break
+      end
+    end
+    if s0.empty?
+      @index = i0
+      r0 = nil
+    else
+      r0 = instantiate_node(SyntaxNode,input, i0...index, s0)
+      r0.extend(HourOfHalfday0)
+    end
+
+    node_cache[:hour_of_halfday][start_index] = r0
+
+    r0
+  end
+
+  module ClockhourOfHalfday0
+    def compile
+      return %Q<sprintf("%0#{text_value.length}d", t.hour / 12)>
+    end
+  end
+
+  def _nt_clockhour_of_halfday
+    start_index = index
+    if node_cache[:clockhour_of_halfday].has_key?(index)
+      cached = node_cache[:clockhour_of_halfday][index]
+      if cached
+        cached = SyntaxNode.new(input, index...(index + 1)) if cached == true
+        @index = cached.interval.end
+      end
+      return cached
+    end
+
+    s0, i0 = [], index
+    loop do
+      if has_terminal?("h", false, index)
+        r1 = instantiate_node(SyntaxNode,input, index...(index + 1))
+        @index += 1
+      else
+        terminal_parse_failure("h")
+        r1 = nil
+      end
+      if r1
+        s0 << r1
+      else
+        break
+      end
+    end
+    if s0.empty?
+      @index = i0
+      r0 = nil
+    else
+      r0 = instantiate_node(SyntaxNode,input, i0...index, s0)
+      r0.extend(ClockhourOfHalfday0)
+    end
+
+    node_cache[:clockhour_of_halfday][start_index] = r0
+
+    r0
+  end
+
+  module HourOfDay0
+    def compile
+      return %Q<sprintf("%0#{text_value.length}d", t.hour)>
+    end
+  end
+
+  def _nt_hour_of_day
+    start_index = index
+    if node_cache[:hour_of_day].has_key?(index)
+      cached = node_cache[:hour_of_day][index]
+      if cached
+        cached = SyntaxNode.new(input, index...(index + 1)) if cached == true
+        @index = cached.interval.end
+      end
+      return cached
+    end
+
+    s0, i0 = [], index
+    loop do
+      if has_terminal?("H", false, index)
+        r1 = instantiate_node(SyntaxNode,input, index...(index + 1))
+        @index += 1
+      else
+        terminal_parse_failure("H")
+        r1 = nil
+      end
+      if r1
+        s0 << r1
+      else
+        break
+      end
+    end
+    if s0.empty?
+      @index = i0
+      r0 = nil
+    else
+      r0 = instantiate_node(SyntaxNode,input, i0...index, s0)
+      r0.extend(HourOfDay0)
+    end
+
+    node_cache[:hour_of_day][start_index] = r0
+
+    r0
+  end
+
+  module ClockhourOfDay0
+    def compile
+      return %Q<sprintf("%0#{text_value.length}d", t.hour + 1)>
+    end
+  end
+
+  def _nt_clockhour_of_day
+    start_index = index
+    if node_cache[:clockhour_of_day].has_key?(index)
+      cached = node_cache[:clockhour_of_day][index]
+      if cached
+        cached = SyntaxNode.new(input, index...(index + 1)) if cached == true
+        @index = cached.interval.end
+      end
+      return cached
+    end
+
+    s0, i0 = [], index
+    loop do
+      if has_terminal?("k", false, index)
+        r1 = instantiate_node(SyntaxNode,input, index...(index + 1))
+        @index += 1
+      else
+        terminal_parse_failure("k")
+        r1 = nil
+      end
+      if r1
+        s0 << r1
+      else
+        break
+      end
+    end
+    if s0.empty?
+      @index = i0
+      r0 = nil
+    else
+      r0 = instantiate_node(SyntaxNode,input, i0...index, s0)
+      r0.extend(ClockhourOfDay0)
+    end
+
+    node_cache[:clockhour_of_day][start_index] = r0
+
+    r0
+  end
+
+  module MinuteOfHour0
+    def compile
+      return %Q<sprintf("%0#{text_value.length}d", t.min)>
+    end
+  end
+
+  def _nt_minute_of_hour
+    start_index = index
+    if node_cache[:minute_of_hour].has_key?(index)
+      cached = node_cache[:minute_of_hour][index]
+      if cached
+        cached = SyntaxNode.new(input, index...(index + 1)) if cached == true
+        @index = cached.interval.end
+      end
+      return cached
+    end
+
+    s0, i0 = [], index
+    loop do
+      if has_terminal?("m", false, index)
+        r1 = instantiate_node(SyntaxNode,input, index...(index + 1))
+        @index += 1
+      else
+        terminal_parse_failure("m")
+        r1 = nil
+      end
+      if r1
+        s0 << r1
+      else
+        break
+      end
+    end
+    if s0.empty?
+      @index = i0
+      r0 = nil
+    else
+      r0 = instantiate_node(SyntaxNode,input, i0...index, s0)
+      r0.extend(MinuteOfHour0)
+    end
+
+    node_cache[:minute_of_hour][start_index] = r0
+
+    r0
+  end
+
+  module SecondOfMinute0
+    def compile
+      return %Q<sprintf("%0#{text_value.length}d", t.sec)>
+    end
+  end
+
+  def _nt_second_of_minute
+    start_index = index
+    if node_cache[:second_of_minute].has_key?(index)
+      cached = node_cache[:second_of_minute][index]
+      if cached
+        cached = SyntaxNode.new(input, index...(index + 1)) if cached == true
+        @index = cached.interval.end
+      end
+      return cached
+    end
+
+    s0, i0 = [], index
+    loop do
+      if has_terminal?("s", false, index)
+        r1 = instantiate_node(SyntaxNode,input, index...(index + 1))
+        @index += 1
+      else
+        terminal_parse_failure("s")
+        r1 = nil
+      end
+      if r1
+        s0 << r1
+      else
+        break
+      end
+    end
+    if s0.empty?
+      @index = i0
+      r0 = nil
+    else
+      r0 = instantiate_node(SyntaxNode,input, i0...index, s0)
+      r0.extend(SecondOfMinute0)
+    end
+
+    node_cache[:second_of_minute][start_index] = r0
+
+    r0
+  end
+
+  module FractionOfSecond0
+    def compile
+      return %Q<sprintf("%0#{text_value.length}d", t.nsec / (10 ** (9 - #{text_value.length})))>
+    end
+  end
+
+  def _nt_fraction_of_second
+    start_index = index
+    if node_cache[:fraction_of_second].has_key?(index)
+      cached = node_cache[:fraction_of_second][index]
+      if cached
+        cached = SyntaxNode.new(input, index...(index + 1)) if cached == true
+        @index = cached.interval.end
+      end
+      return cached
+    end
+
+    s0, i0 = [], index
+    loop do
+      if has_terminal?("S", false, index)
+        r1 = instantiate_node(SyntaxNode,input, index...(index + 1))
+        @index += 1
+      else
+        terminal_parse_failure("S")
+        r1 = nil
+      end
+      if r1
+        s0 << r1
+      else
+        break
+      end
+    end
+    if s0.empty?
+      @index = i0
+      r0 = nil
+    else
+      r0 = instantiate_node(SyntaxNode,input, i0...index, s0)
+      r0.extend(FractionOfSecond0)
+    end
+
+    node_cache[:fraction_of_second][start_index] = r0
+
+    r0
+  end
+
+  def _nt_time_zone_text
+    start_index = index
+    if node_cache[:time_zone_text].has_key?(index)
+      cached = node_cache[:time_zone_text][index]
+      if cached
+        cached = SyntaxNode.new(input, index...(index + 1)) if cached == true
+        @index = cached.interval.end
+      end
+      return cached
+    end
+
+    s0, i0 = [], index
+    loop do
+      if has_terminal?("z", false, index)
+        r1 = instantiate_node(SyntaxNode,input, index...(index + 1))
+        @index += 1
+      else
+        terminal_parse_failure("z")
+        r1 = nil
+      end
+      if r1
+        s0 << r1
+      else
+        break
+      end
+    end
+    if s0.empty?
+      @index = i0
+      r0 = nil
+    else
+      r0 = instantiate_node(SyntaxNode,input, i0...index, s0)
+    end
+
+    node_cache[:time_zone_text][start_index] = r0
+
+    r0
+  end
+
+  module TimeZoneOffsetOrId0
+    def compile
+      return %Q<sprintf("%+05d", t.gmtoff / 36)>
+    end
+  end
+
+  def _nt_time_zone_offset_or_id
+    start_index = index
+    if node_cache[:time_zone_offset_or_id].has_key?(index)
+      cached = node_cache[:time_zone_offset_or_id][index]
+      if cached
+        cached = SyntaxNode.new(input, index...(index + 1)) if cached == true
+        @index = cached.interval.end
+      end
+      return cached
+    end
+
+    s0, i0 = [], index
+    loop do
+      if has_terminal?("Z", false, index)
+        r1 = instantiate_node(SyntaxNode,input, index...(index + 1))
+        @index += 1
+      else
+        terminal_parse_failure("Z")
+        r1 = nil
+      end
+      if r1
+        s0 << r1
+      else
+        break
+      end
+    end
+    if s0.empty?
+      @index = i0
+      r0 = nil
+    else
+      r0 = instantiate_node(SyntaxNode,input, i0...index, s0)
+      r0.extend(TimeZoneOffsetOrId0)
+    end
+
+    node_cache[:time_zone_offset_or_id][start_index] = r0
+
+    r0
+  end
+
+end
+
+class StudTimeFormatParser < Treetop::Runtime::CompiledParser
+  include StudTimeFormat
+end
+
diff --git a/lib/stud/time/format.tt b/lib/stud/time/format.tt
new file mode 100644
index 0000000..400deb6
--- /dev/null
+++ b/lib/stud/time/format.tt
@@ -0,0 +1,236 @@
+grammar StudTimeFormat
+  rule format
+    (time / text)* {
+      def compile
+        return %Q<lambda do |t|
+          return [ 
+            #{elements.collect(&:compile).join(",\n          ")} 
+          ].join("")
+        end>
+      end
+    }
+  end
+
+  rule time
+    (
+      era 
+      / century_of_era
+      / year_of_era
+      / weekyear
+      / week_of_weekyear
+      / day_of_week_number
+      / day_of_week_text
+      / year
+      / day_of_year
+      / month_of_year
+      / day_of_month
+      / halfday_of_day
+      / hour_of_halfday
+      / clockhour_of_halfday
+      / hour_of_day
+      / clockhour_of_day
+      / minute_of_hour
+      / second_of_minute
+      / fraction_of_second
+      / time_zone_text
+      / time_zone_offset_or_id
+    )
+  end
+
+  rule text
+    string / symbol
+  end
+
+  rule string
+    "'" content:[^']* "'" {
+      def compile
+        if content.text_value.length == 0
+          return %Q<"'">
+        else
+          return %Q<"#{content.text_value}">
+        end
+      end
+    }
+  end
+
+  rule symbol
+    [^A-Za-z']+ {
+      def compile
+        return %<"#{text_value}">
+      end
+    }
+  end
+
+  rule quote_char
+    # a literal '
+    "''" {
+      def compile
+        return %Q("'")
+      end
+    }
+  end
+
+  rule era 
+    "G"+
+  end
+
+  rule century_of_era 
+    "C"+ {
+      def compile
+        # TODO(sissel): support eras? I don't really care myself.
+        return "AD"
+      end
+    }
+  end
+
+  rule year_of_era
+    ( "Y"+ / weekyear / year ) {
+      def compile
+        # No 'era' support, so year is just year.
+        return case text_value.length
+          when 2 then "t.year % 100"
+          else "t.year"
+        end
+      end
+    }
+  end
+
+  rule weekyear
+    "x"+ # same as 'year_of_era' used above.
+  end
+
+  rule week_of_weekyear
+    "w"+ {
+      def compile
+        return "(t.yday / 7) + 1"
+      end
+    }
+  end
+
+  rule day_of_week_number
+    "e"+ {
+      def compile
+        return "t.wday"
+      end
+    }
+  end
+
+  rule day_of_week_text
+    "E"+ {
+      def compile
+        if text_value.length < 4
+          return %Q<t.strftime("%a")> 
+        end
+        return %Q<t.strftime("%A")>
+      end
+    }
+  end
+
+  rule year
+    "y"+ # same as 'year_of_era' used above.
+  end
+
+  rule day_of_year
+    "D"+ {
+      def compile
+        return %Q<t.yday>
+      end
+    }
+  end
+
+  rule month_of_year
+    "M"+ {
+      def compile
+        return case text_value.length
+          when 1..2 then %Q<sprintf("%0#{text_value.length}d", t.month)>
+          when 3 then %Q<t.strftime("%b")>
+          else %Q<t.strftime("%B")>
+        end
+      end
+    }
+  end
+
+  rule day_of_month
+    "d"+ {
+      def compile
+        return %Q<t.strftime("%0#{text_value.length}d")>
+      end
+    }
+  end
+
+  rule halfday_of_day
+    "a"+ {
+      def compile
+        return %Q<t.strftime("%p")>
+      end
+    }
+  end
+
+  rule hour_of_halfday
+    "K"+ {
+      def compile
+        return %Q<t.strftime("%I")>
+      end
+    }
+  end
+
+  rule clockhour_of_halfday
+    "h"+ {
+      def compile
+        return %Q<sprintf("%0#{text_value.length}d", t.hour / 12)>
+      end
+    }
+  end
+
+  rule hour_of_day
+    "H"+ {
+      def compile
+        return %Q<sprintf("%0#{text_value.length}d", t.hour)>
+      end
+    }
+  end
+
+  rule clockhour_of_day
+    "k"+ {
+      def compile
+        return %Q<sprintf("%0#{text_value.length}d", t.hour + 1)>
+      end
+    }
+  end
+
+  rule minute_of_hour
+    "m"+ {
+      def compile
+        return %Q<sprintf("%0#{text_value.length}d", t.min)>
+      end
+    }
+  end
+
+  rule second_of_minute
+    "s"+ {
+      def compile
+        return %Q<sprintf("%0#{text_value.length}d", t.sec)>
+      end
+    }
+  end
+
+  rule fraction_of_second
+    "S"+ {
+      def compile
+        return %Q<sprintf("%0#{text_value.length}d", t.nsec / (10 ** (9 - #{text_value.length})))>
+      end
+    }
+  end
+
+  rule time_zone_text
+    "z"+
+  end
+
+  rule time_zone_offset_or_id
+    "Z"+ {
+      def compile
+        return %Q<sprintf("%+05d", t.gmtoff / 36)>
+      end
+    }
+  end
+end
diff --git a/lib/stud/trap.rb b/lib/stud/trap.rb
new file mode 100644
index 0000000..a743835
--- /dev/null
+++ b/lib/stud/trap.rb
@@ -0,0 +1,74 @@
+module Stud
+  # Bind a block to be called when a certain signal is received.
+  #
+  # Same arguments to Signal::trap.
+  #
+  # The behavior of this method is different than Signal::trap because
+  # multiple handlers can request notification for the same signal.
+  #
+  # For example, this is valid:
+  #
+  #     Stud.trap("INT") { puts "Hello" }
+  #     Stud.trap("INT") { puts "World" }
+  #
+  # When SIGINT is received, both callbacks will be invoked, in order.
+  #
+  # This helps avoid the situation where a library traps a signal outside of
+  # your control.
+  #
+  # If something has already used Signal::trap, that callback will be saved
+  # and scheduled the same way as any other Stud::trap.
+  def self.trap(signal, &block)
+    @traps ||= Hash.new { |h,k| h[k] = [] }
+
+    if !@traps.include?(signal)
+      # First trap call for this signal, tell ruby to invoke us.
+      previous_trap = Signal::trap(signal) { simulate_signal(signal) }
+      # If there was a previous trap (via Kernel#trap) set, make sure we remember it.
+      if previous_trap.is_a?(Proc)
+        # MRI's default traps are "DEFAULT" string
+        # JRuby's default traps are Procs with a source_location of "(internal")
+        if RUBY_ENGINE != "jruby" || previous_trap.source_location.first != "(internal)"
+          @traps[signal] << previous_trap
+        end
+      end
+    end
+
+    @traps[signal] << block
+
+    return block.object_id
+  end # def self.trap
+
+  # Simulate a signal. This lets you force an interrupt without
+  # sending a signal to yourself.
+  def self.simulate_signal(signal)
+    #puts "Simulate: #{signal} w/ #{@traps[signal].count} callbacks"
+    @traps[signal].each(&:call)
+  end # def self.simulate_signal
+
+  # Remove a previously set signal trap.
+  #
+  # 'signal' is the name of the signal ("INT", etc)
+  # 'id' is the value returned by a previous Stud.trap() call
+  def self.untrap(signal, id)
+    @traps[signal].delete_if { |block| block.object_id == id }
+  end # def self.untrap
+end # module Stud
+
+# Monkey-patch the main 'trap' stuff? This could be useful.
+#module Signal
+  #def trap(signal, value=nil, &block)
+    #if value.nil?
+      #Stud.trap(signal, &block)
+    #else
+      ## do nothing?
+    #end
+  #end # def trap
+#end
+#
+#module Kernel
+  #def trap(signal, value=nil, &block)
+    #Signal.trap(signal, value, &block)
+  #end
+#end
+
diff --git a/lib/stud/try.rb b/lib/stud/try.rb
new file mode 100644
index 0000000..8320839
--- /dev/null
+++ b/lib/stud/try.rb
@@ -0,0 +1,123 @@
+module Stud
+
+  # A class implementing 'retry-on-failure'
+  #
+  # Example:
+  #
+  #     Try.new.try(5.times) { your_code }
+  #
+  # A failure is indicated by any exception being raised.
+  # On success, the return value of the block is the return value of the try
+  # call.
+  #
+  # On final failure (ran out of things to try), the last exception is raised.
+  class Try
+    # An infinite enumerator
+    class Forever
+      include Enumerable
+      def each(&block)
+        a = 0
+        yield a += 1 while true
+      end
+    end # class Forever
+
+    FOREVER = Forever.new
+
+    BACKOFF_SCHEDULE = [0.01, 0.02, 0.04, 0.08, 0.16, 0.32, 0.64, 1.28, 2.0]
+
+    # Log a failure.
+    #
+    # You should override this method if you want a better logger.
+    def log_failure(exception, fail_count, message)
+      puts "Failed (#{exception}). #{message}"
+    end # def log_failure
+
+    # This method is called when a try attempt fails.
+    #
+    # The default implementation will sleep with exponential backoff up to a
+    # maximum of 2 seconds (see BACKOFF_SCHEDULE)
+    #
+    # exception - the exception causing the failure
+    # fail_count - how many times we have failed.
+    def failure(exception, fail_count)
+      backoff = BACKOFF_SCHEDULE[fail_count] || BACKOFF_SCHEDULE.last
+      log_failure(exception, fail_count, "Sleeping for #{backoff}")
+      sleep(backoff)
+    end # def failure
+
+    # Public: try a block of code until either it succeeds or we give up.
+    #
+    # enumerable - an Enumerable or omitted/nil, #each is invoked and is tried
+    #   that number of times. If this value is omitted or nil, we will try until
+    #   success with no limit on the number of tries.
+    #
+    # Returns the return value of the block once the block succeeds.
+    # Raises the last seen exception if we run out of tries.
+    #
+    # Examples
+    #
+    #   # Try 10 times to fetch http://google.com/
+    #   response = try(10.times) { Net::HTTP.get_response("google.com", "/") }
+    #
+    #   # Try many times, yielding the value of the enumeration to the block.
+    #   # This allows you to try different inputs.
+    #   response = try([0, 2, 4, 6]) { |val| 50 / val }
+    #   
+    #   Output: 
+    #   Failed (divided by 0). Retrying in 0.01 seconds...
+    #   => 25
+    #
+    #   # Try forever
+    #   return_value = try { ... }
+    def try(enumerable=FOREVER, &block)
+      if block.arity == 0
+        # If the block takes no arguments, give none
+        procedure = lambda { |val| return block.call }
+      else
+        # Otherwise, pass the current 'enumerable' value to the block.
+        procedure = lambda { |val| return block.call(val) }
+      end
+
+      # Track the last exception so we can reraise it on failure.
+      last_exception = nil
+
+      # When 'enumerable' runs out of things, if we still haven't succeeded,
+      # we'll reraise
+      fail_count = 0
+      enumerable.each do |val|
+        begin
+          # If the 'procedure' (the block, really) succeeds, we'll break 
+          # and return the return value of the block. Win!
+          return procedure.call(val)
+        rescue NoMethodError, NameError
+          # Abort immediately on exceptions that are unlikely to recover.
+          raise
+        rescue => exception
+          last_exception = exception
+          fail_count += 1
+
+          # Note: Since we can't reliably detect the 'end' of an enumeration, we
+          # will call 'failure' for the final iteration (if it failed) and sleep
+          # even though there's no strong reason to backoff on the last error.
+          failure(exception, fail_count)
+        end
+      end # enumerable.each
+
+      # generally make the exception appear from the 'try' method itself, not from
+      # any deeply nested enumeration/begin/etc
+      # It is my hope that this makes the backtraces easier to read, not more
+      # difficult. If you find this is not the case, please please please let me
+      # know.
+      last_exception.set_backtrace(StandardError.new.backtrace)
+      raise last_exception
+    end # def try
+  end # class Stud::Try
+
+  TRY = Try.new
+  # A simple try method for the common case.
+  def try(enumerable=Stud::Try::FOREVER, &block)
+    return TRY.try(enumerable, &block)
+  end # def try
+
+  extend self
+end # module Stud
diff --git a/lib/stud/with.rb b/lib/stud/with.rb
new file mode 100644
index 0000000..83672a5
--- /dev/null
+++ b/lib/stud/with.rb
@@ -0,0 +1,22 @@
+module Stud
+  module With
+    # Run a block with arguments. This is sometimes useful in lieu of
+    # explicitly assigning variables.
+    # 
+    # I find mainly that using 'with' is a clue that I can factor out
+    # a given segment of code into a method or function.
+    #
+    # Example usage:
+    #
+    #   with(TCPSocket.new("google.com", 80)) do |s|
+    #     s.write("GET / HTTP/1.0\r\nHost: google.com\r\n\r\n")
+    #     puts s.read
+    #     s.close
+    #   end
+    def with(*args, &block)
+      block.call(*args)
+    end
+
+    extend self
+  end
+end
diff --git a/metadata.yml b/metadata.yml
new file mode 100644
index 0000000..0c8dfa8
--- /dev/null
+++ b/metadata.yml
@@ -0,0 +1,127 @@
+--- !ruby/object:Gem::Specification
+name: stud
+version: !ruby/object:Gem::Version
+  version: 0.0.17
+  prerelease: 
+platform: ruby
+authors:
+- Jordan Sissel
+autorequire: 
+bindir: bin
+cert_chain: []
+date: 2013-06-28 00:00:00.000000000 Z
+dependencies:
+- !ruby/object:Gem::Dependency
+  name: metriks
+  requirement: !ruby/object:Gem::Requirement
+    none: false
+    requirements:
+    - - ! '>='
+      - !ruby/object:Gem::Version
+        version: '0'
+  type: :runtime
+  prerelease: false
+  version_requirements: !ruby/object:Gem::Requirement
+    none: false
+    requirements:
+    - - ! '>='
+      - !ruby/object:Gem::Version
+        version: '0'
+- !ruby/object:Gem::Dependency
+  name: ffi
+  requirement: !ruby/object:Gem::Requirement
+    none: false
+    requirements:
+    - - ! '>='
+      - !ruby/object:Gem::Version
+        version: '0'
+  type: :runtime
+  prerelease: false
+  version_requirements: !ruby/object:Gem::Requirement
+    none: false
+    requirements:
+    - - ! '>='
+      - !ruby/object:Gem::Version
+        version: '0'
+- !ruby/object:Gem::Dependency
+  name: rspec
+  requirement: !ruby/object:Gem::Requirement
+    none: false
+    requirements:
+    - - ! '>='
+      - !ruby/object:Gem::Version
+        version: '0'
+  type: :development
+  prerelease: false
+  version_requirements: !ruby/object:Gem::Requirement
+    none: false
+    requirements:
+    - - ! '>='
+      - !ruby/object:Gem::Version
+        version: '0'
+- !ruby/object:Gem::Dependency
+  name: insist
+  requirement: !ruby/object:Gem::Requirement
+    none: false
+    requirements:
+    - - ! '>='
+      - !ruby/object:Gem::Version
+        version: '0'
+  type: :development
+  prerelease: false
+  version_requirements: !ruby/object:Gem::Requirement
+    none: false
+    requirements:
+    - - ! '>='
+      - !ruby/object:Gem::Version
+        version: '0'
+description: small reusable bits of code I'm tired of writing over and over. A library
+  form of my software-patterns github repo.
+email: jls at semicomplete.com
+executables: []
+extensions: []
+extra_rdoc_files: []
+files:
+- lib/stud/secret.rb
+- lib/stud/with.rb
+- lib/stud/benchmark/rusage.rb
+- lib/stud/task.rb
+- lib/stud/pool.rb
+- lib/stud/buffer.rb
+- lib/stud/try.rb
+- lib/stud/time.rb
+- lib/stud/temporary.rb
+- lib/stud/benchmark.rb
+- lib/stud/trap.rb
+- lib/stud/interval.rb
+- lib/stud/time/format.rb
+- lib/stud/time/format.tt
+- LICENSE
+- CHANGELIST
+- README.md
+homepage: https://github.com/jordansissel/ruby-stud
+licenses: []
+post_install_message: 
+rdoc_options: []
+require_paths:
+- lib
+- lib
+required_ruby_version: !ruby/object:Gem::Requirement
+  none: false
+  requirements:
+  - - ! '>='
+    - !ruby/object:Gem::Version
+      version: '0'
+required_rubygems_version: !ruby/object:Gem::Requirement
+  none: false
+  requirements:
+  - - ! '>='
+    - !ruby/object:Gem::Version
+      version: '0'
+requirements: []
+rubyforge_project: 
+rubygems_version: 1.8.25
+signing_key: 
+specification_version: 3
+summary: stud - common code techniques
+test_files: []

-- 
Alioth's /usr/local/bin/git-commit-notice on /srv/git.debian.org/git/pkg-ruby-extras/ruby-stud.git



More information about the Pkg-ruby-extras-commits mailing list