use crate::{
format::{Format, FormatError},
specifier::{CalSemLevel, CalSemSpecifier, CalSpecifier, SemSpecifier, Specifier},
version::{Date, NextError, Version, VersionError},
SemLevel,
};
#[non_exhaustive]
#[derive(thiserror::Error, Debug, PartialEq)]
pub enum CompositeError {
#[error(transparent)]
Format(#[from] FormatError),
#[error(transparent)]
Version(#[from] VersionError),
#[error(transparent)]
Next(#[from] NextError),
}
pub(crate) mod priv_trait {
use super::Specifier as SpecifierT;
use core::fmt::Debug;
pub(crate) trait Scheme: Sized + Debug + PartialEq + Eq {
type Specifier: SpecifierT;
const MAX_SPECIFIERS: usize;
const MAX_TOKENS: usize = Self::MAX_SPECIFIERS * 2 + 1;
fn first_variants_string() -> String {
arr_to_english_or(Self::Specifier::first_variants())
}
fn last_variants_string() -> String {
arr_to_english_or(Self::Specifier::last_variants())
}
fn name() -> &'static str;
}
fn arr_to_english_or(specs: &'static [&'static impl SpecifierT]) -> String {
let spec_strings = specs
.iter()
.map(|spec| format!("`{spec}`"))
.collect::<Vec<_>>();
match spec_strings.as_slice() {
[] => String::new(),
[a] => a.to_string(),
[a, b] => format!("{a} or {b}"),
[firsts @ .., last] => {
let mut joined = firsts.join(", ");
joined.push_str(&format!(", or {last}"));
joined
}
}
}
}
#[allow(private_bounds)]
pub trait Scheme: priv_trait::Scheme {
fn new_format(format_str: &str) -> Result<Format<Self>, FormatError> {
Format::parse(format_str)
}
fn new_version<'vs>(
format_str: &str,
version_str: &'vs str,
) -> Result<Version<'vs, Self>, CompositeError> {
let format = Self::new_format(format_str)?;
let version = format.new_version(version_str)?;
Ok(version)
}
fn is_valid(format_str: &str, version_str: &str) -> Result<bool, FormatError> {
let format = Self::new_format(format_str)?;
let version = format.new_version(version_str);
Ok(version.is_ok())
}
}
#[derive(Debug, PartialEq, Eq)]
pub struct Sem;
impl Sem {
pub fn next_version_string(
format_str: &str,
version_str: &str,
level: SemLevel,
) -> Result<String, CompositeError> {
let version = Self::new_version(format_str, version_str)?;
let next_version = version.next(level)?;
Ok(next_version.to_string())
}
}
impl Scheme for Sem {}
impl priv_trait::Scheme for Sem {
type Specifier = SemSpecifier;
const MAX_SPECIFIERS: usize = 3;
fn name() -> &'static str {
"semantic"
}
}
#[derive(Debug, PartialEq, Eq)]
pub struct Cal;
impl Cal {
pub fn next_version_string(
format_str: &str,
version_str: &str,
date: Date,
) -> Result<String, CompositeError> {
let format = Self::new_format(format_str)?;
let version = Version::parse(version_str, &format)?;
let next_version = version.next(date)?;
Ok(next_version.to_string())
}
}
impl Scheme for Cal {}
impl priv_trait::Scheme for Cal {
type Specifier = CalSpecifier;
const MAX_SPECIFIERS: usize = 3;
fn name() -> &'static str {
"calendar"
}
}
#[derive(Debug, PartialEq, Eq)]
pub struct CalSem;
impl CalSem {
pub fn next_version_string(
format_str: &str,
version_str: &str,
date: Date,
level: CalSemLevel,
) -> Result<String, CompositeError> {
let format = Self::new_format(format_str)?;
let version = Version::parse(version_str, &format)?;
let next_version = version.next(date, level)?;
Ok(next_version.to_string())
}
}
impl Scheme for CalSem {}
impl priv_trait::Scheme for CalSem {
type Specifier = CalSemSpecifier;
const MAX_SPECIFIERS: usize = 5;
fn name() -> &'static str {
"calendar-semantic"
}
}