in command ready!!

This commit is contained in:
Abraham Toriz 2021-07-17 22:04:54 -05:00
parent c942c0a4cc
commit ee44b22efd
No known key found for this signature in database
GPG Key ID: D5B4A746DB5DD42A
4 changed files with 178 additions and 22 deletions

2
.gitignore vendored
View File

@ -1 +1,3 @@
/target
fake_old_config.yml
.env

View File

@ -10,7 +10,9 @@ use crate::editor;
use crate::commands::Command;
use crate::config::Config;
use crate::timeparse::parse_time;
use crate::old::{start_or_warning, warn_if_needed};
#[derive(Default)]
pub struct Args {
at: Option<DateTime<Utc>>,
note: Option<String>,
@ -32,7 +34,7 @@ pub struct InCommand {}
impl<'a> Command<'a> for InCommand {
type Args = Args;
fn handle<D, O, E>(args: Args, db: &mut D, _out: &mut O, _err: &mut E, config: &Config, now: DateTime<Utc>) -> error::Result<()>
fn handle<D, O, E>(args: Args, db: &mut D, out: &mut O, err: &mut E, config: &Config, now: DateTime<Utc>) -> error::Result<()>
where
D: Database,
O: Write,
@ -40,13 +42,30 @@ impl<'a> Command<'a> for InCommand {
{
let start = args.at.unwrap_or(now);
let sheet = db.current_sheet()?.unwrap_or("default".into());
if db.has_running_entry(&sheet)? {
writeln!(out, "Timer is already running for sheet '{}'", sheet)?;
return Ok(());
}
let note = if let Some(note) = args.note {
note
Some(note)
} else {
editor::get_string(config)?
if !config.require_note {
None
} else {
Some(editor::get_string(config)?)
}
};
db.entry_insert(start, None, Some(note), sheet)?;
let (start, needs_warning) = start_or_warning(start, db)?;
db.entry_insert(start, None, note.map(|n| n.trim().to_owned()), &sheet)?;
writeln!(out, "Checked into sheet \"{}\".", sheet)?;
warn_if_needed(err, needs_warning)?;
Ok(())
}
@ -54,13 +73,17 @@ impl<'a> Command<'a> for InCommand {
#[cfg(test)]
mod tests {
use super::*;
use pretty_assertions::assert_eq;
use ansi_term::Color::Yellow;
use chrono::TimeZone;
use crate::test_utils::PrettyString;
use crate::database::SqliteDatabase;
use super::*;
#[test]
#[ignore]
fn test_handles_new_entry() {
fn handles_new_entry() {
let mut d = SqliteDatabase::from_memory().unwrap();
let args = Args {
at: None,
@ -68,16 +91,26 @@ mod tests {
};
let mut out = Vec::new();
let mut err = Vec::new();
let now = Utc::now();
assert!(false, "there are no entries");
d.init().unwrap();
InCommand::handle(args, &mut d, &mut out, &mut err, &Default::default(), Utc::now()).unwrap();
assert_eq!(d.entries_full(None, None).unwrap().len(), 0);
assert!(false, "there is one entry");
InCommand::handle(args, &mut d, &mut out, &mut err, &Default::default(), now).unwrap();
let e = d.entries_full(None, None).unwrap().into_iter().next().unwrap();
assert_eq!(e.note, Some("hola".into()));
assert_eq!(e.start, now);
assert_eq!(e.end, None);
assert_eq!(e.sheet, "default".to_owned());
assert_eq!(PrettyString(&String::from_utf8_lossy(&out)), PrettyString("Checked into sheet \"default\".\n"));
assert_eq!(PrettyString(&String::from_utf8_lossy(&err)), PrettyString(""));
}
#[test]
#[ignore]
fn test_handles_already_running_entry() {
let mut d = SqliteDatabase::from_memory().unwrap();
let args = Args {
@ -86,24 +119,106 @@ mod tests {
};
let mut out = Vec::new();
let mut err = Vec::new();
let now = Utc::now();
assert!(false, "there are no entries");
d.init().unwrap();
d.entry_insert(now, None, None, "default".into()).unwrap();
assert_eq!(d.entries_full(None, None).unwrap().len(), 1);
InCommand::handle(args, &mut d, &mut out, &mut err, &Default::default(), Utc::now()).unwrap();
assert!(false, "there are still no entries");
assert!(false, "a message is issued to the user warning a running entry in this sheet");
assert_eq!(d.entries_full(None, None).unwrap().len(), 1);
assert_eq!(
PrettyString(&String::from_utf8_lossy(&out)),
PrettyString("Timer is already running for sheet 'default'\n")
);
}
#[test]
#[ignore]
fn test_with_no_time_given_uses_now() {
assert!(false);
fn no_note_and_no_mandatory_leaves_none() {
let mut d = SqliteDatabase::from_memory().unwrap();
let args = Default::default();
let mut out = Vec::new();
let mut err = Vec::new();
let now = Utc::now();
let config = Config {
require_note: false,
..Default::default()
};
d.init().unwrap();
assert_eq!(d.entries_full(None, None).unwrap().len(), 0);
InCommand::handle(args, &mut d, &mut out, &mut err, &config, now).unwrap();
let e = d.entries_full(None, None).unwrap().into_iter().next().unwrap();
assert_eq!(e.note, None);
assert_eq!(e.start, now);
assert_eq!(e.end, None);
assert_eq!(e.sheet, "default".to_owned());
}
#[test]
#[ignore]
fn test_sets_given_time() {
assert!(false);
fn warns_if_old_database() {
let mut d = SqliteDatabase::from_memory().unwrap();
let args = Args {
at: None,
note: Some("hola".into()),
};
let mut out = Vec::new();
let mut err = Vec::new();
let now = Utc::now();
d.init_old().unwrap();
assert_eq!(d.entries_full(None, None).unwrap().len(), 0);
InCommand::handle(args, &mut d, &mut out, &mut err, &Default::default(), now).unwrap();
let e = d.entries_full(None, None).unwrap().into_iter().next().unwrap();
assert_eq!(e.note, Some("hola".into()));
assert_eq!(e.start, Utc.from_utc_datetime(&now.naive_local()));
assert_eq!(e.end, None);
assert_eq!(e.sheet, "default".to_owned());
assert_eq!(PrettyString(&String::from_utf8_lossy(&out)), PrettyString("Checked into sheet \"default\".\n"));
assert_eq!(PrettyString(&String::from_utf8_lossy(&err)), PrettyString(&format!(
"{} You are using the old timetrap format, it is advised that \
you update your database using t migrate\n",
Yellow.bold().paint("[WARNING]"))));
}
#[test]
fn notes_are_trimmed() {
let mut d = SqliteDatabase::from_memory().unwrap();
let args = Args {
at: None,
note: Some("hola\n".into()),
};
let mut out = Vec::new();
let mut err = Vec::new();
let now = Utc::now();
d.init().unwrap();
assert_eq!(d.entries_full(None, None).unwrap().len(), 0);
InCommand::handle(args, &mut d, &mut out, &mut err, &Default::default(), now).unwrap();
let e = d.entries_full(None, None).unwrap().into_iter().next().unwrap();
assert_eq!(e.note, Some("hola".into()));
assert_eq!(e.start, now);
assert_eq!(e.end, None);
assert_eq!(e.sheet, "default".to_owned());
assert_eq!(PrettyString(&String::from_utf8_lossy(&out)), PrettyString("Checked into sheet \"default\".\n"));
assert_eq!(PrettyString(&String::from_utf8_lossy(&err)), PrettyString(""));
}
}

View File

@ -22,7 +22,18 @@ pub trait Database {
// ----------
// Migrations
// ----------
/// Create a database in the new database format. Actually the same format
/// just it has an entry in the meta table that indicates the database
/// version.
fn init(&mut self) -> Result<()> {
self.init_old()?;
self.execute("INSERT INTO meta (key, value) VALUES ('database_version', 1)", &[])?;
Ok(())
}
/// Creates the tables for the old database format
fn init_old(&mut self) -> Result<()> {
self.execute("CREATE TABLE `entries`
(
`id` integer NOT NULL PRIMARY KEY AUTOINCREMENT,\
@ -39,7 +50,6 @@ pub trait Database {
`value` varchar(255)
)
", &[])?;
self.execute("INSERT INTO meta (key, value) VALUES ('database_version', 1)", &[])?;
Ok(())
}
@ -134,12 +144,16 @@ pub trait Database {
}
}
fn entry_insert(&mut self, start: DateTime<Utc>, end: Option<DateTime<Utc>>, note: Option<String>, sheet: String) -> Result<()> {
fn entry_insert(&mut self, start: DateTime<Utc>, end: Option<DateTime<Utc>>, note: Option<String>, sheet: &str) -> Result<()> {
self.execute("insert into entries (start, end, note, sheet) values (?1, ?2, ?3, ?4)", &[
&start, &end, &note, &sheet,
])
}
fn has_running_entry(&self, sheet: &str) -> Result<bool> {
Ok(self.entry_query("select * from entries where end is null and sheet=?1", &[&sheet])?.len() != 0)
}
// Meta queries
fn current_sheet(&self) -> Result<Option<String>> {
let results = self.meta_query("select * from meta where key='current_sheet'", &[])?;

View File

@ -7,6 +7,10 @@ use crate::error::{Error, Result};
use crate::models::Entry;
use crate::database::{Database, DBVersion};
/// Treat t as if it wasnt actually in Utc but in the local timezone and return
/// the actual Utc time.
///
/// Used to convert times from the old database version.
fn local_to_utc(t: DateTime<Utc>) -> Result<DateTime<Utc>> {
let local_time = match Local.from_local_datetime(&t.naive_utc()) {
LocalResult::None => return Err(Error::NoneLocalTime(t.naive_utc().to_string())),
@ -21,6 +25,16 @@ fn local_to_utc(t: DateTime<Utc>) -> Result<DateTime<Utc>> {
Ok(Utc.from_utc_datetime(&local_time.naive_utc()))
}
/// takes an otherwise perfectly good timestamp in Utc and turns it into the
/// local timezone, but using the same DateTime<Utc> type.
///
/// Used to insert times into the old database format.
fn utc_to_local(t: DateTime<Utc>) -> DateTime<Utc> {
Utc.from_utc_datetime(&t.naive_local())
}
/// Maps an entire vector of entries from the old database format to the new,
/// converting their timestamps from the local timezone to Utc.
fn local_to_utc_vec(entries: Vec<Entry>) -> Result<Vec<Entry>> {
entries
.into_iter()
@ -34,6 +48,8 @@ fn local_to_utc_vec(entries: Vec<Entry>) -> Result<Vec<Entry>> {
.collect()
}
/// the logic used by many subcommands that transform entries from the old
/// format to the new. Used in conjunction with warn_if_needed.
pub fn entries_or_warning<D: Database>(entries: Vec<Entry>, db: &D) -> Result<(Vec<Entry>, bool)> {
if let DBVersion::Timetrap = db.version()? {
// this indicates that times in the database are specified in the
@ -45,6 +61,15 @@ pub fn entries_or_warning<D: Database>(entries: Vec<Entry>, db: &D) -> Result<(V
}
}
pub fn start_or_warning<D: Database>(time: DateTime<Utc>, db: &D) -> Result<(DateTime<Utc>, bool)> {
if let DBVersion::Timetrap = db.version()? {
Ok((utc_to_local(time), true))
} else {
Ok((time, false))
}
}
/// emits the appropiate warning if the old database format was detected.
pub fn warn_if_needed<E: Write>(err: &mut E, needs_warning: bool) -> Result<()> {
if needs_warning {
writeln!(