be able to query entries

This commit is contained in:
Abraham Toriz 2021-06-21 17:38:51 -05:00
parent 47f4086c59
commit a29c3c40ab
No known key found for this signature in database
GPG Key ID: D5B4A746DB5DD42A
8 changed files with 122 additions and 18 deletions

View File

@ -3,7 +3,9 @@ use std::io::Write;
use clap::ArgMatches; use clap::ArgMatches;
use crate::{error, database::Database}; use crate::error;
use crate::database::Database;
use crate::config::Config;
pub mod r#in; pub mod r#in;
pub mod display; pub mod display;
@ -11,5 +13,5 @@ pub mod display;
pub trait Command<'a> { pub trait Command<'a> {
type Args: TryFrom<&'a ArgMatches<'a>>; type Args: TryFrom<&'a ArgMatches<'a>>;
fn handle<D: Database, W: Write>(args: Self::Args, db: &mut D, out: &mut W) -> error::Result<()>; fn handle<D: Database, W: Write>(args: Self::Args, db: &mut D, out: &mut W, config: &Config) -> error::Result<()>;
} }

View File

@ -3,18 +3,33 @@ use std::io::Write;
use clap::ArgMatches; use clap::ArgMatches;
use crate::{error, database::Database}; use crate::error;
use crate::database::Database;
use crate::types::Time;
use crate::formatters::Formatter;
use crate::config::Config;
use super::Command; use super::Command;
pub struct Args { pub struct Args {
ids: bool,
start: Option<Time>,
end: Option<Time>,
format: Formatter,
grep: Option<String>,
} }
impl<'a> TryFrom<&'a ArgMatches<'a>> for Args { impl<'a> TryFrom<&'a ArgMatches<'a>> for Args {
type Error = error::Error; type Error = error::Error;
fn try_from(matches: &'a ArgMatches) -> error::Result<Args> { fn try_from(matches: &'a ArgMatches) -> error::Result<Args> {
unimplemented!() Ok(Args {
ids: matches.is_present("ids"),
start: matches.value_of("at").map(|s| s.parse()).transpose()?,
end: matches.value_of("at").map(|s| s.parse()).transpose()?,
format: matches.value_of("format").unwrap().parse()?,
grep: matches.value_of("grep").map(|s| s.into()),
})
} }
} }
@ -24,11 +39,13 @@ pub struct DisplayCommand {
impl<'a> Command<'a> for DisplayCommand { impl<'a> Command<'a> for DisplayCommand {
type Args = Args; type Args = Args;
fn handle<D, W>(args: Self::Args, db: &mut D, out: &mut W) -> error::Result<()> fn handle<D, W>(args: Self::Args, db: &mut D, out: &mut W, config: &Config) -> error::Result<()>
where where
D: Database, D: Database,
W: Write, W: Write,
{ {
unimplemented!() let current_sheet = db.current_sheet()?.unwrap_or("default".into());
args.format.print_formatted(db.entries_by_sheet(&current_sheet)?, out)
} }
} }

View File

@ -2,13 +2,13 @@ use std::convert::TryFrom;
use std::io::Write; use std::io::Write;
use clap::ArgMatches; use clap::ArgMatches;
use chrono::{DateTime, Utc};
use crate::database::Database; use crate::database::Database;
use crate::error; use crate::error;
use crate::types::Time; use crate::types::Time;
use crate::editor; use crate::editor;
use crate::commands::Command; use crate::commands::Command;
use crate::config::Config;
pub struct Args { pub struct Args {
at: Option<Time>, at: Option<Time>,
@ -31,7 +31,7 @@ pub struct InCommand {}
impl<'a> Command<'a> for InCommand { impl<'a> Command<'a> for InCommand {
type Args = Args; type Args = Args;
fn handle<D, W>(args: Args, db: &mut D, out: &mut W) -> error::Result<()> fn handle<D, W>(args: Args, db: &mut D, out: &mut W, config: &Config) -> error::Result<()>
where where
D: Database, D: Database,
W: Write, W: Write,
@ -67,7 +67,7 @@ mod tests {
assert!(false, "there are no entries"); assert!(false, "there are no entries");
InCommand::handle(args, &mut d, &mut out).unwrap(); InCommand::handle(args, &mut d, &mut out, &Default::default()).unwrap();
assert!(false, "there is one entry"); assert!(false, "there is one entry");
} }
@ -83,7 +83,7 @@ mod tests {
assert!(false, "there are no entries"); assert!(false, "there are no entries");
InCommand::handle(args, &mut d, &mut out).unwrap(); InCommand::handle(args, &mut d, &mut out, &Default::default()).unwrap();
assert!(false, "there are still no entries"); assert!(false, "there are still no entries");
} }

View File

@ -128,3 +128,22 @@ impl Config {
unimplemented!() unimplemented!()
} }
} }
impl Default for Config {
fn default() -> Config {
Config {
database_file: PathBuf::new(), // "/home/abraham/.timetrap.db"
round_in_seconds: 900, // 900
append_notes_delimiter: " ".into(), //" "
formatter_search_paths: Vec::new(), //- "/home/abraham/.timetrap/formatters"
default_formatter: Formatter::Text, //text
auto_sheet: "dotfiles".into(), //dotfiles
auto_sheet_search_paths: Vec::new(), // - "/home/abraham/.timetrap/auto_sheets"
default_command: None,
auto_checkout: false, // false
require_note: true, // true
note_editor: "vim".into(), // nvim
week_start: WeekDay::Monday, // Monday
}
}
}

