264 lines
7.2 KiB
Rust
264 lines
7.2 KiB
Rust
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<String>,
|
|
|
|
#[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<Flags, String> {
|
|
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<Files, String> {
|
|
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<File>,
|
|
}
|
|
|
|
impl Files {
|
|
fn new(files: &Vec<String>) -> 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, String> {
|
|
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<Vec<Task>, 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::<Vec<serde_yaml::Value>>(&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<TaskAt>,
|
|
popped: Vec<Task>,
|
|
}
|
|
|
|
#[derive(Debug, Clone)]
|
|
struct TaskAt {
|
|
task: Task,
|
|
at: usize,
|
|
}
|
|
|
|
#[derive(Debug, Clone, PartialEq)]
|
|
struct Task(serde_yaml::Value);
|
|
|
|
#[derive(Debug, Clone)]
|
|
struct Events(Vec<Delta>);
|
|
|
|
impl Events {
|
|
fn new(file: &String) -> Result<Events, String> {
|
|
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::<Vec<String>>()),
|
|
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<Vec<Task>, 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)
|
|
}
|
|
}
|