chronic 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (41) hide show
  1. data/README +119 -0
  2. data/lib/chronic.rb +30 -0
  3. data/lib/chronic/chronic.rb +242 -0
  4. data/lib/chronic/grabber.rb +26 -0
  5. data/lib/chronic/handlers.rb +405 -0
  6. data/lib/chronic/ordinal.rb +40 -0
  7. data/lib/chronic/pointer.rb +27 -0
  8. data/lib/chronic/repeater.rb +114 -0
  9. data/lib/chronic/repeaters/repeater_day.rb +40 -0
  10. data/lib/chronic/repeaters/repeater_day_name.rb +41 -0
  11. data/lib/chronic/repeaters/repeater_day_portion.rb +93 -0
  12. data/lib/chronic/repeaters/repeater_fortnight.rb +64 -0
  13. data/lib/chronic/repeaters/repeater_hour.rb +52 -0
  14. data/lib/chronic/repeaters/repeater_minute.rb +21 -0
  15. data/lib/chronic/repeaters/repeater_month.rb +54 -0
  16. data/lib/chronic/repeaters/repeater_month_name.rb +82 -0
  17. data/lib/chronic/repeaters/repeater_season.rb +23 -0
  18. data/lib/chronic/repeaters/repeater_season_name.rb +24 -0
  19. data/lib/chronic/repeaters/repeater_second.rb +34 -0
  20. data/lib/chronic/repeaters/repeater_time.rb +106 -0
  21. data/lib/chronic/repeaters/repeater_week.rb +62 -0
  22. data/lib/chronic/repeaters/repeater_weekend.rb +11 -0
  23. data/lib/chronic/repeaters/repeater_year.rb +55 -0
  24. data/lib/chronic/scalar.rb +74 -0
  25. data/lib/chronic/separator.rb +76 -0
  26. data/test/parse_numbers.rb +50 -0
  27. data/test/suite.rb +9 -0
  28. data/test/test_Chronic.rb +50 -0
  29. data/test/test_Handler.rb +110 -0
  30. data/test/test_RepeaterDayName.rb +52 -0
  31. data/test/test_RepeaterFortnight.rb +63 -0
  32. data/test/test_RepeaterHour.rb +65 -0
  33. data/test/test_RepeaterMonth.rb +47 -0
  34. data/test/test_RepeaterMonthName.rb +57 -0
  35. data/test/test_RepeaterTime.rb +72 -0
  36. data/test/test_RepeaterWeek.rb +63 -0
  37. data/test/test_RepeaterYear.rb +63 -0
  38. data/test/test_Span.rb +24 -0
  39. data/test/test_Token.rb +26 -0
  40. data/test/test_parsing.rb +472 -0
  41. metadata +87 -0
