#[macro_use]
extern crate lazy_static;
pub mod statics;
pub mod error;
pub mod tasks;
pub mod clock;
pub mod doc;
pub mod state;
pub mod terminal;
pub mod clockedit;
pub mod clockeditcli;
pub mod helper;
use rustyline::error::ReadlineError;
use rustyline::Editor;
use std::env::var;
use uuid::Uuid;
use std::io::Write;
use std::path::Path;
use chrono::Local;
use std::rc::Rc;
use error::*;
use tasks::*;
use doc::*;
use state::*;
use clockeditcli::*;
use helper::*;
trait DurationPrint {
fn print(&self) -> String;
}
impl DurationPrint for chrono::Duration {
fn print(&self) -> String {
format!("{}d {}h {}m {}s",
self.num_days(),
self.num_hours() % 24,
self.num_minutes() % 60,
self.num_seconds() % 60
)
}
}
fn main() {
let main_file_path = format!("{}/.tasks.json", var("HOME").unwrap());
let doc = Doc::load(&main_file_path).unwrap_or(Doc::new());
let state = State {
wt: doc.root.clone(),
doc: doc,
parents: Vec::new(),
path: main_file_path.clone(),
autosave: Autosave::ManualOnly,
};
let mut terminal = terminal::Terminal::new(state);
terminal.register_command("exit", Box::new(|_, _| Ok(true)));
terminal.register_command("debug", Box::new(|state, _| { println!("{:?}", state); Ok(false) }));
terminal.register_command("ls", Box::new(|state: &mut State, _| {
let task = state.doc.get(&state.wt);
let mut breadcrumb_item_opn = Some(state.wt.clone());
let mut breadcrumb_data = Vec::new();
loop {
if let Some(breadcrumb_item) = breadcrumb_item_opn {
breadcrumb_data.push(breadcrumb_item.clone());
breadcrumb_item_opn = state.doc.find_parent(&breadcrumb_item);
} else {
break;
}
}
breadcrumb_data.iter().rev().zip(1..).for_each(|(breadcrumb_ref, i)| {
let task = state.doc.get(breadcrumb_ref);
if i > 1 {
print!(" -> ");
}
print!("{}", task.title);
});
let (done, all_subtasks) = state.doc.progress_summary(&task.id);
println!(" [{}/{}]", done, all_subtasks);
println!();
println!("{}", task.body);
println!("--- Children: ");
for (child_id, i) in task.children.iter().zip(1..) {
let child = state.doc.get(child_id);
let progress_str = if let Some(progress) = &child.progress {
progress.to_string()
} else {
String::new()
};
println!("{}: {} {}", i, progress_str, child.title);
}
Ok(false)
}));
terminal.register_command("ed", Box::new(|state: &mut State, _| {
let task = vim_edit_task(state.doc.get(&state.wt));
state.doc.upsert(task);
Ok(false)
}));
terminal.register_command("add", Box::new(|state: &mut State, _| {
let task = vim_edit_task(Rc::new(Task::new()));
state.doc.add_subtask(task, &state.wt);
Ok(false)
}));
terminal.register_command("save", Box::new(|state: &mut State, cmd: &str| {
let mut split = cmd.split(" ");
split.next();
let filename = if let Some(filename) = split.next() {
filename
} else {
&state.path
};
state.doc.save(filename).expect("Couldn't save the file");
Ok(false)
}));
terminal.register_command("load", Box::new(|state: &mut State, cmd: &str| {
let mut split = cmd.split(" ");
split.next();
let filename = if let Some(filename) = split.next() {
filename
} else {
&state.path
};
let doc = Doc::load(filename).expect("Couldn't save the file");
let new_root = doc.root.clone();
state.doc = doc;
state.wt = new_root;
Ok(false)
}));
terminal.register_command("cd", Box::new(|state: &mut State, cmd: &str| {
let mut split = cmd.split(" ");
split.next();
if let Some(child) = split.next() {
if child == ".." {
if let Some(parent) = state.parents.pop() {
state.wt = parent;
}
} else if let Ok(i) = child.parse::<usize>() {
let child_id = state.doc.get(&state.wt).children[i - 1];
state.parents.push(state.wt.clone());
state.wt = child_id;
} else if let Ok(id) = Uuid::parse_str(child) {
state.wt = id.clone();
}
} else {
state.wt = state.doc.root.clone();
state.parents = Vec::new();
}
Ok(false)
}));
terminal.register_command("todo", Box::new(|state: &mut State, _| {
let mut task = state.doc.get(&state.wt);
task.set_progress(Progress::Todo);
state.doc.upsert(task);
Ok(false)
}));
terminal.register_command("work", Box::new(|state: &mut State, _| {
let mut task = state.doc.get(&state.wt);
task.set_progress(Progress::Work);
state.doc.upsert(task);
Ok(false)
}));
terminal.register_command("done", Box::new(|state: &mut State, _| {
let mut task = state.doc.get(&state.wt);
task.set_progress(Progress::Done);
state.doc.upsert(task);
Ok(false)
}));
terminal.register_command("id", Box::new(|state: &mut State, _| {
let task = state.doc.get(&state.wt);
println!("Task ID: {}", task.id);
Ok(false)
}));
terminal.register_command("parent", Box::new(|state: &mut State, _| {
let task = state.doc.get(&state.wt);
println!("Parent Task ID: {}", state.doc.find_parent(&task.id).expect("Parent not found"));
Ok(false)
}));
terminal.register_command("rm", Box::new(|state: &mut State, cmd: &str| {
let mut split = cmd.split(" ");
split.next();
if let Some(child) = split.next() {
if let Ok(i) = child.parse::<usize>() {
let mut task = state.doc.get(&state.wt);
let child_id = state.doc.get(&state.wt).children[i - 1];
task.remove_child(&child_id);
state.doc.upsert(task);
}
}
Ok(false)
}));
terminal.register_command("mv", Box::new(|state: &mut State, cmd: &str| {
let mut split = cmd.split(" ");
split.next();
let dest_id = {
if let Some(dest_string) = split.next() {
if let Ok(dest_id) = Uuid::parse_str(dest_string) {
dest_id.clone()
} else {
println!("Error while parsing first uuid");
return Ok(false)
}
} else {
println!("No first UUID specified");
return Ok(false)
}
};
let to_id = {
if let Some(to_string) = split.next() {
if let Ok(to_id) = Uuid::parse_str(to_string) {
to_id.clone()
} else {
println!("Error while parsing second uuid");
return Ok(false)
}
} else {
println!("No second UUID specified");
return Ok(false)
}
};
let parent_id = if let Some(parent_id) = state.doc.find_parent(&dest_id) {
parent_id.clone()
} else {
println!("Couldn't find parents");
return Ok(false);
};
let mut parent = state.doc.get(&parent_id);
parent.remove_child(&dest_id);
state.doc.upsert(parent);
let mut task = state.doc.get(&to_id);
task.add_child(dest_id);
state.doc.upsert(task);
Ok(false)
}));
terminal.register_command("outline", Box::new(|state: &mut State, cmd: &str| {
let mut split = cmd.split(" ");
split.next();
let max_depth = if let Some(depth_str) = split.next() {
if let Ok(max_depth) = depth_str.parse() {
max_depth
} else {
1000
}
} else {
1000
};
rec_print(&mut state.doc, &state.wt, 0, max_depth);
Ok(false)
}));
terminal.register_command("html", Box::new(|state: &mut State, _| {
if let Err(err) = dump_html(&state.doc, Path::new("html"), &state.wt) {
println!("Couldn't dump html files: {}", err);
}
Ok(false)
}));
terminal.register_command("reorder", Box::new(|state: &mut State, cmd: &str| {
let mut split = cmd.split(" ");
split.next();
let idx_string: &str = split.next().ok_or(Error::UnsufficientInput {})?;
let idx_from: usize = idx_string.parse()?;
let idx_string: &str = split.next().ok_or(Error::UnsufficientInput {})?;
let idx_to: usize = idx_string.parse()?;
let mut task = state.doc.get(&state.wt);
let from_id = task.children[idx_from - 1];
task.remove_child(&from_id);
task.insert_child(from_id, idx_to - 1);
state.doc.upsert(task);
Ok(false)
}));
terminal.register_command("cli", Box::new(|state: &mut State, _| {
state.doc.clock_new()?;
state.doc.clock_assign(state.wt.clone())?;
Ok(false)
}));
terminal.register_command("cln", Box::new(|state: &mut State, _| {
state.doc.clock_new()?;
Ok(false)
}));
terminal.register_command("cla", Box::new(|state: &mut State, _| {
state.doc.clock_assign(state.wt.clone())?;
Ok(false)
}));
terminal.register_command("clo", Box::new(|state: &mut State, _| {
state.doc.clock_out()?;
Ok(false)
}));
terminal.register_command("clc", Box::new(|state: &mut State, _| {
let mut comment = String::new();
print!("Clock comment> ");
std::io::stdout().flush()?;
std::io::stdin().read_line(&mut comment)?;
state.doc.clock_comment(comment.trim())?;
Ok(false)
}));
terminal.register_command("taskclock", Box::new(|state: &mut State, _| {
let mut clocks = state.doc.task_clock(&state.wt);
clocks.sort();
display_clocks(&clocks, &state.doc);
Ok(false)
}));
terminal.register_command("dayclock", Box::new(|state: &mut State, _| {
let mut clocks = state.doc.day_clock(Local::today(), state.wt);
clocks.sort();
display_clocks(&clocks, &state.doc);
Ok(false)
}));
terminal.register_command("autosave", Box::new(|state: &mut State, _| {
state.autosave = Autosave::OnCommand;
Ok(false)
}));
terminal.register_command("noautosave", Box::new(|state: &mut State, _| {
state.autosave = Autosave::ManualOnly;
Ok(false)
}));
terminal.register_command("cle", Box::new(|state: &mut State, _| {
let clockeditcli = ClockEditCli {
clockedit: state.doc.create_clock_edit(Local::today()),
apply_result: ExitAction::Cancel,
doc: &state.doc,
};
let final_value = clockeditcli.run();
if final_value.apply_result == ExitAction::Apply {
for clock in final_value.clockedit.clocks.iter().cloned() {
state.doc.upsert_clock(clock);
}
}
Ok(false)
}));
terminal.register_command("rangeclock", Box::new(|state: &mut State, cmd: &str| {
let mut split_cmd = cmd.split(" ");
split_cmd.next();
if let Some(index_str) = split_cmd.next() {
if let Ok(i) = index_str.parse() {
let end = Local::today();
let duration = chrono::Duration::days(i);
let start = end - duration;
let clocks = state.doc.range_clock(start, end, state.wt);
display_clocks(&clocks, &state.doc);
}
}
Ok(false)
}));
let mut rl = Editor::<()>::new();
if rl.load_history(&*statics::HISTORY_FILE).is_err() {
println!("No previous history.");
}
loop {
match rl.readline("> ") {
Ok(input) => {
let exit = terminal.run_command(&input);
if Autosave::OnCommand == terminal.state.autosave {
if let Err(err) = terminal.state.doc.save(&main_file_path) {
println!("Couldn't save the file, sorry: {}", err);
}
}
rl.add_history_entry(input);
if exit {
break;
}
},
Err(ReadlineError::Eof) => break,
Err(ReadlineError::Interrupted) => break,
Err(err) => println!("Error: {}", err),
}
}
if let Err(err) = rl.save_history(&*statics::HISTORY_FILE) {
println!("Failed to save history: {}", err);
}
}