#![allow(dead_code)]
use std::collections::BTreeSet;
use std::path::Path;
use frame::io::project_io;
use frame::model::task::Task;
use frame::model::track::{Track, TrackNode};
use frame::parse::{
parse_archive, parse_inbox, parse_track, serialize_archive, serialize_inbox, serialize_track,
};
pub fn all_markdown(frame_dir: &Path) -> Vec<(std::path::PathBuf, String)> {
let mut out = Vec::new();
let mut stack = vec![frame_dir.to_path_buf()];
while let Some(dir) = stack.pop() {
let Ok(entries) = std::fs::read_dir(&dir) else {
continue;
};
for entry in entries.flatten() {
let path = entry.path();
if path.is_dir() {
stack.push(path);
} else if path.extension().is_some_and(|e| e == "md")
&& let Ok(text) = std::fs::read_to_string(&path)
{
out.push((path, text));
}
}
}
out.sort();
out
}
pub fn walk<'a>(task: &'a Task, out: &mut Vec<&'a Task>) {
out.push(task);
for sub in &task.subtasks {
walk(sub, out);
}
}
pub fn tasks_of(track: &Track) -> Vec<&Task> {
let mut out = Vec::new();
for node in &track.nodes {
if let TrackNode::Section { tasks, .. } = node {
for task in tasks {
walk(task, &mut out);
}
}
}
out
}
pub fn all_tasks(frame_dir: &Path) -> Vec<Task> {
let mut out = Vec::new();
for (path, text) in all_markdown(frame_dir) {
if path.components().any(|c| c.as_os_str() == "archive") {
continue;
}
let track = parse_track(&text);
out.extend(tasks_of(&track).into_iter().cloned());
}
for (_, tasks) in project_io::load_archives(frame_dir).unwrap_or_default() {
let mut flat = Vec::new();
for task in &tasks {
walk(task, &mut flat);
}
out.extend(flat.into_iter().cloned());
}
let whole = frame_dir.join("archive").join("_tracks");
if let Ok(entries) = std::fs::read_dir(&whole) {
for entry in entries.flatten() {
if entry.path().extension().is_some_and(|e| e == "md")
&& let Ok(text) = std::fs::read_to_string(entry.path())
{
let track = parse_track(&text);
out.extend(tasks_of(&track).into_iter().cloned());
}
}
}
out
}
pub fn present(frame_dir: &Path) -> (BTreeSet<String>, BTreeSet<String>) {
let mut titles = BTreeSet::new();
let mut ids = BTreeSet::new();
for task in all_tasks(frame_dir) {
if !task.title.trim().is_empty() {
titles.insert(task.title.clone());
}
if let Some(id) = &task.id {
ids.insert(id.to_string());
}
}
(titles, ids)
}
pub fn id_tally(frame_dir: &Path) -> Vec<String> {
all_tasks(frame_dir)
.iter()
.filter_map(|t| t.id.as_ref().map(|id| id.to_string()))
.collect()
}
pub fn all_text(frame_dir: &Path) -> String {
all_markdown(frame_dir)
.into_iter()
.map(|(_, t)| t)
.collect::<Vec<_>>()
.join("\n")
}
pub fn unsettled(frame_dir: &Path) -> Option<String> {
for (path, text) in all_markdown(frame_dir) {
let is_archive = path.components().any(|c| c.as_os_str() == "archive");
let is_whole_track = path.components().any(|c| c.as_os_str() == "_tracks");
let is_inbox = path.file_name().is_some_and(|n| n == "inbox.md");
let rewritten = if is_inbox {
serialize_inbox(&parse_inbox(&text).0)
} else if is_archive && !is_whole_track {
serialize_archive(&parse_archive(&text))
} else {
serialize_track(&parse_track(&text))
};
if rewritten != text {
return Some(format!(
"{} is unsettled\nwrote: {text:?}\nrewrites to: {rewritten:?}",
path.display()
));
}
}
None
}