use clap::Parser; use serde_yaml; use std::io::Read; fn main() { for file in Flags::new() .expect("failed to flags") .files() .expect("failed to files") .files .iter() { file.reconcile_snapshot_changes().unwrap(); println!( "{} => {:?}", file.file, file.events().unwrap().snapshot().unwrap() ); } } #[derive(Debug, Parser)] struct Flags { #[arg(short = 'f', long = "path", default_value = "$PTTODO_FILE")] path: String, #[arg(short = 'a', long = "add")] add: Option, #[arg(short = 'e', long = "edit", default_value = "false")] edit: bool, #[arg(short = 'd', long = "dry-run", default_value = "false")] dry_run: bool, } impl Flags { fn new() -> Result { let mut result = Flags::parse(); if result.path.get(..1) == Some("$") { result.path = match std::env::var(result.path.get(1..).unwrap()) { Ok(v) => Ok(v), Err(msg) => Err(format!("'{}' unset: {}", result.path, msg)), }?; } let _ = result.files()?; Ok(result) } fn files(&self) -> Result { let metadata = match std::fs::metadata(self.path.clone()) { Ok(v) => Ok(v), Err(msg) => Err(format!("failed to load {}: {}", self.path.clone(), msg)), }?; let files = match metadata.is_dir() { false => Ok(vec![self.path.clone()]), true => match std::fs::read_dir(self.path.clone()) { Ok(paths) => Ok(paths .filter(|x| x.is_ok()) .map(|x| x.unwrap()) .filter(|x| x.metadata().unwrap().is_file()) // TODO filter out hidden files .map(|x| x.path().display().to_string()) .collect()), Err(msg) => Err(format!("failed to read {}: {}", self.path.clone(), msg)), }, }?; Ok(Files::new(&files)) } } #[derive(Debug, Clone)] struct Files { files: Vec, } impl Files { fn new(files: &Vec) -> Files { Files { files: files.into_iter().map(|x| File::new(x)).collect(), } } } #[derive(Debug, Clone)] struct File { file: String, } impl File { fn new(file: &String) -> File { File { file: file.clone() } } fn events(&self) -> Result { Events::new(&self.file) } fn reconcile_snapshot_changes(&self) -> Result<(), String> { let history = self.events()?.snapshot()?; let cached = self.snapshot()?; match history == cached { true => Ok(()), false => { for task in history.iter() { if !cached.contains(task) { self.pop(task.clone())?; } } for i in 0..cached.len() { if !history.contains(&cached[i]) { self.push(TaskAt { task: cached[i].clone(), at: i, })?; } } panic!("not impl") } } } fn snapshot(&self) -> Result, String> { let mut r = match std::fs::File::open(self.file.clone()) { Ok(f) => Ok(f), Err(msg) => Err(format!("could not open {}: {}", &self.file, msg)), }?; let mut buff = String::new(); match r.read_to_string(&mut buff) { Err(msg) => Err(format!("failed reading {}: {}", &self.file, msg)), _ => Ok({}), }?; let mut result = vec![]; match serde_yaml::from_str::>(&buff) { Ok(v) => { result.extend(v.iter().map(|x| Task(x.clone()))); Ok({}) } Err(msg) => Err(format!("failed parsing {}: {}", &self.file, msg)), }?; Ok(result) } fn push(&self, task_at: TaskAt) -> Result<(), String> { self.append(Delta { pushed_at: vec![task_at], popped: vec![], }) } fn pop(&self, task: Task) -> Result<(), String> { self.append(Delta { pushed_at: vec![], popped: vec![task], }) } fn append(&self, delta: Delta) -> Result<(), String> { use std::fs::OpenOptions; let hostname = gethostname::gethostname(); let mut file = match OpenOptions::new().write(true).append(true).open(&self.file) { Ok(f) => Ok(f), Err(msg) => Err(format!( "failed to open {} for appending: {}", &self.file, msg )), }?; panic!("not impl: {:?}", file) } } #[derive(Debug, Clone)] struct Delta { pushed_at: Vec, popped: Vec, } #[derive(Debug, Clone)] struct TaskAt { task: Task, at: usize, } #[derive(Debug, Clone, PartialEq)] struct Task(serde_yaml::Value); #[derive(Debug, Clone)] struct Events(Vec); impl Events { fn new(file: &String) -> Result { let logs = match std::fs::read_dir(Self::dir(&file)) { Ok(files) => Ok(files .filter(|x| x.is_ok()) .map(|x| x.unwrap()) .filter(|x| x.metadata().unwrap().is_file()) .map(|x| x.path().display().to_string()) .filter(|x| x.starts_with(&Self::log_prefix(&file))) .collect::>()), Err(msg) => Err(format!("failed to read dir {}: {}", Self::dir(&file), msg)), }?; let mut result = vec![]; for log in logs.iter() { panic!("{:?}", log); } Ok(Events(result)) } fn log_prefix(file: &String) -> String { format!("{}/.{}.", Self::dir(&file), Self::basename(&file)).to_string() } fn dir(file: &String) -> String { let path = std::path::Path::new(&file); path.parent() .expect("cannot get dirname") .to_str() .expect("cannot stringify dirname") .to_string() } fn basename(file: &String) -> String { let path = std::path::Path::new(&file); path.file_name() .expect("cannot get basename") .to_str() .expect("cannot stringify basename") .to_string() } fn snapshot(&self) -> Result, String> { let mut result = vec![]; for event in self.0.iter() { for popped in event.popped.iter() { for i in (0..result.len()) .map(|i| i as usize) .filter(|i| result[*i] == *popped) { panic!("not impl") } } for push in event.pushed_at.iter() { result.insert( if push.at < result.len() { push.at } else { result.len() }, push.task.clone(), ); } panic!("not impl: {:?}", result) } Ok(result) } }