use naming::NamingScheme;
pub mod naming;
#[derive(Debug, PartialEq, Eq, Clone, Copy)]
pub enum NapAmount {
N(u8),
X,
AMAYL,
}
#[derive(Debug, PartialEq, Eq, Clone, Copy)]
pub enum ScheduleKind {
MonoCore(NapAmount),
Siesta,
DualCore(NapAmount),
Thinkphasic(NapAmount),
TriCore(NapAmount),
QuadCore(NapAmount),
SCAMAYL,
Coreless(NapAmount),
Coreless30(NapAmount),
Bimaxion,
Trimaxion,
Zoidberg(NapAmount),
Random,
}
impl ScheduleKind {
pub fn from_ident(s: &str) -> Option<ScheduleKind> {
use NapAmount::*;
use ScheduleKind::*;
let s_orig = s.to_uppercase();
let (s, rest) = s_orig
.split_once(|c: char| c.is_numeric() || c == '-')
.unwrap_or((&s_orig, ""));
if s.starts_with("RAN") {
return Some(Random);
}
if s.starts_with("NAPT") {
return Some(Coreless(N(12)));
}
if s.starts_with("M")
&& !s.starts_with("MCA")
&& !s.starts_with("MOCA")
&& !s.starts_with("MONOMAN")
{
return Some(MonoCore(Self::detect_naps(&s_orig).unwrap_or(N(0))));
}
if s.starts_with("BI") {
if s.contains("MA") {
return Some(Bimaxion);
}
if s.contains("X") || rest.starts_with("X") {
return Some(MonoCore(X));
}
return Some(MonoCore(N(1)));
}
if s.starts_with("SEG") {
let naps = if s.contains("Y") || rest.starts_with("Y") {
X
} else {
N(0)
};
return Some(DualCore(naps));
}
if s.starts_with("SIE") {
return Some(Siesta);
}
if s.starts_with("E") {
if s.starts_with("EX") {
return Some(MonoCore(X));
}
return Some(MonoCore(Self::detect_naps(&s_orig).unwrap_or(N(3))));
}
if s.starts_with("DC") || s.starts_with("DUAL") {
return Some(DualCore(Self::detect_naps(&s_orig).unwrap_or(N(0))));
}
if s.starts_with("TH") || s.starts_with("TP") {
return Some(Thinkphasic(Self::detect_naps(&s_orig).unwrap_or(N(1))));
}
if s.starts_with("TC") || s.starts_with("TRI") {
if s.contains("CA") || s.contains("MAY") {
return Some(TriCore(AMAYL));
}
if s.contains("MA") {
return Some(Trimaxion);
}
return Some(TriCore(Self::detect_naps(&s_orig).unwrap_or(N(0))));
}
if !s.starts_with("QCA") && (s.starts_with("QC") || s.starts_with("QUAD")) {
return Some(QuadCore(Self::detect_naps(&s_orig).unwrap_or(N(0))));
}
if (s.starts_with("U")
|| s.starts_with("TES")
|| s.starts_with("CL")
|| s.starts_with("CORELESS")
|| s.starts_with("NC")
|| s.starts_with("NOCORE")
|| s.starts_with("NO")
|| s.starts_with("NAPONLY"))
&& !s.starts_with("UN")
&& !s.starts_with("NOCA")
&& !s.starts_with("NCA")
{
return Some(Coreless(
Self::detect_naps(&s_orig).unwrap_or(N(if s.starts_with("TES") { 4 } else { 6 })),
));
}
if s.starts_with("DY") {
return Some(Coreless30(Self::detect_naps(&s_orig).unwrap_or(N(4))));
}
if !s.starts_with("ZOIDA")
&& !s.starts_with("ZBA")
&& (s.starts_with("ZO") || s.starts_with("ZB"))
{
return Some(Zoidberg(Self::detect_naps(&s_orig).unwrap_or(N(6))));
}
Self::find_amayl(s)
}
fn find_amayl(mut s: &str) -> Option<ScheduleKind> {
use NapAmount::*;
use ScheduleKind::*;
if s.starts_with("CAM") || s.starts_with("SCA") || s.starts_with("SHORT") {
return Some(SCAMAYL);
}
if s.starts_with("S") {
s = &s[1..];
}
if s.starts_with("EV") || s.starts_with("MOCA") || s.starts_with("MCA") {
return Some(MonoCore(AMAYL));
}
if s.starts_with("PA")
|| s.starts_with("NCA")
|| s.starts_with("NOCA")
|| s.starts_with("CLA")
{
return Some(Coreless(AMAYL));
}
if s.starts_with("DMA") || s.starts_with("DYMA") {
return Some(Coreless30(AMAYL));
}
if s.starts_with("DUCA") {
return Some(DualCore(AMAYL));
}
if s.starts_with("TCA") || s.starts_with("TRICA") {
return Some(TriCore(AMAYL));
}
if s.contains("Q") && s.contains("A") {
return Some(QuadCore(AMAYL));
}
if s.starts_with("ZOIDA") {
return Some(Zoidberg(AMAYL));
}
if s.starts_with("THA") || s.starts_with("TPA") || s.starts_with("THINKA") {
return Some(Thinkphasic(AMAYL));
}
None
}
fn detect_naps(full_name: &str) -> Option<NapAmount> {
if full_name.find("AM") != full_name.find("AMO") {
return Some(NapAmount::AMAYL);
}
full_name
.replace("AX", "A_")
.replace("EX", "E_")
.replace("CL", "C_")
.replace("LX", "L_")
.find(|x: char| x.is_numeric() || x == 'X')
.map(|begin| {
full_name[begin..]
.find(|x: char| !x.is_numeric() && x != 'X')
.or(Some(full_name.len() - begin))
.map(|end| (begin, end + begin))
})
.flatten()
.map(|(begin, end)| {
let s = &full_name[begin..end];
if s == "X" {
return Some(NapAmount::X);
}
Some(NapAmount::N(s.parse::<u8>().ok()?))
})
.flatten()
}
}
#[derive(Debug, PartialEq, Eq, Clone, Copy)]
pub struct Schedule {
pub kind: ScheduleKind,
pub extended: bool,
pub flexible: bool,
pub stiff: bool,
pub modified: bool,
pub shortened: bool,
pub recovery: bool,
}
impl Schedule {
pub fn from_ident(s: &str) -> Option<Self> {
let s = s.to_uppercase();
let kind = ScheduleKind::from_ident(&s)?;
let Some(begin) = try_find_with_number(&s)
.or(try_find_with_dash(&s))
.or(try_find_by_string(&s))
else {
return Some(Self {
kind,
extended: false,
flexible: false,
stiff: false,
modified: false,
shortened: false,
recovery: false,
});
};
let s = &s[begin..];
let flexible = s.contains("FLEX") || s.contains("FLX");
let s = s.replace("FLEX", "----");
let extended = s.contains("EX") || s == "E";
let stiff = s.contains("STIFF");
let shortened = s.contains("SHO");
let modified = s.contains("MOD") || s.contains("MD");
let recovery = s.contains("REC");
Some(Self {
kind,
extended,
flexible,
stiff,
modified,
shortened,
recovery,
})
}
pub fn short_id(&self, scheme: &dyn NamingScheme) -> String {
let id = self.kind.short_id(scheme);
let mut modifier = String::new();
if self.shortened {
modifier += "sho";
}
if self.stiff {
modifier += "stiff"
}
if self.extended {
if !self.flexible
&& !self.stiff
&& !self.shortened
&& !self.modified
&& !self.recovery
&& id.contains(char::is_numeric)
{
modifier += "e";
} else {
modifier += "ex";
}
}
if self.flexible {
modifier += "flex";
}
if self.modified {
modifier += "mod";
}
if self.recovery {
modifier += "rec";
}
if id.chars().last().unwrap().is_lowercase() && !modifier.is_empty() {
modifier[0..=0].make_ascii_uppercase();
}
id + &modifier
}
pub fn long_id(&self, scheme: &dyn NamingScheme) -> String {
let mut id = self.kind.long_id(scheme);
if self.shortened {
id += "-Shortened";
}
if self.stiff {
id += "-Stiff"
}
if self.extended {
id += "-Extended"
}
if self.flexible {
id += "-Flexible";
}
if self.modified {
id += "-Modified";
}
if self.recovery {
id += "-Recovery";
}
id
}
}
fn try_find_with_number(s: &str) -> Option<usize> {
s.rfind(char::is_numeric).map(|x| x + 1)
}
fn try_find_with_dash(s: &str) -> Option<usize> {
s.find('-').map(|x| x + 1)
}
fn try_find_by_string(s: &str) -> Option<usize> {
[
s.find("FLEX"),
s.find("MOD"),
s.find("MD"),
s.find("EX"),
s.find("FLX"),
s.find("STIFF"),
s.find("SHO"),
s.find("REC"),
]
.iter()
.map(|x| {
x.map(|x| if x == 0 { usize::MAX } else { x })
.unwrap_or(usize::MAX)
})
.min()
.and_then(|x| match x {
_x @ usize::MAX => None,
x => Some(x),
})
}
#[cfg(test)]
mod test {
use crate::{naming::NamingScheme, Schedule, ScheduleKind};
pub fn test_all(scheme: &dyn NamingScheme) {
use super::NapAmount::*;
use super::ScheduleKind::*;
for naps in (0..5).map(|x| N(x)).chain([X, AMAYL]) {
test_kinds(
[
MonoCore(naps),
DualCore(naps),
TriCore(naps),
QuadCore(naps),
Coreless(naps),
Thinkphasic(naps),
Coreless30(naps),
Zoidberg(naps),
],
scheme,
);
}
test_kinds([Siesta, Bimaxion, Trimaxion, SCAMAYL, Random], scheme);
}
fn test_kinds(schedkinds: impl IntoIterator<Item = ScheduleKind>, scheme: &dyn NamingScheme) {
for kind in schedkinds {
let sched = Schedule {
kind,
extended: false,
flexible: false,
stiff: false,
modified: false,
shortened: false,
recovery: false,
};
for i in 0u8.. {
let Some(sched) = int_to_schedmod(i, sched.clone()) else {
break;
};
test_schedule(sched, scheme);
}
}
}
fn int_to_schedmod(i: u8, sched: Schedule) -> Option<Schedule> {
if i > 0b111111 {
return None;
}
Some(Schedule {
kind: sched.kind,
extended: ((i >> 0) & 1) != 0,
flexible: ((i >> 1) & 1) != 0,
stiff: ((i >> 2) & 1) != 0,
modified: ((i >> 3) & 1) != 0,
shortened: ((i >> 4) & 1) != 0,
recovery: ((i >> 5) & 1) != 0,
})
}
fn test_schedule(sched: Schedule, scheme: &dyn NamingScheme) {
let other = Schedule::from_ident(&sched.short_id(scheme)).expect(&format!(
"schedule {} cannot be reparsed",
sched.short_id(scheme)
));
if sched != other {
panic!(
"{:?} turns into {}, which ended up as {:?}",
sched,
sched.short_id(scheme),
other
)
}
let other = Schedule::from_ident(&sched.long_id(scheme)).expect(&format!(
"schedule {} cannot be reparsed",
sched.long_id(scheme)
));
if sched != other {
panic!(
"{:?} turns into {}, which ended up as {:?}",
sched,
sched.long_id(scheme),
other
)
}
}
}