View File

@ -1,7 +1,6 @@
use std::path::Path; use std::path::Path;
use rusqlite::{Connection, ToSql}; use rusqlite::{Connection, ToSql};
use chrono::{DateTime, Utc};
use crate::error; use crate::error;
use crate::models::{Entry, Meta}; use crate::models::{Entry, Meta};
@ -15,13 +14,22 @@ pub trait Database {
fn entry_query(&self, query: &str, params: &[&dyn ToSql]) -> error::Result<Vec<Entry>>; fn entry_query(&self, query: &str, params: &[&dyn ToSql]) -> error::Result<Vec<Entry>>;
fn meta_query(&self, query: &str, params: &[&dyn ToSql]) -> error::Result<Vec<Meta>>; fn meta_query(&self, query: &str, params: &[&dyn ToSql]) -> error::Result<Vec<Meta>>;
fn entries_by_sheet(&mut self, sheet: String) -> error::Result<Vec<Entry>> { // Entry queries
self.entry_query("some".into(), &[])
fn entries_by_sheet(&mut self, sheet: &str) -> error::Result<Vec<Entry>> {
self.entry_query("select * from entries where sheet=?1", &[&sheet])
} }
fn entry_insert(&mut self, at: Time, note: String) -> error::Result<()> { fn entry_insert(&mut self, at: Time, note: String) -> error::Result<()> {
self.execute("", &[]) self.execute("", &[])
} }
// Meta queries
fn current_sheet(&self) -> error::Result<Option<String>> {
let results = self.meta_query("select * from meta where key=?1", &[&"current_sheet"])?;
Ok(results.into_iter().next().map(|m| m.value))
}
} }
pub struct SqliteDatabase { pub struct SqliteDatabase {
@ -52,7 +60,7 @@ impl Database for SqliteDatabase {
fn entry_query(&self, query: &str, params: &[&dyn ToSql]) -> error::Result<Vec<Entry>> { fn entry_query(&self, query: &str, params: &[&dyn ToSql]) -> error::Result<Vec<Entry>> {
let mut stmt = self.connection.prepare(query)?; let mut stmt = self.connection.prepare(query)?;
let results = stmt.query_map([], |row| { let results = stmt.query_map(params, |row| {
let x = Ok(Entry { let x = Ok(Entry {
id: row.get("id")?, id: row.get("id")?,
note: row.get("note")?, note: row.get("note")?,
@ -69,7 +77,7 @@ impl Database for SqliteDatabase {
fn meta_query(&self, query: &str, params: &[&dyn ToSql]) -> error::Result<Vec<Meta>> { fn meta_query(&self, query: &str, params: &[&dyn ToSql]) -> error::Result<Vec<Meta>> {
let mut stmt = self.connection.prepare(query)?; let mut stmt = self.connection.prepare(query)?;
let results = stmt.query_map([], |row| Ok(Meta { let results = stmt.query_map(params, |row| Ok(Meta {
id: row.get("id")?, id: row.get("id")?,
key: row.get("key")?, key: row.get("key")?,
value: row.get("value")?, value: row.get("value")?,

View File

@ -1,7 +1,43 @@
use std::str::FromStr;
use std::io::Write;
use serde::{Serialize, Deserialize}; use serde::{Serialize, Deserialize};
use crate::error;
use crate::models::Entry;
#[derive(Debug, Serialize, Deserialize)] #[derive(Debug, Serialize, Deserialize)]
#[serde(rename_all = "lowercase")] #[serde(rename_all = "lowercase")]
pub enum Formatter { pub enum Formatter {
Text, Text,
Custom(String),
}
impl Formatter {
pub fn print_formatted<W: Write>(&self, entries: Vec<Entry>, out: &mut W) -> error::Result<()> {
match &self {
Formatter::Text => {
for entry in entries {
writeln!(out, "{:?}", entry);
}
}
Formatter::Custom(name) => {
}
}
Ok(())
}
}
impl FromStr for Formatter {
type Err = error::Error;
fn from_str(s: &str) -> error::Result<Formatter> {
let lower = s.to_lowercase();
Ok(match &*lower {
"text" => Formatter::Text,
custom_format => Formatter::Custom(custom_format.into()),
})
}
} }

View File

@ -12,12 +12,12 @@ use tiempo::commands::{ Command, r#in::InCommand, display::DisplayCommand, };
fn error_trap(matches: ArgMatches) -> error::Result<()> { fn error_trap(matches: ArgMatches) -> error::Result<()> {
let config = Config::read()?; let config = Config::read()?;
let mut conn = SqliteDatabase::from_path(config.database_file)?; let mut conn = SqliteDatabase::from_path(&config.database_file)?;
let mut out = io::stdout(); let mut out = io::stdout();
match matches.subcommand() { match matches.subcommand() {
("in", Some(matches)) => InCommand::handle(matches.try_into()?, &mut conn, &mut out), ("in", Some(matches)) => InCommand::handle(matches.try_into()?, &mut conn, &mut out, &config),
("display", Some(matches)) => DisplayCommand::handle(matches.try_into()?, &mut conn, &mut out), ("display", Some(matches)) => DisplayCommand::handle(matches.try_into()?, &mut conn, &mut out, &config),
(cmd, _) => Err(error::Error::UnimplementedCommand(cmd.into())), (cmd, _) => Err(error::Error::UnimplementedCommand(cmd.into())),
} }
} }
@ -109,6 +109,26 @@ fn main() {
.about("Display the current timesheet or a specific. Pass `all' as SHEET .about("Display the current timesheet or a specific. Pass `all' as SHEET
to display all unarchived sheets or `full' to display archived and to display all unarchived sheets or `full' to display archived and
unarchived sheets.") unarchived sheets.")
.arg(Arg::with_name("ids")
.short("v").long("ids")
.help("Print database ids (for use with edit)"))
.arg(Arg::with_name("start")
.short("s").long("start")
.takes_value(true)
.value_name("DATE")
.help("Include entries that start on this date or later"))
.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 (default). 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."))
) )
.subcommand(SubCommand::with_name("in") .subcommand(SubCommand::with_name("in")

View File

@ -1,5 +1,6 @@
use chrono::{DateTime, Utc}; use chrono::{DateTime, Utc};
#[derive(Debug)]
pub struct Entry { pub struct Entry {
pub id: u64, pub id: u64,
pub note: String, pub note: String,
@ -8,6 +9,7 @@ pub struct Entry {
pub sheet: String, pub sheet: String,
} }
#[derive(Debug)]
pub struct Meta { pub struct Meta {
pub id: u64, pub id: u64,
pub key: String, pub key: String,