118 lines
3.2 KiB
Ruby
118 lines
3.2 KiB
Ruby
class SearchRange
|
|
|
|
attr_accessor :text_range
|
|
|
|
TIME_REGEX = /^([<>]*)\s*([\d -]+)\s*(year|week|month|day|hour)s?(\s*ago)?\s*$/
|
|
NUMBER_REGEX = /^([<>]*)\s*([\d,. -]+)\s*$/
|
|
OUT_OF_RANGE_DATE = 1000.years.ago
|
|
|
|
# Takes a string that includes a time or number range and
|
|
# returns it as a hash that can be used in elasticsearch queries
|
|
def self.parsed(str)
|
|
new(str).parse
|
|
end
|
|
|
|
def initialize(str)
|
|
@text_range = str || ""
|
|
end
|
|
|
|
def parse
|
|
standardize_text
|
|
range = {}
|
|
if match = text_range.match(TIME_REGEX)
|
|
range = time_range(match[1], match[2], match[3])
|
|
elsif match = text_range.match(NUMBER_REGEX)
|
|
range = numerical_range(match[1], match[2].gsub(",", ""))
|
|
end
|
|
range
|
|
end
|
|
|
|
private
|
|
|
|
def standardize_text
|
|
@text_range = @text_range.gsub(">", ">").
|
|
gsub("<", "<").
|
|
downcase
|
|
end
|
|
|
|
|
|
# create numerical range from operand and string
|
|
# operand can be "<", ">" or ""
|
|
# string must be an integer unless operand is ""
|
|
# in which case it can be two numbers connected by "-"
|
|
def numerical_range(operand, string)
|
|
case operand
|
|
when "<"
|
|
{ lt: string.to_i }
|
|
when ">"
|
|
{ gt: string.to_i }
|
|
when ""
|
|
match = string.match(/-/)
|
|
if match
|
|
{ gte: match.pre_match.to_i, lte: match.post_match.to_i }
|
|
else
|
|
{ gte: string.to_i, lte: string.to_i }
|
|
end
|
|
end
|
|
end
|
|
|
|
# create time range from operand, amount and period
|
|
# period must be one known by time_from_string
|
|
def time_range(operand, amount, period)
|
|
case operand
|
|
when "<"
|
|
time = time_from_string(amount, period)
|
|
{ gt: time }
|
|
when ">"
|
|
time = time_from_string(amount, period)
|
|
{ lt: time }
|
|
when ""
|
|
match = amount.match(/-/)
|
|
if match
|
|
time1 = time_from_string(match.pre_match, period)
|
|
time2 = time_from_string(match.post_match, period)
|
|
{ gte: time2, lte: time1 }
|
|
else
|
|
range_from_string(amount, period)
|
|
end
|
|
end
|
|
end
|
|
|
|
# helper method to create times from two strings
|
|
# Elasticsearch gets grumpy with negative years, so the simple fix
|
|
# is just to go back a thousand years
|
|
# TODO: rework date query formats if Homer ever starts posting his stuff to AO3
|
|
def time_from_string(amount, period)
|
|
date = amount.to_i.send(period).ago
|
|
date.year.negative? ? OUT_OF_RANGE_DATE : date
|
|
end
|
|
|
|
# Generate a range based on one number
|
|
# Interval is based on period used, ie 1 month ago = range from beginning to end of month
|
|
def range_from_string(amount, period)
|
|
case period
|
|
when /year/
|
|
a = amount.to_i.year.ago.beginning_of_year
|
|
a2 = a.end_of_year
|
|
when /month/
|
|
a = amount.to_i.month.ago.beginning_of_month
|
|
a2 = a.end_of_month
|
|
when /week/
|
|
a = amount.to_i.week.ago.beginning_of_week
|
|
a2 = a.end_of_week
|
|
when /day/
|
|
a = amount.to_i.day.ago.beginning_of_day
|
|
a2 = a.end_of_day
|
|
when /hour/
|
|
a = amount.to_i.hour.ago.change(min: 0, sec: 0, usec: 0)
|
|
a2 = (a + 60.minutes)
|
|
else
|
|
raise "unknown period: " + period
|
|
end
|
|
a, a2 = [a, a2].map do |date|
|
|
date.year.negative? ? OUT_OF_RANGE_DATE : date
|
|
end
|
|
{ gte: a, lte: a2 }
|
|
end
|
|
|
|
end
|