today command and refactoring
This commit is contained in:
parent
b4f7a10cfc
commit
1e33117660
116
src/commands.rs
116
src/commands.rs
|
@ -1,18 +1,128 @@
|
|||
use std::convert::TryFrom;
|
||||
use std::io::Write;
|
||||
use std::str::FromStr;
|
||||
|
||||
use clap::ArgMatches;
|
||||
use terminal_size::{Width, terminal_size};
|
||||
use chrono::{DateTime, Utc, Local, LocalResult, TimeZone};
|
||||
use ansi_term::Color::Yellow;
|
||||
|
||||
use crate::error;
|
||||
use crate::database::Database;
|
||||
use crate::error::{Result, Error};
|
||||
use crate::database::{Database, DBVersion};
|
||||
use crate::config::Config;
|
||||
use crate::models::Entry;
|
||||
use crate::formatters::Formatter;
|
||||
|
||||
pub mod r#in;
|
||||
pub mod display;
|
||||
pub mod today;
|
||||
pub mod sheet;
|
||||
|
||||
pub trait Command<'a> {
|
||||
type Args: TryFrom<&'a ArgMatches<'a>>;
|
||||
|
||||
fn handle<D: Database, O: Write, E: Write>(args: Self::Args, db: &mut D, out: &mut O, err: &mut E, config: &Config) -> error::Result<()>;
|
||||
fn handle<D: Database, O: Write, E: Write>(args: Self::Args, db: &mut D, out: &mut O, err: &mut E, config: &Config) -> Result<()>;
|
||||
}
|
||||
|
||||
fn local_to_utc(t: DateTime<Utc>) -> Result<DateTime<Utc>> {
|
||||
let local_time = match Local.from_local_datetime(&t.naive_utc()) {
|
||||
LocalResult::None => return Err(Error::NoneLocalTime(t.naive_utc().to_string())),
|
||||
LocalResult::Single(t) => t,
|
||||
LocalResult::Ambiguous(t1, t2) => return Err(Error::AmbiguousLocalTime {
|
||||
orig: t.naive_utc().to_string(),
|
||||
t1: t1.naive_local(),
|
||||
t2: t2.naive_local(),
|
||||
}),
|
||||
};
|
||||
|
||||
Ok(Utc.from_utc_datetime(&local_time.naive_utc()))
|
||||
}
|
||||
|
||||
fn local_to_utc_vec(entries: Vec<Entry>) -> Result<Vec<Entry>> {
|
||||
entries
|
||||
.into_iter()
|
||||
.map(|e| {
|
||||
Ok(Entry {
|
||||
start: local_to_utc(e.start)?,
|
||||
end: e.end.map(|t| local_to_utc(t)).transpose()?,
|
||||
..e
|
||||
})
|
||||
})
|
||||
.collect()
|
||||
}
|
||||
|
||||
fn term_width() -> usize {
|
||||
if let Some((Width(w), _)) = terminal_size() {
|
||||
w as usize
|
||||
} else {
|
||||
80
|
||||
}
|
||||
}
|
||||
|
||||
pub enum Sheet {
|
||||
All,
|
||||
Full,
|
||||
Sheet(String),
|
||||
}
|
||||
|
||||
impl FromStr for Sheet {
|
||||
type Err = Error;
|
||||
|
||||
fn from_str(name: &str) -> Result<Sheet> {
|
||||
Ok(match name {
|
||||
"all" => Sheet::All,
|
||||
"full" => Sheet::Full,
|
||||
name => Sheet::Sheet(name.into()),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
pub fn entries_for_display<D, O, E>(
|
||||
start: Option<DateTime<Utc>>, end: Option<DateTime<Utc>>,
|
||||
sheet: Option<Sheet>, db: &mut D, out: &mut O, err: &mut E,
|
||||
format: Formatter, ids: bool,
|
||||
) -> Result<()>
|
||||
where
|
||||
D: Database,
|
||||
O: Write,
|
||||
E: Write,
|
||||
{
|
||||
let entries = match sheet {
|
||||
Some(Sheet::All) => db.entries_all_visible(start, end)?,
|
||||
Some(Sheet::Full) => db.entries_full(start, end)?,
|
||||
Some(Sheet::Sheet(name)) => db.entries_by_sheet(&name, start, end)?,
|
||||
None => {
|
||||
let current_sheet = db.current_sheet()?.unwrap_or("default".into());
|
||||
|
||||
db.entries_by_sheet(¤t_sheet, start, end)?
|
||||
}
|
||||
};
|
||||
|
||||
let (entries, needs_warning) = if let DBVersion::Timetrap = db.version()? {
|
||||
// this indicates that times in the database are specified in the
|
||||
// local time and need to be converted to utc before displaying
|
||||
|
||||
(local_to_utc_vec(entries)?, true)
|
||||
} else {
|
||||
(entries, false)
|
||||
};
|
||||
|
||||
format.print_formatted(
|
||||
entries,
|
||||
out,
|
||||
Utc::now(),
|
||||
ids,
|
||||
term_width(),
|
||||
)?;
|
||||
|
||||
if needs_warning {
|
||||
writeln!(
|
||||
err,
|
||||
"{} You are using the old timetrap format, it is advised that \
|
||||
you update your database using t migrate",
|
||||
Yellow.bold().paint("[WARNING]"),
|
||||
)?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
|
|
@ -1,73 +1,16 @@
|
|||
use std::convert::TryFrom;
|
||||
use std::io::Write;
|
||||
use std::str::FromStr;
|
||||
|
||||
use clap::ArgMatches;
|
||||
use chrono::{DateTime, Utc, Local, TimeZone, LocalResult};
|
||||
use terminal_size::{Width, terminal_size};
|
||||
use ansi_term::Color::Yellow;
|
||||
use chrono::{DateTime, Utc};
|
||||
|
||||
use crate::error;
|
||||
use crate::database::{Database, DBVersion};
|
||||
use crate::database::Database;
|
||||
use crate::formatters::Formatter;
|
||||
use crate::config::Config;
|
||||
use crate::models::Entry;
|
||||
use crate::timeparse::parse_time;
|
||||
|
||||
use super::Command;
|
||||
|
||||
fn local_to_utc(t: DateTime<Utc>) -> error::Result<DateTime<Utc>> {
|
||||
let local_time = match Local.from_local_datetime(&t.naive_utc()) {
|
||||
LocalResult::None => return Err(error::Error::NoneLocalTime(t.naive_utc().to_string())),
|
||||
LocalResult::Single(t) => t,
|
||||
LocalResult::Ambiguous(t1, t2) => return Err(error::Error::AmbiguousLocalTime {
|
||||
orig: t.naive_utc().to_string(),
|
||||
t1: t1.naive_local(),
|
||||
t2: t2.naive_local(),
|
||||
}),
|
||||
};
|
||||
|
||||
Ok(Utc.from_utc_datetime(&local_time.naive_utc()))
|
||||
}
|
||||
|
||||
fn local_to_utc_vec(entries: Vec<Entry>) -> error::Result<Vec<Entry>> {
|
||||
entries
|
||||
.into_iter()
|
||||
.map(|e| {
|
||||
Ok(Entry {
|
||||
start: local_to_utc(e.start)?,
|
||||
end: e.end.map(|t| local_to_utc(t)).transpose()?,
|
||||
..e
|
||||
})
|
||||
})
|
||||
.collect()
|
||||
}
|
||||
|
||||
fn term_width() -> usize {
|
||||
if let Some((Width(w), _)) = terminal_size() {
|
||||
w as usize
|
||||
} else {
|
||||
80
|
||||
}
|
||||
}
|
||||
|
||||
enum Sheet {
|
||||
All,
|
||||
Full,
|
||||
Sheet(String),
|
||||
}
|
||||
|
||||
impl FromStr for Sheet {
|
||||
type Err = error::Error;
|
||||
|
||||
fn from_str(name: &str) -> error::Result<Sheet> {
|
||||
Ok(match name {
|
||||
"all" => Sheet::All,
|
||||
"full" => Sheet::Full,
|
||||
name => Sheet::Sheet(name.into()),
|
||||
})
|
||||
}
|
||||
}
|
||||
use super::{Command, Sheet, entries_for_display};
|
||||
|
||||
#[derive(Default)]
|
||||
pub struct Args {
|
||||
|
@ -105,54 +48,20 @@ impl<'a> Command<'a> for DisplayCommand {
|
|||
O: Write,
|
||||
E: Write,
|
||||
{
|
||||
let entries = match args.sheet {
|
||||
Some(Sheet::All) => db.entries_all_visible(args.start, args.end)?,
|
||||
Some(Sheet::Full) => db.entries_full(args.start, args.end)?,
|
||||
Some(Sheet::Sheet(name)) => db.entries_by_sheet(&name, args.start, args.end)?,
|
||||
None => {
|
||||
let current_sheet = db.current_sheet()?.unwrap_or("default".into());
|
||||
|
||||
db.entries_by_sheet(¤t_sheet, args.start, args.end)?
|
||||
}
|
||||
};
|
||||
|
||||
let (entries, needs_warning) = if let DBVersion::Timetrap = db.version()? {
|
||||
// this indicates that times in the database are specified in the
|
||||
// local time and need to be converted to utc before displaying
|
||||
|
||||
(local_to_utc_vec(entries)?, true)
|
||||
} else {
|
||||
(entries, false)
|
||||
};
|
||||
|
||||
args.format.print_formatted(
|
||||
entries,
|
||||
out,
|
||||
Utc::now(),
|
||||
args.ids,
|
||||
term_width(),
|
||||
)?;
|
||||
|
||||
if needs_warning {
|
||||
writeln!(
|
||||
err,
|
||||
"{} You are using the old timetrap format, it is advised that \
|
||||
you update your database using t migrate",
|
||||
Yellow.bold().paint("[WARNING]"),
|
||||
)?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
entries_for_display(args.start, args.end, args.sheet, db, out, err, args.format, args.ids)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use chrono::TimeZone;
|
||||
use ansi_term::Color::Yellow;
|
||||
|
||||
use crate::database::SqliteDatabase;
|
||||
use crate::test_utils::PrettyString;
|
||||
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn display_as_local_time_if_previous_version() {
|
||||
let args = Default::default();
|
||||
|
|
|
@ -0,0 +1,53 @@
|
|||
use std::convert::TryFrom;
|
||||
use std::io::Write;
|
||||
|
||||
use clap::ArgMatches;
|
||||
use chrono::{DateTime, Utc, Local};
|
||||
|
||||
use crate::error;
|
||||
use crate::database::Database;
|
||||
use crate::formatters::Formatter;
|
||||
use crate::config::Config;
|
||||
use crate::timeparse::parse_time;
|
||||
|
||||
use super::{Command, Sheet, entries_for_display};
|
||||
|
||||
#[derive(Default)]
|
||||
pub struct Args {
|
||||
ids: bool,
|
||||
end: Option<DateTime<Utc>>,
|
||||
format: Formatter,
|
||||
grep: Option<String>,
|
||||
sheet: Option<Sheet>,
|
||||
}
|
||||
|
||||
impl<'a> TryFrom<&'a ArgMatches<'a>> for Args {
|
||||
type Error = error::Error;
|
||||
|
||||
fn try_from(matches: &'a ArgMatches) -> error::Result<Args> {
|
||||
Ok(Args {
|
||||
ids: matches.is_present("ids"),
|
||||
end: matches.value_of("end").map(|s| parse_time(s)).transpose()?,
|
||||
format: matches.value_of("format").unwrap().parse()?,
|
||||
grep: matches.value_of("grep").map(|s| s.into()),
|
||||
sheet: matches.value_of("sheet").map(|s| s.parse()).transpose()?,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
pub struct TodayCommand { }
|
||||
|
||||
impl<'a> Command<'a> for TodayCommand {
|
||||
type Args = Args;
|
||||
|
||||
fn handle<D, O, E>(args: Self::Args, db: &mut D, out: &mut O, err: &mut E, _config: &Config) -> error::Result<()>
|
||||
where
|
||||
D: Database,
|
||||
O: Write,
|
||||
E: Write,
|
||||
{
|
||||
let start = Some(Local::now().date().and_hms(0, 0, 0).with_timezone(&Utc));
|
||||
|
||||
entries_for_display(start, args.end, args.sheet, db, out, err, args.format, args.ids)
|
||||
}
|
||||
}
|
28
src/main.rs
28
src/main.rs
|
@ -9,6 +9,7 @@ use tiempo::database::SqliteDatabase;
|
|||
use tiempo::config::Config;
|
||||
use tiempo::commands::{
|
||||
Command, r#in::InCommand, display::DisplayCommand, sheet::SheetCommand,
|
||||
today::TodayCommand,
|
||||
};
|
||||
|
||||
fn error_trap(matches: ArgMatches) -> error::Result<()> {
|
||||
|
@ -20,8 +21,12 @@ fn error_trap(matches: ArgMatches) -> error::Result<()> {
|
|||
|
||||
match matches.subcommand() {
|
||||
("in", Some(matches)) => InCommand::handle(matches.try_into()?, &mut conn, &mut out, &mut err, &config),
|
||||
|
||||
("display", Some(matches)) => DisplayCommand::handle(matches.try_into()?, &mut conn, &mut out, &mut err, &config),
|
||||
("today", Some(matches)) => TodayCommand::handle(matches.try_into()?, &mut conn, &mut out, &mut err, &config),
|
||||
|
||||
("sheet", Some(matches)) => SheetCommand::handle(matches.try_into()?, &mut conn, &mut out, &mut err, &config),
|
||||
|
||||
(cmd, _) => Err(error::Error::UnimplementedCommand(cmd.into())),
|
||||
}
|
||||
}
|
||||
|
@ -136,6 +141,29 @@ fn main() {
|
|||
.help("The sheet to display. Pass 'all' to see entries from all sheets or 'full' to see hidden entries"))
|
||||
)
|
||||
|
||||
.subcommand(SubCommand::with_name("today")
|
||||
.visible_alias("t")
|
||||
.about("Display entries that started today")
|
||||
.arg(Arg::with_name("ids")
|
||||
.short("v").long("ids")
|
||||
.help("Print database ids (for use with edit)"))
|
||||
.arg(Arg::with_name("end")
|
||||
.short("e").long("end")
|
||||
.takes_value(true).value_name("DATE")
|
||||
.help("Include entries that start on this date or earlier"))
|
||||
.arg(Arg::with_name("format")
|
||||
.short("f").long("format")
|
||||
.takes_value(true).value_name("FORMAT").default_value("text")
|
||||
.help("The output format. Valid built-in formats are ical, csv, json, ids, factor, and text. Documentation on defining custom formats can be found in the README included in this distribution."))
|
||||
.arg(Arg::with_name("grep")
|
||||
.short("g").long("grep")
|
||||
.takes_value(true).value_name("REGEXP")
|
||||
.help("Include entries where the note matches this regexp."))
|
||||
.arg(Arg::with_name("sheet")
|
||||
.takes_value(true).value_name("SHEET")
|
||||
.help("The sheet to display. Pass 'all' to see entries from all sheets or 'full' to see hidden entries"))
|
||||
)
|
||||
|
||||
.subcommand(SubCommand::with_name("in")
|
||||
.visible_alias("i")
|
||||
.about("Start an activity in the current timesheet")
|
||||
|
|
Loading…
Reference in New Issue