preferences added
This commit is contained in:
137
src/parser.rs
137
src/parser.rs
@@ -4,14 +4,46 @@ use chrono::{
|
||||
};
|
||||
use regex::Regex;
|
||||
|
||||
/// Extracts the first recognizable date/time phrase and returns local RFC 3339.
|
||||
/// Deliberately favors useful, predictable planner phrases over pretending to be
|
||||
/// a complete natural-language parser.
|
||||
pub fn extract_when(text: &str) -> Option<String> {
|
||||
extract_when_from(text, Local::now().naive_local())
|
||||
#[cfg(test)]
|
||||
pub fn extract_when_from(text: &str, now: NaiveDateTime) -> Option<String> {
|
||||
extract_when_from_configured(
|
||||
text, now, "ymd", "monday", "09:00", "09:00", "13:00", "18:00",
|
||||
)
|
||||
}
|
||||
|
||||
pub fn extract_when_from(text: &str, now: NaiveDateTime) -> Option<String> {
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
pub fn extract_when_configured(
|
||||
text: &str,
|
||||
date_order: &str,
|
||||
week_start: &str,
|
||||
default_time: &str,
|
||||
morning_time: &str,
|
||||
afternoon_time: &str,
|
||||
evening_time: &str,
|
||||
) -> Option<String> {
|
||||
extract_when_from_configured(
|
||||
text,
|
||||
Local::now().naive_local(),
|
||||
date_order,
|
||||
week_start,
|
||||
default_time,
|
||||
morning_time,
|
||||
afternoon_time,
|
||||
evening_time,
|
||||
)
|
||||
}
|
||||
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
fn extract_when_from_configured(
|
||||
text: &str,
|
||||
now: NaiveDateTime,
|
||||
date_order: &str,
|
||||
week_start: &str,
|
||||
default_time: &str,
|
||||
morning_time: &str,
|
||||
afternoon_time: &str,
|
||||
evening_time: &str,
|
||||
) -> Option<String> {
|
||||
let lower = text.to_lowercase();
|
||||
let mut date = None;
|
||||
|
||||
@@ -20,6 +52,24 @@ pub fn extract_when_from(text: &str, now: NaiveDateTime) -> Option<String> {
|
||||
date = NaiveDate::from_ymd_opt(c[1].parse().ok()?, c[2].parse().ok()?, c[3].parse().ok()?);
|
||||
}
|
||||
|
||||
if date.is_none() {
|
||||
let numeric = Regex::new(r"\b(\d{1,4})[./-](\d{1,2})[./-](\d{1,4})\b").unwrap();
|
||||
if let Some(c) = numeric.captures(&lower) {
|
||||
let values = [
|
||||
c[1].parse::<i32>().ok()?,
|
||||
c[2].parse::<i32>().ok()?,
|
||||
c[3].parse::<i32>().ok()?,
|
||||
];
|
||||
let (year, month, day) = match date_order {
|
||||
"mdy" => (values[2], values[0], values[1]),
|
||||
"dmy" => (values[2], values[1], values[0]),
|
||||
_ => (values[0], values[1], values[2]),
|
||||
};
|
||||
let year = if year < 100 { year + 2000 } else { year };
|
||||
date = NaiveDate::from_ymd_opt(year, month as u32, day as u32);
|
||||
}
|
||||
}
|
||||
|
||||
if date.is_none() {
|
||||
let in_days = Regex::new(r"\bin\s+(\d+)\s+days?\b").unwrap();
|
||||
if let Some(c) = in_days.captures(&lower) {
|
||||
@@ -33,6 +83,20 @@ pub fn extract_when_from(text: &str, now: NaiveDateTime) -> Option<String> {
|
||||
}
|
||||
}
|
||||
|
||||
if date.is_none() && (lower.contains("this week") || lower.contains("next week")) {
|
||||
let day_from_start = if week_start == "sunday" {
|
||||
now.weekday().num_days_from_sunday() as i64
|
||||
} else {
|
||||
now.weekday().num_days_from_monday() as i64
|
||||
};
|
||||
let current_start = now.date() - Duration::days(day_from_start);
|
||||
date = Some(if lower.contains("next week") {
|
||||
current_start + Duration::weeks(1)
|
||||
} else {
|
||||
current_start
|
||||
});
|
||||
}
|
||||
|
||||
if date.is_none() {
|
||||
let weekdays = [
|
||||
("monday", Weekday::Mon),
|
||||
@@ -76,20 +140,43 @@ pub fn extract_when_from(text: &str, now: NaiveDateTime) -> Option<String> {
|
||||
NaiveTime::from_hms_opt(hour, minute, 0)?
|
||||
} else if let Some(c) = time24_re.captures(&lower) {
|
||||
NaiveTime::from_hms_opt(c[1].parse().ok()?, c[2].parse().ok()?, 0)?
|
||||
} else if lower.contains("morning") {
|
||||
parse_clock(morning_time)?
|
||||
} else if lower.contains("afternoon") {
|
||||
parse_clock(afternoon_time)?
|
||||
} else if lower.contains("evening") || lower.contains("tonight") {
|
||||
parse_clock(evening_time)?
|
||||
} else {
|
||||
NaiveTime::from_hms_opt(9, 0, 0)?
|
||||
parse_clock(default_time)?
|
||||
};
|
||||
|
||||
let local = Local.from_local_datetime(&date.and_time(time)).single()?;
|
||||
Some(local.to_rfc3339())
|
||||
}
|
||||
|
||||
pub fn pretty_when(value: Option<&str>) -> String {
|
||||
fn parse_clock(value: &str) -> Option<NaiveTime> {
|
||||
NaiveTime::parse_from_str(value, "%H:%M").ok()
|
||||
}
|
||||
|
||||
pub fn format_when(value: Option<&str>, date_format: &str, clock_24h: bool) -> String {
|
||||
let Some(value) = value else {
|
||||
return String::new();
|
||||
};
|
||||
chrono::DateTime::parse_from_rfc3339(value)
|
||||
.map(|d| d.format("%Y-%m-%d %H:%M").to_string())
|
||||
.map(|d| {
|
||||
let date = match date_format {
|
||||
"us" => d.format("%m/%d/%Y").to_string(),
|
||||
"european" => d.format("%d/%m/%Y").to_string(),
|
||||
"long" => d.format("%b %-d, %Y").to_string(),
|
||||
_ => d.format("%Y-%m-%d").to_string(),
|
||||
};
|
||||
let time = if clock_24h {
|
||||
d.format("%H:%M").to_string()
|
||||
} else {
|
||||
d.format("%-I:%M%P").to_string()
|
||||
};
|
||||
format!("{date} {time}")
|
||||
})
|
||||
.unwrap_or_else(|_| value.to_owned())
|
||||
}
|
||||
|
||||
@@ -176,4 +263,36 @@ mod tests {
|
||||
.contains("2026-08-30T09:00:00")
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn honors_document_date_and_named_time_settings() {
|
||||
let got = extract_when_from_configured(
|
||||
"Planning on 18/08/2026 in the evening",
|
||||
base(),
|
||||
"dmy",
|
||||
"monday",
|
||||
"08:30",
|
||||
"08:00",
|
||||
"14:00",
|
||||
"19:15",
|
||||
)
|
||||
.unwrap();
|
||||
assert!(got.contains("2026-08-18T19:15:00"));
|
||||
assert_eq!(
|
||||
format_when(Some(&got), "european", false),
|
||||
"18/08/2026 7:15pm"
|
||||
);
|
||||
let next_week = extract_when_from_configured(
|
||||
"Review next week",
|
||||
base(),
|
||||
"ymd",
|
||||
"sunday",
|
||||
"09:00",
|
||||
"09:00",
|
||||
"13:00",
|
||||
"18:00",
|
||||
)
|
||||
.unwrap();
|
||||
assert!(next_week.contains("2026-08-23T09:00:00"));
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user