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