Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Browse files

working gem with tests

  • Loading branch information...
commit 02ccfaff78a700aed76bd5b4530b671101f712e3 1 parent 789855c
@noctivityinc noctivityinc authored
View
136 README.rdoc
@@ -20,34 +20,140 @@ $ gem install tickle
Everything's at Github - http://github.com/noctivityinc/tickle
+-- DEPENDENCIES
+
+chronic gem (gem install chronic)
+thoughtbot's shoulda (gem install shoulda)
+
== USAGE
You can parse strings containing a natural language interval using the Tickle.parse method.
+Tickle.parse returns an array of first occurrence, next occurrence, interval between occurrences.
+
+You can also pass a start date with the word "starting" (e.g. Tickle.parse('every 3 days starting next friday'))
+
+Tickle HEAVILY uses chronic for parsing both the event and the start date.
+
+-- EXAMPLES
+
require 'rubygems'
require 'tickle'
- Time.now #=> 2010-04-21 14:32:02 -0400
-
- Tickle.parse('every 2 days')
- #=> 2010-04-23, 2
-
- Tickle.parse('every Sunday') #=> note, this upcoming Sunday is 4/25/2010
- #=> 2010-04-25, 7
-
- Tickle.parse('every 3 weeks')
- #=> 2010-05-12, 21
-
- Tickle.parse('2 days')
- #=> 2010-04-23, 2
+ Time.now
+ 2010-04-22 16:38:12 -0400
+
+ Tickle.parse('each day')
+ #=> [2010-04-22 16:38:12 -0400, 2010-04-23 16:38:12 -0400, 1]
+
+ Tickle.parse('every day')
+ #=> [2010-04-22 16:38:12 -0400, 2010-04-23 16:38:12 -0400, 1]
+
+ Tickle.parse('every week')
+ #=> [2010-04-22 16:38:12 -0400, 2010-04-23 16:38:12 -0400, 7]
+
+ Tickle.parse('every Month')
+ #=> [2010-04-22 16:38:12 -0400, 2010-04-23 16:38:12 -0400, 30]
+
+ Tickle.parse('every year')
+ #=> [2010-04-22 16:38:12 -0400, 2010-04-23 16:38:12 -0400, 365]
+
+ Tickle.parse('daily')
+ #=> [2010-04-22 16:38:12 -0400, 2010-04-23 16:38:12 -0400, 1]
+
+ Tickle.parse('weekly')
+ #=> [2010-04-22 16:38:12 -0400, 2010-04-23 16:38:12 -0400, 7]
+
+ Tickle.parse('monthly')
+ #=> [2010-04-22 16:38:12 -0400, 2010-04-23 16:38:12 -0400, 30]
+
+ Tickle.parse('yearly')
+ #=> [2010-04-22 16:38:12 -0400, 2010-04-23 16:38:12 -0400, 365]
+
+ Tickle.parse('every 3 days')
+ #=> [2010-04-22 16:38:12 -0400, 2010-04-23 16:38:12 -0400, 3]
+
+ Tickle.parse('every 3 weeks')
+ #=> [2010-04-22 16:38:12 -0400, 2010-04-23 16:38:12 -0400, 21]
+
+ Tickle.parse('every 3 months')
+ #=> [2010-04-22 16:38:12 -0400, 2010-04-23 16:38:12 -0400, 90]
+
+ Tickle.parse('every 3 years')
+ #=> [2010-04-22 16:38:12 -0400, 2010-04-23 16:38:12 -0400, 1095]
+
+ Tickle.parse('every other day')
+ #=> [2010-04-22 16:38:12 -0400, 2010-04-23 16:38:12 -0400, 2]
+
+ Tickle.parse('every other week')
+ #=> [2010-04-22 16:38:12 -0400, 2010-04-23 16:38:12 -0400, 14]
+
+ Tickle.parse('every other month')
+ #=> [2010-04-22 16:38:12 -0400, 2010-06-22 16:38:12 -0400, 60]
+
+ Tickle.parse('every other year')
+ #=> [2010-04-22 16:38:12 -0400, 2012-04-22 16:38:12 -0400, 730]
+
+ Tickle.parse('every other day starting May 1st')
+ #=> [2010-05-01 12:00:00 -0400, 2012-04-22 16:38:12 -0400, 2]
+
+ Tickle.parse('every other week starting this Sunday')
+ #=> [2010-04-25 12:00:00 -0400, 2012-04-22 16:38:12 -0400, 14]
+
+ Tickle.parse('every Monday')
+ #=> [2010-04-26 12:00:00 -0400, 2012-04-22 16:38:12 -0400, 7]
+
+ Tickle.parse('every Wednesday')
+ #=> [2010-04-28 12:00:00 -0400, 2012-04-22 16:38:12 -0400, 7]
+
+ Tickle.parse('every Friday')
+ #=> [2010-04-23 12:00:00 -0400, 2012-04-22 16:38:12 -0400, 7]
+
+ Tickle.parse('every May')
+ #=> [2010-05-01 12:00:00 -0400, 2012-04-22 16:38:12 -0400, 30]
+
+ Tickle.parse('every june')
+ #=> [2010-06-01 12:00:00 -0400, 2012-04-22 16:38:12 -0400, 30]
+
+ Tickle.parse('beginning of the week')
+ #=> [2010-04-25 12:00:00 -0400, 2012-04-22 16:38:12 -0400, 7]
+
+ Tickle.parse('middle of the week')
+ #=> [2010-04-28 12:00:00 -0400, 2012-04-22 16:38:12 -0400, 7]
+
+ Tickle.parse('end of the week')
+ #=> [2010-04-24 12:00:00 -0400, 2012-04-22 16:38:12 -0400, 7]
+
+ Tickle.parse('beginning of the month')
+ #=> [2010-05-01 12:00:00 -0400, 2012-04-22 16:38:12 -0400, 30]
+
+ Tickle.parse('middle of the month')
+ #=> [2010-05-15 12:00:00 -0400, 2012-04-22 16:38:12 -0400, 30]
+
+ Tickle.parse('end of the month')
+ #=> [2010-04-30 00:00:00 -0400, 2012-04-22 16:38:12 -0400, 30]
+
+ Tickle.parse('beginning of the year')
+ #=> [2011-01-01 12:00:00 -0500, 2012-04-22 16:38:12 -0400, 365]
+
+ Tickle.parse('middle of the year')
+ #=> [2010-06-15 00:00:00 -0400, 2012-04-22 16:38:12 -0400, 365]
+
+ Tickle.parse('end of the year')
+ #=> [2010-12-31 00:00:00 -0500, 2012-04-22 16:38:12 -0400, 365]
-You can either pass a string prefixed with the word "every" or simply the time frame.
+You can either pass a string prefixed with the word "every, each or 'on the'" or simply the time frame.
-- LIMITATIONS
-Currently, Tickle only works for day intervals but feel free to fork and add time-based interval support. Also, numbers must be entered in numeric and not string form (i.e. 3 not three).
+Currently, Tickle only works for day intervals but feel free to fork and add time-based interval support or send me a note if you really want me to add it.
+
+== CREDIT
+
+HUGE shout-out to both the creator of Chronic, Tom Preston-Werner (http://chronic.rubyforge.org/) as well as Brian Brownling who maintains a github version at http://github.com/mojombo/chronic.
+Without their work and code structure I'd be lost.
== Note on Patches/Pull Requests
View
2  Rakefile
@@ -11,7 +11,7 @@ begin
gem.homepage = "http://github.com/noctivityinc/tickle"
gem.authors = ["Joshua Lippiner"]
gem.add_dependency('chronic', '>= 0.2.3')
- gem.add_development_dependency "thoughtbot-shoulda", ">= 0"
+ gem.add_development_dependency "shoulda", ">= 2.10.3"
# gem is a Gem::Specification... see http://www.rubygems.org/read/chapter/20 for additional settings
end
View
12 SCENARIOS.rdoc
@@ -0,0 +1,12 @@
+every 2 weeks => word 'week' found plus a number so the interval = number * 7 and start_date = today
+every second tuesday => => day of week found WITH number (ordinal converted to 2). interval = 30 with start_date = Chronic.parse(Ordinal Number Day Of Week 'in' Start Date Month)
+every sunday => day of week found without number. interval = 7, start_date = next day of week occurrence
+every other day => word 'day' found with word 'other.' interval = 2, start_date = today
+every fourth thursday => day of week found WITH number (ordinal converted to 4). interval = 30 with start_date = next occurrence of 'event' as parsed by chronic
+on the 15th of each month => 'each month' becomes interval = 30, number found + start month through chronic equals start date
+on the 15th of November => month found with number. interval = 365, start_date = Chronic.parse(month + number)
+on the second monday in April => month, day and number found. interval = 365, start_date = Chronic.parse(ordinal number form of number, day of week, month)
+every November 15th => month found with number. interval = 365, start_date = Chronic.parse(month + number)
+every day => word 'day' found without a number. interval = 1. start_day = today
+every week => word 'week' found without a number. interval = 7. start_day = today
+every month => word 'month' found without a number. interval = 30. start_day = today
View
24 lib/tickle.rb
@@ -8,13 +8,33 @@
$:.unshift File.dirname(__FILE__) # For use/testing when no gem is installed
+require 'date'
require 'chronic'
-require 'numerizer/numerizer'
require 'tickle/tickle'
+require 'tickle/handler'
+require 'tickle/repeater'
module Tickle
VERSION = "0.0.1"
- def self.debug; false; end
+ def self.debug; true; end
+
+ def self.dwrite(msg)
+ puts msg if Tickle.debug
+ end
+end
+
+class Date
+ def days_in_month
+ d,m,y = mday,month,year
+ d += 1 while Date.valid_civil?(y,m,d)
+ d - 1
+ end
+end
+
+class Array
+ def same?(y)
+ self.sort == y.sort
+ end
end
View
98 lib/tickle/handler.rb
@@ -0,0 +1,98 @@
+module Tickle
+ class << self
+
+ def guess()
+ interval = guess_unit_types
+ interval ||= guess_weekday
+ interval ||= guess_weekday
+ interval ||= guess_month_names
+ interval ||= guess_number_and_unit
+ interval ||= guess_special
+
+ # defines the next occurrence of this tickle if not set in a guess routine
+ @next ||= @start + (interval * 60 * 60 * 24) if interval
+ return [@start.to_time, @next.to_time, interval] if interval
+ end
+
+ def guess_unit_types
+ interval = 1 if token_types.same?([:day])
+ interval = 7 if token_types.same?([:week])
+ interval = 30 if token_types.same?([:month])
+ interval = 365 if token_types.same?([:year])
+ interval
+ end
+
+ def guess_weekday
+ if token_types.same?([:weekday]) then
+ @start = Chronic.parse(token_of_type(:weekday).start.to_s)
+ interval = 7
+ end
+ interval
+ end
+
+ def guess_month_names
+ if token_types.same?([:month_name]) then
+ @start = Chronic.parse("#{token_of_type(:month_name).start.to_s} 1")
+ interval = 30
+ end
+ interval
+ end
+
+ def guess_number_and_unit
+ interval = token_of_type(:number).interval if token_types.same?([:number, :day])
+ interval = (token_of_type(:number).interval * 7) if token_types.same?([:number, :week])
+ interval = (token_of_type(:number).interval * 30) if token_types.same?([:number, :month])
+ interval = (token_of_type(:number).interval * 365) if token_types.same?([:number, :year])
+ interval
+ end
+
+ def guess_special
+ interval = guess_special_other
+ interval ||= guess_special_beginning
+ interval ||= guess_special_middle
+ interval ||= guess_special_end
+ end
+
+ private
+
+ def guess_special_other
+ interval = 2 if token_types.same?([:special, :day]) && token_of_type(:special).start == :other
+ interval = 14 if token_types.same?([:special, :week]) && token_of_type(:special).start == :other
+ if token_types.same?([:special, :month]) && token_of_type(:special).start == :other then interval = 60; @next = Chronic.parse('2 months from now'); end
+ if token_types.same?([:special, :year]) && token_of_type(:special).start == :other then interval = 730; @next = Chronic.parse('2 years from now'); end
+ interval
+ end
+
+ def guess_special_beginning
+ if token_types.same?([:special, :week]) && token_of_type(:special).start == :beginning then interval = 7; @start = Chronic.parse('Sunday'); end
+ if token_types.same?([:special, :month]) && token_of_type(:special).start == :beginning then interval = 30; @start = Chronic.parse('1st day next month'); end
+ if token_types.same?([:special, :year]) && token_of_type(:special).start == :beginning then interval = 365; @start = Chronic.parse('1st day next year'); end
+ interval
+ end
+
+ def guess_special_end
+ if token_types.same?([:special, :week]) && token_of_type(:special).start == :end then interval = 7; @start = Chronic.parse('Saturday'); end
+ if token_types.same?([:special, :month]) && token_of_type(:special).start == :end then interval = 30; @start = Date.new(Date.today.year, Date.today.month, Date.today.days_in_month); end
+ if token_types.same?([:special, :year]) && token_of_type(:special).start == :end then interval = 365; @start = Date.new(Date.today.year, 12, 31); end
+ interval
+ end
+
+ def guess_special_middle
+ if token_types.same?([:special, :week]) && token_of_type(:special).start == :middle then interval = 7; @start = Chronic.parse('Wednesday'); end
+ if token_types.same?([:special, :month]) && token_of_type(:special).start == :middle then
+ interval = 30;
+ @start = (Date.today.day >= 15 ? Chronic.parse('15th day of next month') : Date.new(Date.today.year, Date.today.month, 15))
+ end
+ if token_types.same?([:special, :year]) && token_of_type(:special).start == :middle then
+ interval = 365;
+ @start = (Date.today.day >= 15 && Date.today.month >= 6 ? Date.new(Date.today.year+1, 6, 15) : Date.new(Date.today.year, 6, 15))
+ end
+ interval
+ end
+
+ def token_of_type(type)
+ @tokens.detect {|token| token.type == type}
+ end
+
+ end
+end
View
85 lib/tickle/repeater.rb
@@ -0,0 +1,85 @@
+class Tickle::Repeater < Chronic::Tag #:nodoc:
+ #
+ def self.scan(tokens)
+ # for each token
+ tokens.each do |token|
+ token = self.scan_for_numbers(token)
+ token = self.scan_for_month_names(token)
+ token = self.scan_for_day_names(token)
+ token = self.scan_for_special_text(token)
+ token = self.scan_for_units(token)
+ end
+ tokens
+ end
+
+ def self.scan_for_numbers(token)
+ num = Float(token.word) rescue nil
+ token.update(:number, nil, num.to_i) if num
+ token
+ end
+
+ def self.scan_for_month_names(token)
+ scanner = {/^jan\.?(uary)?$/ => :january,
+ /^feb\.?(ruary)?$/ => :february,
+ /^mar\.?(ch)?$/ => :march,
+ /^apr\.?(il)?$/ => :april,
+ /^may$/ => :may,
+ /^jun\.?e?$/ => :june,
+ /^jul\.?y?$/ => :july,
+ /^aug\.?(ust)?$/ => :august,
+ /^sep\.?(t\.?|tember)?$/ => :september,
+ /^oct\.?(ober)?$/ => :october,
+ /^nov\.?(ember)?$/ => :november,
+ /^dec\.?(ember)?$/ => :december}
+ scanner.keys.each do |scanner_item|
+ token.update(:month_name, scanner[scanner_item], 30) if scanner_item =~ token.word
+ end
+ token
+ end
+
+ def self.scan_for_day_names(token)
+ scanner = {/^m[ou]n(day)?$/ => :monday,
+ /^t(ue|eu|oo|u|)s(day)?$/ => :tuesday,
+ /^tue$/ => :tuesday,
+ /^we(dnes|nds|nns)day$/ => :wednesday,
+ /^wed$/ => :wednesday,
+ /^th(urs|ers)day$/ => :thursday,
+ /^thu$/ => :thursday,
+ /^fr[iy](day)?$/ => :friday,
+ /^sat(t?[ue]rday)?$/ => :saturday,
+ /^su[nm](day)?$/ => :sunday}
+ scanner.keys.each do |scanner_item|
+ token.update(:weekday, scanner[scanner_item], 7) if scanner_item =~ token.word
+ end
+ token
+ end
+
+ def self.scan_for_special_text(token)
+ scanner = {/^other$/ => :other,
+ /^begin(ing|ning)?$/ => :beginning,
+ /^start$/ => :beginning,
+ /^end$/ => :end,
+ /^mid(d)?le$/ => :middle}
+ scanner.keys.each do |scanner_item|
+ token.update(:special, scanner[scanner_item], 7) if scanner_item =~ token.word
+ end
+ token
+ end
+
+ def self.scan_for_units(token)
+ scanner = {/^year(ly)?s?$/ => {:type => :year, :interval => 365, :start => :today},
+ /^month(ly)?s?$/ => {:type => :month, :interval => 30, :start => :today},
+ /^fortnights?$/ => {:type => :fortnight, :interval => 365, :start => :today},
+ /^week(ly)?s?$/ => {:type => :week, :interval => 7, :start => :today},
+ /^weekends?$/ => {:type => :weekend, :interval => 7, :start => :saturday},
+ /^days?$/ => {:type => :day, :interval => 1, :start => :today},
+ /^daily?$/ => {:type => :day, :interval => 1, :start => :today}}
+ scanner.keys.each do |scanner_item|
+ if scanner_item =~ token.word
+ token.update(scanner[scanner_item][:type], scanner[scanner_item][:start], scanner[scanner_item][:interval]) if scanner_item =~ token.word
+ end
+ end
+ token
+ end
+
+end
View
85 lib/tickle/tickle.rb
@@ -1,25 +1,86 @@
module Tickle
class << self
-
+
def parse(text, specified_options = {})
# get options and set defaults if necessary
- default_options = {:now => Time.now}
+ default_options = {:start => Time.now}
options = default_options.merge specified_options
-
+
# ensure the specified options are valid
specified_options.keys.each do |key|
default_options.keys.include?(key) || raise(InvalidArgumentException, "#{key} is not a valid option key.")
end
- Chronic.parse(options[:now]) || rails(InvalidArgumentException, ':now specified is not a valid datetime.')
-
- # store now for later =)
- @now = options[:now]
-
+ Chronic.parse(specified_options[:start]) || raise(InvalidArgumentException, ':start specified is not a valid datetime.') if specified_options[:start]
+
+ # remove every is specified
+ text = text.gsub(/^every\s\b/, '')
+
# put the text into a normal format to ease scanning using Chronic
+ text = pre_normalize(text)
text = Chronic.pre_normalize(text)
-
- return text
+ text = numericize_ordinals(text)
+
+ # check to see if this event starts some other time and reset now
+ event, starting = text.split('starting')
+ @start = (Chronic.parse(starting) || options[:start])
+
+ # split into tokens
+ @tokens = base_tokenize(event)
+
+ # scan the tokens with each token scanner
+ @tokens = Repeater.scan(@tokens)
+
+ # remove all tokens without a type
+ @tokens.reject! {|token| token.type.nil? }
+
+ # dwrite @tokens.inspect
+
+ return guess
end
-
+
+ # Normalize natural string removing prefix language
+ def pre_normalize(text)
+ normalized_text = text.gsub(/^every\s\b/, '')
+ normalized_text = text.gsub(/^each\s\b/, '')
+ normalized_text = text.gsub(/^on the\s\b/, '')
+ normalized_text
+ end
+
+ # Split the text on spaces and convert each word into
+ # a Token
+ def base_tokenize(text) #:nodoc:
+ text.split(' ').map { |word| Token.new(word) }
+ end
+
+ # Convert ordinal words to numeric ordinals (third => 3rd)
+ def numericize_ordinals(text) #:nodoc:
+ text = text.gsub(/\b(\d*)(st|nd|rd|th)\b/, '\1')
+ end
+
+ # Returns an array of types for all tokens
+ def token_types
+ @tokens.map(&:type)
+ end
+ end
+
+ class Token #:nodoc:
+ attr_accessor :word, :type, :interval, :start
+
+ def initialize(word)
+ @word = word
+ @type = @interval = @start = nil
+ end
+
+ def update(type, start=nil, interval=nil)
+ @start = start
+ @type = type
+ @interval = interval
+ end
+ end
+
+ # This exception is raised if an invalid argument is provided to
+ # any of Tickle's methods
+ class InvalidArgumentException < Exception
+
end
-end
+end
View
3  test/helper.rb
@@ -4,7 +4,8 @@
$LOAD_PATH.unshift(File.join(File.dirname(__FILE__), '..', 'lib'))
$LOAD_PATH.unshift(File.dirname(__FILE__))
-require 'tickle'
+
+require File.join(File.dirname(__FILE__), '..', 'lib', 'tickle')
class Test::Unit::TestCase
end
View
9 test/suite.rb
@@ -0,0 +1,9 @@
+require 'test/unit'
+
+tests = Dir["#{File.dirname(__FILE__)}/test_*.rb"]
+tests.delete_if { |o| o =~ /test_parsing/ }
+tests.each do |file|
+ require file
+end
+
+require File.dirname(__FILE__) + '/test_parsing.rb'
View
50 test/test_Time.rb
@@ -0,0 +1,50 @@
+require 'chronic'
+require 'test/unit'
+
+class TestTime < Test::Unit::TestCase
+
+ def setup
+ end
+
+ def test_normal
+ assert_equal Time.local(2006, 1, 2, 0, 0, 0), Time.construct(2006, 1, 2, 0, 0, 0)
+ assert_equal Time.local(2006, 1, 2, 3, 0, 0), Time.construct(2006, 1, 2, 3, 0, 0)
+ assert_equal Time.local(2006, 1, 2, 3, 4, 0), Time.construct(2006, 1, 2, 3, 4, 0)
+ assert_equal Time.local(2006, 1, 2, 3, 4, 5), Time.construct(2006, 1, 2, 3, 4, 5)
+ end
+
+ def test_second_overflow
+ assert_equal Time.local(2006, 1, 1, 0, 1, 30), Time.construct(2006, 1, 1, 0, 0, 90)
+ assert_equal Time.local(2006, 1, 1, 0, 5, 0), Time.construct(2006, 1, 1, 0, 0, 300)
+ end
+
+ def test_minute_overflow
+ assert_equal Time.local(2006, 1, 1, 1, 30), Time.construct(2006, 1, 1, 0, 90)
+ assert_equal Time.local(2006, 1, 1, 5), Time.construct(2006, 1, 1, 0, 300)
+ end
+
+ def test_hour_overflow
+ assert_equal Time.local(2006, 1, 2, 12), Time.construct(2006, 1, 1, 36)
+ assert_equal Time.local(2006, 1, 7), Time.construct(2006, 1, 1, 144)
+ end
+
+ def test_day_overflow
+ assert_equal Time.local(2006, 2, 1), Time.construct(2006, 1, 32)
+ assert_equal Time.local(2006, 3, 5), Time.construct(2006, 2, 33)
+ assert_equal Time.local(2004, 3, 4), Time.construct(2004, 2, 33)
+ assert_equal Time.local(2000, 3, 5), Time.construct(2000, 2, 33)
+
+ assert_nothing_raised do
+ Time.construct(2006, 1, 56)
+ end
+
+ assert_raise(RuntimeError) do
+ Time.construct(2006, 1, 57)
+ end
+ end
+
+ def test_month_overflow
+ assert_equal Time.local(2006, 1), Time.construct(2005, 13)
+ assert_equal Time.local(2005, 12), Time.construct(2000, 72)
+ end
+end
View
26 test/test_Token.rb
@@ -0,0 +1,26 @@
+require 'chronic'
+require 'test/unit'
+
+class TestToken < Test::Unit::TestCase
+
+ def setup
+ # Wed Aug 16 14:00:00 UTC 2006
+ @now = Time.local(2006, 8, 16, 14, 0, 0, 0)
+ end
+
+ def test_token
+ token = Chronic::Token.new('foo')
+ assert_equal 0, token.tags.size
+ assert !token.tagged?
+ token.tag("mytag")
+ assert_equal 1, token.tags.size
+ assert token.tagged?
+ assert_equal String, token.get_tag(String).class
+ token.tag(5)
+ assert_equal 2, token.tags.size
+ token.untag(String)
+ assert_equal 1, token.tags.size
+ assert_equal 'foo', token.word
+ end
+
+end
View
76 test/test_parsing.rb
@@ -0,0 +1,76 @@
+require 'helper'
+require 'time'
+require 'test/unit'
+
+class TestParsing < Test::Unit::TestCase
+
+ def setup
+
+ end
+
+ def test_parse_best_guess
+ puts "Time.now"
+ p Time.now
+
+ parse_now('each day')
+
+ parse_now('every day')
+ parse_now('every week')
+ parse_now('every Month')
+ parse_now('every year')
+
+ parse_now('daily')
+ parse_now('weekly')
+ parse_now('monthly')
+ parse_now('yearly')
+
+ parse_now('every 3 days')
+ parse_now('every 3 weeks')
+ parse_now('every 3 months')
+ parse_now('every 3 years')
+
+ parse_now('every other day')
+ parse_now('every other week')
+ parse_now('every other month')
+ parse_now('every other year')
+ parse_now('every other day starting May 1st')
+ parse_now('every other week starting this Sunday')
+
+ parse_now('every Monday')
+ parse_now('every Wednesday')
+ parse_now('every Friday')
+
+ parse_now('every May')
+ parse_now('every june')
+
+ parse_now('beginning of the week')
+ parse_now('middle of the week')
+ parse_now('end of the week')
+
+ parse_now('beginning of the month')
+ parse_now('middle of the month')
+ parse_now('end of the month')
+
+ parse_now('beginning of the year')
+ parse_now('middle of the year')
+ parse_now('end of the year')
+ end
+
+ def test_argument_validation
+ assert_raise(Tickle::InvalidArgumentException) do
+ time = Tickle.parse("may 27", :today => 'something odd')
+ end
+
+ assert_raise(Tickle::InvalidArgumentException) do
+ time = Tickle.parse("may 27", :foo => :bar)
+ end
+ end
+
+ private
+ def parse_now(string, options={})
+ puts ("attempting to parse '#{string}'")
+ out = Tickle.parse(string, {}.merge(options))
+ p ("output: #{out}")
+ out
+ end
+end
View
59 tickle.gemspec
@@ -0,0 +1,59 @@
+# Generated by jeweler
+# DO NOT EDIT THIS FILE DIRECTLY
+# Instead, edit Jeweler::Tasks in Rakefile, and run the gemspec command
+# -*- encoding: utf-8 -*-
+
+Gem::Specification.new do |s|
+ s.name = %q{tickle}
+ s.version = "0.0.1"
+
+ s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
+ s.authors = ["Joshua Lippiner"]
+ s.date = %q{2010-04-21}
+ s.description = %q{Tickle is a date/time helper gem to help parse natural language into a recurring pattern. Tickle is designed to be a compliment of Chronic and can interpret things such as "every 2 days, every Sunday, Sundays, Weekly, etc.}
+ s.email = %q{jlippiner@noctivity.com}
+ s.extra_rdoc_files = [
+ "LICENSE",
+ "README.rdoc"
+ ]
+ s.files = [
+ ".document",
+ ".gitignore",
+ ".rvmrc",
+ "LICENSE",
+ "README.rdoc",
+ "Rakefile",
+ "VERSION",
+ "lib/numerizer/numerizer.rb",
+ "lib/tickle.rb",
+ "lib/tickle/tickle.rb",
+ "test/helper.rb",
+ "test/test_tickle.rb"
+ ]
+ s.homepage = %q{http://github.com/noctivityinc/tickle}
+ s.rdoc_options = ["--charset=UTF-8"]
+ s.require_paths = ["lib"]
+ s.rubygems_version = %q{1.3.6}
+ s.summary = %q{natural language parser for recurring events}
+ s.test_files = [
+ "test/helper.rb",
+ "test/test_tickle.rb"
+ ]
+
+ if s.respond_to? :specification_version then
+ current_version = Gem::Specification::CURRENT_SPECIFICATION_VERSION
+ s.specification_version = 3
+
+ if Gem::Version.new(Gem::RubyGemsVersion) >= Gem::Version.new('1.2.0') then
+ s.add_runtime_dependency(%q<chronic>, [">= 0.2.3"])
+ s.add_development_dependency(%q<thoughtbot-shoulda>, [">= 0"])
+ else
+ s.add_dependency(%q<chronic>, [">= 0.2.3"])
+ s.add_dependency(%q<thoughtbot-shoulda>, [">= 0"])
+ end
+ else
+ s.add_dependency(%q<chronic>, [">= 0.2.3"])
+ s.add_dependency(%q<thoughtbot-shoulda>, [">= 0"])
+ end
+end
+
Please sign in to comment.
Something went wrong with that request. Please try again.