use serde::Deserialize;
use crate::paths::routines_dir;
use crate::routines::{slugify, Routine};
#[derive(Deserialize)]
struct IdOnlyRoutineToml {
id: Option<String>,
}
fn rel_string(path: &std::path::Path) -> String {
path.components()
.map(|component| component.as_os_str().to_string_lossy())
.collect::<Vec<_>>()
.join("/")
}
fn read_id(path: &std::path::Path) -> Option<String> {
std::fs::read_to_string(path)
.ok()
.and_then(|text| toml::from_str::<IdOnlyRoutineToml>(&text).ok())
.and_then(|routine| routine.id)
}
fn find_rel_dir_by_id(base: &std::path::Path, dir: &std::path::Path, id: &str) -> Option<String> {
let entries = std::fs::read_dir(dir).ok()?;
for entry in entries.flatten() {
if !entry.file_type().is_ok_and(|ft| ft.is_dir()) {
continue;
}
let path = entry.path();
let toml = path.join("routine.toml");
if toml.exists() && read_id(&toml).as_deref() == Some(id) {
return path.strip_prefix(base).ok().map(rel_string);
}
if let Some(found) = find_rel_dir_by_id(base, &path, id) {
return Some(found);
}
}
None
}
pub(crate) fn routine_rel_dir_by_id(id: &str) -> Option<String> {
let base = routines_dir();
find_rel_dir_by_id(&base, &base, id)
}
pub(crate) fn routine_rel_dir(routine: &Routine) -> String {
routine_rel_dir_by_id(&routine.id).unwrap_or_else(|| slugify(&routine.title))
}
pub(crate) fn routine_slug(routine: &Routine) -> String {
let rel_dir = routine_rel_dir(routine);
rel_dir.rsplit('/').next().unwrap_or("").to_string()
}
pub(crate) fn routine_folder(routine: &Routine) -> Option<String> {
std::path::Path::new(&routine_rel_dir(routine))
.parent()
.filter(|parent| !parent.as_os_str().is_empty())
.map(rel_string)
}