in command ready!!
This commit is contained in:
parent
c942c0a4cc
commit
ee44b22efd
|
@ -1 +1,3 @@
|
|||
/target
|
||||
fake_old_config.yml
|
||||
.env
|
||||
|
|
|
@ -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(""));
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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, ¬e, &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'", &[])?;
|
||||
|
|
25
src/old.rs
25
src/old.rs
|
@ -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!(
|
||||
|
|
Loading…
Reference in New Issue