hizuke 0.0.4 → 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,495 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Hizuke
4
+ # Module for working with time patterns
5
+ module TimePatternMatcher
6
+ include Constants
7
+
8
+ # Extract time references from the text
9
+ # @param text [String] the original text
10
+ # @return [Array] an array containing the extracted time and clean text
11
+ def extract_time_references(text)
12
+ clean_text = text
13
+ extracted_time = nil
14
+
15
+ # Try each type of time pattern
16
+ result = try_word_time_patterns(clean_text) ||
17
+ try_numeric_time_pattern(clean_text)
18
+
19
+ extracted_time, clean_text = result if result
20
+
21
+ [extracted_time, clean_text]
22
+ end
23
+
24
+ # Try to match word-based time patterns
25
+ # @param text [String] the original text
26
+ # @return [Array, nil] an array containing the extracted time and clean text, or nil if no match
27
+ def try_word_time_patterns(text)
28
+ match_noon_pattern(text) ||
29
+ match_midnight_pattern(text) ||
30
+ match_morning_pattern(text) ||
31
+ match_evening_pattern(text)
32
+ end
33
+
34
+ # Match the noon pattern in the text
35
+ # @param text [String] the original text
36
+ # @return [Array, nil] an array containing the extracted time and clean text, or nil if no match
37
+ def match_noon_pattern(text)
38
+ match_and_process(text, NOON_PATTERN) do
39
+ TimeOfDay.new(12, 0, 0)
40
+ end
41
+ end
42
+
43
+ # Match the midnight pattern in the text
44
+ # @param text [String] the original text
45
+ # @return [Array, nil] an array containing the extracted time and clean text, or nil if no match
46
+ def match_midnight_pattern(text)
47
+ match_and_process(text, MIDNIGHT_PATTERN) do
48
+ TimeOfDay.new(0, 0, 0)
49
+ end
50
+ end
51
+
52
+ # Match the morning pattern in the text
53
+ # @param text [String] the original text
54
+ # @return [Array, nil] an array containing the extracted time and clean text, or nil if no match
55
+ def match_morning_pattern(text)
56
+ match_and_process(text, MORNING_PATTERN) do
57
+ config = Hizuke.configuration
58
+ TimeOfDay.new(config.morning_time[:hour], config.morning_time[:min], 0)
59
+ end
60
+ end
61
+
62
+ # Match the evening pattern in the text
63
+ # @param text [String] the original text
64
+ # @return [Array, nil] an array containing the extracted time and clean text, or nil if no match
65
+ def match_evening_pattern(text)
66
+ match_and_process(text, EVENING_PATTERN) do
67
+ config = Hizuke.configuration
68
+ TimeOfDay.new(config.evening_time[:hour], config.evening_time[:min], 0)
69
+ end
70
+ end
71
+
72
+ # Match a pattern and process it with the given block
73
+ # @param text [String] the original text
74
+ # @param pattern [Regexp] the pattern to match
75
+ # @yield a block that creates a TimeOfDay object
76
+ # @return [Array, nil] an array containing the extracted time and clean text, or nil if no match
77
+ def match_and_process(text, pattern)
78
+ return nil unless (match = text.match(pattern))
79
+
80
+ time = yield
81
+ clean_text = text.gsub(match[0], '').strip
82
+ [time, clean_text]
83
+ end
84
+
85
+ # Try to match numeric time pattern
86
+ # @param text [String] the original text
87
+ # @return [Array, nil] an array containing the extracted time and clean text, or nil if no match
88
+ def try_numeric_time_pattern(text)
89
+ if (time_match = text.match(TIME_PATTERN))
90
+ extracted_time = process_time_match(time_match)
91
+ # Remove the time expression from the text
92
+ clean_text = text.gsub(time_match[0], '').strip
93
+ return [extracted_time, clean_text]
94
+ end
95
+
96
+ nil
97
+ end
98
+
99
+ # Process a time match and create a TimeOfDay object
100
+ # @param time_match [MatchData] the regex match data
101
+ # @return [TimeOfDay] the created time of day object
102
+ def process_time_match(time_match)
103
+ hour = parse_hour(time_match[1])
104
+ min = parse_minute(time_match[2])
105
+ sec = parse_second(time_match[3])
106
+
107
+ # Adjust for AM/PM
108
+ hour = adjust_hour_for_meridiem(hour, time_match[4])
109
+
110
+ TimeOfDay.new(hour, min, sec)
111
+ end
112
+
113
+ # Parse hour from match data
114
+ # @param hour_str [String, nil] the hour part of the match
115
+ # @return [Integer] the parsed hour
116
+ def parse_hour(hour_str)
117
+ hour_str.to_i
118
+ end
119
+
120
+ # Parse minute from match data
121
+ # @param min_str [String, nil] the minute part of the match
122
+ # @return [Integer] the parsed minute
123
+ def parse_minute(min_str)
124
+ min_str ? min_str.to_i : 0
125
+ end
126
+
127
+ # Parse second from match data
128
+ # @param sec_str [String, nil] the second part of the match
129
+ # @return [Integer] the parsed second
130
+ def parse_second(sec_str)
131
+ sec_str ? sec_str.to_i : 0
132
+ end
133
+
134
+ # Adjust hour based on AM/PM designation
135
+ # @param hour [Integer] the hour to adjust
136
+ # @param meridiem [String, nil] the meridiem designation (am/pm)
137
+ # @return [Integer] the adjusted hour
138
+ def adjust_hour_for_meridiem(hour, meridiem)
139
+ if meridiem&.downcase == 'pm' && hour < 12
140
+ hour + 12
141
+ elsif meridiem&.downcase == 'am' && hour == 12
142
+ 0
143
+ else
144
+ hour
145
+ end
146
+ end
147
+ end
148
+
149
+ # Module for handling day of week patterns
150
+ module DayOfWeekPatternMatcher
151
+ include Constants
152
+
153
+ # Check for day of week patterns (this Monday, next Tuesday, last Friday, etc.)
154
+ # @param text [String] the text to check
155
+ # @return [Hizuke::Result, nil] the result or nil if no match
156
+ def check_day_of_week_patterns(text)
157
+ # Try each day of week pattern
158
+ check_this_day_pattern(text) ||
159
+ check_next_day_pattern(text) ||
160
+ check_last_day_pattern(text)
161
+ end
162
+
163
+ # Check for "this [day]" pattern
164
+ # @param text [String] the text to check
165
+ # @return [Hizuke::Result, nil] the result or nil if no match
166
+ def check_this_day_pattern(text)
167
+ if (match = text.match(THIS_DAY_PATTERN))
168
+ day_name = match[1].downcase
169
+ day_value = DAYS_OF_WEEK[day_name]
170
+ date = calculate_this_day(day_value)
171
+ clean_text = text.gsub(match[0], '').strip
172
+ return Result.new(clean_text, date)
173
+ end
174
+
175
+ nil
176
+ end
177
+
178
+ # Check for "next [day]" pattern
179
+ # @param text [String] the text to check
180
+ # @return [Hizuke::Result, nil] the result or nil if no match
181
+ def check_next_day_pattern(text)
182
+ if (match = text.match(NEXT_DAY_PATTERN))
183
+ day_name = match[1].downcase
184
+ day_value = DAYS_OF_WEEK[day_name]
185
+ date = calculate_next_day(day_value)
186
+ clean_text = text.gsub(match[0], '').strip
187
+ return Result.new(clean_text, date)
188
+ end
189
+
190
+ nil
191
+ end
192
+
193
+ # Check for "last [day]" pattern
194
+ # @param text [String] the text to check
195
+ # @return [Hizuke::Result, nil] the result or nil if no match
196
+ def check_last_day_pattern(text)
197
+ if (match = text.match(LAST_DAY_PATTERN))
198
+ day_name = match[1].downcase
199
+ day_value = DAYS_OF_WEEK[day_name]
200
+ date = calculate_last_day(day_value)
201
+ clean_text = text.gsub(match[0], '').strip
202
+ return Result.new(clean_text, date)
203
+ end
204
+
205
+ nil
206
+ end
207
+ end
208
+
209
+ # Module for dynamic date patterns (in X days, X days ago, etc.)
210
+ module DynamicPatternMatcher
211
+ include Constants
212
+
213
+ # Check for dynamic date patterns like "in X days" or "X days ago"
214
+ # @param text [String] the text to check
215
+ # @return [Hizuke::Result, nil] the result or nil if no match
216
+ def check_dynamic_patterns(text)
217
+ # Try each pattern type
218
+ check_days_patterns(text) ||
219
+ check_weeks_patterns(text) ||
220
+ check_months_patterns(text) ||
221
+ check_years_patterns(text)
222
+ end
223
+
224
+ # Check for days-related patterns
225
+ # @param text [String] the text to check
226
+ # @return [Hizuke::Result, nil] the result or nil if no match
227
+ def check_days_patterns(text)
228
+ check_in_x_days_pattern(text) || check_x_days_ago_pattern(text)
229
+ end
230
+
231
+ # Check for "in X days" pattern
232
+ # @param text [String] the text to check
233
+ # @return [Hizuke::Result, nil] the result or nil if no match
234
+ def check_in_x_days_pattern(text)
235
+ if (match = text.match(IN_X_DAYS_PATTERN))
236
+ days = match[1].to_i
237
+ date = Date.today + days
238
+ clean_text = text.gsub(match[0], '').strip
239
+ return Result.new(clean_text, date)
240
+ end
241
+ nil
242
+ end
243
+
244
+ # Check for "X days ago" pattern
245
+ # @param text [String] the text to check
246
+ # @return [Hizuke::Result, nil] the result or nil if no match
247
+ def check_x_days_ago_pattern(text)
248
+ if (match = text.match(X_DAYS_AGO_PATTERN))
249
+ days = match[1].to_i
250
+ date = Date.today - days
251
+ clean_text = text.gsub(match[0], '').strip
252
+ return Result.new(clean_text, date)
253
+ end
254
+ nil
255
+ end
256
+
257
+ # Check for weeks-related patterns
258
+ # @param text [String] the text to check
259
+ # @return [Hizuke::Result, nil] the result or nil if no match
260
+ def check_weeks_patterns(text)
261
+ check_in_x_weeks_pattern(text) || check_x_weeks_ago_pattern(text)
262
+ end
263
+
264
+ # Check for "in X weeks" pattern
265
+ # @param text [String] the text to check
266
+ # @return [Hizuke::Result, nil] the result or nil if no match
267
+ def check_in_x_weeks_pattern(text)
268
+ if (match = text.match(IN_X_WEEKS_PATTERN))
269
+ weeks = match[1].to_i
270
+ date = Date.today + (weeks * 7)
271
+ clean_text = text.gsub(match[0], '').strip
272
+ return Result.new(clean_text, date)
273
+ end
274
+ nil
275
+ end
276
+
277
+ # Check for "X weeks ago" pattern
278
+ # @param text [String] the text to check
279
+ # @return [Hizuke::Result, nil] the result or nil if no match
280
+ def check_x_weeks_ago_pattern(text)
281
+ if (match = text.match(X_WEEKS_AGO_PATTERN))
282
+ weeks = match[1].to_i
283
+ date = Date.today - (weeks * 7)
284
+ clean_text = text.gsub(match[0], '').strip
285
+ return Result.new(clean_text, date)
286
+ end
287
+ nil
288
+ end
289
+
290
+ # Check for months-related patterns
291
+ # @param text [String] the text to check
292
+ # @return [Hizuke::Result, nil] the result or nil if no match
293
+ def check_months_patterns(text)
294
+ check_in_x_months_pattern(text) || check_x_months_ago_pattern(text)
295
+ end
296
+
297
+ # Check for "in X months" pattern
298
+ # @param text [String] the text to check
299
+ # @return [Hizuke::Result, nil] the result or nil if no match
300
+ def check_in_x_months_pattern(text)
301
+ if (match = text.match(IN_X_MONTHS_PATTERN))
302
+ months = match[1].to_i
303
+ date = Date.today >> months
304
+ clean_text = text.gsub(match[0], '').strip
305
+ return Result.new(clean_text, date)
306
+ end
307
+ nil
308
+ end
309
+
310
+ # Check for "X months ago" pattern
311
+ # @param text [String] the text to check
312
+ # @return [Hizuke::Result, nil] the result or nil if no match
313
+ def check_x_months_ago_pattern(text)
314
+ if (match = text.match(X_MONTHS_AGO_PATTERN))
315
+ months = match[1].to_i
316
+ date = Date.today << months
317
+ clean_text = text.gsub(match[0], '').strip
318
+ return Result.new(clean_text, date)
319
+ end
320
+ nil
321
+ end
322
+
323
+ # Check for years-related patterns
324
+ # @param text [String] the text to check
325
+ # @return [Hizuke::Result, nil] the result or nil if no match
326
+ def check_years_patterns(text)
327
+ check_in_x_years_pattern(text) || check_x_years_ago_pattern(text)
328
+ end
329
+
330
+ # Check for "in X years" pattern
331
+ # @param text [String] the text to check
332
+ # @return [Hizuke::Result, nil] the result or nil if no match
333
+ def check_in_x_years_pattern(text)
334
+ if (match = text.match(IN_X_YEARS_PATTERN))
335
+ years = match[1].to_i
336
+ date = Date.new(Date.today.year + years, Date.today.month, Date.today.day)
337
+ clean_text = text.gsub(match[0], '').strip
338
+ return Result.new(clean_text, date)
339
+ end
340
+ nil
341
+ end
342
+
343
+ # Check for "X years ago" pattern
344
+ # @param text [String] the text to check
345
+ # @return [Hizuke::Result, nil] the result or nil if no match
346
+ def check_x_years_ago_pattern(text)
347
+ if (match = text.match(X_YEARS_AGO_PATTERN))
348
+ years = match[1].to_i
349
+ date = Date.new(Date.today.year - years, Date.today.month, Date.today.day)
350
+ clean_text = text.gsub(match[0], '').strip
351
+ return Result.new(clean_text, date)
352
+ end
353
+ nil
354
+ end
355
+ end
356
+
357
+ # Module for compound date expressions and keywords
358
+ module DateKeywordMatcher
359
+ include Constants
360
+
361
+ # Try different parsing strategies to find a date reference
362
+ # @param clean_text [String] the text without time references
363
+ # @return [Hizuke::Result, nil] the parsing result or nil if no date reference is found
364
+ def try_parsing_strategies(clean_text)
365
+ # Check for dynamic patterns first (in X days, X days ago)
366
+ result = check_dynamic_patterns(clean_text)
367
+ return result if result
368
+
369
+ # Check for day of week patterns (this Monday, next Tuesday, etc.)
370
+ result = check_day_of_week_patterns(clean_text)
371
+ return result if result
372
+
373
+ # Try to find compound date expressions (like "next week")
374
+ result = check_compound_date_expressions(clean_text)
375
+ return result if result
376
+
377
+ # Try to find single-word date references
378
+ result = check_single_word_date_references(clean_text)
379
+ return result if result
380
+
381
+ # If no date reference was found, return nil instead of today's date
382
+ nil
383
+ end
384
+
385
+ # Check for compound date expressions like "next week"
386
+ # @param clean_text [String] the text to check
387
+ # @return [Hizuke::Result, nil] the result or nil if no match
388
+ def check_compound_date_expressions(clean_text)
389
+ compound_matches = find_compound_matches(clean_text)
390
+
391
+ # If we found compound matches, handle them specially
392
+ return nil if compound_matches.empty?
393
+
394
+ # Use the first match (in case there are multiple)
395
+ match_key, indices = compound_matches.min_by { |_, v| v[0] }
396
+
397
+ process_compound_match(clean_text, match_key, indices)
398
+ end
399
+
400
+ # Process a compound date expression match
401
+ # @param clean_text [String] the text to check
402
+ # @param match_key [String] the matched keyword
403
+ # @param indices [Array<Integer>] the start and end indices of the match
404
+ # @return [Hizuke::Result] the result
405
+ def process_compound_match(clean_text, match_key, indices)
406
+ # Calculate date based on the keyword
407
+ date_value = DATE_KEYWORDS[match_key]
408
+ date = calculate_date(date_value)
409
+
410
+ # Remove the date expression from the text
411
+ final_text = clean_text.dup
412
+ final_text.slice!(indices[0]..indices[1])
413
+
414
+ Result.new(final_text.strip, date)
415
+ end
416
+
417
+ # Find compound date expressions in the text
418
+ # @param clean_text [String] the text to check
419
+ # @return [Hash] a hash of matches and their indices
420
+ def find_compound_matches(clean_text)
421
+ compound_matches = {}
422
+
423
+ find_compound_keywords.each do |compound_key|
424
+ next unless clean_text.downcase.include?(compound_key)
425
+
426
+ start_idx = clean_text.downcase.index(compound_key)
427
+ end_idx = start_idx + compound_key.length - 1
428
+ compound_matches[compound_key] = [start_idx, end_idx]
429
+ end
430
+
431
+ compound_matches
432
+ end
433
+
434
+ # Find compound keywords (containing spaces)
435
+ # @return [Array<String>] array of compound keywords
436
+ def find_compound_keywords
437
+ DATE_KEYWORDS.keys.select { |k| k.include?(' ') }
438
+ end
439
+
440
+ # Check for single-word date references
441
+ # @param clean_text [String] the text to check
442
+ # @return [Hizuke::Result, nil] the parsing result or nil if no keyword found
443
+ def check_single_word_date_references(clean_text)
444
+ # Split the text into words
445
+ words = clean_text.split
446
+
447
+ # Find the matching date keyword
448
+ date_match = find_date_keyword_match(words)
449
+
450
+ # If no reference was found, return nil
451
+ return nil unless date_match
452
+
453
+ # Calculate the date based on the keyword
454
+ date = calculate_date(date_match[:value])
455
+
456
+ # Create the clean text by removing the date keyword
457
+ final_text = remove_date_keyword_from_text(words, date_match[:index])
458
+
459
+ Result.new(final_text, date)
460
+ end
461
+
462
+ # Find a date keyword match in the words
463
+ # @param words [Array<String>] the words to check
464
+ # @return [Hash, nil] a hash with the index and value of the match or nil if no match
465
+ def find_date_keyword_match(words)
466
+ words.each_with_index do |word, index|
467
+ clean_word = word.downcase.gsub(/[^a-z]/, '')
468
+ next unless DATE_KEYWORDS.key?(clean_word)
469
+
470
+ return { index: index, value: DATE_KEYWORDS[clean_word] }
471
+ end
472
+
473
+ nil
474
+ end
475
+
476
+ # Remove the date keyword from the text
477
+ # @param words [Array<String>] the words array
478
+ # @param index [Integer] the index of the keyword to remove
479
+ # @return [String] the text without the keyword
480
+ def remove_date_keyword_from_text(words, index)
481
+ clean_words = words.dup
482
+ clean_words.delete_at(index)
483
+ clean_words.join(' ').strip
484
+ end
485
+ end
486
+
487
+ # Main module for pattern matching in text
488
+ module PatternMatcher
489
+ include Constants
490
+ include TimePatternMatcher
491
+ include DayOfWeekPatternMatcher
492
+ include DynamicPatternMatcher
493
+ include DateKeywordMatcher
494
+ end
495
+ end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Hizuke
4
- VERSION = "0.0.4"
5
- end
4
+ VERSION = '0.1.0'
5
+ end
data/lib/hizuke.rb CHANGED
@@ -1,9 +1,9 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require_relative "hizuke/version"
4
- require_relative "hizuke/parser"
3
+ require_relative 'hizuke/version'
4
+ require_relative 'hizuke/parser'
5
5
 
