wheeee
This commit is contained in:
@@ -1,3 +1,263 @@
|
||||
use clap::Parser;
|
||||
use serde_yaml;
|
||||
use std::io::Read;
|
||||
|
||||
fn main() {
|
||||
println!("Hello, world!");
|
||||
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)
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user