Skip to content

Commit

Permalink
Refactored Jarvis time parsing for some reason
Browse files Browse the repository at this point in the history
  • Loading branch information
Rockster160 committed Oct 30, 2024
1 parent 25ffd6e commit 9c36b26
Show file tree
Hide file tree
Showing 5 changed files with 28 additions and 18 deletions.
2 changes: 2 additions & 0 deletions app/javascript/jil/statement.js
Original file line number Diff line number Diff line change
Expand Up @@ -315,6 +315,8 @@ export default class Statement {
throw new Error("Name must match [_a-z0-9]")
} else if (!newname.match(/^[_a-z]/)) {
throw new Error("Name must begin with a lowercase letter!")
} else if (!newname.match(/^(nil|null|true|false)$/)) {
throw new Error("Name cannot be a reserved word!")
} else if (Statement.nameTaken(newname, this)) {
throw new Error("Name has already been taken!")
}
Expand Down
1 change: 1 addition & 0 deletions app/service/jarvis/schedule_parser.rb
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ def relative_time
end

def valid_words?
# context can still be overridden with `x time ago`
time_str, @scheduled_time = Jarvis::Times.extract_time(@msg.downcase.squish, context: :future)
return false unless @scheduled_time

Expand Down
30 changes: 20 additions & 10 deletions app/service/jarvis/times.rb
Original file line number Diff line number Diff line change
Expand Up @@ -13,10 +13,12 @@ def extract_time(words, chronic_opts={})
day_words = (Date::DAYNAMES + Date::ABBR_DAYNAMES + [:today, :tomorrow, :yesterday, :morning, :night, :afternoon, :evening, :tonight]).map { |w| w.to_s.downcase.to_sym }
day_words_regex = rx.words(day_words)
time_words = [:second, :minute, :hour, :day, :week, :month, :year]
time_words_regex = rx.words(time_words, suffix: "s?")
iso8601_regex = /\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}-07:00/
time_str = words[/\b(in) (#{drx}|an?) #{time_words_regex}(( and)?( a)?( half)?)?( (and )?#{drx}( #{time_words_regex})?)?/]
time_str ||= words[/\b(in) (#{drx}|an?)( (and )?(a )?half( #{time_words_regex})?)?/]
rel_words_regex = rx.words(time_words, suffix: "s?")
iso8601_regex = /\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}-\d{2}:\d{2}/
and_some = /(?: (?:and )?(?:an? )?(#{drx})?(?: ?\bhalf\b ?)?(?: (#{rel_words_regex}))?)?/
time_words_regex = /(#{rel_words_regex})#{and_some}/
time_str = words[/\bin (#{drx}|an?) #{time_words_regex}(?: ?(#{drx}))?/]
time_str ||= words[/\bin (#{drx}|an?)#{and_some}(#{rel_words_regex})/]
time_str ||= words[/(\b(?:on|next|last) )?(#{month_words_regex} \d{1,2}(\w{2})?(,? '?\d{2,4})? )?((in the )?(#{day_words_regex} ?)+ )?\b(at) \d+:?\d*( ?(am|pm))?( (#{day_words_regex} ?)+)?/]
time_str ||= words[/(\b(?:on|next|last) )?#{month_words_regex} \d{1,2}(\w{2})?(,? '?\d{2,4})?/]
time_str ||= words[/(\b(?:on|next|last))(?:^| )?\d{1,2}\/\d{1,2}(\/(\d{2}|\d{4})\b)?/]
Expand All @@ -42,11 +44,12 @@ def extract_time(words, chronic_opts={})
parsed_time += 12.hours
end
else
m = time_str.match(/in (#{drx}) (#{time_words_regex}) ?(?:and )?(\d*) ?(#{time_words_regex})?/)
m = time_str.match(/(#{drx})\s+(#{rel_words_regex})#{and_some}/)
parsed_time = m&.to_a&.then { |_, n1, t1, n2, t2|
t = Time.current
t += (n1 || 1).to_f.send(t1 || :hours)
t += n2.to_i.send(t2 || :minutes)
interval = (n1 || 1).to_f.send(t1 || :hours)
interval += n2.to_i.send(t2 || :minutes)
interval = chronic_opts[:context] == :past ? -interval : interval
Time.current + interval
}
end

Expand All @@ -55,14 +58,21 @@ def extract_time(words, chronic_opts={})
end

def safe_date_parse(timestamp, chronic_opts={})
# Force override the context if using `in x time` or `x time ago`
chronic_opts[:context] = :future if timestamp.match?(/^\s*in\b/)
chronic_opts[:context] = :past if timestamp.match?(/\b(from now|ago)\s*$/)
opts = chronic_opts.reverse_merge(ambiguous_time_range: 8)
::Chronic.time_class = ::ActiveSupport::TimeZone.new("Mountain Time (US & Canada)")
::Chronic.parse(timestamp, opts).then { |time|
next if time.nil?
::Chronic.parse(timestamp, opts)&.then { |time|
skip = timestamp.match?(/(a|p)m/) ? 24.hours : 12.hours
time += skip while chronic_opts[:context] == :future && time < Time.current
time -= skip while chronic_opts[:context] == :past && time > Time.current
time
}
rescue => e
### Rescue various Chronic errors, specifically
# -- https://github.com/mojombo/chronic/issues/415
# ::Chronic.parse("3 hours and 30 minutes before")
# → NoMethodError: undefined method `start=' for nil:NilClass
end
end
12 changes: 4 additions & 8 deletions app/service/jil/parser.rb
Original file line number Diff line number Diff line change
Expand Up @@ -94,14 +94,10 @@ def self.syntax_highlighting(code, tk=nil)
"\e[#{COLORS[:string]}m",
].join
}
when /^\d+$/
col[:numeric, arg]
when /^(nil|null|true|false)$/
col[:constant, arg]
when /^\w+$/
col[:variable, arg]
else
col[:err, "<dunno>[Invalid String?]#{arg.inspect}</dunno>"]
when /^\d+$/ then col[:numeric, arg]
when /^(nil|null|true|false)$/ then col[:constant, arg] # reserved words
when /^\w+$/ then col[:variable, arg]
else col[:err, "<dunno>[Invalid String?]#{arg.inspect}</dunno>"]
end
# else col[:err, "<dunno>[#{arg.class}]#{arg.inspect}</dunno>"]
end
Expand Down
1 change: 1 addition & 0 deletions spec/services/jarvis_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -514,6 +514,7 @@ def jarvis(msg, user=@user)
"in an hour and a half" => [Time.local(2022, 6, 24, 7, 15), "today at 7:15am"],
"in 3 and a half hours" => [Time.local(2022, 6, 24, 9, 15), "today at 9:15am"],
"in 3 hours and 30 minutes" => [Time.local(2022, 6, 24, 9, 15), "today at 9:15am"],
"3 hours and 30 minutes ago" => [Time.local(2022, 6, 24, 2, 15), "today at 2:15am"],
"in 3.5 hours" => [Time.local(2022, 6, 24, 9, 15), "today at 9:15am"],
"tonight" => [Time.local(2022, 6, 24, 22, 0), "today at 10pm"],
"at 11:15 tomorrow" => [Time.local(2022, 6, 25, 11, 15), "tomorrow at 11:15am"],
Expand Down

0 comments on commit 9c36b26

Please sign in to comment.