@@ -0,0 +1,74 @@
1
+ module Chronic
2
+
3
+ class Scalar < Tag #:nodoc:
4
+ def self.scan(tokens)
5
+ # for each token
6
+ tokens.each_index do |i|
7
+ if t = self.scan_for_scalars(tokens[i], tokens[i + 1]) then tokens[i].tag(t) end
8
+ if t = self.scan_for_days(tokens[i], tokens[i + 1]) then tokens[i].tag(t) end
9
+ if t = self.scan_for_months(tokens[i], tokens[i + 1]) then tokens[i].tag(t) end
10
+ if t = self.scan_for_years(tokens[i], tokens[i + 1]) then tokens[i].tag(t) end
11
+ end
12
+ tokens
13
+ end
14
+
15
+ def self.scan_for_scalars(token, post_token)
16
+ if token.word =~ /^\d*$/
17
+ unless post_token && %w{am pm morning afternoon evening night}.include?(post_token)
18
+ return Scalar.new(token.word.to_i)
19
+ end
20
+ end
21
+ return nil
22
+ end
23
+
24
+ def self.scan_for_days(token, post_token)
25
+ if token.word =~ /^\d\d?$/
26
+ unless token.word.to_i > 31 || (post_token && %w{am pm morning afternoon evening night}.include?(post_token))
27
+ return ScalarDay.new(token.word.to_i)
28
+ end
29
+ end
30
+ return nil
31
+ end
32
+
33
+ def self.scan_for_months(token, post_token)
34
+ if token.word =~ /^\d\d?$/
35
+ unless token.word.to_i > 12 || (post_token && %w{am pm morning afternoon evening night}.include?(post_token))
36
+ return ScalarMonth.new(token.word.to_i)
37
+ end
38
+ end
39
+ return nil
40
+ end
41
+
42
+ def self.scan_for_years(token, post_token)
43
+ if token.word =~ /^\d\d(\d\d)?$/
44
+ unless post_token && %w{am pm morning afternoon evening night}.include?(post_token)
45
+ return ScalarYear.new(token.word.to_i)
46
+ end
47
+ end
48
+ return nil
49
+ end
50
+
51
+ def to_s
52
+ 'scalar'
53
+ end
54
+ end
55
+
56
+ class ScalarDay < Scalar #:nodoc:
57
+ def to_s
58
+ super << '-day-' << @type.to_s
59
+ end
60
+ end
61
+
62
+ class ScalarMonth < Scalar #:nodoc:
63
+ def to_s
64
+ super << '-month-' << @type.to_s
65
+ end
66
+ end
67
+
68
+ class ScalarYear < Scalar #:nodoc:
69
+ def to_s
70
+ super << '-year-' << @type.to_s
71
+ end
72
+ end
73
+
74
+ end
@@ -0,0 +1,76 @@
1
+ module Chronic
2
+
3
+ class Separator < Tag #:nodoc:
4
+ def self.scan(tokens)
5
+ tokens.each_index do |i|
6
+ if t = self.scan_for_commas(tokens[i]) then tokens[i].tag(t); next end
7
+ if t = self.scan_for_slash_or_dash(tokens[i]) then tokens[i].tag(t); next end
8
+ if t = self.scan_for_at(tokens[i]) then tokens[i].tag(t); next end
9
+ if t = self.scan_for_in(tokens[i]) then tokens[i].tag(t); next end
10
+ end
11
+ tokens
12
+ end
13
+
14
+ def self.scan_for_commas(token)
15
+ scanner = {/^,$/ => :comma}
16
+ scanner.keys.each do |scanner_item|
17
+ return SeparatorComma.new(scanner[scanner_item]) if scanner_item =~ token.word
18
+ end
19
+ return nil
20
+ end
21
+
22
+ def self.scan_for_slash_or_dash(token)
23
+ scanner = {/^-$/ => :dash,
24
+ /^\/$/ => :slash}
25
+ scanner.keys.each do |scanner_item|
26
+ return SeparatorSlashOrDash.new(scanner[scanner_item]) if scanner_item =~ token.word
27
+ end
28
+ return nil
29
+ end
30
+
31
+ def self.scan_for_at(token)
32
+ scanner = {/^(at|@)$/ => :at}
33
+ scanner.keys.each do |scanner_item|
34
+ return SeparatorAt.new(scanner[scanner_item]) if scanner_item =~ token.word
35
+ end
36
+ return nil
37
+ end
38
+
39
+ def self.scan_for_in(token)
40
+ scanner = {/^in$/ => :in}
41
+ scanner.keys.each do |scanner_item|
42
+ return SeparatorIn.new(scanner[scanner_item]) if scanner_item =~ token.word
43
+ end
44
+ return nil
45
+ end
46
+
47
+ def to_s
48
+ 'separator'
49
+ end
50
+ end
51
+
52
+ class SeparatorComma < Separator #:nodoc:
53
+ def to_s
54
+ super << '-comma'
55
+ end
56
+ end
57
+
58
+ class SeparatorSlashOrDash < Separator #:nodoc:
59
+ def to_s
60
+ super << '-slashordash-' << @type.to_s
61
+ end
62
+ end
63
+
64
+ class SeparatorAt < Separator #:nodoc:
65
+ def to_s
66
+ super << '-at'
67
+ end
68
+ end
69
+
70
+ class SeparatorIn < Separator #:nodoc:
71
+ def to_s
72
+ super << '-in'
73
+ end
74
+ end
75
+
76
+ end
@@ -0,0 +1,50 @@
1
+ __END__
2
+
3
+ require 'test/unit'
4
+
5
+ class ParseNumbersTest < Test::Unit::TestCase
6
+
7
+ def test_parse
8
+ strings = {1 => 'one',
9
+ 5 => 'five',
10
+ 10 => 'ten',
11
+ 11 => 'eleven',
12
+ 12 => 'twelve',
13
+ 13 => 'thirteen',
14
+ 14 => 'fourteen',
15
+ 15 => 'fifteen',
16
+ 16 => 'sixteen',
17
+ 17 => 'seventeen',
18
+ 18 => 'eighteen',
19
+ 19 => 'nineteen',
20
+ 20 => 'twenty',
21
+ 27 => 'twenty seven',
22
+ 31 => 'thirty-one',
23
+ 59 => 'fifty nine',
24
+ 100 => 'a hundred',
25
+ 100 => 'one hundred',
26
+ 150 => 'one hundred and fifty',
27
+ 150 => 'one fifty',
28
+ 200 => 'two-hundred',
29
+ 500 => '5 hundred',
30
+ 999 => 'nine hundred and ninety nine',
31
+ 1_000 => 'one thousand',
32
+ 1_200 => 'twelve hundred',
33
+ 1_200 => 'one thousand two hundred',
34
+ 17_000 => 'seventeen thousand',
35
+ 21_473 => 'twentyone-thousand-four-hundred-and-seventy-three',
36
+ 74_002 => 'seventy four thousand and two',
37
+ 99_999 => 'ninety nine thousand nine hundred ninety nine',
38
+ 100_000 => '100 thousand',
39
+ 250_000 => 'two hundred fifty thousand',
40
+ 1_000_000 => 'one million',
41
+ 1_250_007 => 'one million two hundred fifty thousand and seven',
42
+ 1_000_000_000 => 'one billion',
43
+ 1_000_000_001 => 'one billion and one'}
44
+
45
+ strings.keys.sort.each do |key|
46
+ assert_equal key, JW::NumberMagick.convert(strings[key])
47
+ end
48
+ end
49
+
50
+ end
data/test/suite.rb ADDED
@@ -0,0 +1,9 @@
1
+ require 'test/unit'
2
+
3
+ tests = Dir["#{File.dirname(__FILE__)}/test_*.rb"]
4
+ tests.delete_if { |o| o =~ /test_parsing/ }
5
+ tests.each do |file|
6
+ require file
7
+ end
8
+
9
+ require File.dirname(__FILE__) + '/test_parsing.rb'
@@ -0,0 +1,50 @@
1
+ require 'chronic'
2
+ require 'test/unit'
3
+
4
+ class TestChronic < Test::Unit::TestCase
5
+
6
+ def setup
7
+ # Wed Aug 16 14:00:00 UTC 2006
8
+ @now = Time.local(2006, 8, 16, 14, 0, 0, 0)
9
+ end
10
+
11
+ def test_post_normalize_am_pm_aliases
12
+ # affect wanted patterns
13
+
14
+ tokens = [Chronic::Token.new("5:00"), Chronic::Token.new("morning")]
15
+ tokens[0].tag(Chronic::RepeaterTime.new("5:00"))
16
+ tokens[1].tag(Chronic::RepeaterDayPortion.new(:morning))
17
+
18
+ assert_equal :morning, tokens[1].tags[0].type
19
+
20
+ tokens = Chronic.dealias_and_disambiguate_times(tokens, {})
21
+
22
+ assert_equal :am, tokens[1].tags[0].type
23
+ assert_equal 2, tokens.size
24
+
25
+ # don't affect unwanted patterns
26
+
27
+ tokens = [Chronic::Token.new("friday"), Chronic::Token.new("morning")]
28
+ tokens[0].tag(Chronic::RepeaterDayName.new(:friday))
29
+ tokens[1].tag(Chronic::RepeaterDayPortion.new(:morning))
30
+
31
+ assert_equal :morning, tokens[1].tags[0].type
32
+
33
+ tokens = Chronic.dealias_and_disambiguate_times(tokens, {})
34
+
35
+ assert_equal :morning, tokens[1].tags[0].type
36
+ assert_equal 2, tokens.size
37
+ end
38
+
39
+ def test_guess
40
+ span = Chronic::Span.new(Time.local(2006, 8, 16, 0), Time.local(2006, 8, 17, 0))
41
+ assert_equal Time.local(2006, 8, 16, 12), Chronic.guess(span)
42
+
43
+ span = Chronic::Span.new(Time.local(2006, 8, 16, 0), Time.local(2006, 8, 17, 0, 0, 1))
44
+ assert_equal Time.local(2006, 8, 16, 12), Chronic.guess(span)
45
+
46
+ span = Chronic::Span.new(Time.local(2006, 11), Time.local(2006, 12))
47
+ assert_equal Time.local(2006, 11, 16), Chronic.guess(span)
48
+ end
49
+
50
+ end
@@ -0,0 +1,110 @@
1
+ require 'chronic'
2
+ require 'test/unit'
3
+
4
+ class TestHandler < Test::Unit::TestCase
5
+
6
+ def setup
7
+ # Wed Aug 16 14:00:00 UTC 2006
8
+ @now = Time.local(2006, 8, 16, 14, 0, 0, 0)
9
+ end
10
+
11
+ def test_handler_class_1
12
+ handler = Chronic::Handler.new([:repeater], :handler)
13
+
14
+ tokens = [Chronic::Token.new('friday')]
15
+ tokens[0].tag(Chronic::RepeaterDayName.new(:friday))
16
+
17
+ assert handler.match(tokens, Chronic.definitions)
18
+
19
+ tokens << Chronic::Token.new('afternoon')
20
+ tokens[1].tag(Chronic::RepeaterDayPortion.new(:afternoon))
21
+
22
+ assert !handler.match(tokens, Chronic.definitions)
23
+ end
24
+
25
+ def test_handler_class_2
26
+ handler = Chronic::Handler.new([:repeater, :repeater?], :handler)
27
+
28
+ tokens = [Chronic::Token.new('friday')]
29
+ tokens[0].tag(Chronic::RepeaterDayName.new(:friday))
30
+
31
+ assert handler.match(tokens, Chronic.definitions)
32
+
33
+ tokens << Chronic::Token.new('afternoon')
34
+ tokens[1].tag(Chronic::RepeaterDayPortion.new(:afternoon))
35
+
36
+ assert handler.match(tokens, Chronic.definitions)
37
+
38
+ tokens << Chronic::Token.new('afternoon')
39
+ tokens[2].tag(Chronic::RepeaterDayPortion.new(:afternoon))
40
+
41
+ assert !handler.match(tokens, Chronic.definitions)
42
+ end
43
+
44
+ def test_handler_class_3
45
+ handler = Chronic::Handler.new([:repeater, 'time?'], :handler)
46
+
47
+ tokens = [Chronic::Token.new('friday')]
48
+ tokens[0].tag(Chronic::RepeaterDayName.new(:friday))
49
+
50
+ assert handler.match(tokens, Chronic.definitions)
51
+
52
+ tokens << Chronic::Token.new('afternoon')
53
+ tokens[1].tag(Chronic::RepeaterDayPortion.new(:afternoon))
54
+
55
+ assert !handler.match(tokens, Chronic.definitions)
56
+ end
57
+
58
+ def test_handler_class_4
59
+ handler = Chronic::Handler.new([:repeater_month_name, :scalar_day, 'time?'], :handler)
60
+
61
+ tokens = [Chronic::Token.new('may')]
62
+ tokens[0].tag(Chronic::RepeaterMonthName.new(:may))
63
+
64
+ assert !handler.match(tokens, Chronic.definitions)
65
+
66
+ tokens << Chronic::Token.new('27')
67
+ tokens[1].tag(Chronic::ScalarDay.new(27))
68
+
69
+ assert handler.match(tokens, Chronic.definitions)
70
+ end
71
+
72
+ def test_handler_class_5
73
+ handler = Chronic::Handler.new([:repeater, 'time?'], :handler)
74
+
75
+ tokens = [Chronic::Token.new('friday')]
76
+ tokens[0].tag(Chronic::RepeaterDayName.new(:friday))
77
+
78
+ assert handler.match(tokens, Chronic.definitions)
79
+
80
+ tokens << Chronic::Token.new('5:00')
81
+ tokens[1].tag(Chronic::RepeaterTime.new('5:00'))
82
+
83
+ assert handler.match(tokens, Chronic.definitions)
84
+
85
+ tokens << Chronic::Token.new('pm')
86
+ tokens[2].tag(Chronic::RepeaterDayPortion.new(:pm))
87
+
88
+ assert handler.match(tokens, Chronic.definitions)
89
+ end
90
+
91
+ def test_handler_class_6
92
+ handler = Chronic::Handler.new([:scalar, :repeater, :pointer], :handler)
93
+
94
+ tokens = [Chronic::Token.new('3'),
95
+ Chronic::Token.new('years'),
96
+ Chronic::Token.new('past')]
97
+
98
+ tokens[0].tag(Chronic::Scalar.new(3))
99
+ tokens[1].tag(Chronic::RepeaterYear.new(:year))
100
+ tokens[2].tag(Chronic::Pointer.new(:past))
101
+
102
+ assert handler.match(tokens, Chronic.definitions)
103
+ end
104
+
105
+ def test_constantize
106
+ handler = Chronic::Handler.new([], :handler)
107
+ assert_equal Chronic::RepeaterTime, handler.constantize(:repeater_time)
108
+ end
109
+
110
+ end
@@ -0,0 +1,52 @@
1
+ require 'chronic'
2
+ require 'test/unit'
3
+
4
+ class TestRepeaterDayName < Test::Unit::TestCase
5
+
6
+ def setup
7
+ @now = Time.local(2006, 8, 16, 14, 0, 0, 0)
8
+ end
9
+
10
+ def test_match
11
+ token = Chronic::Token.new('saturday')
12
+ repeater = Chronic::Repeater.scan_for_day_names(token)
13
+ assert_equal Chronic::RepeaterDayName, repeater.class
14
+ assert_equal :saturday, repeater.type
15
+
16
+ token = Chronic::Token.new('sunday')
17
+ repeater = Chronic::Repeater.scan_for_day_names(token)
18
+ assert_equal Chronic::RepeaterDayName, repeater.class
19
+ assert_equal :sunday, repeater.type
20
+ end
21
+
22
+ def test_next_future
23
+ mondays = Chronic::RepeaterDayName.new(:monday)
24
+ mondays.start = @now
25
+
26
+ span = mondays.next(:future)
27
+
28
+ assert_equal Time.local(2006, 8, 21), span.begin
29
+ assert_equal Time.local(2006, 8, 22), span.end
30
+
31
+ span = mondays.next(:future)
32
+
33
+ assert_equal Time.local(2006, 8, 28), span.begin
34
+ assert_equal Time.local(2006, 8, 29), span.end
35
+ end
36
+
37
+ def test_next_past
38
+ mondays = Chronic::RepeaterDayName.new(:monday)
39
+ mondays.start = @now
40
+
41
+ span = mondays.next(:past)
42
+
43
+ assert_equal Time.local(2006, 8, 14), span.begin
44
+ assert_equal Time.local(2006, 8, 15), span.end
45
+
46
+ span = mondays.next(:past)
47
+
48
+ assert_equal Time.local(2006, 8, 7), span.begin
49
+ assert_equal Time.local(2006, 8, 8), span.end
50
+ end
51
+
52
+ end
@@ -0,0 +1,63 @@
1
+ require 'chronic'
2
+ require 'test/unit'
3
+
4
+ class TestRepeaterFortnight < Test::Unit::TestCase
5
+
6
+ def setup
7
+ @now = Time.local(2006, 8, 16, 14, 0, 0, 0)
8
+ end
9
+
10
+ def test_next_future
11
+ fortnights = Chronic::RepeaterFortnight.new(:fortnight)
12
+ fortnights.start = @now
13
+
14
+ next_fortnight = fortnights.next(:future)
15
+ assert_equal Time.local(2006, 8, 20), next_fortnight.begin
16
+ assert_equal Time.local(2006, 9, 3), next_fortnight.end
17
+
18
+ next_next_fortnight = fortnights.next(:future)
19
+ assert_equal Time.local(2006, 9, 3), next_next_fortnight.begin
20
+ assert_equal Time.local(2006, 9, 17), next_next_fortnight.end
21
+ end
22
+
23
+ def test_next_past
24
+ fortnights = Chronic::RepeaterFortnight.new(:fortnight)
25
+ fortnights.start = @now
26
+
27
+ last_fortnight = fortnights.next(:past)
28
+ assert_equal Time.local(2006, 7, 30), last_fortnight.begin
29
+ assert_equal Time.local(2006, 8, 13), last_fortnight.end
30
+
31
+ last_last_fortnight = fortnights.next(:past)
32
+ assert_equal Time.local(2006, 7, 16), last_last_fortnight.begin
33
+ assert_equal Time.local(2006, 7, 30), last_last_fortnight.end
34
+ end
35
+
36
+ def test_this_future
37
+ fortnights = Chronic::RepeaterFortnight.new(:fortnight)
38
+ fortnights.start = @now
39
+
40
+ this_fortnight = fortnights.this(:future)
41
+ assert_equal Time.local(2006, 8, 16, 15), this_fortnight.begin
42
+ assert_equal Time.local(2006, 8, 27), this_fortnight.end
43
+ end
44
+
45
+ def test_this_past
46
+ fortnights = Chronic::RepeaterFortnight.new(:fortnight)
47
+ fortnights.start = @now
48
+
49
+ this_fortnight = fortnights.this(:past)
50
+ assert_equal Time.local(2006, 8, 13, 0), this_fortnight.begin
51
+ assert_equal Time.local(2006, 8, 16, 14), this_fortnight.end
52
+ end
53
+
54
+ def test_offset
55
+ span = Chronic::Span.new(@now, @now + 1)
56
+
57
+ offset_span = Chronic::RepeaterWeek.new(:week).offset(span, 3, :future)
58
+
59
+ assert_equal Time.local(2006, 9, 6, 14), offset_span.begin
60
+ assert_equal Time.local(2006, 9, 6, 14, 0, 1), offset_span.end
61
+ end
62
+
63
+ end