titleformat-rs 0.2.0

A foobar2000 titleformat parser for rust
Documentation
use crate::environment::value_string;
use crate::environment::Value;
use crate::types::Error;
use crate::types::Error::*;

use iso_8601::{ApproxDate, DateTime, PartialDateTime};
use std::str::FromStr;

pub fn year(args: Vec<Value>) -> Result<Value, Error> {
    match args.len() {
        1 => {
            let val = &args[0];

            // Try parsing the year ourselves to avoid an overflow when iso_8601 parses the year
            let idx = val
                .val
                .as_bytes()
                .iter()
                .fold((true, 0usize), |(accum, idx), &c| {
                    if !accum {
                        (accum, idx)
                    } else if (c as char).is_ascii_digit() {
                        (true, idx + 1)
                    } else {
                        (false, idx)
                    }
                })
                .1;
            let Ok(num) = i32::from_str(&val.val[..idx]) else {
                return Ok(value_string("", val.cond));
            };
            if num > 9999 {
                return Ok(value_string("", val.cond));
            }

            let date = match PartialDateTime::from_str(&val.val) {
                Ok(date) => date,
                Err(_) => return Ok(value_string("", val.cond)),
            };

            let year = match date {
                PartialDateTime::Date(ApproxDate::YMD(ymd)) => ymd.year,
                PartialDateTime::Date(ApproxDate::YM(ym)) => ym.year,
                PartialDateTime::Date(ApproxDate::Y(y)) => y.year,
                PartialDateTime::Date(ApproxDate::WD(wd)) => wd.year,
                PartialDateTime::Date(ApproxDate::W(w)) => w.year,
                PartialDateTime::Date(ApproxDate::O(o)) => o.year,
                PartialDateTime::DateTime(DateTime {
                    date: ApproxDate::YMD(ymd),
                    time: _,
                }) => ymd.year,
                PartialDateTime::DateTime(DateTime {
                    date: ApproxDate::YM(ym),
                    time: _,
                }) => ym.year,
                PartialDateTime::DateTime(DateTime {
                    date: ApproxDate::Y(y),
                    time: _,
                }) => y.year,
                PartialDateTime::DateTime(DateTime {
                    date: ApproxDate::WD(wd),
                    time: _,
                }) => wd.year,
                PartialDateTime::DateTime(DateTime {
                    date: ApproxDate::W(w),
                    time: _,
                }) => w.year,
                PartialDateTime::DateTime(DateTime {
                    date: ApproxDate::O(o),
                    time: _,
                }) => o.year,
                _ => return Ok(value_string("", val.cond)),
            };
            Ok(value_string(year.to_string().as_str(), val.cond))
        }
        _ => Err(InvalidNativeFunctionArgs(String::from("year"), args.len())),
    }
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn wrong_n_arguments() {
        assert_eq!(
            year(vec![]).err().unwrap(),
            InvalidNativeFunctionArgs(String::from("year"), 0)
        );
    }

    #[test]
    fn test_year() {
        assert_eq!(
            year(vec![value_string("2000-06-04", true)]).unwrap(),
            value_string("2000", true)
        );
        assert_eq!(
            year(vec![value_string("2000-06", true)]).unwrap(),
            value_string("2000", true)
        );
        assert_eq!(
            year(vec![value_string("2000", true)]).unwrap(),
            value_string("2000", true)
        );
        assert_eq!(
            year(vec![value_string("abcd", true)]).unwrap(),
            value_string("", true)
        );
    }
}