Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with
or
.
Download ZIP
Browse files

initial commit

  • Loading branch information...
commit 9bfe8d7935813c10a22455e7ab6e3c93453cde24 0 parents
@joshbuddy authored
14 README.rdoc
@@ -0,0 +1,14 @@
+= Spanner
+
+Easy way to parse natural language time spans as periods expressed in seconds. Supports float point notions of spans as well.
+
+== Usage
+
+ require 'spanner'
+
+ Spanner.parse('1s')
+ => 1
+
+ Spanner.parse('23 hours 12 minutes')
+ => 83520
+
28 Rakefile
@@ -0,0 +1,28 @@
+begin
+ require 'jeweler'
+ Jeweler::Tasks.new do |s|
+ s.name = "spanner"
+ s.description = s.summary = "Natural language time span parsing"
+ s.email = "joshbuddy@gmail.com"
+ s.homepage = "http://github.com/joshbuddy/spanner"
+ s.authors = ["Joshua Hull"]
+ s.files = FileList["[A-Z]*", "{lib,spec}/**/*"]
+ end
+ Jeweler::GemcutterTasks.new
+rescue LoadError
+ puts "Jeweler not available. Install it with: sudo gem install technicalpickles-jeweler -s http://gems.github.com"
+end
+
+require 'spec'
+require 'spec/rake/spectask'
+task :spec => 'spec:all'
+namespace(:spec) do
+ Spec::Rake::SpecTask.new(:all) do |t|
+ t.spec_opts ||= []
+ t.spec_opts << "-rubygems -rdirge"
+ t.spec_opts << "--options" << "spec/spec.opts"
+ t.spec_files = FileList['spec/**/*_spec.rb']
+ end
+
+end
+
82 lib/spanner.rb
@@ -0,0 +1,82 @@
+class Spanner
+
+ ParseError = Class.new(RuntimeError)
+
+ def self.parse(str, opts = nil)
+ Spanner.new(str, opts).parse
+ end
+
+ attr_reader :value, :raise_on_error, :from
+
+ def initialize(value, opts)
+ @value = value
+ @on_error = opts && opts.key?(:on_error) ? opts[:on_error] : :raise
+
+
+ @from = if opts && opts.key?(:from)
+ case opts[:from]
+ when :now
+ Time.new.to_i
+ else
+ opts[:from].to_i
+ end
+ else
+ 0
+ end
+ end
+
+ def error(err)
+ if on_error == :raise
+ raise ParseError.new(err)
+ end
+ end
+
+ def parse
+ parts = []
+ part_contextualized = nil
+ value.scan(/[\+\-]?(?:\d*\.\d+|\d+)|[a-z]+/i).each do |part|
+ part_as_float = Float(part) rescue nil
+ if part_as_float
+ parts << part_as_float
+ part_contextualized = nil
+ else
+ if part_contextualized
+ error "Part has already been contextualized with #{part_contextualized}"
+ return nil
+ end
+
+ if parts.empty?
+ parts << 1
+ end
+
+ # part is context
+ multiplier = case part
+ when 's', 'sec', 'seconds' then 1
+ when 'h', 'hours', 'hrs' then 3600
+ when 'm', 'min', 'minutes' then 60
+ when 'd', 'days' then 86_400
+ when 'w', 'wks', 'weeks' then 604_800
+ when 'months', 'month', 'm' then 2_629_743.83
+ when 'years', 'y' then 31_556_926
+ when /\As/ then 1
+ when /\Am/ then 60
+ when /\Ah/ then 86_400
+ when /\Aw/ then 604_800
+ when /\Am/ then 2_629_743.83
+ when /\Ay/ then 31_556_926
+ end
+
+ part_contextualized = part
+ parts << (parts.pop * multiplier)
+ end
+ end
+
+ if parts.empty?
+ nil
+ else
+ value = parts.inject(from) {|s, p| s += p}
+ value.ceil == value ? value.ceil : value
+ end
+ end
+
+end
36 spec/spanner_spec.rb
@@ -0,0 +1,36 @@
+require ~'spec_helper'
+
+describe Spanner do
+
+ it "should return nil for empty strings" do
+ Spanner.parse('').should be_nil
+ end
+
+ it "should assume seconds" do
+ Spanner.parse('1').should == 1
+ end
+
+ #simple
+ { '.5s' => 0.5, '1s' => 1, '1.5s' => 1.5, '1m' => 60, '1.5m' => 90, '1d' => 86400, '1.7233312d' => 148895.81568 }.each do |input, output|
+ it "should parse #{input} and return #{output}" do
+ Spanner.parse(input).should == output
+ end
+ end
+
+ #complex
+ { '1m23s' => 83 }.each do |input, output|
+ it "should parse #{input} and return #{output}" do
+ Spanner.parse(input).should == output
+ end
+ end
+
+ it "should accept time as from option" do
+ now = Time.new
+ Spanner.parse('23s', :from => now).should == now.to_i + 23
+ end
+
+ it "should accept special :now as from option" do
+ Spanner.parse('23s', :from => :now).should == Time.new.to_i + 23
+ end
+
+end
7 spec/spec.opts
@@ -0,0 +1,7 @@
+--colour
+--format
+specdoc
+--loadby
+mtime
+--reverse
+--backtrace
1  spec/spec_helper.rb
@@ -0,0 +1 @@
+require ~'../lib/spanner'
Please sign in to comment.
Something went wrong with that request. Please try again.