[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