Command trait redesing
This commit is contained in:
parent
9bb4b6d6ba
commit
ba632c88a3
|
@ -623,6 +623,7 @@ name = "tiempo"
|
|||
version = "1.0.4"
|
||||
dependencies = [
|
||||
"ansi_term 0.12.1",
|
||||
"atty",
|
||||
"chrono",
|
||||
"clap",
|
||||
"csv",
|
||||
|
|
|
@ -28,6 +28,7 @@ lazy_static = "1.4"
|
|||
tempfile = "3"
|
||||
serde_json = "1.0"
|
||||
hostname = "0.3"
|
||||
atty = "0.2"
|
||||
|
||||
[dependencies.chrono]
|
||||
version = "0.4"
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
use std::convert::TryFrom;
|
||||
use std::io::Write;
|
||||
use std::io::{BufRead, Write};
|
||||
|
||||
use clap::ArgMatches;
|
||||
use chrono::{DateTime, Utc};
|
||||
|
@ -7,6 +7,8 @@ use chrono::{DateTime, Utc};
|
|||
use crate::error::Result;
|
||||
use crate::database::Database;
|
||||
use crate::config::Config;
|
||||
use crate::io::Streams;
|
||||
use crate::env::Env;
|
||||
|
||||
pub mod r#in;
|
||||
pub mod display;
|
||||
|
@ -25,8 +27,44 @@ pub mod edit;
|
|||
pub mod archive;
|
||||
pub mod configure;
|
||||
|
||||
pub struct Facts {
|
||||
pub now: DateTime<Utc>,
|
||||
pub config: Config,
|
||||
pub env: Env,
|
||||
}
|
||||
|
||||
impl Facts {
|
||||
pub fn new() -> Facts {
|
||||
Facts {
|
||||
now: Utc::now(),
|
||||
config: Default::default(),
|
||||
env: Default::default(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn with_config(self, config: Config) -> Facts {
|
||||
Facts {
|
||||
config,
|
||||
..self
|
||||
}
|
||||
}
|
||||
|
||||
pub fn with_now(self, now: DateTime<Utc>) -> Facts {
|
||||
Facts {
|
||||
now,
|
||||
..self
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for Facts {
|
||||
fn default() -> Facts {
|
||||
Facts::new()
|
||||
}
|
||||
}
|
||||
|
||||
pub trait Command<'a> {
|
||||
type Args: TryFrom<&'a ArgMatches<'a>>;
|
||||
|
||||
fn handle<D: Database, O: Write, E: Write>(args: Self::Args, db: &mut D, out: &mut O, err: &mut E, config: &Config, now: DateTime<Utc>) -> Result<()>;
|
||||
fn handle<D: Database, I: BufRead, O: Write, E: Write>(args: Self::Args, streams: &mut Streams<D, I, O, E>, facts: &Facts) -> Result<()>;
|
||||
}
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
use std::convert::TryFrom;
|
||||
use std::io::Write;
|
||||
use std::io::{BufRead, Write};
|
||||
|
||||
use clap::ArgMatches;
|
||||
use chrono::{DateTime, Utc};
|
||||
|
@ -7,13 +7,13 @@ use regex::Regex;
|
|||
|
||||
use crate::database::Database;
|
||||
use crate::error::{Error, Result};
|
||||
use crate::commands::Command;
|
||||
use crate::config::Config;
|
||||
use crate::commands::{Command, Facts};
|
||||
use crate::timeparse::parse_time;
|
||||
use crate::old::{entries_or_warning, time_or_warning};
|
||||
use crate::formatters::text;
|
||||
use crate::regex::parse_regex;
|
||||
use crate::interactive::ask;
|
||||
use crate::io::Streams;
|
||||
|
||||
#[derive(Default)]
|
||||
pub struct Args {
|
||||
|
@ -43,19 +43,20 @@ pub struct ArchiveCommand {}
|
|||
impl<'a> Command<'a> for ArchiveCommand {
|
||||
type Args = Args;
|
||||
|
||||
fn handle<D, O, E>(args: Args, db: &mut D, out: &mut O, _err: &mut E, _config: &Config, now: DateTime<Utc>) -> Result<()>
|
||||
fn handle<D, I, O, E>(args: Args, streams: &mut Streams<D, I, O, E>, facts: &Facts) -> Result<()>
|
||||
where
|
||||
D: Database,
|
||||
I: BufRead,
|
||||
O: Write,
|
||||
E: Write,
|
||||
{
|
||||
let mut entries = {
|
||||
let start = args.start.map(|s| time_or_warning(s, db)).transpose()?.map(|s| s.0);
|
||||
let end = args.end.map(|e| time_or_warning(e, db)).transpose()?.map(|e| e.0);
|
||||
let current_sheet = db.current_sheet()?.unwrap_or_else(|| "default".into());
|
||||
let start = args.start.map(|s| time_or_warning(s, &streams.db)).transpose()?.map(|s| s.0);
|
||||
let end = args.end.map(|e| time_or_warning(e, &streams.db)).transpose()?.map(|e| e.0);
|
||||
let current_sheet = streams.db.current_sheet()?.unwrap_or_else(|| "default".into());
|
||||
let sheet = args.sheet.unwrap_or(current_sheet);
|
||||
|
||||
db.entries_by_sheet(&sheet, start, end)?
|
||||
streams.db.entries_by_sheet(&sheet, start, end)?
|
||||
};
|
||||
|
||||
if let Some(re) = args.grep {
|
||||
|
@ -63,20 +64,20 @@ impl<'a> Command<'a> for ArchiveCommand {
|
|||
}
|
||||
|
||||
if args.fake {
|
||||
let (entries, _) = entries_or_warning(entries, db)?;
|
||||
let (entries, _) = entries_or_warning(entries, &streams.db)?;
|
||||
|
||||
text::print_formatted(
|
||||
entries,
|
||||
out,
|
||||
now,
|
||||
&mut streams.out,
|
||||
facts.now,
|
||||
true,
|
||||
)?;
|
||||
} else if ask(out, &format!("Archive {} entries?", entries.len()))? {
|
||||
} else if ask(streams, &format!("Archive {} entries?", entries.len()))? {
|
||||
for entry in entries {
|
||||
db.entry_update(entry.id, entry.start, entry.end, entry.note, &format!("_{}", entry.sheet))?;
|
||||
streams.db.entry_update(entry.id, entry.start, entry.end, entry.note, &format!("_{}", entry.sheet))?;
|
||||
}
|
||||
} else {
|
||||
writeln!(out, "Ok, they're still there")?;
|
||||
writeln!(streams.out, "Ok, they're still there")?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
|
|
|
@ -1,15 +1,15 @@
|
|||
use std::convert::TryFrom;
|
||||
use std::io::Write;
|
||||
use std::io::{BufRead, Write};
|
||||
use std::path::PathBuf;
|
||||
|
||||
use clap::ArgMatches;
|
||||
use chrono::{DateTime, Utc};
|
||||
|
||||
use crate::database::Database;
|
||||
use crate::error::{Error, Result};
|
||||
use crate::commands::Command;
|
||||
use crate::config::{Config, WeekDay};
|
||||
use crate::commands::{Command, Facts};
|
||||
use crate::config::WeekDay;
|
||||
use crate::formatters::Formatter;
|
||||
use crate::io::Streams;
|
||||
|
||||
#[derive(Default)]
|
||||
pub struct Args {
|
||||
|
@ -85,22 +85,23 @@ pub struct ConfigureCommand {}
|
|||
impl<'a> Command<'a> for ConfigureCommand {
|
||||
type Args = Args;
|
||||
|
||||
fn handle<D, O, E>(args: Args, _db: &mut D, out: &mut O, _err: &mut E, config: &Config, _now: DateTime<Utc>) -> Result<()>
|
||||
fn handle<D, I, O, E>(args: Args, streams: &mut Streams<D, I, O, E>, facts: &Facts) -> Result<()>
|
||||
where
|
||||
D: Database,
|
||||
I: BufRead,
|
||||
O: Write,
|
||||
E: Write,
|
||||
{
|
||||
if args.none_given() {
|
||||
if let Some(path) = config.path.as_deref() {
|
||||
writeln!(out, "{}", path.display())?;
|
||||
if let Some(path) = facts.config.path.as_deref() {
|
||||
writeln!(streams.out, "{}", path.display())?;
|
||||
} else {
|
||||
writeln!(out, "Config file is in memory")?;
|
||||
writeln!(streams.out, "Config file is in memory")?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
} else {
|
||||
let mut new_config = config.clone();
|
||||
let mut new_config = facts.config.clone();
|
||||
|
||||
if let Some(path) = args.database_file {
|
||||
new_config.database_file = path;
|
||||
|
@ -150,14 +151,14 @@ impl<'a> Command<'a> for ConfigureCommand {
|
|||
new_config.week_start = val;
|
||||
}
|
||||
|
||||
if let Some(path) = config.path.as_deref() {
|
||||
if let Some(path) = facts.config.path.as_deref() {
|
||||
let output = new_config.write(path)?;
|
||||
|
||||
writeln!(out, "Your new config:\n")?;
|
||||
writeln!(streams.out, "Your new config:\n")?;
|
||||
|
||||
out.write_all(output.as_bytes())?;
|
||||
streams.out.write_all(output.as_bytes())?;
|
||||
} else {
|
||||
writeln!(out, "Your config file is in memory and cannot be written to")?;
|
||||
writeln!(streams.out, "Your config file is in memory and cannot be written to")?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
use std::convert::TryFrom;
|
||||
use std::io::Write;
|
||||
use std::io::{BufRead, Write};
|
||||
use std::str::FromStr;
|
||||
|
||||
use clap::ArgMatches;
|
||||
|
@ -9,12 +9,12 @@ use regex::Regex;
|
|||
use crate::error::{Result, Error};
|
||||
use crate::database::Database;
|
||||
use crate::formatters::Formatter;
|
||||
use crate::config::Config;
|
||||
use crate::timeparse::parse_time;
|
||||
use crate::regex::parse_regex;
|
||||
use crate::old::{entries_or_warning, time_or_warning, warn_if_needed};
|
||||
use crate::io::Streams;
|
||||
|
||||
use super::Command;
|
||||
use super::{Command, Facts};
|
||||
|
||||
// ----------------------------------------------------------------
|
||||
// Things that are general to all commands that display in some way
|
||||
|
@ -39,28 +39,29 @@ impl FromStr for Sheet {
|
|||
}
|
||||
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
pub fn entries_for_display<D, O, E>(
|
||||
pub fn entries_for_display<D, I, O, E>(
|
||||
start: Option<DateTime<Utc>>, end: Option<DateTime<Utc>>,
|
||||
sheet: Option<Sheet>, db: &mut D, out: &mut O, err: &mut E,
|
||||
sheet: Option<Sheet>, streams: &mut Streams<D, I, O, E>,
|
||||
format: Formatter, ids: bool, grep: Option<Regex>,
|
||||
now: DateTime<Utc>,
|
||||
facts: &Facts,
|
||||
) -> Result<()>
|
||||
where
|
||||
D: Database,
|
||||
I: BufRead,
|
||||
O: Write,
|
||||
E: Write,
|
||||
{
|
||||
let start = start.map(|s| time_or_warning(s, db)).transpose()?.map(|s| s.0);
|
||||
let end = end.map(|e| time_or_warning(e, db)).transpose()?.map(|e| e.0);
|
||||
let start = start.map(|s| time_or_warning(s, &streams.db)).transpose()?.map(|s| s.0);
|
||||
let end = end.map(|e| time_or_warning(e, &streams.db)).transpose()?.map(|e| e.0);
|
||||
|
||||
let mut entries = match sheet {
|
||||
Some(Sheet::All) => db.entries_all_visible(start, end)?,
|
||||
Some(Sheet::Full) => db.entries_full(start, end)?,
|
||||
Some(Sheet::Sheet(name)) => db.entries_by_sheet(&name, start, end)?,
|
||||
Some(Sheet::All) => streams.db.entries_all_visible(start, end)?,
|
||||
Some(Sheet::Full) => streams.db.entries_full(start, end)?,
|
||||
Some(Sheet::Sheet(name)) => streams.db.entries_by_sheet(&name, start, end)?,
|
||||
None => {
|
||||
let current_sheet = db.current_sheet()?.unwrap_or_else(|| "default".into());
|
||||
let current_sheet = streams.db.current_sheet()?.unwrap_or_else(|| "default".into());
|
||||
|
||||
db.entries_by_sheet(¤t_sheet, start, end)?
|
||||
streams.db.entries_by_sheet(¤t_sheet, start, end)?
|
||||
}
|
||||
};
|
||||
|
||||
|
@ -68,16 +69,16 @@ where
|
|||
entries.retain(|e| re.is_match(&e.note.clone().unwrap_or_else(String::new)));
|
||||
}
|
||||
|
||||
let (entries, needs_warning) = entries_or_warning(entries, db)?;
|
||||
let (entries, needs_warning) = entries_or_warning(entries, &streams.db)?;
|
||||
|
||||
format.print_formatted(
|
||||
entries,
|
||||
out,
|
||||
now,
|
||||
&mut streams.out,
|
||||
facts.now,
|
||||
ids,
|
||||
)?;
|
||||
|
||||
warn_if_needed(err, needs_warning)?;
|
||||
warn_if_needed(&mut streams.err, needs_warning, &facts.env)?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
@ -116,9 +117,10 @@ pub struct DisplayCommand { }
|
|||
impl<'a> Command<'a> for DisplayCommand {
|
||||
type Args = Args;
|
||||
|
||||
fn handle<D, O, E>(args: Self::Args, db: &mut D, out: &mut O, err: &mut E, config: &Config, now: DateTime<Utc>) -> Result<()>
|
||||
fn handle<D, I, O, E>(args: Self::Args, streams: &mut Streams<D, I, O, E>, facts: &Facts) -> Result<()>
|
||||
where
|
||||
D: Database,
|
||||
I: BufRead,
|
||||
O: Write,
|
||||
E: Write,
|
||||
{
|
||||
|
@ -126,13 +128,11 @@ impl<'a> Command<'a> for DisplayCommand {
|
|||
args.start,
|
||||
args.end,
|
||||
args.sheet,
|
||||
db,
|
||||
out,
|
||||
err,
|
||||
args.format.unwrap_or_else(|| config.default_formatter.clone()),
|
||||
streams,
|
||||
args.format.unwrap_or_else(|| facts.config.default_formatter.clone()),
|
||||
args.ids,
|
||||
args.grep,
|
||||
now
|
||||
facts
|
||||
)
|
||||
}
|
||||
}
|
||||
|
@ -140,26 +140,27 @@ impl<'a> Command<'a> for DisplayCommand {
|
|||
#[cfg(test)]
|
||||
mod tests {
|
||||
use chrono::TimeZone;
|
||||
use ansi_term::Color::Yellow;
|
||||
use pretty_assertions::assert_eq;
|
||||
|
||||
use crate::database::SqliteDatabase;
|
||||
use crate::test_utils::Ps;
|
||||
use crate::config::Config;
|
||||
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn display_as_local_time_if_previous_version() {
|
||||
std::env::set_var("TZ", "CST+6");
|
||||
|
||||
let args = Default::default();
|
||||
let mut db = SqliteDatabase::from_path("assets/test_old_db.db").unwrap();
|
||||
let mut out = Vec::new();
|
||||
let mut err = Vec::new();
|
||||
let config = Default::default();
|
||||
let mut streams = Streams::fake(b"").with_db(
|
||||
SqliteDatabase::from_path("assets/test_old_db.db").unwrap()
|
||||
);
|
||||
let facts = Facts::new();
|
||||
|
||||
DisplayCommand::handle(args, &mut db, &mut out, &mut err, &config, Utc::now()).unwrap();
|
||||
DisplayCommand::handle(args, &mut streams, &facts).unwrap();
|
||||
|
||||
assert_eq!(Ps(&String::from_utf8_lossy(&out)), Ps("Timesheet: default
|
||||
assert_eq!(Ps(&String::from_utf8_lossy(&streams.out)), Ps("Timesheet: default
|
||||
Day Start End Duration Notes
|
||||
Tue Jun 29, 2021 06:26:49 - 07:26:52 1:00:03 lets do some rust
|
||||
1:00:03
|
||||
|
@ -168,8 +169,8 @@ mod tests {
|
|||
"));
|
||||
|
||||
assert_eq!(
|
||||
String::from_utf8_lossy(&err),
|
||||
format!("{} You are using the old timetrap format, it is advised that you update your database using t migrate\n", Yellow.bold().paint("[WARNING]")),
|
||||
String::from_utf8_lossy(&streams.err),
|
||||
"[WARNING] You are using the old timetrap format, it is advised that you update your database using t migrate\n"
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -180,47 +181,40 @@ mod tests {
|
|||
start: Some(Utc.ymd(2021, 6, 30).and_hms(10, 5, 0)),
|
||||
..Default::default()
|
||||
};
|
||||
let mut db = SqliteDatabase::from_memory().unwrap();
|
||||
let mut out = Vec::new();
|
||||
let mut err = Vec::new();
|
||||
let config = Default::default();
|
||||
let mut streams = Streams::fake(b"");
|
||||
let facts = Facts::new();
|
||||
|
||||
db.init().unwrap();
|
||||
streams.db.entry_insert(Utc.ymd(2021, 6, 30).and_hms(10, 0, 0), None, Some("hola".into()), "default".into()).unwrap();
|
||||
streams.db.entry_insert(Utc.ymd(2021, 6, 30).and_hms(10, 10, 0), None, Some("hola".into()), "default".into()).unwrap();
|
||||
|
||||
db.entry_insert(Utc.ymd(2021, 6, 30).and_hms(10, 0, 0), None, Some("hola".into()), "default".into()).unwrap();
|
||||
db.entry_insert(Utc.ymd(2021, 6, 30).and_hms(10, 10, 0), None, Some("hola".into()), "default".into()).unwrap();
|
||||
DisplayCommand::handle(args, &mut streams, &facts).unwrap();
|
||||
|
||||
DisplayCommand::handle(args, &mut db, &mut out, &mut err, &config, Utc::now()).unwrap();
|
||||
|
||||
assert_eq!(Ps(&String::from_utf8_lossy(&out)), Ps("start,end,note,sheet
|
||||
assert_eq!(Ps(&String::from_utf8_lossy(&streams.out)), Ps("start,end,note,sheet
|
||||
2021-06-30T10:10:00.000000Z,,hola,default
|
||||
"));
|
||||
|
||||
assert_eq!(
|
||||
String::from_utf8_lossy(&err),
|
||||
String::from_utf8_lossy(&streams.err),
|
||||
String::new(),
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn filter_by_match() {
|
||||
let mut db = SqliteDatabase::from_memory().unwrap();
|
||||
let mut out = Vec::new();
|
||||
let mut err = Vec::new();
|
||||
let mut streams = Streams::fake(b"");
|
||||
let facts = Facts::new();
|
||||
|
||||
db.init().unwrap();
|
||||
streams.db.entry_insert(Utc.ymd(2021, 6, 30).and_hms(10, 0, 0), None, Some("hola".into()), "default".into()).unwrap();
|
||||
streams.db.entry_insert(Utc.ymd(2021, 6, 30).and_hms(10, 10, 0), None, Some("adios".into()), "default".into()).unwrap();
|
||||
|
||||
db.entry_insert(Utc.ymd(2021, 6, 30).and_hms(10, 0, 0), None, Some("hola".into()), "default".into()).unwrap();
|
||||
db.entry_insert(Utc.ymd(2021, 6, 30).and_hms(10, 10, 0), None, Some("adios".into()), "default".into()).unwrap();
|
||||
entries_for_display(None, None, None, &mut streams, Formatter::Csv, true, Some("io".parse().unwrap()), &facts).unwrap();
|
||||
|
||||
entries_for_display(None, None, None, &mut db, &mut out, &mut err, Formatter::Csv, true, Some("io".parse().unwrap()), Utc::now()).unwrap();
|
||||
|
||||
assert_eq!(Ps(&String::from_utf8_lossy(&out)), Ps("id,start,end,note,sheet
|
||||
assert_eq!(Ps(&String::from_utf8_lossy(&streams.out)), Ps("id,start,end,note,sheet
|
||||
2,2021-06-30T10:10:00.000000Z,,adios,default
|
||||
"));
|
||||
|
||||
assert_eq!(
|
||||
String::from_utf8_lossy(&err),
|
||||
String::from_utf8_lossy(&streams.err),
|
||||
String::new(),
|
||||
);
|
||||
}
|
||||
|
@ -231,21 +225,17 @@ mod tests {
|
|||
sheet: Some(Sheet::All),
|
||||
..Default::default()
|
||||
};
|
||||
let mut db = SqliteDatabase::from_memory().unwrap();
|
||||
let mut out = Vec::new();
|
||||
let mut err = Vec::new();
|
||||
let config = Default::default();
|
||||
let mut streams = Streams::fake(b"");
|
||||
let facts = Facts::new();
|
||||
std::env::set_var("TZ", "CST+6");
|
||||
|
||||
db.init().unwrap();
|
||||
streams.db.entry_insert(Utc.ymd(2021, 6, 30).and_hms(10, 0, 0), Some(Utc.ymd(2021, 6, 30).and_hms(11, 0, 0)), None, "sheet1".into()).unwrap();
|
||||
streams.db.entry_insert(Utc.ymd(2021, 6, 30).and_hms(11, 0, 0), Some(Utc.ymd(2021, 6, 30).and_hms(12, 0, 0)), None, "sheet2".into()).unwrap();
|
||||
streams.db.entry_insert(Utc.ymd(2021, 6, 30).and_hms(12, 0, 0), Some(Utc.ymd(2021, 6, 30).and_hms(13, 0, 0)), None, "sheet1".into()).unwrap();
|
||||
|
||||
db.entry_insert(Utc.ymd(2021, 6, 30).and_hms(10, 0, 0), Some(Utc.ymd(2021, 6, 30).and_hms(11, 0, 0)), None, "sheet1".into()).unwrap();
|
||||
db.entry_insert(Utc.ymd(2021, 6, 30).and_hms(11, 0, 0), Some(Utc.ymd(2021, 6, 30).and_hms(12, 0, 0)), None, "sheet2".into()).unwrap();
|
||||
db.entry_insert(Utc.ymd(2021, 6, 30).and_hms(12, 0, 0), Some(Utc.ymd(2021, 6, 30).and_hms(13, 0, 0)), None, "sheet1".into()).unwrap();
|
||||
DisplayCommand::handle(args, &mut streams, &facts).unwrap();
|
||||
|
||||
DisplayCommand::handle(args, &mut db, &mut out, &mut err, &config, Utc::now()).unwrap();
|
||||
|
||||
assert_eq!(Ps(&String::from_utf8_lossy(&out)), Ps("Timesheet: sheet1
|
||||
assert_eq!(Ps(&String::from_utf8_lossy(&streams.out)), Ps("Timesheet: sheet1
|
||||
Day Start End Duration Notes
|
||||
Wed Jun 30, 2021 04:00:00 - 05:00:00 1:00:00
|
||||
06:00:00 - 07:00:00 1:00:00
|
||||
|
@ -270,21 +260,17 @@ Timesheet: sheet2
|
|||
sheet: Some(Sheet::Full),
|
||||
..Default::default()
|
||||
};
|
||||
let mut db = SqliteDatabase::from_memory().unwrap();
|
||||
let mut out = Vec::new();
|
||||
let mut err = Vec::new();
|
||||
let config = Default::default();
|
||||
let mut streams = Streams::fake(b"");
|
||||
let facts = Facts::new();
|
||||
std::env::set_var("TZ", "CST+6");
|
||||
|
||||
db.init().unwrap();
|
||||
streams.db.entry_insert(Utc.ymd(2021, 6, 30).and_hms(10, 0, 0), Some(Utc.ymd(2021, 6, 30).and_hms(11, 0, 0)), None, "sheet1".into()).unwrap();
|
||||
streams.db.entry_insert(Utc.ymd(2021, 6, 30).and_hms(11, 0, 0), Some(Utc.ymd(2021, 6, 30).and_hms(12, 0, 0)), None, "_sheet2".into()).unwrap();
|
||||
streams.db.entry_insert(Utc.ymd(2021, 6, 30).and_hms(12, 0, 0), Some(Utc.ymd(2021, 6, 30).and_hms(13, 0, 0)), None, "sheet1".into()).unwrap();
|
||||
|
||||
db.entry_insert(Utc.ymd(2021, 6, 30).and_hms(10, 0, 0), Some(Utc.ymd(2021, 6, 30).and_hms(11, 0, 0)), None, "sheet1".into()).unwrap();
|
||||
db.entry_insert(Utc.ymd(2021, 6, 30).and_hms(11, 0, 0), Some(Utc.ymd(2021, 6, 30).and_hms(12, 0, 0)), None, "_sheet2".into()).unwrap();
|
||||
db.entry_insert(Utc.ymd(2021, 6, 30).and_hms(12, 0, 0), Some(Utc.ymd(2021, 6, 30).and_hms(13, 0, 0)), None, "sheet1".into()).unwrap();
|
||||
DisplayCommand::handle(args, &mut streams, &facts).unwrap();
|
||||
|
||||
DisplayCommand::handle(args, &mut db, &mut out, &mut err, &config, Utc::now()).unwrap();
|
||||
|
||||
assert_eq!(Ps(&String::from_utf8_lossy(&out)), Ps("\
|
||||
assert_eq!(Ps(&String::from_utf8_lossy(&streams.out)), Ps("\
|
||||
Timesheet: _sheet2
|
||||
Day Start End Duration Notes
|
||||
Wed Jun 30, 2021 05:00:00 - 06:00:00 1:00:00
|
||||
|
@ -314,24 +300,24 @@ Timesheet: sheet1
|
|||
end: Some(Utc.ymd(2021, 6, 29).and_hms(13, 0, 0)),
|
||||
..Default::default()
|
||||
};
|
||||
let mut db = SqliteDatabase::from_path("assets/test_old_db.db").unwrap();
|
||||
let mut out = Vec::new();
|
||||
let mut err = Vec::new();
|
||||
let config = Default::default();
|
||||
let mut streams = Streams::fake(b"").with_db(
|
||||
SqliteDatabase::from_path("assets/test_old_db.db").unwrap()
|
||||
);
|
||||
let facts = Facts::new();
|
||||
|
||||
// item in database:
|
||||
// start: 2021-06-29 06:26:49.580565
|
||||
// end: 2021-06-29 07:26:52.816747
|
||||
|
||||
DisplayCommand::handle(args, &mut db, &mut out, &mut err, &config, Utc::now()).unwrap();
|
||||
DisplayCommand::handle(args, &mut streams, &facts).unwrap();
|
||||
|
||||
assert_eq!(Ps(&String::from_utf8_lossy(&out)), Ps("start,end,note,sheet
|
||||
assert_eq!(Ps(&String::from_utf8_lossy(&streams.out)), Ps("start,end,note,sheet
|
||||
2021-06-29T12:26:49.580565Z,2021-06-29T13:26:52.816747Z,lets do some rust,default
|
||||
"));
|
||||
|
||||
assert_eq!(
|
||||
String::from_utf8_lossy(&err),
|
||||
format!("{} You are using the old timetrap format, it is advised that you update your database using t migrate\n", Yellow.bold().paint("[WARNING]")),
|
||||
String::from_utf8_lossy(&streams.err),
|
||||
"[WARNING] You are using the old timetrap format, it is advised that you update your database using t migrate\n"
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -340,22 +326,18 @@ Timesheet: sheet1
|
|||
std::env::set_var("TZ", "CST+6");
|
||||
|
||||
let args = Default::default();
|
||||
let mut db = SqliteDatabase::from_memory().unwrap();
|
||||
let mut out = Vec::new();
|
||||
let mut err = Vec::new();
|
||||
let config = Config {
|
||||
let mut streams = Streams::fake(b"");
|
||||
let facts = Facts::new().with_config(Config {
|
||||
default_formatter: Formatter::Ids,
|
||||
..Default::default()
|
||||
};
|
||||
});
|
||||
|
||||
db.init().unwrap();
|
||||
streams.db.entry_insert(Utc.ymd(2021, 6, 30).and_hms(10, 0, 0), None, Some("hola".into()), "default".into()).unwrap();
|
||||
streams.db.entry_insert(Utc.ymd(2021, 6, 30).and_hms(10, 10, 0), None, Some("hola".into()), "default".into()).unwrap();
|
||||
|
||||
db.entry_insert(Utc.ymd(2021, 6, 30).and_hms(10, 0, 0), None, Some("hola".into()), "default".into()).unwrap();
|
||||
db.entry_insert(Utc.ymd(2021, 6, 30).and_hms(10, 10, 0), None, Some("hola".into()), "default".into()).unwrap();
|
||||
DisplayCommand::handle(args, &mut streams, &facts).unwrap();
|
||||
|
||||
DisplayCommand::handle(args, &mut db, &mut out, &mut err, &config, Utc::now()).unwrap();
|
||||
|
||||
assert_eq!(&String::from_utf8_lossy(&out), "1 2\n");
|
||||
assert_eq!(String::from_utf8_lossy(&err), "");
|
||||
assert_eq!(&String::from_utf8_lossy(&streams.out), "1 2\n");
|
||||
assert_eq!(String::from_utf8_lossy(&streams.err), "");
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,17 +1,17 @@
|
|||
use std::convert::TryFrom;
|
||||
use std::io::Write;
|
||||
use std::io::{BufRead, Write};
|
||||
|
||||
use clap::ArgMatches;
|
||||
use chrono::{DateTime, Utc};
|
||||
|
||||
use crate::database::{Database, DBVersion};
|
||||
use crate::error::{Error, Result};
|
||||
use crate::commands::Command;
|
||||
use crate::config::Config;
|
||||
use crate::commands::{Facts, Command};
|
||||
use crate::timeparse::parse_time;
|
||||
use crate::old::{entries_or_warning, time_or_warning, warn_if_needed};
|
||||
use crate::formatters::text;
|
||||
use crate::editor;
|
||||
use crate::io::Streams;
|
||||
|
||||
#[derive(Default)]
|
||||
pub struct Args {
|
||||
|
@ -52,69 +52,70 @@ pub struct EditCommand {}
|
|||
impl<'a> Command<'a> for EditCommand {
|
||||
type Args = Args;
|
||||
|
||||
fn handle<D, O, E>(args: Args, db: &mut D, out: &mut O, err: &mut E, config: &Config, now: DateTime<Utc>) -> Result<()>
|
||||
fn handle<D, I, O, E>(args: Args, streams: &mut Streams<D, I, O, E>, facts: &Facts) -> Result<()>
|
||||
where
|
||||
D: Database,
|
||||
I: BufRead,
|
||||
O: Write,
|
||||
E: Write,
|
||||
{
|
||||
let current_sheet = db.current_sheet()?.unwrap_or_else(|| "default".to_owned());
|
||||
let needs_warning = db.version()? == DBVersion::Timetrap;
|
||||
let current_sheet = streams.db.current_sheet()?.unwrap_or_else(|| "default".to_owned());
|
||||
let needs_warning = streams.db.version()? == DBVersion::Timetrap;
|
||||
|
||||
let entry = if let Some(id) = args.id {
|
||||
if let Some(entry) = db.entry_by_id(id)? {
|
||||
if let Some(entry) = streams.db.entry_by_id(id)? {
|
||||
entry
|
||||
} else {
|
||||
writeln!(out, "Entry with id \"{}\" does not exist. Perhaps it was deleted", id)?;
|
||||
writeln!(streams.out, "Entry with id \"{}\" does not exist. Perhaps it was deleted", id)?;
|
||||
|
||||
return Ok(());
|
||||
}
|
||||
} else if let Some(entry) = db.last_entry_of_sheet(¤t_sheet)? {
|
||||
} else if let Some(entry) = streams.db.last_entry_of_sheet(¤t_sheet)? {
|
||||
entry
|
||||
} else {
|
||||
writeln!(out, "No entries to edit in sheet \"{}\".", current_sheet)?;
|
||||
writeln!(streams.out, "No entries to edit in sheet \"{}\".", current_sheet)?;
|
||||
|
||||
return Ok(());
|
||||
};
|
||||
|
||||
// normalize the entry in case it comes from an old database
|
||||
let entry = entries_or_warning(vec![entry], db)?.0.into_iter().next().unwrap();
|
||||
let entry = entries_or_warning(vec![entry], &streams.db)?.0.into_iter().next().unwrap();
|
||||
|
||||
// try really hard to obtain the note
|
||||
let note = if let Some(new_note) = args.note {
|
||||
// either from the command's args
|
||||
if args.append {
|
||||
Some(entry.note.unwrap_or_else(|| "".to_owned()) + &config.append_notes_delimiter + &new_note)
|
||||
Some(entry.note.unwrap_or_else(|| "".to_owned()) + &facts.config.append_notes_delimiter + &new_note)
|
||||
} else {
|
||||
Some(new_note)
|
||||
}
|
||||
} else if args.none_given() {
|
||||
// or from the editor if no arguments where given
|
||||
Some(editor::get_string(config.note_editor.as_deref(), entry.note)?)
|
||||
Some(editor::get_string(facts.config.note_editor.as_deref(), entry.note)?)
|
||||
} else {
|
||||
// or just use watever was previously there is the user is editing
|
||||
// something else
|
||||
entry.note
|
||||
};
|
||||
|
||||
db.entry_update(
|
||||
streams.db.entry_update(
|
||||
entry.id,
|
||||
time_or_warning(args.start.unwrap_or(entry.start), db)?.0,
|
||||
args.end.or(entry.end).map(|e| time_or_warning(e, db)).transpose()?.map(|o| o.0),
|
||||
time_or_warning(args.start.unwrap_or(entry.start), &streams.db)?.0,
|
||||
args.end.or(entry.end).map(|e| time_or_warning(e, &streams.db)).transpose()?.map(|o| o.0),
|
||||
note,
|
||||
&args.r#move.unwrap_or(entry.sheet),
|
||||
)?;
|
||||
|
||||
let updated_entry = entries_or_warning(vec![db.entry_by_id(entry.id)?.unwrap()], db)?.0;
|
||||
let updated_entry = entries_or_warning(vec![streams.db.entry_by_id(entry.id)?.unwrap()], &streams.db)?.0;
|
||||
|
||||
text::print_formatted(
|
||||
updated_entry,
|
||||
out,
|
||||
now,
|
||||
&mut streams.out,
|
||||
facts.now,
|
||||
true,
|
||||
)?;
|
||||
|
||||
warn_if_needed(err, needs_warning)?;
|
||||
warn_if_needed(&mut streams.err, needs_warning, &facts.env)?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
@ -126,55 +127,50 @@ mod tests {
|
|||
|
||||
use pretty_assertions::assert_eq;
|
||||
use chrono::{Duration, TimeZone};
|
||||
use ansi_term::Color::Yellow;
|
||||
use tempfile::NamedTempFile;
|
||||
|
||||
use crate::database::SqliteDatabase;
|
||||
use crate::test_utils::Ps;
|
||||
use crate::config::Config;
|
||||
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn edit_last_note() {
|
||||
std::env::set_var("TZ", "CST+6");
|
||||
let mut db = SqliteDatabase::from_memory().unwrap();
|
||||
let mut out = Vec::new();
|
||||
let mut err = Vec::new();
|
||||
let mut streams = Streams::fake(b"");
|
||||
let args = Args {
|
||||
note: Some("new note".into()),
|
||||
..Default::default()
|
||||
};
|
||||
let now = Utc.ymd(2021, 8, 3).and_hms(20, 29, 0);
|
||||
let an_hour_ago = now - Duration::hours(1);
|
||||
let facts = Facts::new().with_now(now);
|
||||
|
||||
db.init().unwrap();
|
||||
streams.db.entry_insert(an_hour_ago, Some(now), Some("a note".into()), "default").unwrap();
|
||||
streams.db.entry_insert(an_hour_ago, Some(now), Some("should be left intact".into()), "sheet1").unwrap();
|
||||
|
||||
db.entry_insert(an_hour_ago, Some(now), Some("a note".into()), "default").unwrap();
|
||||
db.entry_insert(an_hour_ago, Some(now), Some("should be left intact".into()), "sheet1").unwrap();
|
||||
EditCommand::handle(args, &mut streams, &facts).unwrap();
|
||||
|
||||
EditCommand::handle(args, &mut db, &mut out, &mut err, &Default::default(), now).unwrap();
|
||||
|
||||
let entry2 = db.entry_by_id(2).unwrap().unwrap();
|
||||
let entry2 = streams.db.entry_by_id(2).unwrap().unwrap();
|
||||
|
||||
assert_eq!(entry2.note, Some("should be left intact".into()));
|
||||
|
||||
assert_eq!(Ps(&String::from_utf8_lossy(&out)), Ps("Timesheet: default
|
||||
assert_eq!(Ps(&String::from_utf8_lossy(&streams.out)), Ps("Timesheet: default
|
||||
ID Day Start End Duration Notes
|
||||
1 Tue Aug 03, 2021 13:29:00 - 14:29:00 1:00:00 new note
|
||||
1:00:00
|
||||
--------------------------------------------------------------
|
||||
Total 1:00:00
|
||||
"));
|
||||
assert_eq!(Ps(&String::from_utf8_lossy(&err)), Ps(""));
|
||||
assert_eq!(Ps(&String::from_utf8_lossy(&streams.err)), Ps(""));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn edit_with_id() {
|
||||
std::env::set_var("TZ", "CST+6");
|
||||
|
||||
let mut db = SqliteDatabase::from_memory().unwrap();
|
||||
let mut out = Vec::new();
|
||||
let mut err = Vec::new();
|
||||
let mut streams = Streams::fake(b"");
|
||||
let args = Args {
|
||||
id: Some(2),
|
||||
note: Some("new note".into()),
|
||||
|
@ -182,88 +178,79 @@ mod tests {
|
|||
};
|
||||
let now = Utc.ymd(2021, 8, 3).and_hms(20, 29, 0);
|
||||
let an_hour_ago = now - Duration::hours(1);
|
||||
let facts = Facts::new().with_now(now);
|
||||
|
||||
db.init().unwrap();
|
||||
streams.db.entry_insert(an_hour_ago, Some(now), Some("a note".into()), "default").unwrap();
|
||||
streams.db.entry_insert(an_hour_ago, Some(now), Some("should be left intact".into()), "sheet1").unwrap();
|
||||
|
||||
db.entry_insert(an_hour_ago, Some(now), Some("a note".into()), "default").unwrap();
|
||||
db.entry_insert(an_hour_ago, Some(now), Some("should be left intact".into()), "sheet1").unwrap();
|
||||
EditCommand::handle(args, &mut streams, &facts).unwrap();
|
||||
|
||||
EditCommand::handle(args, &mut db, &mut out, &mut err, &Default::default(), now).unwrap();
|
||||
|
||||
assert_eq!(Ps(&String::from_utf8_lossy(&out)), Ps("Timesheet: sheet1
|
||||
assert_eq!(Ps(&String::from_utf8_lossy(&streams.out)), Ps("Timesheet: sheet1
|
||||
ID Day Start End Duration Notes
|
||||
2 Tue Aug 03, 2021 13:29:00 - 14:29:00 1:00:00 new note
|
||||
1:00:00
|
||||
--------------------------------------------------------------
|
||||
Total 1:00:00
|
||||
"));
|
||||
assert_eq!(Ps(&String::from_utf8_lossy(&err)), Ps(""));
|
||||
assert_eq!(Ps(&String::from_utf8_lossy(&streams.err)), Ps(""));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn edit_start() {
|
||||
std::env::set_var("TZ", "CST+6");
|
||||
let mut db = SqliteDatabase::from_memory().unwrap();
|
||||
let mut out = Vec::new();
|
||||
let mut err = Vec::new();
|
||||
let mut streams = Streams::fake(b"");
|
||||
let now = Utc.ymd(2021, 8, 3).and_hms(20, 29, 0);
|
||||
let args = Args {
|
||||
start: Some(now - Duration::minutes(30)),
|
||||
..Default::default()
|
||||
};
|
||||
let an_hour_ago = now - Duration::hours(1);
|
||||
let facts = Facts::new().with_now(now);
|
||||
|
||||
db.init().unwrap();
|
||||
streams.db.entry_insert(an_hour_ago, Some(now), Some("a note".into()), "default").unwrap();
|
||||
|
||||
db.entry_insert(an_hour_ago, Some(now), Some("a note".into()), "default").unwrap();
|
||||
EditCommand::handle(args, &mut streams, &facts).unwrap();
|
||||
|
||||
EditCommand::handle(args, &mut db, &mut out, &mut err, &Default::default(), now).unwrap();
|
||||
|
||||
assert_eq!(Ps(&String::from_utf8_lossy(&out)), Ps("Timesheet: default
|
||||
assert_eq!(Ps(&String::from_utf8_lossy(&streams.out)), Ps("Timesheet: default
|
||||
ID Day Start End Duration Notes
|
||||
1 Tue Aug 03, 2021 13:59:00 - 14:29:00 0:30:00 a note
|
||||
0:30:00
|
||||
------------------------------------------------------------
|
||||
Total 0:30:00
|
||||
"));
|
||||
assert_eq!(Ps(&String::from_utf8_lossy(&err)), Ps(""));
|
||||
assert_eq!(Ps(&String::from_utf8_lossy(&streams.err)), Ps(""));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn edit_end() {
|
||||
std::env::set_var("TZ", "CST+6");
|
||||
let mut db = SqliteDatabase::from_memory().unwrap();
|
||||
let mut out = Vec::new();
|
||||
let mut err = Vec::new();
|
||||
let mut streams = Streams::fake(b"");
|
||||
let now = Utc.ymd(2021, 8, 3).and_hms(20, 29, 0);
|
||||
let args = Args {
|
||||
end: Some(now - Duration::minutes(30)),
|
||||
..Default::default()
|
||||
};
|
||||
let an_hour_ago = now - Duration::hours(1);
|
||||
let facts = Facts::new().with_now(now);
|
||||
|
||||
db.init().unwrap();
|
||||
streams.db.entry_insert(an_hour_ago, Some(now), Some("a note".into()), "default").unwrap();
|
||||
|
||||
db.entry_insert(an_hour_ago, Some(now), Some("a note".into()), "default").unwrap();
|
||||
EditCommand::handle(args, &mut streams, &facts).unwrap();
|
||||
|
||||
EditCommand::handle(args, &mut db, &mut out, &mut err, &Default::default(), now).unwrap();
|
||||
|
||||
assert_eq!(Ps(&String::from_utf8_lossy(&out)), Ps("Timesheet: default
|
||||
assert_eq!(Ps(&String::from_utf8_lossy(&streams.out)), Ps("Timesheet: default
|
||||
ID Day Start End Duration Notes
|
||||
1 Tue Aug 03, 2021 13:29:00 - 13:59:00 0:30:00 a note
|
||||
0:30:00
|
||||
------------------------------------------------------------
|
||||
Total 0:30:00
|
||||
"));
|
||||
assert_eq!(Ps(&String::from_utf8_lossy(&err)), Ps(""));
|
||||
assert_eq!(Ps(&String::from_utf8_lossy(&streams.err)), Ps(""));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn edit_append() {
|
||||
std::env::set_var("TZ", "CST+6");
|
||||
let mut db = SqliteDatabase::from_memory().unwrap();
|
||||
let mut out = Vec::new();
|
||||
let mut err = Vec::new();
|
||||
let mut streams = Streams::fake(b"");
|
||||
let args = Args {
|
||||
note: Some("new note".into()),
|
||||
append: true,
|
||||
|
@ -271,59 +258,53 @@ mod tests {
|
|||
};
|
||||
let now = Utc.ymd(2021, 8, 3).and_hms(20, 29, 0);
|
||||
let an_hour_ago = now - Duration::hours(1);
|
||||
let facts = Facts::new().with_now(now);
|
||||
|
||||
db.init().unwrap();
|
||||
streams.db.entry_insert(an_hour_ago, Some(now), Some("a note".into()), "default").unwrap();
|
||||
|
||||
db.entry_insert(an_hour_ago, Some(now), Some("a note".into()), "default").unwrap();
|
||||
EditCommand::handle(args, &mut streams, &facts).unwrap();
|
||||
|
||||
EditCommand::handle(args, &mut db, &mut out, &mut err, &Default::default(), now).unwrap();
|
||||
|
||||
assert_eq!(Ps(&String::from_utf8_lossy(&out)), Ps("Timesheet: default
|
||||
assert_eq!(Ps(&String::from_utf8_lossy(&streams.out)), Ps("Timesheet: default
|
||||
ID Day Start End Duration Notes
|
||||
1 Tue Aug 03, 2021 13:29:00 - 14:29:00 1:00:00 a note new note
|
||||
1:00:00
|
||||
---------------------------------------------------------------------
|
||||
Total 1:00:00
|
||||
"));
|
||||
assert_eq!(Ps(&String::from_utf8_lossy(&err)), Ps(""));
|
||||
assert_eq!(Ps(&String::from_utf8_lossy(&streams.err)), Ps(""));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn edit_move() {
|
||||
std::env::set_var("TZ", "CST+6");
|
||||
let mut db = SqliteDatabase::from_memory().unwrap();
|
||||
let mut out = Vec::new();
|
||||
let mut err = Vec::new();
|
||||
let mut streams = Streams::fake(b"");
|
||||
let now = Utc.ymd(2021, 8, 3).and_hms(20, 29, 0);
|
||||
let args = Args {
|
||||
r#move: Some("new sheet".to_owned()),
|
||||
..Default::default()
|
||||
};
|
||||
let an_hour_ago = now - Duration::hours(1);
|
||||
let facts = Facts::new().with_now(now);
|
||||
|
||||
db.init().unwrap();
|
||||
streams.db.entry_insert(an_hour_ago, Some(now), Some("a note".into()), "default").unwrap();
|
||||
|
||||
db.entry_insert(an_hour_ago, Some(now), Some("a note".into()), "default").unwrap();
|
||||
EditCommand::handle(args, &mut streams, &facts).unwrap();
|
||||
|
||||
EditCommand::handle(args, &mut db, &mut out, &mut err, &Default::default(), now).unwrap();
|
||||
|
||||
assert_eq!(Ps(&String::from_utf8_lossy(&out)), Ps("Timesheet: new sheet
|
||||
assert_eq!(Ps(&String::from_utf8_lossy(&streams.out)), Ps("Timesheet: new sheet
|
||||
ID Day Start End Duration Notes
|
||||
1 Tue Aug 03, 2021 13:29:00 - 14:29:00 1:00:00 a note
|
||||
1:00:00
|
||||
------------------------------------------------------------
|
||||
Total 1:00:00
|
||||
"));
|
||||
assert_eq!(Ps(&String::from_utf8_lossy(&err)), Ps(""));
|
||||
assert_eq!(Ps(&String::from_utf8_lossy(&streams.err)), Ps(""));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn non_default_delimiter() {
|
||||
std::env::set_var("TZ", "CST+6");
|
||||
|
||||
let mut db = SqliteDatabase::from_memory().unwrap();
|
||||
let mut out = Vec::new();
|
||||
let mut err = Vec::new();
|
||||
let mut streams = Streams::fake(b"");
|
||||
let args = Args {
|
||||
note: Some("new note".into()),
|
||||
append: true,
|
||||
|
@ -331,25 +312,23 @@ mod tests {
|
|||
};
|
||||
let now = Utc.ymd(2021, 8, 3).and_hms(20, 29, 0);
|
||||
let an_hour_ago = now - Duration::hours(1);
|
||||
let config = Config {
|
||||
let facts = Facts::new().with_now(now).with_config(Config {
|
||||
append_notes_delimiter: ";".to_owned(),
|
||||
..Default::default()
|
||||
};
|
||||
});
|
||||
|
||||
db.init().unwrap();
|
||||
streams.db.entry_insert(an_hour_ago, Some(now), Some("a note".into()), "default").unwrap();
|
||||
|
||||
db.entry_insert(an_hour_ago, Some(now), Some("a note".into()), "default").unwrap();
|
||||
EditCommand::handle(args, &mut streams, &facts).unwrap();
|
||||
|
||||
EditCommand::handle(args, &mut db, &mut out, &mut err, &config, now).unwrap();
|
||||
|
||||
assert_eq!(Ps(&String::from_utf8_lossy(&out)), Ps("Timesheet: default
|
||||
assert_eq!(Ps(&String::from_utf8_lossy(&streams.out)), Ps("Timesheet: default
|
||||
ID Day Start End Duration Notes
|
||||
1 Tue Aug 03, 2021 13:29:00 - 14:29:00 1:00:00 a note;new note
|
||||
1:00:00
|
||||
---------------------------------------------------------------------
|
||||
Total 1:00:00
|
||||
"));
|
||||
assert_eq!(Ps(&String::from_utf8_lossy(&err)), Ps(""));
|
||||
assert_eq!(Ps(&String::from_utf8_lossy(&streams.err)), Ps(""));
|
||||
}
|
||||
|
||||
fn copy_db(path: &str) -> NamedTempFile {
|
||||
|
@ -365,19 +344,20 @@ mod tests {
|
|||
std::env::set_var("TZ", "CST+6");
|
||||
|
||||
let database_file = copy_db("assets/test_old_db.db");
|
||||
let mut db = SqliteDatabase::from_path(&database_file).unwrap();
|
||||
let mut out = Vec::new();
|
||||
let mut err = Vec::new();
|
||||
let mut streams = Streams::fake(b"").with_db(
|
||||
SqliteDatabase::from_path(&database_file).unwrap()
|
||||
);
|
||||
let now = Utc.ymd(2021, 8, 3).and_hms(20, 29, 0);
|
||||
let new_end = Utc.ymd(2021, 06, 29).and_hms(14, 26, 52);
|
||||
let args = Args {
|
||||
end: Some(new_end),
|
||||
..Default::default()
|
||||
};
|
||||
let facts = Facts::new().with_now(now);
|
||||
|
||||
EditCommand::handle(args, &mut db, &mut out, &mut err, &Default::default(), now).unwrap();
|
||||
EditCommand::handle(args, &mut streams, &facts).unwrap();
|
||||
|
||||
assert_eq!(Ps(&String::from_utf8_lossy(&out)), Ps("Timesheet: default
|
||||
assert_eq!(Ps(&String::from_utf8_lossy(&streams.out)), Ps("Timesheet: default
|
||||
ID Day Start End Duration Notes
|
||||
1 Tue Jun 29, 2021 06:26:49 - 08:26:52 2:00:02 lets do some rust
|
||||
2:00:02
|
||||
|
@ -385,11 +365,11 @@ mod tests {
|
|||
Total 2:00:02
|
||||
"));
|
||||
assert_eq!(
|
||||
String::from_utf8_lossy(&err),
|
||||
format!("{} You are using the old timetrap format, it is advised that you update your database using t migrate\n", Yellow.bold().paint("[WARNING]")),
|
||||
String::from_utf8_lossy(&streams.err),
|
||||
"[WARNING] You are using the old timetrap format, it is advised that you update your database using t migrate\n"
|
||||
);
|
||||
|
||||
std::mem::drop(db);
|
||||
std::mem::drop(streams.db);
|
||||
std::mem::drop(database_file);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
use std::convert::TryFrom;
|
||||
use std::io::Write;
|
||||
use std::io::{BufRead, Write};
|
||||
|
||||
use clap::ArgMatches;
|
||||
use chrono::{DateTime, Utc};
|
||||
|
@ -7,10 +7,10 @@ use chrono::{DateTime, Utc};
|
|||
use crate::database::Database;
|
||||
use crate::error::{Error, Result};
|
||||
use crate::editor;
|
||||
use crate::commands::Command;
|
||||
use crate::config::Config;
|
||||
use crate::commands::{Command, Facts};
|
||||
use crate::timeparse::parse_time;
|
||||
use crate::old::{time_or_warning, warn_if_needed};
|
||||
use crate::io::Streams;
|
||||
|
||||
#[derive(Default)]
|
||||
pub struct Args {
|
||||
|
@ -34,36 +34,37 @@ 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>) -> Result<()>
|
||||
fn handle<D, I, O, E>(args: Args, streams: &mut Streams<D, I, O, E>, facts: &Facts) -> Result<()>
|
||||
where
|
||||
D: Database,
|
||||
I: BufRead,
|
||||
O: Write,
|
||||
E: Write,
|
||||
{
|
||||
let start = args.at.unwrap_or(now);
|
||||
let sheet = db.current_sheet()?.unwrap_or_else(|| "default".into());
|
||||
let start = args.at.unwrap_or(facts.now);
|
||||
let sheet = streams.db.current_sheet()?.unwrap_or_else(|| "default".into());
|
||||
|
||||
if db.running_entry(&sheet)?.is_some() {
|
||||
writeln!(out, "Timer is already running for sheet '{}'", sheet)?;
|
||||
if streams.db.running_entry(&sheet)?.is_some() {
|
||||
writeln!(streams.out, "Timer is already running for sheet '{}'", sheet)?;
|
||||
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
let note = if let Some(note) = args.note {
|
||||
Some(note.trim().to_owned())
|
||||
} else if !config.require_note {
|
||||
} else if !facts.config.require_note {
|
||||
None
|
||||
} else {
|
||||
Some(editor::get_string(config.note_editor.as_deref(), None)?)
|
||||
Some(editor::get_string(facts.config.note_editor.as_deref(), None)?)
|
||||
};
|
||||
|
||||
let (start, needs_warning) = time_or_warning(start, db)?;
|
||||
let (start, needs_warning) = time_or_warning(start, &streams.db)?;
|
||||
|
||||
db.entry_insert(start, None, note, &sheet)?;
|
||||
streams.db.entry_insert(start, None, note, &sheet)?;
|
||||
|
||||
writeln!(out, "Checked into sheet \"{}\".", sheet)?;
|
||||
writeln!(streams.out, "Checked into sheet \"{}\".", sheet)?;
|
||||
|
||||
warn_if_needed(err, needs_warning)?;
|
||||
warn_if_needed(&mut streams.err, needs_warning, &facts.env)?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
@ -72,151 +73,135 @@ impl<'a> Command<'a> for InCommand {
|
|||
#[cfg(test)]
|
||||
mod tests {
|
||||
use pretty_assertions::assert_eq;
|
||||
use ansi_term::Color::Yellow;
|
||||
use chrono::{TimeZone, Local};
|
||||
|
||||
use crate::test_utils::Ps;
|
||||
use crate::database::SqliteDatabase;
|
||||
use crate::config::Config;
|
||||
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn handles_new_entry() {
|
||||
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();
|
||||
let mut streams = Streams::fake(b"");
|
||||
let facts = Facts::new();
|
||||
|
||||
d.init().unwrap();
|
||||
assert_eq!(streams.db.entries_full(None, None).unwrap().len(), 0);
|
||||
|
||||
assert_eq!(d.entries_full(None, None).unwrap().len(), 0);
|
||||
InCommand::handle(args, &mut streams, &facts).unwrap();
|
||||
|
||||
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();
|
||||
let e = streams.db.entries_full(None, None).unwrap().into_iter().next().unwrap();
|
||||
|
||||
assert_eq!(e.note, Some("hola".into()));
|
||||
assert_eq!(e.start, now);
|
||||
assert_eq!(e.start, facts.now);
|
||||
assert_eq!(e.end, None);
|
||||
assert_eq!(e.sheet, "default".to_owned());
|
||||
|
||||
assert_eq!(Ps(&String::from_utf8_lossy(&out)), Ps("Checked into sheet \"default\".\n"));
|
||||
assert_eq!(Ps(&String::from_utf8_lossy(&err)), Ps(""));
|
||||
assert_eq!(Ps(&String::from_utf8_lossy(&streams.out)), Ps("Checked into sheet \"default\".\n"));
|
||||
assert_eq!(Ps(&String::from_utf8_lossy(&streams.err)), Ps(""));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_handles_already_running_entry() {
|
||||
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();
|
||||
let mut streams = Streams::fake(b"");
|
||||
let facts = Facts::new();
|
||||
|
||||
d.init().unwrap();
|
||||
streams.db.entry_insert(facts.now, None, None, "default".into()).unwrap();
|
||||
|
||||
d.entry_insert(now, None, None, "default".into()).unwrap();
|
||||
assert_eq!(streams.db.entries_full(None, None).unwrap().len(), 1);
|
||||
|
||||
assert_eq!(d.entries_full(None, None).unwrap().len(), 1);
|
||||
InCommand::handle(args, &mut streams, &facts).unwrap();
|
||||
|
||||
InCommand::handle(args, &mut d, &mut out, &mut err, &Default::default(), Utc::now()).unwrap();
|
||||
|
||||
assert_eq!(d.entries_full(None, None).unwrap().len(), 1);
|
||||
assert_eq!(streams.db.entries_full(None, None).unwrap().len(), 1);
|
||||
|
||||
assert_eq!(
|
||||
Ps(&String::from_utf8_lossy(&out)),
|
||||
Ps(&String::from_utf8_lossy(&streams.out)),
|
||||
Ps("Timer is already running for sheet 'default'\n")
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
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 {
|
||||
let mut streams = Streams::fake(b"");
|
||||
let facts = Facts::new().with_config(Config {
|
||||
require_note: false,
|
||||
..Default::default()
|
||||
};
|
||||
});
|
||||
|
||||
d.init().unwrap();
|
||||
assert_eq!(streams.db.entries_full(None, None).unwrap().len(), 0);
|
||||
|
||||
assert_eq!(d.entries_full(None, None).unwrap().len(), 0);
|
||||
InCommand::handle(args, &mut streams, &facts).unwrap();
|
||||
|
||||
InCommand::handle(args, &mut d, &mut out, &mut err, &config, now).unwrap();
|
||||
|
||||
let e = d.entries_full(None, None).unwrap().into_iter().next().unwrap();
|
||||
let e = streams.db.entries_full(None, None).unwrap().into_iter().next().unwrap();
|
||||
|
||||
assert_eq!(e.note, None);
|
||||
assert_eq!(e.start, now);
|
||||
assert_eq!(e.start, facts.now);
|
||||
assert_eq!(e.end, None);
|
||||
assert_eq!(e.sheet, "default".to_owned());
|
||||
}
|
||||
|
||||
#[test]
|
||||
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();
|
||||
let mut streams = Streams::fake(b"").with_db({
|
||||
let mut db = SqliteDatabase::from_memory().unwrap();
|
||||
|
||||
d.init_old().unwrap();
|
||||
db.init_old().unwrap();
|
||||
|
||||
assert_eq!(d.entries_full(None, None).unwrap().len(), 0);
|
||||
db
|
||||
});
|
||||
let facts = Facts::new();
|
||||
|
||||
InCommand::handle(args, &mut d, &mut out, &mut err, &Default::default(), now).unwrap();
|
||||
assert_eq!(streams.db.entries_full(None, None).unwrap().len(), 0);
|
||||
|
||||
let e = d.entries_full(None, None).unwrap().into_iter().next().unwrap();
|
||||
InCommand::handle(args, &mut streams, &facts).unwrap();
|
||||
|
||||
let e = streams.db.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.with_timezone(&Local).naive_local()));
|
||||
assert_eq!(e.start, Utc.from_utc_datetime(&facts.now.with_timezone(&Local).naive_local()));
|
||||
assert_eq!(e.end, None);
|
||||
assert_eq!(e.sheet, "default".to_owned());
|
||||
|
||||
assert_eq!(Ps(&String::from_utf8_lossy(&out)), Ps("Checked into sheet \"default\".\n"));
|
||||
assert_eq!(Ps(&String::from_utf8_lossy(&err)), Ps(&format!(
|
||||
"{} You are using the old timetrap format, it is advised that \
|
||||
you update your database using t migrate\n",
|
||||
Yellow.bold().paint("[WARNING]"))));
|
||||
assert_eq!(Ps(&String::from_utf8_lossy(&streams.out)), Ps("Checked into sheet \"default\".\n"));
|
||||
assert_eq!(Ps(&String::from_utf8_lossy(&streams.err)), Ps(
|
||||
"[WARNING] You are using the old timetrap format, it is advised that \
|
||||
you update your database using t migrate\n"));
|
||||
}
|
||||
|
||||
#[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();
|
||||
let mut streams = Streams::fake(b"");
|
||||
let facts = Facts::new();
|
||||
|
||||
d.init().unwrap();
|
||||
assert_eq!(streams.db.entries_full(None, None).unwrap().len(), 0);
|
||||
|
||||
assert_eq!(d.entries_full(None, None).unwrap().len(), 0);
|
||||
InCommand::handle(args, &mut streams, &facts).unwrap();
|
||||
|
||||
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();
|
||||
let e = streams.db.entries_full(None, None).unwrap().into_iter().next().unwrap();
|
||||
|
||||
assert_eq!(e.note, Some("hola".into()));
|
||||
assert_eq!(e.start, now);
|
||||
assert_eq!(e.start, facts.now);
|
||||
assert_eq!(e.end, None);
|
||||
assert_eq!(e.sheet, "default".to_owned());
|
||||
|
||||
assert_eq!(Ps(&String::from_utf8_lossy(&out)), Ps("Checked into sheet \"default\".\n"));
|
||||
assert_eq!(Ps(&String::from_utf8_lossy(&err)), Ps(""));
|
||||
assert_eq!(Ps(&String::from_utf8_lossy(&streams.out)), Ps("Checked into sheet \"default\".\n"));
|
||||
assert_eq!(Ps(&String::from_utf8_lossy(&streams.err)), Ps(""));
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,15 +1,14 @@
|
|||
use std::convert::TryFrom;
|
||||
use std::io::Write;
|
||||
use std::io::{BufRead, Write};
|
||||
|
||||
use clap::ArgMatches;
|
||||
use chrono::{DateTime, Utc};
|
||||
|
||||
use crate::error::{Error, Result};
|
||||
use crate::database::Database;
|
||||
use crate::config::Config;
|
||||
use crate::interactive::ask;
|
||||
use crate::io::Streams;
|
||||
|
||||
use super::Command;
|
||||
use super::{Command, Facts};
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum Args {
|
||||
|
@ -34,33 +33,34 @@ pub struct KillCommand;
|
|||
impl<'a> Command<'a> for KillCommand {
|
||||
type Args = Args;
|
||||
|
||||
fn handle<D, O, E>(args: Args, db: &mut D, out: &mut O, _err: &mut E, _config: &Config, _now: DateTime<Utc>) -> Result<()>
|
||||
fn handle<D, I, O, E>(args: Args, streams: &mut Streams<D, I, O, E>, _facts: &Facts) -> Result<()>
|
||||
where
|
||||
D: Database,
|
||||
I: BufRead,
|
||||
O: Write,
|
||||
E: Write,
|
||||
{
|
||||
match args {
|
||||
Args::Id(id) => {
|
||||
if let Some(entry) = db.entry_by_id(id)? {
|
||||
if ask(out, &format!("are you sure you want to delete entry {}? ({})", entry.id, entry.note.unwrap_or_else(|| "".into())))? {
|
||||
db.delete_entry_by_id(id)?;
|
||||
writeln!(out, "It's dead")?;
|
||||
if let Some(entry) = streams.db.entry_by_id(id)? {
|
||||
if ask(streams, &format!("are you sure you want to delete entry {}? ({})", entry.id, entry.note.unwrap_or_else(|| "".into())))? {
|
||||
streams.db.delete_entry_by_id(id)?;
|
||||
writeln!(streams.out, "It's dead")?;
|
||||
} else {
|
||||
writeln!(out, "Don't worry, it's still there")?;
|
||||
writeln!(streams.out, "Don't worry, it's still there")?;
|
||||
}
|
||||
} else {
|
||||
writeln!(out, "There's no entry with id {}. Someone found it before we did.", id)?;
|
||||
writeln!(streams.out, "There's no entry with id {}. Someone found it before we did.", id)?;
|
||||
}
|
||||
},
|
||||
Args::Sheet(sheet) => {
|
||||
let n = db.entries_by_sheet(&sheet, None, None)?.len();
|
||||
let n = streams.db.entries_by_sheet(&sheet, None, None)?.len();
|
||||
|
||||
if ask(out, &format!("are you sure you want to delete {} entries on sheet \"{}\"?", n, sheet))? {
|
||||
db.delete_entries_in_sheet(&sheet)?;
|
||||
writeln!(out, "They're gone")?;
|
||||
if ask(streams, &format!("are you sure you want to delete {} entries on sheet \"{}\"?", n, sheet))? {
|
||||
streams.db.delete_entries_in_sheet(&sheet)?;
|
||||
writeln!(streams.out, "They're gone")?;
|
||||
} else {
|
||||
writeln!(out, "Don't worry, they're still there")?;
|
||||
writeln!(streams.out, "Don't worry, they're still there")?;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,20 +1,20 @@
|
|||
use std::convert::TryFrom;
|
||||
use std::io::Write;
|
||||
use std::io::{BufRead, Write};
|
||||
|
||||
use clap::ArgMatches;
|
||||
use chrono::{DateTime, Utc, Duration, Local};
|
||||
use chrono::{Utc, Duration, Local};
|
||||
use itertools::Itertools;
|
||||
use ansi_term::Style;
|
||||
|
||||
use crate::error::{Error, Result};
|
||||
use crate::database::Database;
|
||||
use crate::config::Config;
|
||||
use crate::tabulate::{Tabulate, Col, Align::*};
|
||||
use crate::formatters::text::format_duration;
|
||||
use crate::models::Entry;
|
||||
use crate::old::{entries_or_warning, warn_if_needed};
|
||||
use crate::io::Streams;
|
||||
|
||||
use super::Command;
|
||||
use super::{Command, Facts};
|
||||
|
||||
#[derive(Default)]
|
||||
pub struct Args {
|
||||
|
@ -36,28 +36,29 @@ pub struct ListCommand {}
|
|||
impl<'a> Command<'a> for ListCommand {
|
||||
type Args = Args;
|
||||
|
||||
fn handle<D, O, E>(args: Args, db: &mut D, out: &mut O, err: &mut E, _config: &Config, now: DateTime<Utc>) -> Result<()>
|
||||
fn handle<D, I, O, E>(args: Args, streams: &mut Streams<D, I, O, E>, facts: &Facts) -> Result<()>
|
||||
where
|
||||
D: Database,
|
||||
I: BufRead,
|
||||
O: Write,
|
||||
E: Write,
|
||||
{
|
||||
let today = now.with_timezone(&Local).date().and_hms(0, 0, 0).with_timezone(&Utc);
|
||||
let today = facts.now.with_timezone(&Local).date().and_hms(0, 0, 0).with_timezone(&Utc);
|
||||
let entries = if args.all {
|
||||
db.entries_full(None, None)?
|
||||
streams.db.entries_full(None, None)?
|
||||
} else {
|
||||
db.entries_all_visible(None, None)?
|
||||
streams.db.entries_all_visible(None, None)?
|
||||
};
|
||||
|
||||
let (mut entries, needs_warning) = entries_or_warning(entries, db)?;
|
||||
let (mut entries, needs_warning) = entries_or_warning(entries, &streams.db)?;
|
||||
|
||||
let current = db.current_sheet()?;
|
||||
let last = db.last_sheet()?;
|
||||
let current = streams.db.current_sheet()?;
|
||||
let last = streams.db.last_sheet()?;
|
||||
|
||||
// introducte two fake entries to make both current and last show up
|
||||
if let Some(ref current) = current {
|
||||
entries.push(Entry {
|
||||
id: 1, sheet: current.clone(), start: now, end: Some(now), note: None,
|
||||
id: 1, sheet: current.clone(), start: facts.now, end: Some(facts.now), note: None,
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -73,12 +74,12 @@ impl<'a> Command<'a> for ListCommand {
|
|||
.into_iter()
|
||||
.map(|(key, group)| {
|
||||
let entries: Vec<_> = group.into_iter().collect();
|
||||
let s_running = now - entries.iter().find(|e| e.end.is_none()).map(|e| e.start).unwrap_or(now);
|
||||
let s_running = facts.now - entries.iter().find(|e| e.end.is_none()).map(|e| e.start).unwrap_or(facts.now);
|
||||
let s_today = entries.iter().filter(|e| e.start > today).fold(Duration::seconds(0), |acc, e| {
|
||||
acc + (e.end.unwrap_or(now) - e.start)
|
||||
acc + (e.end.unwrap_or(facts.now) - e.start)
|
||||
});
|
||||
let s_total = entries.into_iter().fold(Duration::seconds(0), |acc, e| {
|
||||
acc + (e.end.unwrap_or(now) - e.start)
|
||||
acc + (e.end.unwrap_or(facts.now) - e.start)
|
||||
});
|
||||
|
||||
total_running = total_running + s_running;
|
||||
|
@ -139,9 +140,9 @@ impl<'a> Command<'a> for ListCommand {
|
|||
format_duration(total),
|
||||
]);
|
||||
|
||||
out.write_all(tabs.print().as_bytes())?;
|
||||
streams.out.write_all(tabs.print().as_bytes())?;
|
||||
|
||||
warn_if_needed(err, needs_warning)?;
|
||||
warn_if_needed(&mut streams.err, needs_warning, &facts.env)?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
@ -151,7 +152,7 @@ impl<'a> Command<'a> for ListCommand {
|
|||
mod tests {
|
||||
use chrono::{Utc, TimeZone};
|
||||
use pretty_assertions::assert_eq;
|
||||
use ansi_term::{Color::Yellow, Style};
|
||||
use ansi_term::Style;
|
||||
|
||||
use crate::database::{SqliteDatabase, Database};
|
||||
use crate::test_utils::Ps;
|
||||
|
@ -163,25 +164,23 @@ mod tests {
|
|||
std::env::set_var("TZ", "CST+6");
|
||||
|
||||
let args = Default::default();
|
||||
let mut db = SqliteDatabase::from_memory().unwrap();
|
||||
let mut out = Vec::new();
|
||||
let mut err = Vec::new();
|
||||
let config = Default::default();
|
||||
db.init().unwrap();
|
||||
db.set_current_sheet("sheet2").unwrap();
|
||||
db.set_last_sheet("sheet4").unwrap();
|
||||
let mut streams = Streams::fake(b"");
|
||||
|
||||
db.entry_insert(Utc.ymd(2021, 1, 1).and_hms(0, 0, 0), Some(Utc.ymd(2021, 1, 1).and_hms(1, 0, 0)), None, "_archived".into()).unwrap();
|
||||
db.entry_insert(Utc.ymd(2021, 1, 1).and_hms(0, 0, 0), Some(Utc.ymd(2021, 1, 1).and_hms(10,13, 55)), None, "sheet1".into()).unwrap();
|
||||
db.entry_insert(Utc.ymd(2021, 1, 1).and_hms(0, 0, 0), Some(Utc.ymd(2021, 1, 1).and_hms(7, 39, 18)), None, "sheet3".into()).unwrap();
|
||||
db.entry_insert(Utc.ymd(2021, 1, 1).and_hms(12, 0, 0), Some(Utc.ymd(2021, 1, 1).and_hms(13, 52, 45)), None, "sheet3".into()).unwrap();
|
||||
db.entry_insert(Utc.ymd(2021, 1, 1).and_hms(12, 0, 0), None, None, "sheet4".into()).unwrap();
|
||||
streams.db.set_current_sheet("sheet2").unwrap();
|
||||
streams.db.set_last_sheet("sheet4").unwrap();
|
||||
|
||||
streams.db.entry_insert(Utc.ymd(2021, 1, 1).and_hms(0, 0, 0), Some(Utc.ymd(2021, 1, 1).and_hms(1, 0, 0)), None, "_archived".into()).unwrap();
|
||||
streams.db.entry_insert(Utc.ymd(2021, 1, 1).and_hms(0, 0, 0), Some(Utc.ymd(2021, 1, 1).and_hms(10,13, 55)), None, "sheet1".into()).unwrap();
|
||||
streams.db.entry_insert(Utc.ymd(2021, 1, 1).and_hms(0, 0, 0), Some(Utc.ymd(2021, 1, 1).and_hms(7, 39, 18)), None, "sheet3".into()).unwrap();
|
||||
streams.db.entry_insert(Utc.ymd(2021, 1, 1).and_hms(12, 0, 0), Some(Utc.ymd(2021, 1, 1).and_hms(13, 52, 45)), None, "sheet3".into()).unwrap();
|
||||
streams.db.entry_insert(Utc.ymd(2021, 1, 1).and_hms(12, 0, 0), None, None, "sheet4".into()).unwrap();
|
||||
|
||||
let now = Utc.ymd(2021, 1, 1).and_hms(13, 52, 45);
|
||||
let facts = Facts::new().with_now(now);
|
||||
|
||||
ListCommand::handle(args, &mut db, &mut out, &mut err, &config, now).unwrap();
|
||||
ListCommand::handle(args, &mut streams, &facts).unwrap();
|
||||
|
||||
assert_eq!(Ps(&String::from_utf8_lossy(&out)), Ps(&format!(" Timesheet Running Today Total Time
|
||||
assert_eq!(Ps(&String::from_utf8_lossy(&streams.out)), Ps(&format!(" Timesheet Running Today Total Time
|
||||
|
||||
sheet1 {0} {0} 10:13:55
|
||||
* sheet2 {0} {0} 0:00:00
|
||||
|
@ -192,15 +191,15 @@ mod tests {
|
|||
", Style::new().dimmed().paint(" 0:00:00"))));
|
||||
|
||||
// now show all the sheets
|
||||
let mut out = Vec::new();
|
||||
let mut err = Vec::new();
|
||||
streams.reset_io();
|
||||
|
||||
let args = Args {
|
||||
all: true,
|
||||
};
|
||||
|
||||
ListCommand::handle(args, &mut db, &mut out, &mut err, &config, now).unwrap();
|
||||
ListCommand::handle(args, &mut streams, &facts).unwrap();
|
||||
|
||||
assert_eq!(Ps(&String::from_utf8_lossy(&out)), Ps(&format!(" Timesheet Running Today Total Time
|
||||
assert_eq!(Ps(&String::from_utf8_lossy(&streams.out)), Ps(&format!(" Timesheet Running Today Total Time
|
||||
|
||||
_archived {0} {0} 1:00:00
|
||||
sheet1 {0} {0} 10:13:55
|
||||
|
@ -215,16 +214,16 @@ mod tests {
|
|||
#[test]
|
||||
fn old_database() {
|
||||
let args = Default::default();
|
||||
let mut db = SqliteDatabase::from_path("assets/test_list_old_database.db").unwrap();
|
||||
let mut out = Vec::new();
|
||||
let mut err = Vec::new();
|
||||
let config = Default::default();
|
||||
let mut streams = Streams::fake(b"").with_db(
|
||||
SqliteDatabase::from_path("assets/test_list_old_database.db").unwrap()
|
||||
);
|
||||
|
||||
let now = Local.ymd(2021, 7, 16).and_hms(11, 30, 45);
|
||||
let facts = Facts::new().with_now(now.with_timezone(&Utc));
|
||||
|
||||
ListCommand::handle(args, &mut db, &mut out, &mut err, &config, now.with_timezone(&Utc)).unwrap();
|
||||
ListCommand::handle(args, &mut streams, &facts).unwrap();
|
||||
|
||||
assert_eq!(Ps(&String::from_utf8_lossy(&out)), Ps(" Timesheet Running Today Total Time
|
||||
assert_eq!(Ps(&String::from_utf8_lossy(&streams.out)), Ps(" Timesheet Running Today Total Time
|
||||
|
||||
* default 0:10:24 0:10:26 0:10:26
|
||||
--------------------------------------------
|
||||
|
@ -232,8 +231,8 @@ mod tests {
|
|||
"));
|
||||
|
||||
assert_eq!(
|
||||
String::from_utf8_lossy(&err),
|
||||
format!("{} You are using the old timetrap format, it is advised that you update your database using t migrate\n", Yellow.bold().paint("[WARNING]")),
|
||||
String::from_utf8_lossy(&streams.err),
|
||||
"[WARNING] You are using the old timetrap format, it is advised that you update your database using t migrate\n"
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
use std::convert::TryFrom;
|
||||
use std::io::Write;
|
||||
use std::io::{BufRead, Write};
|
||||
use std::str::FromStr;
|
||||
|
||||
use clap::ArgMatches;
|
||||
|
@ -9,10 +9,10 @@ use regex::Regex;
|
|||
use crate::error::{Result, Error};
|
||||
use crate::database::Database;
|
||||
use crate::formatters::Formatter;
|
||||
use crate::config::Config;
|
||||
use crate::regex::parse_regex;
|
||||
use crate::io::Streams;
|
||||
|
||||
use super::{Command, display::{Sheet, entries_for_display}};
|
||||
use super::{Command, Facts, display::{Sheet, entries_for_display}};
|
||||
|
||||
/// Given a local datetime, returns the time when the month it belongs started
|
||||
fn beginning_of_month(time: DateTime<Local>) -> DateTime<Utc> {
|
||||
|
@ -93,12 +93,14 @@ pub struct MonthCommand { }
|
|||
impl<'a> Command<'a> for MonthCommand {
|
||||
type Args = Args;
|
||||
|
||||
fn handle<D, O, E>(args: Self::Args, db: &mut D, out: &mut O, err: &mut E, config: &Config, now: DateTime<Utc>) -> Result<()>
|
||||
fn handle<D, I, O, E>(args: Self::Args, streams: &mut Streams<D, I, O, E>, facts: &Facts) -> Result<()>
|
||||
where
|
||||
D: Database,
|
||||
I: BufRead,
|
||||
O: Write,
|
||||
E: Write,
|
||||
{
|
||||
let now = facts.now;
|
||||
let (start, end) = match args.month {
|
||||
MonthSpec::This => (beginning_of_month(now.with_timezone(&Local)), now),
|
||||
MonthSpec::Last => {
|
||||
|
@ -129,13 +131,22 @@ impl<'a> Command<'a> for MonthCommand {
|
|||
},
|
||||
};
|
||||
|
||||
entries_for_display(Some(start), Some(end), args.sheet, db, out, err, args.format.unwrap_or_else(|| config.default_formatter.clone()), args.ids, args.grep, now)
|
||||
entries_for_display(
|
||||
Some(start),
|
||||
Some(end),
|
||||
args.sheet,
|
||||
streams,
|
||||
args.format.unwrap_or_else(|| facts.config.default_formatter.clone()),
|
||||
args.ids,
|
||||
args.grep,
|
||||
facts
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use crate::database::SqliteDatabase;
|
||||
use crate::config::Config;
|
||||
|
||||
use super::*;
|
||||
|
||||
|
@ -144,22 +155,19 @@ mod tests {
|
|||
std::env::set_var("TZ", "CST+6");
|
||||
|
||||
let args = Default::default();
|
||||
let mut db = SqliteDatabase::from_memory().unwrap();
|
||||
let mut out = Vec::new();
|
||||
let mut err = Vec::new();
|
||||
let config = Config {
|
||||
let mut streams = Streams::fake(b"");
|
||||
let now = Utc.ymd(2021, 6, 30).and_hms(11, 0, 0);
|
||||
let facts = Facts::new().with_config(Config {
|
||||
default_formatter: Formatter::Ids,
|
||||
..Default::default()
|
||||
};
|
||||
}).with_now(now);
|
||||
|
||||
db.init().unwrap();
|
||||
streams.db.entry_insert(Utc.ymd(2021, 6, 30).and_hms(10, 0, 0), None, Some("hola".into()), "default".into()).unwrap();
|
||||
streams.db.entry_insert(Utc.ymd(2021, 6, 30).and_hms(10, 10, 0), None, Some("hola".into()), "default".into()).unwrap();
|
||||
|
||||
db.entry_insert(Utc.ymd(2021, 6, 30).and_hms(10, 0, 0), None, Some("hola".into()), "default".into()).unwrap();
|
||||
db.entry_insert(Utc.ymd(2021, 6, 30).and_hms(10, 10, 0), None, Some("hola".into()), "default".into()).unwrap();
|
||||
MonthCommand::handle(args, &mut streams, &facts).unwrap();
|
||||
|
||||
MonthCommand::handle(args, &mut db, &mut out, &mut err, &config, Utc.ymd(2021, 6, 30).and_hms(11, 0, 0)).unwrap();
|
||||
|
||||
assert_eq!(&String::from_utf8_lossy(&out), "1 2\n");
|
||||
assert_eq!(String::from_utf8_lossy(&err), "");
|
||||
assert_eq!(&String::from_utf8_lossy(&streams.out), "1 2\n");
|
||||
assert_eq!(String::from_utf8_lossy(&streams.err), "");
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,17 +1,16 @@
|
|||
use std::convert::TryFrom;
|
||||
use std::io::Write;
|
||||
use std::io::{BufRead, Write};
|
||||
|
||||
use clap::ArgMatches;
|
||||
use chrono::{DateTime, Utc};
|
||||
|
||||
use crate::error::{Result, Error};
|
||||
use crate::database::Database;
|
||||
use crate::config::Config;
|
||||
use crate::old::{entries_or_warning, warn_if_needed};
|
||||
use crate::tabulate::{Tabulate, Col, Align::*};
|
||||
use crate::formatters::text::format_duration;
|
||||
use crate::io::Streams;
|
||||
|
||||
use super::Command;
|
||||
use super::{Command, Facts};
|
||||
|
||||
#[derive(Default)]
|
||||
pub struct Args {
|
||||
|
@ -30,18 +29,19 @@ pub struct NowCommand { }
|
|||
impl<'a> Command<'a> for NowCommand {
|
||||
type Args = Args;
|
||||
|
||||
fn handle<D, O, E>(_args: Self::Args, db: &mut D, out: &mut O, err: &mut E, _config: &Config, now: DateTime<Utc>) -> Result<()>
|
||||
fn handle<D, I, O, E>(_args: Self::Args, streams: &mut Streams<D, I, O, E>, facts: &Facts) -> Result<()>
|
||||
where
|
||||
D: Database,
|
||||
I: BufRead,
|
||||
O: Write,
|
||||
E: Write,
|
||||
{
|
||||
let entries = db.running_entries()?;
|
||||
let entries = streams.db.running_entries()?;
|
||||
|
||||
let (entries, needs_warning) = entries_or_warning(entries, db)?;
|
||||
let (entries, needs_warning) = entries_or_warning(entries, &streams.db)?;
|
||||
|
||||
let current = db.current_sheet()?;
|
||||
let last = db.last_sheet()?;
|
||||
let current = streams.db.current_sheet()?;
|
||||
let last = streams.db.last_sheet()?;
|
||||
|
||||
let mut tabs = Tabulate::with_columns(vec![
|
||||
// indicator of current or prev sheet
|
||||
|
@ -70,14 +70,14 @@ impl<'a> Command<'a> for NowCommand {
|
|||
"".into()
|
||||
},
|
||||
entry.sheet,
|
||||
format_duration(now - entry.start),
|
||||
format_duration(facts.now - entry.start),
|
||||
entry.note.unwrap_or_else(|| "".into())
|
||||
]);
|
||||
}
|
||||
|
||||
out.write_all(tabs.print().as_bytes())?;
|
||||
streams.out.write_all(tabs.print().as_bytes())?;
|
||||
|
||||
warn_if_needed(err, needs_warning)?;
|
||||
warn_if_needed(&mut streams.err, needs_warning, &facts.env)?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
@ -87,7 +87,6 @@ impl<'a> Command<'a> for NowCommand {
|
|||
mod tests {
|
||||
use chrono::{Utc, TimeZone, Local};
|
||||
use pretty_assertions::assert_eq;
|
||||
use ansi_term::Color::Yellow;
|
||||
|
||||
use crate::database::{SqliteDatabase, Database};
|
||||
use crate::test_utils::Ps;
|
||||
|
@ -98,25 +97,22 @@ mod tests {
|
|||
fn list_sheets() {
|
||||
std::env::set_var("TZ", "CST+6");
|
||||
|
||||
let mut db = SqliteDatabase::from_memory().unwrap();
|
||||
let mut out = Vec::new();
|
||||
let mut err = Vec::new();
|
||||
let config = Default::default();
|
||||
db.init().unwrap();
|
||||
db.set_current_sheet("sheet2").unwrap();
|
||||
db.set_last_sheet("sheet4").unwrap();
|
||||
|
||||
db.entry_insert(Utc.ymd(2021, 1, 1).and_hms(0, 0, 0), Some(Utc.ymd(2021, 1, 1).and_hms(1, 0, 0)), None, "_archived".into()).unwrap();
|
||||
db.entry_insert(Utc.ymd(2021, 1, 1).and_hms(0, 0, 0), Some(Utc.ymd(2021, 1, 1).and_hms(10,13, 55)), None, "sheet1".into()).unwrap();
|
||||
db.entry_insert(Utc.ymd(2021, 1, 1).and_hms(0, 0, 0), Some(Utc.ymd(2021, 1, 1).and_hms(7, 39, 18)), None, "sheet3".into()).unwrap();
|
||||
db.entry_insert(Utc.ymd(2021, 1, 1).and_hms(12, 0, 0), Some(Utc.ymd(2021, 1, 1).and_hms(13, 52, 45)), None, "sheet3".into()).unwrap();
|
||||
db.entry_insert(Utc.ymd(2021, 1, 1).and_hms(12, 0, 0), None, Some("some".into()), "sheet4".into()).unwrap();
|
||||
|
||||
let mut streams = Streams::fake(b"");
|
||||
let now = Utc.ymd(2021, 1, 1).and_hms(13, 52, 45);
|
||||
let facts = Facts::new().with_now(now);
|
||||
|
||||
NowCommand::handle(Default::default(), &mut db, &mut out, &mut err, &config, now).unwrap();
|
||||
streams.db.set_current_sheet("sheet2").unwrap();
|
||||
streams.db.set_last_sheet("sheet4").unwrap();
|
||||
|
||||
assert_eq!(Ps(&String::from_utf8_lossy(&out)), Ps(" Timesheet Running Activity
|
||||
streams.db.entry_insert(Utc.ymd(2021, 1, 1).and_hms(0, 0, 0), Some(Utc.ymd(2021, 1, 1).and_hms(1, 0, 0)), None, "_archived".into()).unwrap();
|
||||
streams.db.entry_insert(Utc.ymd(2021, 1, 1).and_hms(0, 0, 0), Some(Utc.ymd(2021, 1, 1).and_hms(10,13, 55)), None, "sheet1".into()).unwrap();
|
||||
streams.db.entry_insert(Utc.ymd(2021, 1, 1).and_hms(0, 0, 0), Some(Utc.ymd(2021, 1, 1).and_hms(7, 39, 18)), None, "sheet3".into()).unwrap();
|
||||
streams.db.entry_insert(Utc.ymd(2021, 1, 1).and_hms(12, 0, 0), Some(Utc.ymd(2021, 1, 1).and_hms(13, 52, 45)), None, "sheet3".into()).unwrap();
|
||||
streams.db.entry_insert(Utc.ymd(2021, 1, 1).and_hms(12, 0, 0), None, Some("some".into()), "sheet4".into()).unwrap();
|
||||
|
||||
NowCommand::handle(Default::default(), &mut streams, &facts).unwrap();
|
||||
|
||||
assert_eq!(Ps(&String::from_utf8_lossy(&streams.out)), Ps(" Timesheet Running Activity
|
||||
|
||||
- sheet4 1:52:45 some
|
||||
"));
|
||||
|
@ -124,23 +120,23 @@ mod tests {
|
|||
|
||||
#[test]
|
||||
fn old_database() {
|
||||
let mut db = SqliteDatabase::from_path("assets/test_list_old_database.db").unwrap();
|
||||
let mut out = Vec::new();
|
||||
let mut err = Vec::new();
|
||||
let config = Default::default();
|
||||
let mut streams = Streams::fake(b"").with_db(
|
||||
SqliteDatabase::from_path("assets/test_list_old_database.db").unwrap()
|
||||
);
|
||||
|
||||
let now = Local.ymd(2021, 7, 16).and_hms(11, 30, 45);
|
||||
let facts = Facts::new().with_now(now.with_timezone(&Utc));
|
||||
|
||||
NowCommand::handle(Default::default(), &mut db, &mut out, &mut err, &config, now.with_timezone(&Utc)).unwrap();
|
||||
NowCommand::handle(Default::default(), &mut streams, &facts).unwrap();
|
||||
|
||||
assert_eq!(Ps(&String::from_utf8_lossy(&out)), Ps(" Timesheet Running Activity
|
||||
assert_eq!(Ps(&String::from_utf8_lossy(&streams.out)), Ps(" Timesheet Running Activity
|
||||
|
||||
* default 0:10:24 que
|
||||
"));
|
||||
|
||||
assert_eq!(
|
||||
String::from_utf8_lossy(&err),
|
||||
format!("{} You are using the old timetrap format, it is advised that you update your database using t migrate\n", Yellow.bold().paint("[WARNING]")),
|
||||
String::from_utf8_lossy(&streams.err),
|
||||
"[WARNING] You are using the old timetrap format, it is advised that you update your database using t migrate\n"
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,16 +1,16 @@
|
|||
use std::io::Write;
|
||||
use std::io::{BufRead, Write};
|
||||
use std::convert::TryFrom;
|
||||
|
||||
use chrono::{DateTime, Utc};
|
||||
use clap::ArgMatches;
|
||||
|
||||
use crate::database::Database;
|
||||
use crate::config::Config;
|
||||
use crate::error::{Error, Result};
|
||||
use crate::timeparse::parse_time;
|
||||
use crate::old::{time_or_warning, warn_if_needed};
|
||||
use crate::io::Streams;
|
||||
|
||||
use super::Command;
|
||||
use super::{Command, Facts};
|
||||
|
||||
#[derive(Default)]
|
||||
pub struct Args {
|
||||
|
@ -32,21 +32,27 @@ pub struct OutCommand{}
|
|||
impl<'a> Command<'a> for OutCommand {
|
||||
type Args = Args;
|
||||
|
||||
fn handle<D: Database, O: Write, E: Write>(args: Self::Args, db: &mut D, out: &mut O, err: &mut E, _config: &Config, now: DateTime<Utc>) -> Result<()> {
|
||||
let end = args.at.unwrap_or(now);
|
||||
let sheet = db.current_sheet()?.unwrap_or_else(|| "default".into());
|
||||
fn handle<D, I, O, E>(args: Self::Args, streams: &mut Streams<D, I, O, E>, facts: &Facts) -> Result<()>
|
||||
where
|
||||
D: Database,
|
||||
I: BufRead,
|
||||
O: Write,
|
||||
E: Write,
|
||||
{
|
||||
let end = args.at.unwrap_or(facts.now);
|
||||
let sheet = streams.db.current_sheet()?.unwrap_or_else(|| "default".into());
|
||||
|
||||
let (end, needs_warning) = time_or_warning(end, db)?;
|
||||
let (end, needs_warning) = time_or_warning(end, &streams.db)?;
|
||||
|
||||
if let Some(entry) = db.running_entry(&sheet)? {
|
||||
writeln!(out, "Checked out of sheet \"{}\".", sheet)?;
|
||||
if let Some(entry) = streams.db.running_entry(&sheet)? {
|
||||
writeln!(streams.out, "Checked out of sheet \"{}\".", sheet)?;
|
||||
|
||||
db.entry_update(entry.id, entry.start, Some(end), entry.note, &entry.sheet)?;
|
||||
streams.db.entry_update(entry.id, entry.start, Some(end), entry.note, &entry.sheet)?;
|
||||
} else {
|
||||
writeln!(out, "No running entry on sheet \"{}\".", sheet)?;
|
||||
writeln!(streams.out, "No running entry on sheet \"{}\".", sheet)?;
|
||||
}
|
||||
|
||||
warn_if_needed(err, needs_warning)?;
|
||||
warn_if_needed(&mut streams.err, needs_warning, &facts.env)?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
@ -54,77 +60,62 @@ impl<'a> Command<'a> for OutCommand {
|
|||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use ansi_term::Color::Yellow;
|
||||
use pretty_assertions::assert_eq;
|
||||
use chrono::{TimeZone, Local};
|
||||
|
||||
use crate::test_utils::Ps;
|
||||
use crate::database::SqliteDatabase;
|
||||
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn finishes_entry() {
|
||||
let mut db = SqliteDatabase::from_memory().unwrap();
|
||||
let args = Default::default();
|
||||
let mut out = Vec::new();
|
||||
let mut err = Vec::new();
|
||||
let now = Utc::now();
|
||||
let mut streams = Streams::fake(b"");
|
||||
let facts = Facts::new();
|
||||
|
||||
db.init().unwrap();
|
||||
streams.db.entry_insert(facts.now, None, None, "default").unwrap();
|
||||
|
||||
db.entry_insert(now, None, None, "default").unwrap();
|
||||
OutCommand::handle(args, &mut streams, &facts).unwrap();
|
||||
|
||||
OutCommand::handle(args, &mut db, &mut out, &mut err, &Default::default(), now).unwrap();
|
||||
let e = streams.db.entries_full(None, None).unwrap().into_iter().next().unwrap();
|
||||
|
||||
let e = db.entries_full(None, None).unwrap().into_iter().next().unwrap();
|
||||
assert_eq!(e.end, Some(facts.now));
|
||||
|
||||
assert_eq!(e.end, Some(now));
|
||||
|
||||
assert_eq!(Ps(&String::from_utf8_lossy(&out)), Ps("Checked out of sheet \"default\".\n"));
|
||||
assert_eq!(Ps(&String::from_utf8_lossy(&err)), Ps(""));
|
||||
assert_eq!(Ps(&String::from_utf8_lossy(&streams.out)), Ps("Checked out of sheet \"default\".\n"));
|
||||
assert_eq!(Ps(&String::from_utf8_lossy(&streams.err)), Ps(""));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn tells_if_no_running_entry() {
|
||||
let mut db = SqliteDatabase::from_memory().unwrap();
|
||||
let args = Default::default();
|
||||
let mut out = Vec::new();
|
||||
let mut err = Vec::new();
|
||||
let now = Utc::now();
|
||||
let mut streams = Streams::fake(b"");
|
||||
let facts = Facts::new();
|
||||
|
||||
db.init().unwrap();
|
||||
streams.db.entry_insert(facts.now, None, None, "non-default").unwrap();
|
||||
|
||||
db.entry_insert(now, None, None, "non-default").unwrap();
|
||||
OutCommand::handle(args, &mut streams, &facts).unwrap();
|
||||
|
||||
OutCommand::handle(args, &mut db, &mut out, &mut err, &Default::default(), now).unwrap();
|
||||
|
||||
assert_eq!(Ps(&String::from_utf8_lossy(&out)), Ps("No running entry on sheet \"default\".\n"));
|
||||
assert_eq!(Ps(&String::from_utf8_lossy(&err)), Ps(""));
|
||||
assert_eq!(Ps(&String::from_utf8_lossy(&streams.out)), Ps("No running entry on sheet \"default\".\n"));
|
||||
assert_eq!(Ps(&String::from_utf8_lossy(&streams.err)), Ps(""));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn warns_if_old_database() {
|
||||
let mut db = SqliteDatabase::from_memory().unwrap();
|
||||
let args = Default::default();
|
||||
let mut out = Vec::new();
|
||||
let mut err = Vec::new();
|
||||
let now = Utc::now();
|
||||
let mut streams = Streams::fake_old(b"");
|
||||
let facts = Facts::new();
|
||||
|
||||
db.init_old().unwrap();
|
||||
streams.db.entry_insert(facts.now, None, None, "default").unwrap();
|
||||
|
||||
db.entry_insert(now, None, None, "default").unwrap();
|
||||
OutCommand::handle(args, &mut streams, &facts).unwrap();
|
||||
|
||||
OutCommand::handle(args, &mut db, &mut out, &mut err, &Default::default(), now).unwrap();
|
||||
let e = streams.db.entries_full(None, None).unwrap().into_iter().next().unwrap();
|
||||
|
||||
let e = db.entries_full(None, None).unwrap().into_iter().next().unwrap();
|
||||
assert_eq!(e.end, Some(Utc.from_utc_datetime(&facts.now.with_timezone(&Local).naive_local())));
|
||||
|
||||
assert_eq!(e.end, Some(Utc.from_utc_datetime(&now.with_timezone(&Local).naive_local())));
|
||||
|
||||
assert_eq!(Ps(&String::from_utf8_lossy(&out)), Ps("Checked out of sheet \"default\".\n"));
|
||||
assert_eq!(Ps(&String::from_utf8_lossy(&err)), Ps(&format!(
|
||||
"{} You are using the old timetrap format, it is advised that \
|
||||
you update your database using t migrate\n",
|
||||
Yellow.bold().paint("[WARNING]"))));
|
||||
assert_eq!(Ps(&String::from_utf8_lossy(&streams.out)), Ps("Checked out of sheet \"default\".\n"));
|
||||
assert_eq!(Ps(&String::from_utf8_lossy(&streams.err)), Ps(
|
||||
"[WARNING] You are using the old timetrap format, it is advised that \
|
||||
you update your database using t migrate\n"));
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,16 +1,16 @@
|
|||
use std::convert::TryFrom;
|
||||
use std::io::Write;
|
||||
use std::io::{BufRead, Write};
|
||||
|
||||
use clap::ArgMatches;
|
||||
use chrono::{DateTime, Utc};
|
||||
|
||||
use crate::error::{Error, Result};
|
||||
use crate::timeparse::parse_time;
|
||||
use crate::config::Config;
|
||||
use crate::database::Database;
|
||||
use crate::models::Entry;
|
||||
use crate::io::Streams;
|
||||
|
||||
use super::{Command, r#in, sheet};
|
||||
use super::{Command, Facts, r#in, sheet};
|
||||
|
||||
#[derive(Default)]
|
||||
pub struct Args {
|
||||
|
@ -29,14 +29,15 @@ impl<'a> TryFrom<&'a ArgMatches<'a>> for Args {
|
|||
}
|
||||
}
|
||||
|
||||
fn resume<D, O, E>(args: Args, db: &mut D, out: &mut O, err: &mut E, config: &Config, entry: Entry, now: DateTime<Utc>) -> Result<()>
|
||||
fn resume<D, I, O, E>(args: Args, streams: &mut Streams<D, I, O, E>, facts: &Facts, entry: Entry) -> Result<()>
|
||||
where
|
||||
D: Database,
|
||||
I: BufRead,
|
||||
O: Write,
|
||||
E: Write,
|
||||
{
|
||||
writeln!(
|
||||
out,
|
||||
streams.out,
|
||||
"Resuming \"{}\" from entry #{}",
|
||||
entry.note.clone().unwrap_or_else(|| "".to_owned()), entry.id
|
||||
)?;
|
||||
|
@ -44,7 +45,7 @@ where
|
|||
r#in::InCommand::handle(r#in::Args {
|
||||
at: args.at,
|
||||
note: entry.note,
|
||||
}, db, out, err, config, now)
|
||||
}, streams, facts)
|
||||
}
|
||||
|
||||
pub struct ResumeCommand;
|
||||
|
@ -52,27 +53,28 @@ pub struct ResumeCommand;
|
|||
impl<'a> Command<'a> for ResumeCommand {
|
||||
type Args = Args;
|
||||
|
||||
fn handle<D, O, E>(args: Args, db: &mut D, out: &mut O, err: &mut E, config: &Config, now: DateTime<Utc>) -> Result<()>
|
||||
fn handle<D, I, O, E>(args: Args, streams: &mut Streams<D, I, O, E>, facts: &Facts) -> Result<()>
|
||||
where
|
||||
D: Database,
|
||||
I: BufRead,
|
||||
O: Write,
|
||||
E: Write,
|
||||
{
|
||||
let current_sheet = db.current_sheet()?.unwrap_or_else(|| "default".to_owned());
|
||||
let current_sheet = streams.db.current_sheet()?.unwrap_or_else(|| "default".to_owned());
|
||||
|
||||
// First try to process using the given id
|
||||
if let Some(entry_id) = args.id {
|
||||
if let Some(entry) = db.entry_by_id(entry_id)? {
|
||||
if let Some(entry) = streams.db.entry_by_id(entry_id)? {
|
||||
if entry.sheet != current_sheet {
|
||||
// first swith to the sheet
|
||||
sheet::SheetCommand::handle(sheet::Args {
|
||||
sheet: Some(entry.sheet.clone()),
|
||||
}, db, out, err, config, now)?;
|
||||
}, streams, facts)?;
|
||||
}
|
||||
|
||||
return resume(args, db, out, err, config, entry, now);
|
||||
return resume(args, streams, facts, entry);
|
||||
} else {
|
||||
writeln!(out, "The entry with id '{}' could not be found to be resumed. Perhaps it was deleted?", entry_id)?;
|
||||
writeln!(streams.out, "The entry with id '{}' could not be found to be resumed. Perhaps it was deleted?", entry_id)?;
|
||||
|
||||
return Ok(());
|
||||
}
|
||||
|
@ -80,10 +82,10 @@ impl<'a> Command<'a> for ResumeCommand {
|
|||
|
||||
// No id specified, try to find something suitable to switch to in the
|
||||
// database
|
||||
if let Some(entry) = db.last_checkout_of_sheet(¤t_sheet)? {
|
||||
resume(args ,db, out, err, config, entry, now)
|
||||
if let Some(entry) = streams.db.last_checkout_of_sheet(¤t_sheet)? {
|
||||
resume(args, streams, facts, entry)
|
||||
} else {
|
||||
writeln!(out, "No entry to resume in the sheet '{}'. Perhaps start a new one?
|
||||
writeln!(streams.out, "No entry to resume in the sheet '{}'. Perhaps start a new one?
|
||||
Hint: use t in", current_sheet)?;
|
||||
|
||||
Ok(())
|
||||
|
@ -97,56 +99,47 @@ mod tests {
|
|||
use pretty_assertions::assert_eq;
|
||||
|
||||
use crate::test_utils::Ps;
|
||||
use crate::database::SqliteDatabase;
|
||||
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn resume_an_entry() {
|
||||
let args = Default::default();
|
||||
let mut db = SqliteDatabase::from_memory().unwrap();
|
||||
let mut out = Vec::new();
|
||||
let mut err = Vec::new();
|
||||
let now = Utc::now();
|
||||
let one_hour_ago = now - Duration::hours(1);
|
||||
let two_hours_ago = now - Duration::hours(2);
|
||||
let mut streams = Streams::fake(b"");
|
||||
let facts = Facts::new();
|
||||
let one_hour_ago = facts.now - Duration::hours(1);
|
||||
let two_hours_ago = facts.now - Duration::hours(2);
|
||||
|
||||
db.init().unwrap();
|
||||
streams.db.entry_insert(two_hours_ago, Some(one_hour_ago), Some("fake note".into()), "default").unwrap();
|
||||
|
||||
db.entry_insert(two_hours_ago, Some(one_hour_ago), Some("fake note".into()), "default").unwrap();
|
||||
assert_eq!(streams.db.entries_full(None, None).unwrap().len(), 1);
|
||||
|
||||
assert_eq!(db.entries_full(None, None).unwrap().len(), 1);
|
||||
ResumeCommand::handle(args, &mut streams, &facts).unwrap();
|
||||
|
||||
ResumeCommand::handle(args, &mut db, &mut out, &mut err, &Default::default(), now).unwrap();
|
||||
|
||||
let all_entries = db.entries_full(None, None).unwrap();
|
||||
let all_entries = streams.db.entries_full(None, None).unwrap();
|
||||
|
||||
assert_eq!(all_entries.len(), 2);
|
||||
|
||||
assert_eq!(all_entries[1].id, 2);
|
||||
assert_eq!(all_entries[1].start, now);
|
||||
assert_eq!(all_entries[1].start, facts.now);
|
||||
assert_eq!(all_entries[1].end, None);
|
||||
assert_eq!(all_entries[1].note, Some("fake note".into()));
|
||||
assert_eq!(all_entries[1].sheet, "default");
|
||||
|
||||
assert_eq!(Ps(&String::from_utf8_lossy(&out)), Ps("Resuming \"fake note\" from entry #1
|
||||
assert_eq!(Ps(&String::from_utf8_lossy(&streams.out)), Ps("Resuming \"fake note\" from entry #1
|
||||
Checked into sheet \"default\".\n"));
|
||||
assert_eq!(Ps(&String::from_utf8_lossy(&err)), Ps(""));
|
||||
assert_eq!(Ps(&String::from_utf8_lossy(&streams.err)), Ps(""));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn no_entries_to_resume() {
|
||||
let args = Default::default();
|
||||
let mut db = SqliteDatabase::from_memory().unwrap();
|
||||
let mut out = Vec::new();
|
||||
let mut err = Vec::new();
|
||||
let now = Utc::now();
|
||||
let mut streams = Streams::fake(b"");
|
||||
let facts = Facts::new();
|
||||
|
||||
db.init().unwrap();
|
||||
ResumeCommand::handle(args, &mut streams, &facts).unwrap();
|
||||
|
||||
ResumeCommand::handle(args, &mut db, &mut out, &mut err, &Default::default(), now).unwrap();
|
||||
|
||||
assert_eq!(Ps(&String::from_utf8_lossy(&out)), Ps("\
|
||||
assert_eq!(Ps(&String::from_utf8_lossy(&streams.out)), Ps("\
|
||||
No entry to resume in the sheet 'default'. Perhaps start a new one?
|
||||
Hint: use t in
|
||||
"));
|
||||
|
|
|
@ -1,14 +1,13 @@
|
|||
use std::convert::TryFrom;
|
||||
use std::io::Write;
|
||||
use std::io::{BufRead, Write};
|
||||
|
||||
use clap::ArgMatches;
|
||||
use chrono::{DateTime, Utc};
|
||||
|
||||
use crate::database::Database;
|
||||
use crate::error::{Error, Result};
|
||||
use crate::commands::Command;
|
||||
use crate::config::Config;
|
||||
use crate::commands::{Command, Facts};
|
||||
use crate::commands::list::ListCommand;
|
||||
use crate::io::Streams;
|
||||
|
||||
#[derive(Default)]
|
||||
pub struct Args {
|
||||
|
@ -30,42 +29,43 @@ pub struct SheetCommand {}
|
|||
impl<'a> Command<'a> for SheetCommand {
|
||||
type Args = Args;
|
||||
|
||||
fn handle<D, O, E>(args: Args, db: &mut D, out: &mut O, err: &mut E, config: &Config, now: DateTime<Utc>) -> Result<()>
|
||||
fn handle<D, I, O, E>(args: Args, streams: &mut Streams<D, I, O, E>, facts: &Facts) -> Result<()>
|
||||
where
|
||||
D: Database,
|
||||
I: BufRead,
|
||||
O: Write,
|
||||
E: Write,
|
||||
{
|
||||
if let Some(sheet) = args.sheet {
|
||||
let current_sheet = db.current_sheet()?.unwrap_or_else(|| "default".into());
|
||||
let current_sheet = streams.db.current_sheet()?.unwrap_or_else(|| "default".into());
|
||||
|
||||
// sheet given, switch to it
|
||||
let move_to = if sheet == "-" {
|
||||
if let Some(move_to) = db.last_sheet()? {
|
||||
if let Some(move_to) = streams.db.last_sheet()? {
|
||||
move_to
|
||||
} else {
|
||||
writeln!(out, "No previous sheet to move to. Staying on '{}'.
|
||||
writeln!(streams.out, "No previous sheet to move to. Staying on '{}'.
|
||||
Hint: remember that giving - (a dash) as argument to t sheet switches to the last active sheet", current_sheet)?;
|
||||
|
||||
return Ok(());
|
||||
}
|
||||
} else if sheet == current_sheet {
|
||||
writeln!(out, "Already on sheet '{}'", sheet)?;
|
||||
writeln!(streams.out, "Already on sheet '{}'", sheet)?;
|
||||
|
||||
return Ok(());
|
||||
} else {
|
||||
sheet
|
||||
};
|
||||
|
||||
db.set_last_sheet(¤t_sheet)?;
|
||||
db.set_current_sheet(&move_to)?;
|
||||
streams.db.set_last_sheet(¤t_sheet)?;
|
||||
streams.db.set_current_sheet(&move_to)?;
|
||||
|
||||
writeln!(out, "Switching to sheet '{}'", move_to)?;
|
||||
writeln!(streams.out, "Switching to sheet '{}'", move_to)?;
|
||||
|
||||
Ok(())
|
||||
} else {
|
||||
// call list
|
||||
ListCommand::handle(Default::default(), db, out, err, config, now)
|
||||
ListCommand::handle(Default::default(), streams, facts)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -74,7 +74,6 @@ Hint: remember that giving - (a dash) as argument to t sheet switches to the las
|
|||
mod tests {
|
||||
use pretty_assertions::assert_eq;
|
||||
|
||||
use crate::database::SqliteDatabase;
|
||||
use crate::test_utils::Ps;
|
||||
|
||||
use super::*;
|
||||
|
@ -84,18 +83,14 @@ mod tests {
|
|||
let args = Args {
|
||||
sheet: Some("new_sheet".into()),
|
||||
};
|
||||
let mut db = SqliteDatabase::from_memory().unwrap();
|
||||
let mut out = Vec::new();
|
||||
let mut err = Vec::new();
|
||||
let now = Utc::now();
|
||||
let mut streams = Streams::fake(b"");
|
||||
let facts = Facts::new();
|
||||
|
||||
db.init().unwrap();
|
||||
SheetCommand::handle(args, &mut streams, &facts).unwrap();
|
||||
|
||||
SheetCommand::handle(args, &mut db, &mut out, &mut err, &Default::default(), now).unwrap();
|
||||
|
||||
assert_eq!(db.current_sheet().unwrap().unwrap(), "new_sheet");
|
||||
assert_eq!(Ps(&String::from_utf8_lossy(&out)), Ps("Switching to sheet 'new_sheet'\n"));
|
||||
assert_eq!(Ps(&String::from_utf8_lossy(&err)), Ps(""));
|
||||
assert_eq!(streams.db.current_sheet().unwrap().unwrap(), "new_sheet");
|
||||
assert_eq!(Ps(&String::from_utf8_lossy(&streams.out)), Ps("Switching to sheet 'new_sheet'\n"));
|
||||
assert_eq!(Ps(&String::from_utf8_lossy(&streams.err)), Ps(""));
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
@ -103,19 +98,16 @@ mod tests {
|
|||
let args = Args {
|
||||
sheet: Some("foo".into()),
|
||||
};
|
||||
let mut db = SqliteDatabase::from_memory().unwrap();
|
||||
let mut out = Vec::new();
|
||||
let mut err = Vec::new();
|
||||
let now = Utc::now();
|
||||
let mut streams = Streams::fake(b"");
|
||||
let facts = Facts::new();
|
||||
|
||||
db.init().unwrap();
|
||||
db.set_current_sheet("foo").unwrap();
|
||||
streams.db.set_current_sheet("foo").unwrap();
|
||||
|
||||
SheetCommand::handle(args, &mut db, &mut out, &mut err, &Default::default(), now).unwrap();
|
||||
SheetCommand::handle(args, &mut streams, &facts).unwrap();
|
||||
|
||||
assert_eq!(db.current_sheet().unwrap().unwrap(), "foo");
|
||||
assert_eq!(Ps(&String::from_utf8_lossy(&out)), Ps("Already on sheet 'foo'\n"));
|
||||
assert_eq!(Ps(&String::from_utf8_lossy(&err)), Ps(""));
|
||||
assert_eq!(streams.db.current_sheet().unwrap().unwrap(), "foo");
|
||||
assert_eq!(Ps(&String::from_utf8_lossy(&streams.out)), Ps("Already on sheet 'foo'\n"));
|
||||
assert_eq!(Ps(&String::from_utf8_lossy(&streams.err)), Ps(""));
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
@ -123,20 +115,17 @@ mod tests {
|
|||
let args = Args {
|
||||
sheet: Some("-".into()),
|
||||
};
|
||||
let mut db = SqliteDatabase::from_memory().unwrap();
|
||||
let mut out = Vec::new();
|
||||
let mut err = Vec::new();
|
||||
let now = Utc::now();
|
||||
let mut streams = Streams::fake(b"");
|
||||
let facts = Facts::new();
|
||||
|
||||
db.init().unwrap();
|
||||
db.set_current_sheet("foo").unwrap();
|
||||
db.set_last_sheet("var").unwrap();
|
||||
streams.db.set_current_sheet("foo").unwrap();
|
||||
streams.db.set_last_sheet("var").unwrap();
|
||||
|
||||
SheetCommand::handle(args, &mut db, &mut out, &mut err, &Default::default(), now).unwrap();
|
||||
SheetCommand::handle(args, &mut streams, &facts).unwrap();
|
||||
|
||||
assert_eq!(db.current_sheet().unwrap().unwrap(), "var");
|
||||
assert_eq!(db.last_sheet().unwrap().unwrap(), "foo");
|
||||
assert_eq!(Ps(&String::from_utf8_lossy(&out)), Ps("Switching to sheet 'var'\n"));
|
||||
assert_eq!(Ps(&String::from_utf8_lossy(&err)), Ps(""));
|
||||
assert_eq!(streams.db.current_sheet().unwrap().unwrap(), "var");
|
||||
assert_eq!(streams.db.last_sheet().unwrap().unwrap(), "foo");
|
||||
assert_eq!(Ps(&String::from_utf8_lossy(&streams.out)), Ps("Switching to sheet 'var'\n"));
|
||||
assert_eq!(Ps(&String::from_utf8_lossy(&streams.err)), Ps(""));
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
use std::convert::TryFrom;
|
||||
use std::io::Write;
|
||||
use std::io::{BufRead, Write};
|
||||
|
||||
use clap::ArgMatches;
|
||||
use chrono::{DateTime, Utc, Local};
|
||||
|
@ -8,11 +8,11 @@ use regex::Regex;
|
|||
use crate::error::{Result, Error};
|
||||
use crate::database::Database;
|
||||
use crate::formatters::Formatter;
|
||||
use crate::config::Config;
|
||||
use crate::timeparse::parse_time;
|
||||
use crate::regex::parse_regex;
|
||||
use crate::io::Streams;
|
||||
|
||||
use super::{Command, display::{Sheet, entries_for_display}};
|
||||
use super::{Command, Facts, display::{Sheet, entries_for_display}};
|
||||
|
||||
#[derive(Default)]
|
||||
pub struct Args {
|
||||
|
@ -42,15 +42,25 @@ pub struct TodayCommand { }
|
|||
impl<'a> Command<'a> for TodayCommand {
|
||||
type Args = Args;
|
||||
|
||||
fn handle<D, O, E>(args: Self::Args, db: &mut D, out: &mut O, err: &mut E, config: &Config, now: DateTime<Utc>) -> Result<()>
|
||||
fn handle<D, I, O, E>(args: Self::Args, streams: &mut Streams<D, I, O, E>, facts: &Facts) -> Result<()>
|
||||
where
|
||||
D: Database,
|
||||
I: BufRead,
|
||||
O: Write,
|
||||
E: Write,
|
||||
{
|
||||
let start = Some(now.with_timezone(&Local).date().and_hms(0, 0, 0).with_timezone(&Utc));
|
||||
let start = Some(facts.now.with_timezone(&Local).date().and_hms(0, 0, 0).with_timezone(&Utc));
|
||||
|
||||
entries_for_display(start, args.end, args.sheet, db, out, err, args.format.unwrap_or_else(|| config.default_formatter.clone()), args.ids, args.grep, now)
|
||||
entries_for_display(
|
||||
start,
|
||||
args.end,
|
||||
args.sheet,
|
||||
streams,
|
||||
args.format.unwrap_or_else(|| facts.config.default_formatter.clone()),
|
||||
args.ids,
|
||||
args.grep,
|
||||
facts
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -58,7 +68,7 @@ impl<'a> Command<'a> for TodayCommand {
|
|||
mod tests {
|
||||
use chrono::TimeZone;
|
||||
|
||||
use crate::database::SqliteDatabase;
|
||||
use crate::config::Config;
|
||||
|
||||
use super::*;
|
||||
|
||||
|
@ -67,22 +77,18 @@ mod tests {
|
|||
std::env::set_var("TZ", "CST+6");
|
||||
|
||||
let args = Default::default();
|
||||
let mut db = SqliteDatabase::from_memory().unwrap();
|
||||
let mut out = Vec::new();
|
||||
let mut err = Vec::new();
|
||||
let config = Config {
|
||||
let mut streams = Streams::fake(b"");
|
||||
let facts = Facts::new().with_config(Config {
|
||||
default_formatter: Formatter::Ids,
|
||||
..Default::default()
|
||||
};
|
||||
}).with_now(Utc.ymd(2021, 6, 30).and_hms(11, 0, 0));
|
||||
|
||||
db.init().unwrap();
|
||||
streams.db.entry_insert(Utc.ymd(2021, 6, 30).and_hms(10, 0, 0), None, Some("hola".into()), "default".into()).unwrap();
|
||||
streams.db.entry_insert(Utc.ymd(2021, 6, 30).and_hms(10, 10, 0), None, Some("hola".into()), "default".into()).unwrap();
|
||||
|
||||
db.entry_insert(Utc.ymd(2021, 6, 30).and_hms(10, 0, 0), None, Some("hola".into()), "default".into()).unwrap();
|
||||
db.entry_insert(Utc.ymd(2021, 6, 30).and_hms(10, 10, 0), None, Some("hola".into()), "default".into()).unwrap();
|
||||
TodayCommand::handle(args, &mut streams, &facts).unwrap();
|
||||
|
||||
TodayCommand::handle(args, &mut db, &mut out, &mut err, &config, Utc.ymd(2021, 6, 30).and_hms(11, 0, 0)).unwrap();
|
||||
|
||||
assert_eq!(&String::from_utf8_lossy(&out), "1 2\n");
|
||||
assert_eq!(String::from_utf8_lossy(&err), "");
|
||||
assert_eq!(&String::from_utf8_lossy(&streams.out), "1 2\n");
|
||||
assert_eq!(String::from_utf8_lossy(&streams.err), "");
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
use std::convert::TryFrom;
|
||||
use std::io::Write;
|
||||
use std::io::{BufRead, Write};
|
||||
|
||||
use clap::ArgMatches;
|
||||
use chrono::{DateTime, Utc, Local, Duration, Weekday, Datelike};
|
||||
|
@ -8,11 +8,12 @@ use regex::Regex;
|
|||
use crate::error::{Result, Error};
|
||||
use crate::database::Database;
|
||||
use crate::formatters::Formatter;
|
||||
use crate::config::{Config, WeekDay};
|
||||
use crate::config::WeekDay;
|
||||
use crate::regex::parse_regex;
|
||||
use crate::timeparse::parse_time;
|
||||
use crate::io::Streams;
|
||||
|
||||
use super::{Command, display::{Sheet, entries_for_display}};
|
||||
use super::{Command, Facts, display::{Sheet, entries_for_display}};
|
||||
|
||||
trait AsNum {
|
||||
fn as_num(&self) -> i64;
|
||||
|
@ -86,15 +87,25 @@ pub struct WeekCommand { }
|
|||
impl<'a> Command<'a> for WeekCommand {
|
||||
type Args = Args;
|
||||
|
||||
fn handle<D, O, E>(args: Self::Args, db: &mut D, out: &mut O, err: &mut E, config: &Config, now: DateTime<Utc>) -> Result<()>
|
||||
fn handle<D, I, O, E>(args: Self::Args, streams: &mut Streams<D, I, O, E>, facts: &Facts) -> Result<()>
|
||||
where
|
||||
D: Database,
|
||||
I: BufRead,
|
||||
O: Write,
|
||||
E: Write,
|
||||
{
|
||||
let start = prev_day(now.with_timezone(&Local), config.week_start);
|
||||
let start = prev_day(facts.now.with_timezone(&Local), facts.config.week_start);
|
||||
|
||||
entries_for_display(Some(start), args.end, args.sheet, db, out, err, args.format.unwrap_or_else(|| config.default_formatter.clone()), args.ids, args.grep, now)
|
||||
entries_for_display(
|
||||
Some(start),
|
||||
args.end,
|
||||
args.sheet,
|
||||
streams,
|
||||
args.format.unwrap_or_else(|| facts.config.default_formatter.clone()),
|
||||
args.ids,
|
||||
args.grep,
|
||||
facts
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -102,7 +113,7 @@ impl<'a> Command<'a> for WeekCommand {
|
|||
mod tests {
|
||||
use chrono::TimeZone;
|
||||
|
||||
use crate::database::SqliteDatabase;
|
||||
use crate::config::Config;
|
||||
|
||||
use super::*;
|
||||
|
||||
|
@ -125,22 +136,19 @@ mod tests {
|
|||
std::env::set_var("TZ", "CST+6");
|
||||
|
||||
let args = Default::default();
|
||||
let mut db = SqliteDatabase::from_memory().unwrap();
|
||||
let mut out = Vec::new();
|
||||
let mut err = Vec::new();
|
||||
let config = Config {
|
||||
let mut streams = Streams::fake(b"");
|
||||
let now = Utc.ymd(2021, 7, 1).and_hms(10, 0, 0);
|
||||
let facts = Facts::new().with_config(Config {
|
||||
default_formatter: Formatter::Ids,
|
||||
..Default::default()
|
||||
};
|
||||
}).with_now(now);
|
||||
|
||||
db.init().unwrap();
|
||||
streams.db.entry_insert(Utc.ymd(2021, 6, 30).and_hms(10, 0, 0), None, Some("hola".into()), "default".into()).unwrap();
|
||||
streams.db.entry_insert(Utc.ymd(2021, 6, 30).and_hms(10, 10, 0), None, Some("hola".into()), "default".into()).unwrap();
|
||||
|
||||
db.entry_insert(Utc.ymd(2021, 6, 30).and_hms(10, 0, 0), None, Some("hola".into()), "default".into()).unwrap();
|
||||
db.entry_insert(Utc.ymd(2021, 6, 30).and_hms(10, 10, 0), None, Some("hola".into()), "default".into()).unwrap();
|
||||
WeekCommand::handle(args, &mut streams, &facts).unwrap();
|
||||
|
||||
WeekCommand::handle(args, &mut db, &mut out, &mut err, &config, Utc.ymd(2021, 7, 1).and_hms(10, 0, 0)).unwrap();
|
||||
|
||||
assert_eq!(&String::from_utf8_lossy(&out), "1 2\n");
|
||||
assert_eq!(String::from_utf8_lossy(&err), "");
|
||||
assert_eq!(&String::from_utf8_lossy(&streams.out), "1 2\n");
|
||||
assert_eq!(String::from_utf8_lossy(&streams.err), "");
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,17 +1,17 @@
|
|||
use std::convert::TryFrom;
|
||||
use std::io::Write;
|
||||
use std::io::{BufRead, Write};
|
||||
|
||||
use clap::ArgMatches;
|
||||
use chrono::{DateTime, Utc, Local, Duration};
|
||||
use chrono::{Utc, Local, Duration};
|
||||
use regex::Regex;
|
||||
|
||||
use crate::error::{Result, Error};
|
||||
use crate::database::Database;
|
||||
use crate::formatters::Formatter;
|
||||
use crate::config::Config;
|
||||
use crate::regex::parse_regex;
|
||||
use crate::io::Streams;
|
||||
|
||||
use super::{Command, display::{Sheet, entries_for_display}};
|
||||
use super::{Command, Facts, display::{Sheet, entries_for_display}};
|
||||
|
||||
#[derive(Default)]
|
||||
pub struct Args {
|
||||
|
@ -39,17 +39,27 @@ pub struct YesterdayCommand { }
|
|||
impl<'a> Command<'a> for YesterdayCommand {
|
||||
type Args = Args;
|
||||
|
||||
fn handle<D, O, E>(args: Self::Args, db: &mut D, out: &mut O, err: &mut E, config: &Config, now: DateTime<Utc>) -> Result<()>
|
||||
fn handle<D, I, O, E>(args: Self::Args, streams: &mut Streams<D, I, O, E>, facts: &Facts) -> Result<()>
|
||||
where
|
||||
D: Database,
|
||||
I: BufRead,
|
||||
O: Write,
|
||||
E: Write,
|
||||
{
|
||||
let today = now.with_timezone(&Local).date();
|
||||
let today = facts.now.with_timezone(&Local).date();
|
||||
let start = Some((today - Duration::days(1)).and_hms(0, 0, 0).with_timezone(&Utc));
|
||||
let end = Some(today.and_hms(0, 0, 0).with_timezone(&Utc));
|
||||
|
||||
entries_for_display(start, end, args.sheet, db, out, err, args.format.unwrap_or_else(|| config.default_formatter.clone()), args.ids, args.grep, now)
|
||||
entries_for_display(
|
||||
start,
|
||||
end,
|
||||
args.sheet,
|
||||
streams,
|
||||
args.format.unwrap_or_else(|| facts.config.default_formatter.clone()),
|
||||
args.ids,
|
||||
args.grep,
|
||||
facts
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -58,8 +68,8 @@ mod tests {
|
|||
use chrono::{Duration, TimeZone};
|
||||
use pretty_assertions::assert_eq;
|
||||
|
||||
use crate::database::SqliteDatabase;
|
||||
use crate::test_utils::Ps;
|
||||
use crate::config::Config;
|
||||
|
||||
use super::*;
|
||||
|
||||
|
@ -69,29 +79,24 @@ mod tests {
|
|||
format: Some(Formatter::Csv),
|
||||
..Default::default()
|
||||
};
|
||||
let mut db = SqliteDatabase::from_memory().unwrap();
|
||||
let mut out = Vec::new();
|
||||
let mut err = Vec::new();
|
||||
let config = Default::default();
|
||||
|
||||
db.init().unwrap();
|
||||
|
||||
let mut streams = Streams::fake(b"");
|
||||
let two_days_ago = Local::now().date() - Duration::days(2);
|
||||
let yesterday = Local::now().date() - Duration::days(1);
|
||||
let today = Local::now().date();
|
||||
let facts = Facts::new();
|
||||
|
||||
db.entry_insert(two_days_ago.and_hms(1, 2, 3).with_timezone(&Utc), None, None, "default".into()).unwrap();
|
||||
db.entry_insert(yesterday.and_hms(1, 2, 3).with_timezone(&Utc), None, Some("This!".into()), "default".into()).unwrap();
|
||||
db.entry_insert(today.and_hms(1, 2, 3).with_timezone(&Utc), None, None, "default".into()).unwrap();
|
||||
streams.db.entry_insert(two_days_ago.and_hms(1, 2, 3).with_timezone(&Utc), None, None, "default".into()).unwrap();
|
||||
streams.db.entry_insert(yesterday.and_hms(1, 2, 3).with_timezone(&Utc), None, Some("This!".into()), "default".into()).unwrap();
|
||||
streams.db.entry_insert(today.and_hms(1, 2, 3).with_timezone(&Utc), None, None, "default".into()).unwrap();
|
||||
|
||||
YesterdayCommand::handle(args, &mut db, &mut out, &mut err, &config, Utc::now()).unwrap();
|
||||
YesterdayCommand::handle(args, &mut streams, &facts).unwrap();
|
||||
|
||||
assert_eq!(Ps(&String::from_utf8_lossy(&out)), Ps(&format!("start,end,note,sheet
|
||||
assert_eq!(Ps(&String::from_utf8_lossy(&streams.out)), Ps(&format!("start,end,note,sheet
|
||||
{},,This!,default
|
||||
", yesterday.and_hms(1, 2, 3).with_timezone(&Utc).to_rfc3339_opts(chrono::SecondsFormat::Micros, true))));
|
||||
|
||||
assert_eq!(
|
||||
String::from_utf8_lossy(&err),
|
||||
String::from_utf8_lossy(&streams.err),
|
||||
String::new(),
|
||||
);
|
||||
}
|
||||
|
@ -101,22 +106,19 @@ mod tests {
|
|||
std::env::set_var("TZ", "CST+6");
|
||||
|
||||
let args = Default::default();
|
||||
let mut db = SqliteDatabase::from_memory().unwrap();
|
||||
let mut out = Vec::new();
|
||||
let mut err = Vec::new();
|
||||
let config = Config {
|
||||
let mut streams = Streams::fake(b"");
|
||||
let now = Utc.ymd(2021, 7, 1).and_hms(10, 0, 0);
|
||||
let facts = Facts::new().with_config(Config {
|
||||
default_formatter: Formatter::Ids,
|
||||
..Default::default()
|
||||
};
|
||||
}).with_now(now);
|
||||
|
||||
db.init().unwrap();
|
||||
streams.db.entry_insert(Utc.ymd(2021, 6, 30).and_hms(10, 0, 0), None, Some("hola".into()), "default".into()).unwrap();
|
||||
streams.db.entry_insert(Utc.ymd(2021, 6, 30).and_hms(10, 10, 0), None, Some("hola".into()), "default".into()).unwrap();
|
||||
|
||||
db.entry_insert(Utc.ymd(2021, 6, 30).and_hms(10, 0, 0), None, Some("hola".into()), "default".into()).unwrap();
|
||||
db.entry_insert(Utc.ymd(2021, 6, 30).and_hms(10, 10, 0), None, Some("hola".into()), "default".into()).unwrap();
|
||||
YesterdayCommand::handle(args, &mut streams, &facts).unwrap();
|
||||
|
||||
YesterdayCommand::handle(args, &mut db, &mut out, &mut err, &config, Utc.ymd(2021, 7, 1).and_hms(10, 0, 0)).unwrap();
|
||||
|
||||
assert_eq!(&String::from_utf8_lossy(&out), "1 2\n");
|
||||
assert_eq!(String::from_utf8_lossy(&err), "");
|
||||
assert_eq!(&String::from_utf8_lossy(&streams.out), "1 2\n");
|
||||
assert_eq!(String::from_utf8_lossy(&streams.err), "");
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,4 +1,3 @@
|
|||
use std::env;
|
||||
use std::path::{Path, PathBuf};
|
||||
use std::fs::{File, create_dir_all};
|
||||
use std::io::{Read, Write};
|
||||
|
@ -90,9 +89,9 @@ pub struct Config {
|
|||
impl Config {
|
||||
/// Tries as hard as possible to read the current configuration. Retrieving
|
||||
/// the path to it from the environment or common locations.
|
||||
pub fn read() -> Result<Config> {
|
||||
pub fn read(timetrap_config_file: Option<&str>) -> Result<Config> {
|
||||
// first try from env variable TIMETRAP_CONFIG_FILE
|
||||
if let Ok(value) = env::var("TIMETRAP_CONFIG_FILE") {
|
||||
if let Some(value) = timetrap_config_file {
|
||||
return if value.ends_with(".toml") {
|
||||
let config_path = PathBuf::from(&value);
|
||||
|
||||
|
|
|
@ -246,19 +246,19 @@ pub struct SqliteDatabase {
|
|||
}
|
||||
|
||||
impl SqliteDatabase {
|
||||
pub fn from_memory() -> Result<impl Database> {
|
||||
pub fn from_memory() -> Result<SqliteDatabase> {
|
||||
Ok(SqliteDatabase {
|
||||
connection: Connection::open_in_memory()?,
|
||||
})
|
||||
}
|
||||
|
||||
pub fn from_path<P: AsRef<Path>>(path: P) -> Result<impl Database> {
|
||||
pub fn from_path<P: AsRef<Path>>(path: P) -> Result<SqliteDatabase> {
|
||||
Ok(SqliteDatabase {
|
||||
connection: Connection::open(path)?,
|
||||
})
|
||||
}
|
||||
|
||||
pub fn from_path_or_create<P: AsRef<Path>>(path: P) -> Result<impl Database> {
|
||||
pub fn from_path_or_create<P: AsRef<Path>>(path: P) -> Result<SqliteDatabase> {
|
||||
if path.as_ref().is_file() {
|
||||
Self::from_path(path)
|
||||
} else {
|
||||
|
|
|
@ -0,0 +1,39 @@
|
|||
use std::env;
|
||||
|
||||
use atty::Stream;
|
||||
#[cfg(windows)]
|
||||
use ansi_term::enable_ansi_support;
|
||||
|
||||
/// Reads the given environment variable and decides if its value is true or
|
||||
/// false
|
||||
fn bool_env(name: &str) -> bool {
|
||||
if let Some(value) = env::var_os(name) {
|
||||
!(value == "0" || value == "false" || value == "")
|
||||
} else {
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Default)]
|
||||
pub struct Env {
|
||||
pub timetrap_config_file: Option<String>,
|
||||
pub supress_warming: bool,
|
||||
pub stdout_is_tty: bool,
|
||||
pub stderr_is_tty: bool,
|
||||
}
|
||||
|
||||
impl Env {
|
||||
pub fn read() -> Env {
|
||||
#[cfg(windows)]
|
||||
let tty = enable_ansi_support().is_some();
|
||||
#[cfg(not(windows))]
|
||||
let tty = true;
|
||||
|
||||
Env {
|
||||
timetrap_config_file: env::var("TIMETRAP_CONFIG_FILE").ok(),
|
||||
supress_warming: bool_env("TIEMPO_SUPRESS_TIMETRAP_WARNING"),
|
||||
stdout_is_tty: tty && atty::is(Stream::Stdout),
|
||||
stderr_is_tty: tty && atty::is(Stream::Stderr),
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,14 +1,17 @@
|
|||
use std::io::{self, Write};
|
||||
use std::io::{self, BufRead, Write};
|
||||
|
||||
fn read_line() -> io::Result<String> {
|
||||
use crate::io::Streams;
|
||||
use crate::database::Database;
|
||||
|
||||
fn read_line<I: BufRead>(mut r#in: I) -> io::Result<String> {
|
||||
let mut pre_n = String::new();
|
||||
io::stdin().read_line(&mut pre_n)?;
|
||||
r#in.read_line(&mut pre_n)?;
|
||||
Ok(pre_n)
|
||||
}
|
||||
|
||||
pub fn ask<W: Write>(out: &mut W, question: &str) -> io::Result<bool> {
|
||||
write!(out, "{} [y/N] ", question)?;
|
||||
out.flush()?;
|
||||
pub fn ask<D: Database, I: BufRead, O: Write, E: Write>(streams: &mut Streams<D, I, O, E>, question: &str) -> io::Result<bool> {
|
||||
write!(streams.out, "{} [y/N] ", question)?;
|
||||
streams.out.flush()?;
|
||||
|
||||
Ok(read_line()?.to_lowercase().starts_with('y'))
|
||||
Ok(read_line(&mut streams.r#in)?.to_lowercase().starts_with('y'))
|
||||
}
|
||||
|
|
|
@ -0,0 +1,56 @@
|
|||
use std::io::{BufRead, Write};
|
||||
|
||||
use crate::database::{Database, SqliteDatabase};
|
||||
|
||||
pub struct Streams<D, I, O, E>
|
||||
where
|
||||
D: Database,
|
||||
I: BufRead,
|
||||
O: Write,
|
||||
E: Write,
|
||||
{
|
||||
pub db: D,
|
||||
pub r#in: I,
|
||||
pub out: O,
|
||||
pub err: E,
|
||||
}
|
||||
|
||||
impl Streams<SqliteDatabase, &[u8], Vec<u8>, Vec<u8>> {
|
||||
pub fn fake(r#in: &[u8]) -> Streams<SqliteDatabase, &[u8], Vec<u8>, Vec<u8>> {
|
||||
let mut db = SqliteDatabase::from_memory().unwrap();
|
||||
|
||||
db.init().unwrap();
|
||||
|
||||
Streams {
|
||||
db,
|
||||
r#in,
|
||||
out: Vec::new(),
|
||||
err: Vec::new(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn fake_old(r#in: &[u8]) -> Streams<SqliteDatabase, &[u8], Vec<u8>, Vec<u8>> {
|
||||
let mut db = SqliteDatabase::from_memory().unwrap();
|
||||
|
||||
db.init_old().unwrap();
|
||||
|
||||
Streams {
|
||||
db,
|
||||
r#in,
|
||||
out: Vec::new(),
|
||||
err: Vec::new(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn with_db(self, db: SqliteDatabase) -> Self {
|
||||
Streams {
|
||||
db,
|
||||
..self
|
||||
}
|
||||
}
|
||||
|
||||
pub fn reset_io(&mut self) {
|
||||
self.out = Vec::new();
|
||||
self.err = Vec::new();
|
||||
}
|
||||
}
|
|
@ -13,6 +13,8 @@ pub mod regex;
|
|||
pub mod tabulate;
|
||||
pub mod old;
|
||||
pub mod interactive;
|
||||
pub mod env;
|
||||
pub mod io;
|
||||
|
||||
#[cfg(test)]
|
||||
pub mod test_utils;
|
||||
|
|
57
src/main.rs
57
src/main.rs
|
@ -11,46 +11,55 @@ use regex::Regex;
|
|||
|
||||
use tiempo::error;
|
||||
use tiempo::database::SqliteDatabase;
|
||||
use tiempo::env::Env;
|
||||
use tiempo::config::Config;
|
||||
use tiempo::commands::{
|
||||
Command, r#in::InCommand, display::DisplayCommand, sheet::SheetCommand,
|
||||
today::TodayCommand, yesterday::YesterdayCommand, week::WeekCommand,
|
||||
month::MonthCommand, list::ListCommand, out::OutCommand,
|
||||
Command, Facts, r#in::InCommand, display::DisplayCommand,
|
||||
sheet::SheetCommand, today::TodayCommand, yesterday::YesterdayCommand,
|
||||
week::WeekCommand, month::MonthCommand, list::ListCommand, out::OutCommand,
|
||||
resume::ResumeCommand, backend::BackendCommand, kill::KillCommand,
|
||||
now::NowCommand, edit::EditCommand, archive::ArchiveCommand,
|
||||
configure::ConfigureCommand,
|
||||
};
|
||||
use tiempo::io::Streams;
|
||||
|
||||
fn error_trap(matches: ArgMatches) -> error::Result<()> {
|
||||
let config = Config::read()?;
|
||||
let env = Env::read();
|
||||
let facts = Facts {
|
||||
config: Config::read(env.timetrap_config_file.as_deref())?,
|
||||
env,
|
||||
now: Utc::now(),
|
||||
};
|
||||
|
||||
if let Some(_matches) = matches.subcommand_matches("backend") {
|
||||
return BackendCommand::handle(&config);
|
||||
return BackendCommand::handle(&facts.config);
|
||||
}
|
||||
|
||||
let mut conn = SqliteDatabase::from_path_or_create(&config.database_file)?;
|
||||
let mut out = io::stdout();
|
||||
let mut err = io::stderr();
|
||||
let now = Utc::now();
|
||||
let mut streams = Streams {
|
||||
db: SqliteDatabase::from_path_or_create(&facts.config.database_file)?,
|
||||
r#in: io::BufReader::new(io::stdin()),
|
||||
out: io::stdout(),
|
||||
err: io::stderr(),
|
||||
};
|
||||
|
||||
match matches.subcommand() {
|
||||
("in", Some(matches)) => InCommand::handle(matches.try_into()?, &mut conn, &mut out, &mut err, &config, now),
|
||||
("out", Some(matches)) => OutCommand::handle(matches.try_into()?, &mut conn, &mut out, &mut err, &config, now),
|
||||
("resume", Some(matches)) => ResumeCommand::handle(matches.try_into()?, &mut conn, &mut out, &mut err, &config, now),
|
||||
("in", Some(matches)) => InCommand::handle(matches.try_into()?, &mut streams, &facts),
|
||||
("out", Some(matches)) => OutCommand::handle(matches.try_into()?, &mut streams, &facts),
|
||||
("resume", Some(matches)) => ResumeCommand::handle(matches.try_into()?, &mut streams, &facts),
|
||||
|
||||
("display", Some(matches)) => DisplayCommand::handle(matches.try_into()?, &mut conn, &mut out, &mut err, &config, now),
|
||||
("today", Some(matches)) => TodayCommand::handle(matches.try_into()?, &mut conn, &mut out, &mut err, &config, now),
|
||||
("yesterday", Some(matches)) => YesterdayCommand::handle(matches.try_into()?, &mut conn, &mut out, &mut err, &config, now),
|
||||
("week", Some(matches)) => WeekCommand::handle(matches.try_into()?, &mut conn, &mut out, &mut err, &config, now),
|
||||
("month", Some(matches)) => MonthCommand::handle(matches.try_into()?, &mut conn, &mut out, &mut err, &config, now),
|
||||
("display", Some(matches)) => DisplayCommand::handle(matches.try_into()?, &mut streams, &facts),
|
||||
("today", Some(matches)) => TodayCommand::handle(matches.try_into()?, &mut streams, &facts),
|
||||
("yesterday", Some(matches)) => YesterdayCommand::handle(matches.try_into()?, &mut streams, &facts),
|
||||
("week", Some(matches)) => WeekCommand::handle(matches.try_into()?, &mut streams, &facts),
|
||||
("month", Some(matches)) => MonthCommand::handle(matches.try_into()?, &mut streams, &facts),
|
||||
|
||||
("sheet", Some(matches)) => SheetCommand::handle(matches.try_into()?, &mut conn, &mut out, &mut err, &config, now),
|
||||
("list", Some(matches)) => ListCommand::handle(matches.try_into()?, &mut conn, &mut out, &mut err, &config, now),
|
||||
("kill", Some(matches)) => KillCommand::handle(matches.try_into()?, &mut conn, &mut out, &mut err, &config, now),
|
||||
("now", Some(matches)) => NowCommand::handle(matches.try_into()?, &mut conn, &mut out, &mut err, &config, now),
|
||||
("edit", Some(matches)) => EditCommand::handle(matches.try_into()?, &mut conn, &mut out, &mut err, &config, now),
|
||||
("archive", Some(matches)) => ArchiveCommand::handle(matches.try_into()?, &mut conn, &mut out, &mut err, &config, now),
|
||||
("configure", Some(matches)) => ConfigureCommand::handle(matches.try_into()?, &mut conn, &mut out, &mut err, &config, now),
|
||||
("sheet", Some(matches)) => SheetCommand::handle(matches.try_into()?, &mut streams, &facts),
|
||||
("list", Some(matches)) => ListCommand::handle(matches.try_into()?, &mut streams, &facts),
|
||||
("kill", Some(matches)) => KillCommand::handle(matches.try_into()?, &mut streams, &facts),
|
||||
("now", Some(matches)) => NowCommand::handle(matches.try_into()?, &mut streams, &facts),
|
||||
("edit", Some(matches)) => EditCommand::handle(matches.try_into()?, &mut streams, &facts),
|
||||
("archive", Some(matches)) => ArchiveCommand::handle(matches.try_into()?, &mut streams, &facts),
|
||||
("configure", Some(matches)) => ConfigureCommand::handle(matches.try_into()?, &mut streams, &facts),
|
||||
|
||||
(cmd, _) => Err(error::Error::UnimplementedCommand(cmd.into())),
|
||||
}
|
||||
|
|
13
src/old.rs
13
src/old.rs
|
@ -1,11 +1,12 @@
|
|||
use std::io::Write;
|
||||
|
||||
use chrono::{DateTime, Utc, Local, LocalResult, TimeZone};
|
||||
use ansi_term::Color::Yellow;
|
||||
use ansi_term::{Color::Yellow, Style};
|
||||
|
||||
use crate::error::{Error::*, Result};
|
||||
use crate::models::Entry;
|
||||
use crate::database::{Database, DBVersion};
|
||||
use crate::env::Env;
|
||||
|
||||
/// Treat t as if it wasnt actually in Utc but in the local timezone and return
|
||||
/// the actual Utc time.
|
||||
|
@ -72,13 +73,17 @@ pub fn time_or_warning<D: Database>(time: DateTime<Utc>, db: &D) -> Result<(Date
|
|||
}
|
||||
|
||||
/// 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 && std::env::var_os("TIEMPO_SUPRESS_TIMETRAP_WARNING").is_none() {
|
||||
pub fn warn_if_needed<E: Write>(err: &mut E, needs_warning: bool, env: &Env) -> Result<()> {
|
||||
if needs_warning && !env.supress_warming {
|
||||
writeln!(
|
||||
err,
|
||||
"{} You are using the old timetrap format, it is advised that \
|
||||
you update your database using t migrate",
|
||||
Yellow.bold().paint("[WARNING]"),
|
||||
if env.stderr_is_tty {
|
||||
Yellow.bold().paint("[WARNING]")
|
||||
} else {
|
||||
Style::new().paint("[WARNING]")
|
||||
},
|
||||
).map_err(IOError)?;
|
||||
}
|
||||
|
||||
|
|
Loading…
Reference in New Issue