6
- # Hizuke is a simple date parser that extracts dates from text
6
+ # Hizuke is a simple date parser that extracts dates from tex
7
7
  # containing time references like "yesterday", "today", and "tomorrow".
8
8
  #
9
9
  # Example:
@@ -11,15 +11,42 @@ require_relative "hizuke/parser"
11
11
  # result.text # => "wash car"
12
12
  # result.date # => <Date: 2023-04-01>
13
13
  module Hizuke
14
+ # Configuration class to hold Hizuke settings
15
+ class Configuration
16
+ attr_accessor :morning_time, :evening_time
17
+
18
+ def initialize
19
+ @morning_time = { hour: 8, min: 0 }
20
+ @evening_time = { hour: 20, min: 0 }
21
+ end
22
+ end
23
+
24
+ # Returns the current configuration
25
+ # @return [Hizuke::Configuration] the current configuration
26
+ def self.configuration
27
+ @configuration ||= Configuration.new
28
+ end
29
+
30
+ # Configure Hizuke settings
31
+ # @yield [config] Gives the configuration object to the block
32
+ # @return [Hizuke::Configuration] the updated configuration
33
+ def self.configure
34
+ yield(configuration)
35
+ configuration
36
+ end
37
+
14
38
  # Parse text containing time references and extract both
15
39
  # the clean text and the date.
16
40
  #
17
41
  # @param text [String] the text to parse
18
42
  # @return [Hizuke::Result] the parsing result containing text and date
19
43
  def self.parse(text)
44
+ raise ParseError, 'Cannot parse nil input' if text.nil?
45
+ raise ParseError, 'Cannot parse empty input' if text.empty?
46
+
20
47
  Parser.parse(text)
21
48
  end
22
49
 
23
50
  # Error raised when parsing fails
24
51
  class ParseError < StandardError; end
25
- end
52
+ end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: hizuke
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.4
4
+ version: 0.1.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Juraj Maťaše
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2025-04-29 00:00:00.000000000 Z
11
+ date: 2025-05-07 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: minitest
@@ -69,7 +69,10 @@ files:
69
69
  - Rakefile
70
70
  - hizuke.gemspec
71
71
  - lib/hizuke.rb
72
+ - lib/hizuke/constants.rb
73
+ - lib/hizuke/date_calculator.rb
72
74
  - lib/hizuke/parser.rb
75
+ - lib/hizuke/pattern_matcher.rb
73
76
  - lib/hizuke/version.rb
74
77
  homepage: https://github.com/majur/hizuke
75
78
  licenses: