serialization order change to avoid data loss; time tracking command

This commit is contained in:
aaron-jack-manning 2022-08-22 20:27:17 +10:00
parent 6aa3eb4e83
commit a1a1713c28
3 changed files with 75 additions and 21 deletions

View File

@ -18,7 +18,7 @@ struct Args {
} }
#[derive(clap::Subcommand, Debug, PartialEq, Eq)] #[derive(clap::Subcommand, Debug, PartialEq, Eq)]
#[clap(version, about, author, global_setting=clap::AppSettings::DisableHelpSubcommand)] #[clap(version, help_short='H', about, author, global_setting=clap::AppSettings::DisableHelpSubcommand)]
enum Command { enum Command {
/// Create a new task. /// Create a new task.
New { New {
@ -74,6 +74,14 @@ enum Command {
// - which columns to include // - which columns to include
// - filters which exclude values // - filters which exclude values
}, },
/// For tracking time against a note.
Track {
id_or_name : String,
#[clap(short, default_value_t=0)]
hours : u16,
#[clap(short, default_value_t=0)]
minutes : u16,
},
/// For making changes to global configuration. /// For making changes to global configuration.
#[clap(subcommand)] #[clap(subcommand)]
Config(ConfigCommand), Config(ConfigCommand),
@ -227,6 +235,13 @@ fn program() -> Result<(), error::Error> {
} }
println!("Updated task {}", colour::id(&id.to_string())); println!("Updated task {}", colour::id(&id.to_string()));
}, },
Track { id_or_name, hours, minutes } => {
let id = state.name_or_id_to_id(&id_or_name)?;
let mut task = tasks::Task::load(id, vault_folder, false)?;
let entry = tasks::TimeEntry::new(hours, minutes);
task.data.time_entries.push(entry);
task.save()?;
},
Discard { id_or_name } => { Discard { id_or_name } => {
let id = state.name_or_id_to_id(&id_or_name)?; let id = state.name_or_id_to_id(&id_or_name)?;
let mut task = tasks::Task::load(id, vault_folder, false)?; let mut task = tasks::Task::load(id, vault_folder, false)?;

View File

@ -80,9 +80,11 @@ impl State {
.create(true) .create(true)
.open(&path)?; .open(&path)?;
let file_contents = toml::to_string(&data)?;
file.set_len(0)?; file.set_len(0)?;
file.seek(io::SeekFrom::Start(0))?; file.seek(io::SeekFrom::Start(0))?;
file.write_all(toml::to_string(&data)?.as_bytes())?; file.write_all(file_contents.as_bytes())?;
let task = Self { let task = Self {
file, file,
@ -100,9 +102,11 @@ impl State {
data, data,
} = self; } = self;
let file_contents = toml::to_string(&data)?;
file.set_len(0)?; file.set_len(0)?;
file.seek(io::SeekFrom::Start(0))?; file.seek(io::SeekFrom::Start(0))?;
file.write_all(toml::to_string(&data)?.as_bytes())?; file.write_all(file_contents.as_bytes())?;
Ok(()) Ok(())
} }

View File

@ -51,10 +51,26 @@ impl Priority {
} }
} }
#[derive(Debug, serde::Serialize, serde::Deserialize)] #[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
pub struct TimeEntry { pub struct TimeEntry {
hours : u32, logged_date : chrono::NaiveDate,
minutes : u8, hours : u16,
minutes : u16,
}
impl TimeEntry {
pub fn new(hours : u16, minutes : u16) -> Self {
let (hours, minutes) = {
(hours + minutes / 60, minutes % 60)
};
Self {
logged_date : chrono::Utc::now().naive_local().date(),
hours,
minutes,
}
}
} }
#[derive(Debug, serde::Serialize, serde::Deserialize)] #[derive(Debug, serde::Serialize, serde::Deserialize)]
@ -66,10 +82,10 @@ pub struct InternalTask {
pub dependencies : HashSet<Id>, pub dependencies : HashSet<Id>,
pub priority : Priority, pub priority : Priority,
//due : Option<chrono::NaiveDateTime>, //due : Option<chrono::NaiveDateTime>,
pub time_entries : Vec<TimeEntry>,
pub created : chrono::NaiveDateTime, pub created : chrono::NaiveDateTime,
pub complete : bool, pub complete : bool,
pub discarded : bool, pub discarded : bool,
pub time_entries : Vec<TimeEntry>,
} }
impl Task { impl Task {
@ -102,9 +118,11 @@ impl Task {
discarded : false, discarded : false,
}; };
let file_contents = toml::to_string(&data)?;
file.set_len(0)?; file.set_len(0)?;
file.seek(io::SeekFrom::Start(0))?; file.seek(io::SeekFrom::Start(0))?;
file.write_all(toml::to_string(&data)?.as_bytes())?; file.write_all(file_contents.as_bytes())?;
state.index_insert(data.name.clone(), id); state.index_insert(data.name.clone(), id);
@ -186,9 +204,11 @@ impl Task {
data, data,
} = self; } = self;
let file_contents = toml::to_string(&data)?;
file.set_len(0)?; file.set_len(0)?;
file.seek(io::SeekFrom::Start(0))?; file.seek(io::SeekFrom::Start(0))?;
file.write_all(toml::to_string(&data)?.as_bytes())?; file.write_all(file_contents.as_bytes())?;
Ok(()) Ok(())
} }
@ -215,12 +235,19 @@ impl Task {
println!(); println!();
} }
let (heading, heading_length) = {
let id = &self.data.id.to_string(); let id = &self.data.id.to_string();
let discarded = if self.data.discarded { String::from(" (discarded)") } else { String::new() }; let discarded = if self.data.discarded { String::from(" (discarded)") } else { String::new() };
let heading = format!("[{}] {} {}{}", if self.data.complete {"X"} else {" "}, colour::id(id), colour::task_name(&self.data.name), colour::greyed_out(&discarded));
println!("{}", heading);
line(5 + self.data.name.chars().count() + id.chars().count() + discarded.chars().count()); (
format!("[{}] {} {}{}", if self.data.complete {"X"} else {" "}, colour::id(id), colour::task_name(&self.data.name), colour::greyed_out(&discarded)),
5 + self.data.name.chars().count() + id.chars().count() + discarded.chars().count()
)
};
println!("{}", heading);
line(heading_length);
println!("Priority: {}", self.data.priority.coloured()); println!("Priority: {}", self.data.priority.coloured());
println!("Tags: [{}]", format_hash_set(&self.data.tags)?); println!("Tags: [{}]", format_hash_set(&self.data.tags)?);
println!("Created: {}", self.data.created); println!("Created: {}", self.data.created);
@ -238,10 +265,18 @@ impl Task {
max_line_width = usize::max(max_line_width, line.chars().count() + 4); max_line_width = usize::max(max_line_width, line.chars().count() + 4);
println!(" {}", line); println!(" {}", line);
} }
line(usize::min(max_line_width, usize::try_from(termsize::get().map(|s| s.cols).unwrap_or(0)).unwrap()));
} }
else {
// Need to work out appropriate line size. if !self.data.time_entries.is_empty() {
let mut entries = self.data.time_entries.clone();
// Sort entries by date.
entries.sort_by(|e1, e2| e1.logged_date.cmp(&e2.logged_date));
println!("Time Entries:");
for entry in &entries {
println!(" {}:{:0>2} [{}]", entry.hours, entry.minutes, entry.logged_date);
}
} }
// dependencies as a tree // dependencies as a tree