mirror of
https://github.com/gosticks/acari.git
synced 2026-08-14 21:30:24 +00:00
Layout missing commands
This commit is contained in:
@@ -2,6 +2,11 @@ function __fish_is_arg_n --argument-names n
|
||||
test $n -eq (count (string match -v -- '-*' (commandline -poc)))
|
||||
end
|
||||
|
||||
function __fish_arg_n --argument-names n
|
||||
set -l args (string match -v -- '-*' (commandline -poc))
|
||||
echo -n $args[(math "$n + 1")]
|
||||
end
|
||||
|
||||
# options
|
||||
complete -c acari -s o -l output -a "flat json pretty" -d "set output format"
|
||||
complete -c acari -s h -l help -d "show help"
|
||||
@@ -12,8 +17,13 @@ complete -f -c acari -n "__fish_use_subcommand" -a init -d "initialize connectio
|
||||
complete -f -c acari -n "__fish_use_subcommand" -a check -d "check connection"
|
||||
complete -f -c acari -n "__fish_use_subcommand" -a clear-cache -d "Clear local cache"
|
||||
complete -f -c acari -n "__fish_use_subcommand" -a customers -d "list customers"
|
||||
complete -f -c acari -n "__fish_use_subcommand" -a entries -d "list time entries"
|
||||
complete -f -c acari -n "__fish_use_subcommand" -a projects -d "list projects"
|
||||
complete -f -c acari -n "__fish_use_subcommand" -a services -d "list services"
|
||||
complete -f -c acari -n "__fish_use_subcommand" -a set -d "set time entry"
|
||||
complete -f -c acari -n "__fish_use_subcommand" -a start -d "start time tracking"
|
||||
complete -f -c acari -n "__fish_use_subcommand" -a stop -d "stop time tracking"
|
||||
complete -f -c acari -n "__fish_use_subcommand" -a tracking -d "show current time tracking"
|
||||
|
||||
# check
|
||||
complete -f -c acari -n "__fish_seen_subcommand_from check"
|
||||
@@ -24,9 +34,30 @@ complete -f -c acari -n "__fish_seen_subcommand_from clear-cache"
|
||||
# customers
|
||||
complete -f -c acari -n "__fish_seen_subcommand_from customers"
|
||||
|
||||
# entries
|
||||
complete -f -c acari -n "__fish_seen_subcommand_from entries"
|
||||
|
||||
# projects
|
||||
complete -f -c acari -n "__fish_seen_subcommand_from projects"
|
||||
complete -f -c acari -n "__fish_seen_subcommand_from projects; and __fish_is_arg_n 2" -a "(acari -oflat customers)"
|
||||
|
||||
# services
|
||||
complete -f -c acari -n "__fish_seen_subcommand_from services"
|
||||
|
||||
# set
|
||||
complete -f -c acari -n "__fish_seen_subcommand_from set"
|
||||
complete -f -c acari -n "__fish_seen_subcommand_from set; and __fish_is_arg_n 2" -a "(acari -oflat customers)"
|
||||
complete -f -c acari -n "__fish_seen_subcommand_from set; and __fish_is_arg_n 3" -a "(acari -oflat projects (__fish_arg_n 2))"
|
||||
complete -f -c acari -n "__fish_seen_subcommand_from set; and __fish_is_arg_n 4" -a "(acari -oflat services)"
|
||||
|
||||
# start
|
||||
complete -f -c acari -n "__fish_seen_subcommand_from start"
|
||||
complete -f -c acari -n "__fish_seen_subcommand_from start; and __fish_is_arg_n 2" -a "(acari -oflat customers)"
|
||||
complete -f -c acari -n "__fish_seen_subcommand_from start; and __fish_is_arg_n 3" -a "(acari -oflat projects (__fish_arg_n 2))"
|
||||
complete -f -c acari -n "__fish_seen_subcommand_from start; and __fish_is_arg_n 4" -a "(acari -oflat services)"
|
||||
|
||||
# stop
|
||||
complete -f -c acari -n "__fish_seen_subcommand_from stop"
|
||||
|
||||
# tracking
|
||||
complete -f -c acari -n "__fish_seen_subcommand_from tracking"
|
||||
|
||||
@@ -6,6 +6,10 @@ mod entries;
|
||||
mod init;
|
||||
mod projects_of_customer;
|
||||
mod services;
|
||||
mod set;
|
||||
mod start;
|
||||
mod stop;
|
||||
mod tracking;
|
||||
|
||||
pub use all_projects::*;
|
||||
pub use check::*;
|
||||
@@ -15,6 +19,10 @@ pub use entries::*;
|
||||
pub use init::*;
|
||||
pub use projects_of_customer::*;
|
||||
pub use services::*;
|
||||
pub use set::*;
|
||||
pub use start::*;
|
||||
pub use stop::*;
|
||||
pub use tracking::*;
|
||||
|
||||
use acari_lib::AcariError;
|
||||
|
||||
|
||||
@@ -2,7 +2,7 @@ use super::OutputFormat;
|
||||
use acari_lib::{AcariError, Client, Project};
|
||||
use prettytable::{cell, format, row, Table};
|
||||
|
||||
pub fn projects_of_customer(client: &dyn Client, customer_name: &str, output_format: OutputFormat) -> Result<(), AcariError> {
|
||||
pub fn projects_of_customer(client: &dyn Client, output_format: OutputFormat, customer_name: &str) -> Result<(), AcariError> {
|
||||
let mut projects = client.get_projects()?;
|
||||
|
||||
projects.retain(|p| p.customer_name == customer_name);
|
||||
|
||||
@@ -0,0 +1,14 @@
|
||||
use super::OutputFormat;
|
||||
use acari_lib::{AcariError, Client, Tracker};
|
||||
use prettytable::{cell, format, row, Table};
|
||||
|
||||
pub fn set(
|
||||
client: &dyn Client,
|
||||
output_format: OutputFormat,
|
||||
customer_name: &str,
|
||||
project_name: &str,
|
||||
service_name: &str,
|
||||
minutes: u32,
|
||||
) -> Result<(), AcariError> {
|
||||
Ok(())
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
use super::OutputFormat;
|
||||
use acari_lib::{AcariError, Client, Tracker};
|
||||
use prettytable::{cell, format, row, Table};
|
||||
|
||||
pub fn start(
|
||||
client: &dyn Client,
|
||||
output_format: OutputFormat,
|
||||
customer_name: &str,
|
||||
project_name: &str,
|
||||
service_name: &str,
|
||||
minutes_offset: u32,
|
||||
) -> Result<(), AcariError> {
|
||||
Ok(())
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
use super::OutputFormat;
|
||||
use acari_lib::{AcariError, Client, Tracker};
|
||||
use prettytable::{cell, format, row, Table};
|
||||
|
||||
pub fn stop(client: &dyn Client, output_format: OutputFormat) -> Result<(), AcariError> {
|
||||
Ok(())
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
use super::OutputFormat;
|
||||
use acari_lib::{AcariError, Client, Tracker};
|
||||
use prettytable::{cell, format, row, Table};
|
||||
|
||||
pub fn tracking(client: &dyn Client, output_format: OutputFormat) -> Result<(), AcariError> {
|
||||
Ok(())
|
||||
}
|
||||
+2
-2
@@ -1,4 +1,4 @@
|
||||
use acari_lib::{AcariError, CachedClient, Client, StdClient};
|
||||
use acari_lib::{internal_error, AcariError, CachedClient, Client, StdClient};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::fs::{self, File};
|
||||
use std::io::{self, Read, Write};
|
||||
@@ -46,7 +46,7 @@ impl Config {
|
||||
fs::create_dir_all(
|
||||
&config_file
|
||||
.parent()
|
||||
.ok_or_else(|| AcariError::InternalError("Invalid config path".to_string()))?,
|
||||
.ok_or_else(|| internal_error!("Invalid config path: {}", config_file.to_string_lossy()))?,
|
||||
)?;
|
||||
|
||||
let mut file = File::create(&config_file)?;
|
||||
|
||||
+73
-15
@@ -1,5 +1,5 @@
|
||||
use acari_lib::{AcariError, DateSpan};
|
||||
use clap::{App, Arg, SubCommand};
|
||||
use acari_lib::{user_error, AcariError, DateSpan};
|
||||
use clap::{App, Arg, ArgMatches, SubCommand};
|
||||
|
||||
mod commands;
|
||||
mod config;
|
||||
@@ -23,12 +23,6 @@ fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||
.subcommand(SubCommand::with_name("check").about("Check connection to mite"))
|
||||
.subcommand(SubCommand::with_name("clear-cache").about("Clear the local cache"))
|
||||
.subcommand(SubCommand::with_name("customers").about("List all customers"))
|
||||
.subcommand(
|
||||
SubCommand::with_name("projects")
|
||||
.arg(Arg::with_name("customer").help("Optional: List only projects of a specific customer"))
|
||||
.about("List all projects"),
|
||||
)
|
||||
.subcommand(SubCommand::with_name("services").about("List all services"))
|
||||
.subcommand(
|
||||
SubCommand::with_name("entries")
|
||||
.arg(
|
||||
@@ -37,7 +31,32 @@ fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||
.help("Date span to query\n(today, yesterday, this-week, last-week,\n this-month, last-month, yyyy-mm-dd, yyyy-mm-dd|yyyy-mm-dd)"),
|
||||
)
|
||||
.about("Query time entries"),
|
||||
);
|
||||
)
|
||||
.subcommand(
|
||||
SubCommand::with_name("projects")
|
||||
.arg(Arg::with_name("customer").help("Optional: List only projects of a specific customer"))
|
||||
.about("List all projects"),
|
||||
)
|
||||
.subcommand(SubCommand::with_name("services").about("List all services"))
|
||||
.subcommand(
|
||||
SubCommand::with_name("set")
|
||||
.arg(Arg::with_name("customer").required(true).help("Customer name"))
|
||||
.arg(Arg::with_name("project").required(true).help("Project name"))
|
||||
.arg(Arg::with_name("service").required(true).help("Service name"))
|
||||
.arg(Arg::with_name("time").required(true).help("Time (minutes or hh:mm)"))
|
||||
.about("Start tracking time"),
|
||||
)
|
||||
.subcommand(
|
||||
SubCommand::with_name("start")
|
||||
.arg(Arg::with_name("customer").required(true).help("Customer name"))
|
||||
.arg(Arg::with_name("project").required(true).help("Project name"))
|
||||
.arg(Arg::with_name("service").required(true).help("Service name"))
|
||||
.arg(Arg::with_name("offset").help("Optional: Starting offset (minutes or hh:mm)"))
|
||||
.about("Start tracking time"),
|
||||
)
|
||||
.subcommand(SubCommand::with_name("stop").about("Stop current time tracking"))
|
||||
.subcommand(SubCommand::with_name("tracking").about("Show currently tracked time entry"));
|
||||
|
||||
let matches = app.get_matches();
|
||||
|
||||
let output_format = matches.value_of("output").map(OutputFormat::from_string).unwrap_or(Ok(OutputFormat::Pretty))?;
|
||||
@@ -50,17 +69,33 @@ fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||
("check", _) => commands::check(client.as_ref(), output_format)?,
|
||||
("clear-cache", _) => commands::clear_cache()?,
|
||||
("customers", _) => commands::customers(client.as_ref(), output_format)?,
|
||||
("entries", Some(sub_matches)) => {
|
||||
let span = required_arg(sub_matches, "span")?;
|
||||
commands::entries(client.as_ref(), output_format, DateSpan::from_string(span)?)?;
|
||||
}
|
||||
("projects", Some(sub_matches)) => match sub_matches.value_of("customer") {
|
||||
Some(customer) => commands::projects_of_customer(client.as_ref(), customer, output_format)?,
|
||||
Some(customer) => commands::projects_of_customer(client.as_ref(), output_format, customer)?,
|
||||
None => commands::all_projects(client.as_ref(), output_format)?,
|
||||
},
|
||||
("services", _) => commands::services(client.as_ref(), output_format)?,
|
||||
("entries", Some(sub_matches)) => {
|
||||
let span_arg = sub_matches
|
||||
.value_of("span")
|
||||
.ok_or(AcariError::UserError("Missing <span> argument".to_string()))?;
|
||||
commands::entries(client.as_ref(), output_format, DateSpan::from_string(span_arg)?)?;
|
||||
("set", Some(sub_matches)) => {
|
||||
let customer = required_arg(sub_matches, "customer")?;
|
||||
let project = required_arg(sub_matches, "project")?;
|
||||
let service = required_arg(sub_matches, "service")?;
|
||||
let time = parse_minutes(required_arg(sub_matches, "time")?)?;
|
||||
|
||||
commands::set(client.as_ref(), output_format, customer, project, service, time)?;
|
||||
}
|
||||
("start", Some(sub_matches)) => {
|
||||
let customer = required_arg(sub_matches, "customer")?;
|
||||
let project = required_arg(sub_matches, "project")?;
|
||||
let service = required_arg(sub_matches, "service")?;
|
||||
let offset = minutes_arg(sub_matches, "offset")?;
|
||||
|
||||
commands::start(client.as_ref(), output_format, customer, project, service, offset.unwrap_or(0))?;
|
||||
}
|
||||
("stop", _) => commands::stop(client.as_ref(), output_format)?,
|
||||
("tracking", _) => commands::tracking(client.as_ref(), output_format)?,
|
||||
(invalid, _) => Err(AcariError::UserError(format!("Unknown command: {}", invalid)))?,
|
||||
}
|
||||
}
|
||||
@@ -72,3 +107,26 @@ fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn required_arg<'a>(matches: &'a ArgMatches, name: &str) -> Result<&'a str, AcariError> {
|
||||
matches.value_of(name).ok_or_else(|| user_error!("Missing <{}> argument", name))
|
||||
}
|
||||
|
||||
fn minutes_arg(matches: &ArgMatches, name: &str) -> Result<Option<u32>, AcariError> {
|
||||
match matches.value_of(name) {
|
||||
Some(value) => parse_minutes(value).map(Some),
|
||||
None => Ok(None),
|
||||
}
|
||||
}
|
||||
|
||||
fn parse_minutes(expr: &str) -> Result<u32, AcariError> {
|
||||
match expr.find(":") {
|
||||
Some(idx) => {
|
||||
let hours = expr[..idx].parse::<u32>().map_err(|e| user_error!("Invalid time format: {}", e))?;
|
||||
let minutes = expr[idx + 1..].parse::<u32>().map_err(|e| user_error!("Invalid time format: {}", e))?;
|
||||
|
||||
Ok(hours * 60 + minutes)
|
||||
}
|
||||
None => expr.parse::<u32>().map_err(|e| user_error!("Invalid time format: {}", e)),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
use crate::error::AcariError;
|
||||
use crate::model::{Account, Customer, Project, Service, TimeEntry, User};
|
||||
use crate::model::{Account, Customer, Project, Service, TimeEntry, Tracker, User};
|
||||
use crate::query::DateSpan;
|
||||
use crate::std_client::StdClient;
|
||||
use crate::Client;
|
||||
@@ -81,9 +81,17 @@ impl Client for CachedClient {
|
||||
self.cache_data("services.json", || self.client.get_services())
|
||||
}
|
||||
|
||||
fn get_time_entry(&self, entry_id: u32) -> Result<TimeEntry, AcariError> {
|
||||
self.client.get_time_entry(entry_id) // This should not be cached
|
||||
}
|
||||
|
||||
fn get_time_entries(&self, date_span: DateSpan) -> Result<Vec<TimeEntry>, AcariError> {
|
||||
self.client.get_time_entries(date_span) // This should not be cached
|
||||
}
|
||||
|
||||
fn get_tracker(&self) -> Result<Tracker, AcariError> {
|
||||
self.client.get_tracker() // This should not be cached
|
||||
}
|
||||
}
|
||||
|
||||
fn file_age(path: &PathBuf) -> Result<Option<Duration>, AcariError> {
|
||||
|
||||
@@ -21,5 +21,23 @@ pub trait Client {
|
||||
|
||||
fn get_services(&self) -> Result<Vec<Service>, AcariError>;
|
||||
|
||||
fn get_time_entry(&self, entry_id: u32) -> Result<TimeEntry, AcariError>;
|
||||
|
||||
fn get_time_entries(&self, date_span: DateSpan) -> Result<Vec<TimeEntry>, AcariError>;
|
||||
|
||||
fn get_tracker(&self) -> Result<Tracker, AcariError>;
|
||||
}
|
||||
|
||||
#[macro_export]
|
||||
macro_rules! user_error {
|
||||
( $( $arg:expr ),* ) => {
|
||||
AcariError::UserError(format!($($arg),*))
|
||||
}
|
||||
}
|
||||
|
||||
#[macro_export]
|
||||
macro_rules! internal_error {
|
||||
( $( $arg:expr ),* ) => {
|
||||
AcariError::InternalError(format!($($arg),*))
|
||||
}
|
||||
}
|
||||
|
||||
+1
-1
@@ -13,7 +13,7 @@ impl Day {
|
||||
match day.to_lowercase().as_str() {
|
||||
"today" | "now" => Ok(Day::Today),
|
||||
"yesterday" => Ok(Day::Yesterday),
|
||||
date => Ok(Day::Date(NaiveDate::parse_from_str(date, "%Y-%m-%d")?))
|
||||
date => Ok(Day::Date(NaiveDate::parse_from_str(date, "%Y-%m-%d")?)),
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+15
-1
@@ -1,5 +1,5 @@
|
||||
use crate::error::AcariError;
|
||||
use crate::model::{Account, Customer, MiteEntity, Project, Service, TimeEntry, User};
|
||||
use crate::model::{Account, Customer, MiteEntity, Project, Service, TimeEntry, Tracker, User};
|
||||
use crate::query::DateSpan;
|
||||
use crate::Client;
|
||||
use serde::de::DeserializeOwned;
|
||||
@@ -91,6 +91,13 @@ impl Client for StdClient {
|
||||
)
|
||||
}
|
||||
|
||||
fn get_time_entry(&self, entry_id: u32) -> Result<TimeEntry, AcariError> {
|
||||
match self.get(&format!("/time_entries/{}.json", entry_id))? {
|
||||
MiteEntity::TimeEntry(time_entry) => Ok(time_entry),
|
||||
response => Err(AcariError::Mite(400, format!("Unexpected response: {:?}", response))),
|
||||
}
|
||||
}
|
||||
|
||||
fn get_time_entries(&self, date_span: DateSpan) -> Result<Vec<TimeEntry>, AcariError> {
|
||||
Ok(
|
||||
self
|
||||
@@ -103,6 +110,13 @@ impl Client for StdClient {
|
||||
.collect(),
|
||||
)
|
||||
}
|
||||
|
||||
fn get_tracker(&self) -> Result<Tracker, AcariError> {
|
||||
match self.get("/tracker.json")? {
|
||||
MiteEntity::Tracker(tracker) => Ok(tracker),
|
||||
response => Err(AcariError::Mite(400, format!("Unexpected response: {:?}", response))),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn handle_response<T: DeserializeOwned>(response: blocking::Response) -> Result<T, AcariError> {
|
||||
|
||||
Reference in New Issue
Block a user