gitlab-chronic 0.10.3

Sign up to get free protection for your applications and to get access to all the features.
Files changed (75) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +8 -0
  3. data/.gitlab-ci.yml +14 -0
  4. data/.travis.yml +10 -0
  5. data/Gemfile +3 -0
  6. data/HISTORY.md +252 -0
  7. data/LICENSE +21 -0
  8. data/README.md +188 -0
  9. data/Rakefile +68 -0
  10. data/chronic.gemspec +25 -0
  11. data/lib/chronic.rb +155 -0
  12. data/lib/chronic/date.rb +81 -0
  13. data/lib/chronic/definition.rb +128 -0
  14. data/lib/chronic/dictionary.rb +36 -0
  15. data/lib/chronic/handler.rb +97 -0
  16. data/lib/chronic/handlers.rb +672 -0
  17. data/lib/chronic/mini_date.rb +38 -0
  18. data/lib/chronic/parser.rb +222 -0
  19. data/lib/chronic/repeaters/repeater_day.rb +54 -0
  20. data/lib/chronic/repeaters/repeater_day_name.rb +53 -0
  21. data/lib/chronic/repeaters/repeater_day_portion.rb +109 -0
  22. data/lib/chronic/repeaters/repeater_fortnight.rb +72 -0
  23. data/lib/chronic/repeaters/repeater_hour.rb +59 -0
  24. data/lib/chronic/repeaters/repeater_minute.rb +59 -0
  25. data/lib/chronic/repeaters/repeater_month.rb +80 -0
  26. data/lib/chronic/repeaters/repeater_month_name.rb +95 -0
  27. data/lib/chronic/repeaters/repeater_quarter.rb +59 -0
  28. data/lib/chronic/repeaters/repeater_quarter_name.rb +40 -0
  29. data/lib/chronic/repeaters/repeater_season.rb +111 -0
  30. data/lib/chronic/repeaters/repeater_season_name.rb +43 -0
  31. data/lib/chronic/repeaters/repeater_second.rb +43 -0
  32. data/lib/chronic/repeaters/repeater_time.rb +159 -0
  33. data/lib/chronic/repeaters/repeater_week.rb +76 -0
  34. data/lib/chronic/repeaters/repeater_weekday.rb +86 -0
  35. data/lib/chronic/repeaters/repeater_weekend.rb +67 -0
  36. data/lib/chronic/repeaters/repeater_year.rb +78 -0
  37. data/lib/chronic/season.rb +26 -0
  38. data/lib/chronic/span.rb +31 -0
  39. data/lib/chronic/tag.rb +89 -0
  40. data/lib/chronic/tags/grabber.rb +29 -0
  41. data/lib/chronic/tags/ordinal.rb +52 -0
  42. data/lib/chronic/tags/pointer.rb +28 -0
  43. data/lib/chronic/tags/repeater.rb +160 -0
  44. data/lib/chronic/tags/scalar.rb +89 -0
  45. data/lib/chronic/tags/separator.rb +123 -0
  46. data/lib/chronic/tags/sign.rb +35 -0
  47. data/lib/chronic/tags/time_zone.rb +32 -0
  48. data/lib/chronic/time.rb +40 -0
  49. data/lib/chronic/token.rb +61 -0
  50. data/lib/chronic/tokenizer.rb +38 -0
  51. data/lib/chronic/version.rb +3 -0
  52. data/test/helper.rb +12 -0
  53. data/test/test_chronic.rb +203 -0
  54. data/test/test_daylight_savings.rb +122 -0
  55. data/test/test_handler.rb +128 -0
  56. data/test/test_mini_date.rb +32 -0
  57. data/test/test_parsing.rb +1537 -0
  58. data/test/test_repeater_day_name.rb +51 -0
  59. data/test/test_repeater_day_portion.rb +254 -0
  60. data/test/test_repeater_fortnight.rb +62 -0
  61. data/test/test_repeater_hour.rb +68 -0
  62. data/test/test_repeater_minute.rb +34 -0
  63. data/test/test_repeater_month.rb +50 -0
  64. data/test/test_repeater_month_name.rb +56 -0
  65. data/test/test_repeater_quarter.rb +70 -0
  66. data/test/test_repeater_quarter_name.rb +198 -0
  67. data/test/test_repeater_season.rb +40 -0
  68. data/test/test_repeater_time.rb +88 -0
  69. data/test/test_repeater_week.rb +115 -0
  70. data/test/test_repeater_weekday.rb +55 -0
  71. data/test/test_repeater_weekend.rb +74 -0
  72. data/test/test_repeater_year.rb +69 -0
  73. data/test/test_span.rb +23 -0
  74. data/test/test_token.rb +31 -0
  75. metadata +215 -0
@@ -0,0 +1,67 @@
1
+ module Chronic
2
+ class RepeaterWeekend < Repeater #:nodoc:
3
+ WEEKEND_SECONDS = 172_800 # (2 * 24 * 60 * 60)
4
+
5
+ def initialize(type, width = nil, options = {})
6
+ super
7
+ @current_week_start = nil
8
+ end
9
+
10
+ def next(pointer)
11
+ super
12
+
13
+ unless @current_week_start
14
+ case pointer
15
+ when :future
16
+ saturday_repeater = RepeaterDayName.new(:saturday)
17
+ saturday_repeater.start = @now
18
+ next_saturday_span = saturday_repeater.next(:future)
19
+ @current_week_start = next_saturday_span.begin
20
+ when :past
21
+ saturday_repeater = RepeaterDayName.new(:saturday)
22
+ saturday_repeater.start = (@now + RepeaterDay::DAY_SECONDS)
23
+ last_saturday_span = saturday_repeater.next(:past)
24
+ @current_week_start = last_saturday_span.begin
25
+ end
26
+ else
27
+ direction = pointer == :future ? 1 : -1
28
+ @current_week_start += direction * RepeaterWeek::WEEK_SECONDS
29
+ end
30
+
31
+ Span.new(@current_week_start, @current_week_start + WEEKEND_SECONDS)
32
+ end
33
+
34
+ def this(pointer = :future)
35
+ super
36
+
37
+ case pointer
38
+ when :future, :none
39
+ saturday_repeater = RepeaterDayName.new(:saturday)
40
+ saturday_repeater.start = @now
41
+ this_saturday_span = saturday_repeater.this(:future)
42
+ Span.new(this_saturday_span.begin, this_saturday_span.begin + WEEKEND_SECONDS)
43
+ when :past
44
+ saturday_repeater = RepeaterDayName.new(:saturday)
45
+ saturday_repeater.start = @now
46
+ last_saturday_span = saturday_repeater.this(:past)
47
+ Span.new(last_saturday_span.begin, last_saturday_span.begin + WEEKEND_SECONDS)
48
+ end
49
+ end
50
+
51
+ def offset(span, amount, pointer)
52
+ direction = pointer == :future ? 1 : -1
53
+ weekend = RepeaterWeekend.new(:weekend)
54
+ weekend.start = span.begin
55
+ start = weekend.next(pointer).begin + (amount - 1) * direction * RepeaterWeek::WEEK_SECONDS
56
+ Span.new(start, start + (span.end - span.begin))
57
+ end
58
+
59
+ def width
60
+ WEEKEND_SECONDS
61
+ end
62
+
63
+ def to_s
64
+ super << '-weekend'
65
+ end
66
+ end
67
+ end
@@ -0,0 +1,78 @@
1
+ module Chronic
2
+ class RepeaterYear < Repeater #:nodoc:
3
+ YEAR_SECONDS = 31536000 # 365 * 24 * 60 * 60
4
+
5
+ def initialize(type, width = nil, options = {})
6
+ super
7
+ @current_year_start = nil
8
+ end
9
+
10
+ def next(pointer)
11
+ super
12
+
13
+ unless @current_year_start
14
+ case pointer
15
+ when :future
16
+ @current_year_start = Chronic.construct(@now.year + 1)
17
+ when :past
18
+ @current_year_start = Chronic.construct(@now.year - 1)
19
+ end
20
+ else
21
+ diff = pointer == :future ? 1 : -1
22
+ @current_year_start = Chronic.construct(@current_year_start.year + diff)
23
+ end
24
+
25
+ Span.new(@current_year_start, Chronic.construct(@current_year_start.year + 1))
26
+ end
27
+
28
+ def this(pointer = :future)
29
+ super
30
+
31
+ case pointer
32
+ when :future
33
+ this_year_start = Chronic.construct(@now.year, @now.month, @now.day + 1)
34
+ this_year_end = Chronic.construct(@now.year + 1, 1, 1)
35
+ when :past
36
+ this_year_start = Chronic.construct(@now.year, 1, 1)
37
+ this_year_end = Chronic.construct(@now.year, @now.month, @now.day)
38
+ when :none
39
+ this_year_start = Chronic.construct(@now.year, 1, 1)
40
+ this_year_end = Chronic.construct(@now.year + 1, 1, 1)
41
+ end
42
+
43
+ Span.new(this_year_start, this_year_end)
44
+ end
45
+
46
+ def offset(span, amount, pointer)
47
+ direction = pointer == :future ? 1 : -1
48
+ new_begin = build_offset_time(span.begin, amount, direction)
49
+ new_end = build_offset_time(span.end, amount, direction)
50
+ Span.new(new_begin, new_end)
51
+ end
52
+
53
+ def width
54
+ YEAR_SECONDS
55
+ end
56
+
57
+ def to_s
58
+ super << '-year'
59
+ end
60
+
61
+ private
62
+
63
+ def build_offset_time(time, amount, direction)
64
+ year = time.year + (amount * direction)
65
+ days = month_days(year, time.month)
66
+ day = time.day > days ? days : time.day
67
+ Chronic.construct(year, time.month, day, time.hour, time.min, time.sec)
68
+ end
69
+
70
+ def month_days(year, month)
71
+ if ::Date.leap?(year)
72
+ RepeaterMonth::MONTH_DAYS_LEAP[month - 1]
73
+ else
74
+ RepeaterMonth::MONTH_DAYS[month - 1]
75
+ end
76
+ end
77
+ end
78
+ end
@@ -0,0 +1,26 @@
1
+ module Chronic
2
+ class Season
3
+
4
+ attr_reader :start
5
+ attr_reader :end
6
+
7
+ def initialize(start_date, end_date)
8
+ @start = start_date
9
+ @end = end_date
10
+ end
11
+
12
+ def self.find_next_season(season, pointer)
13
+ lookup = [:spring, :summer, :autumn, :winter]
14
+ next_season_num = (lookup.index(season) + 1 * pointer) % 4
15
+ lookup[next_season_num]
16
+ end
17
+
18
+ def self.season_after(season)
19
+ find_next_season(season, +1)
20
+ end
21
+
22
+ def self.season_before(season)
23
+ find_next_season(season, -1)
24
+ end
25
+ end
26
+ end
@@ -0,0 +1,31 @@
1
+ module Chronic
2
+ # A Span represents a range of time. Since this class extends
3
+ # Range, you can use #begin and #end to get the beginning and
4
+ # ending times of the span (they will be of class Time)
5
+ class Span < Range
6
+ # Returns the width of this span in seconds
7
+ def width
8
+ (self.end - self.begin).to_i
9
+ end
10
+
11
+ # Add a number of seconds to this span, returning the
12
+ # resulting Span
13
+ def +(seconds)
14
+ Span.new(self.begin + seconds, self.end + seconds)
15
+ end
16
+
17
+ # Subtract a number of seconds to this span, returning the
18
+ # resulting Span
19
+ def -(seconds)
20
+ self + -seconds
21
+ end
22
+
23
+ # Prints this span in a nice fashion
24
+ def to_s
25
+ '(' << self.begin.to_s << '..' << self.end.to_s << ')'
26
+ end
27
+
28
+ alias :cover? :include? if RUBY_VERSION =~ /^1.8/
29
+
30
+ end
31
+ end
@@ -0,0 +1,89 @@
1
+ module Chronic
2
+ # Tokens are tagged with subclassed instances of this class when
3
+ # they match specific criteria.
4
+ class Tag
5
+
6
+ attr_accessor :type
7
+ attr_accessor :width
8
+
9
+ # type - The Symbol type of this tag.
10
+ def initialize(type, width = nil, options = {})
11
+ @type = type
12
+ @width = width
13
+ @options = options
14
+ end
15
+
16
+ # time - Set the start Time for this Tag.
17
+ def start=(time)
18
+ @now = time
19
+ end
20
+
21
+ class << self
22
+ # Public: Scan an Array of Token objects.
23
+ #
24
+ # tokens - An Array of tokens to scan.
25
+ # options - The Hash of options specified in Chronic::parse.
26
+ #
27
+ # Returns an Array of tokens.
28
+ def scan(tokens, options)
29
+ raise NotImplementedError, 'Subclasses must override scan!'
30
+ end
31
+
32
+ private
33
+
34
+ # Internal: Match item and create respective Tag class.
35
+ # When item is a Symbol it will match only when it's identical to Token.
36
+ # When it's a String it will case-insesitively match partial token,
37
+ # but only if item's last char have different type than token text's next char.
38
+ # When item is a Regexp it will match by it.
39
+ #
40
+ # item - Item to match. It can be String, Symbol or Regexp.
41
+ # klass - Tag class to create.
42
+ # symbol - Tag type as symbol or string to pass to Tag class.
43
+ # token - Token to match against.
44
+ # options - Options as hash to pass to Tag class.
45
+ #
46
+ # Returns an instance of specified Tag klass or nil if item didn't match.
47
+ def match_item(item, klass, symbol, token, options)
48
+ match = false
49
+ case item
50
+ when String
51
+ item_type = Tokenizer.char_type(item.to_s[-1])
52
+ text_type = token.text[token.position+item.length]
53
+ text_type = Tokenizer.char_type(text_type) if text_type
54
+ compatible = true
55
+ compatible = item_type != text_type if text_type && (item_type == :letter || item_type == :digit)
56
+ match = compatible && token.text[token.position, item.length].casecmp(item).zero?
57
+ when Symbol
58
+ match = token.word == item.to_s
59
+ when Regexp
60
+ match = token.word =~ item
61
+ end
62
+ return klass.new(symbol, nil, options) if match
63
+ nil
64
+ end
65
+
66
+ # Internal: Scan for specified items and create respective Tag class.
67
+ #
68
+ # token - Token to match against.
69
+ # klass - Tag class to create.
70
+ # items - Item(s) to match. It can be Hash, String, Symbol or Regexp.
71
+ # Hash keys can be String, Symbol or Regexp, but values much be Symbol.
72
+ # options - Options as hash to pass to Tag class.
73
+ #
74
+ # Returns an instance of specified Tag klass or nil if item(s) didn't match.
75
+ def scan_for(token, klass, items, options = {})
76
+ if items.kind_of?(Hash)
77
+ items.each do |item, symbol|
78
+ scanned = match_item(item, klass, symbol, token, options)
79
+ return scanned if scanned
80
+ end
81
+ else
82
+ return match_item(items, klass, token.word, token, options)
83
+ end
84
+ nil
85
+ end
86
+ end
87
+
88
+ end
89
+ end
@@ -0,0 +1,29 @@
1
+ module Chronic
2
+ class Grabber < Tag
3
+
4
+ # Scan an Array of Tokens and apply any necessary Grabber tags to
5
+ # each token.
6
+ #
7
+ # tokens - An Array of Token objects to scan.
8
+ # options - The Hash of options specified in Chronic::parse.
9
+ #
10
+ # Returns an Array of Token objects.
11
+ def self.scan(tokens, options)
12
+ tokens.each do |token|
13
+ token.tag scan_for(token, self, patterns, options)
14
+ end
15
+ end
16
+
17
+ def self.patterns
18
+ @@patterns ||= {
19
+ 'last' => :last,
20
+ 'this' => :this,
21
+ 'next' => :next
22
+ }
23
+ end
24
+
25
+ def to_s
26
+ 'grabber-' << @type.to_s
27
+ end
28
+ end
29
+ end
@@ -0,0 +1,52 @@
1
+ module Chronic
2
+ class Ordinal < Tag
3
+
4
+ # Scan an Array of Token objects and apply any necessary Ordinal
5
+ # tags to each token.
6
+ #
7
+ # tokens - An Array of tokens to scan.
8
+ # options - The Hash of options specified in Chronic::parse.
9
+ #
10
+ # Returns an Array of tokens.
11
+ def self.scan(tokens, options)
12
+ tokens.each_index do |i|
13
+ if tokens[i].word =~ /^(\d+)(st|nd|rd|th|\.)$/
14
+ width = $1.length
15
+ ordinal = $1.to_i
16
+ tokens[i].tag(Ordinal.new(ordinal, width))
17
+ tokens[i].tag(OrdinalDay.new(ordinal, width)) if Chronic::Date::could_be_day?(ordinal, width)
18
+ tokens[i].tag(OrdinalMonth.new(ordinal, width)) if Chronic::Date::could_be_month?(ordinal, width)
19
+ if Chronic::Date::could_be_year?(ordinal, width)
20
+ year = Chronic::Date::make_year(ordinal, options[:ambiguous_year_future_bias])
21
+ tokens[i].tag(OrdinalYear.new(year.to_i, width))
22
+ end
23
+ elsif tokens[i].word =~ /^second$/
24
+ tokens[i].tag(Ordinal.new(2, 1))
25
+ end
26
+ end
27
+ end
28
+
29
+ def to_s
30
+ 'ordinal'
31
+ end
32
+ end
33
+
34
+ class OrdinalDay < Ordinal #:nodoc:
35
+ def to_s
36
+ super << '-day-' << @type.to_s
37
+ end
38
+ end
39
+
40
+ class OrdinalMonth < Ordinal #:nodoc:
41
+ def to_s
42
+ super << '-month-' << @type.to_s
43
+ end
44
+ end
45
+
46
+ class OrdinalYear < Ordinal #:nodoc:
47
+ def to_s
48
+ super << '-year-' << @type.to_s
49
+ end
50
+ end
51
+
52
+ end
@@ -0,0 +1,28 @@
1
+ module Chronic
2
+ class Pointer < Tag
3
+
4
+ # Scan an Array of Token objects and apply any necessary Pointer
5
+ # tags to each token.
6
+ #
7
+ # tokens - An Array of tokens to scan.
8
+ # options - The Hash of options specified in Chronic::parse.
9
+ #
10
+ # Returns an Array of tokens.
11
+ def self.scan(tokens, options)
12
+ tokens.each do |token|
13
+ token.tag scan_for(token, self, patterns, options)
14
+ end
15
+ end
16
+
17
+ def self.patterns
18
+ @@patterns ||= {
19
+ 'past' => :past,
20
+ /^future|in$/i => :future,
21
+ }
22
+ end
23
+
24
+ def to_s
25
+ 'pointer-' << @type.to_s
26
+ end
27
+ end
28
+ end
@@ -0,0 +1,160 @@
1
+ module Chronic
2
+ class Repeater < Tag
3
+
4
+ # Scan an Array of Token objects and apply any necessary Repeater
5
+ # tags to each token.
6
+ #
7
+ # tokens - An Array of tokens to scan.
8
+ # options - The Hash of options specified in Chronic::parse.
9
+ #
10
+ # Returns an Array of tokens.
11
+ def self.scan(tokens, options)
12
+ tokens.each do |token|
13
+ token.tag scan_for_quarter_names(token, options)
14
+ token.tag scan_for_season_names(token, options)
15
+ token.tag scan_for_month_names(token, options)
16
+ token.tag scan_for_day_names(token, options)
17
+ token.tag scan_for_day_portions(token, options)
18
+ token.tag scan_for_times(token, options)
19
+ token.tag scan_for_units(token, options)
20
+ end
21
+ end
22
+
23
+ # token - The Token object we want to scan.
24
+ #
25
+ # Returns a new Repeater object.
26
+ def self.scan_for_quarter_names(token, options = {})
27
+ scan_for token, RepeaterQuarterName,
28
+ {
29
+ /^q1$/ => :q1,
30
+ /^q2$/ => :q2,
31
+ /^q3$/ => :q3,
32
+ /^q4$/ => :q4
33
+ }, options
34
+ end
35
+
36
+ # token - The Token object we want to scan.
37
+ #
38
+ # Returns a new Repeater object.
39
+ def self.scan_for_season_names(token, options = {})
40
+ scan_for token, RepeaterSeasonName,
41
+ {
42
+ /^springs?$/ => :spring,
43
+ /^summers?$/ => :summer,
44
+ /^(autumn)|(fall)s?$/ => :autumn,
45
+ /^winters?$/ => :winter
46
+ }, options
47
+ end
48
+
49
+ # token - The Token object we want to scan.
50
+ #
51
+ # Returns a new Repeater object.
52
+ def self.scan_for_month_names(token, options = {})
53
+ scan_for token, RepeaterMonthName,
54
+ {
55
+ /^jan[:\.]?(uary)?$/ => :january,
56
+ /^feb[:\.]?(ruary)?$/ => :february,
57
+ /^mar[:\.]?(ch)?$/ => :march,
58
+ /^apr[:\.]?(il)?$/ => :april,
59
+ /^may$/ => :may,
60
+ /^jun[:\.]?e?$/ => :june,
61
+ /^jul[:\.]?y?$/ => :july,
62
+ /^aug[:\.]?(ust)?$/ => :august,
63
+ /^sep[:\.]?(t[:\.]?|tember)?$/ => :september,
64
+ /^oct[:\.]?(ober)?$/ => :october,
65
+ /^nov[:\.]?(ember)?$/ => :november,
66
+ /^dec[:\.]?(ember)?$/ => :december
67
+ }, options
68
+ end
69
+
70
+ # token - The Token object we want to scan.
71
+ #
72
+ # Returns a new Repeater object.
73
+ def self.scan_for_day_names(token, options = {})
74
+ scan_for token, RepeaterDayName,
75
+ {
76
+ /^m[ou]n(day)?$/ => :monday,
77
+ /^t(ue|eu|oo|u)s?(day)?$/ => :tuesday,
78
+ /^we(d|dnes|nds|nns)(day)?$/ => :wednesday,
79
+ /^th(u|ur|urs|ers)(day)?$/ => :thursday,
80
+ /^fr[iy](day)?$/ => :friday,
81
+ /^sat(t?[ue]rday)?$/ => :saturday,
82
+ /^su[nm](day)?$/ => :sunday
83
+ }, options
84
+ end
85
+
86
+ # token - The Token object we want to scan.
87
+ #
88
+ # Returns a new Repeater object.
89
+ def self.scan_for_day_portions(token, options = {})
90
+ scan_for token, RepeaterDayPortion,
91
+ {
92
+ /^ams?$/ => :am,
93
+ /^pms?$/ => :pm,
94
+ /^mornings?$/ => :morning,
95
+ /^afternoons?$/ => :afternoon,
96
+ /^evenings?$/ => :evening,
97
+ /^(night|nite)s?$/ => :night
98
+ }, options
99
+ end
100
+
101
+ # token - The Token object we want to scan.
102
+ #
103
+ # Returns a new Repeater object.
104
+ def self.scan_for_times(token, options = {})
105
+ scan_for token, RepeaterTime, /^\d{1,2}(:?\d{1,2})?([\.:]?\d{1,2}([\.:]\d{1,6})?)?$/, options
106
+ end
107
+
108
+ # token - The Token object we want to scan.
109
+ #
110
+ # Returns a new Repeater object.
111
+ def self.scan_for_units(token, options = {})
112
+ {
113
+ /^years?$/ => :year,
114
+ /^q$/ => :quarter,
115
+ /^seasons?$/ => :season,
116
+ /^months?$/ => :month,
117
+ /^fortnights?$/ => :fortnight,
118
+ /^weeks?$/ => :week,
119
+ /^weekends?$/ => :weekend,
120
+ /^(week|business)days?$/ => :weekday,
121
+ /^days?$/ => :day,
122
+ /^hrs?$/ => :hour,
123
+ /^hours?$/ => :hour,
124
+ /^mins?$/ => :minute,
125
+ /^minutes?$/ => :minute,
126
+ /^secs?$/ => :second,
127
+ /^seconds?$/ => :second
128
+ }.each do |item, symbol|
129
+ if item =~ token.word
130
+ klass_name = 'Repeater' + symbol.to_s.capitalize
131
+ klass = Chronic.const_get(klass_name)
132
+ return klass.new(symbol, nil, options)
133
+ end
134
+ end
135
+ return nil
136
+ end
137
+
138
+ def <=>(other)
139
+ width <=> other.width
140
+ end
141
+
142
+ # returns the width (in seconds or months) of this repeatable.
143
+ def width
144
+ raise('Repeater#width must be overridden in subclasses')
145
+ end
146
+
147
+ # returns the next occurance of this repeatable.
148
+ def next(pointer)
149
+ raise('Start point must be set before calling #next') unless @now
150
+ end
151
+
152
+ def this(pointer)
153
+ raise('Start point must be set before calling #this') unless @now
154
+ end
155
+
156
+ def to_s
157
+ 'repeater'
158
+ end
159
+ end
160
+ end