Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 10 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,7 @@ clap = { version = "4", features = [
"unicode",
"wrap_help",
] }
clap_complete = "4"
anyhow = "1"
serde = { version = "1", features = ["derive"] }
serde_json = "1"
Expand Down
14 changes: 13 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,19 @@ Interact with track.toggl.com via terminal.

## Shell completions

WIP
Generate shell completions for your shell:

```bash
# Bash
fbtoggl --completions bash > ~/.local/share/bash-completion/completions/fbtoggl

# Zsh
fbtoggl --completions zsh > ~/.zfunc/_fbtoggl
# Add to ~/.zshrc: fpath=(~/.zfunc $fpath)

# Fish
fbtoggl --completions fish > ~/.config/fish/completions/fbtoggl.fish
```

## Usage

Expand Down
14 changes: 12 additions & 2 deletions src/cli.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,10 @@ use crate::model::Range;
use crate::types::TimeEntryId;
use chrono::{DateTime, Duration, Local};
use clap::{Parser, Subcommand, ValueEnum};
use clap_complete::{Generator, Shell, generate};
use jackdauer::duration;
use serde::Serialize;
use std::io;

pub const APP_NAME: &str = "fbtoggl";

Expand All @@ -18,8 +20,12 @@ pub struct Options {
#[arg(long)]
pub debug: bool,

/// Generate shell completions
#[arg(long, value_enum)]
pub completions: Option<Shell>,

#[clap(subcommand)]
pub subcommand: SubCommand,
pub subcommand: Option<SubCommand>,
}

#[derive(Debug, Clone, ValueEnum)]
Expand Down Expand Up @@ -53,7 +59,7 @@ pub enum SubCommand {
Reports(Reports),
}

#[derive(Subcommand, Debug)]
#[derive(Subcommand, Debug, Clone, Copy)]
pub enum Settings {
/// Initialize settings
Init,
Expand Down Expand Up @@ -250,3 +256,7 @@ pub fn output_values_json<T: Serialize>(values: &[T]) {
}
}
}

pub fn print_completions<G: Generator>(generator: G, cmd: &mut clap::Command) {
generate(generator, cmd, cmd.get_name().to_owned(), &mut io::stdout());
}
220 changes: 138 additions & 82 deletions src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,9 @@
//! This application provides a command-line interface to interact with
//! Toggl Track's time tracking service.

use crate::cli::{Clients, Options, SubCommand, TimeEntries};
use crate::cli::{Clients, Format, Options, SubCommand, TimeEntries};
use crate::config::init_settings_file;
use clap::Parser;
use clap::{CommandFactory, Parser};
use cli::{Projects, Reports, Settings};
use client::init_client;
use report_client::init_report_client;
Expand All @@ -27,95 +27,151 @@ mod client_tests;

fn main() -> anyhow::Result<()> {
let options = Options::parse();

// Handle completion generation if requested
if let Some(shell) = options.completions {
let mut cmd = Options::command();
cli::print_completions(shell, &mut cmd);
return Ok(());
}

let format = options.format;
let debug = options.debug;

match options.subcommand {
if let Some(subcommand) = options.subcommand {
execute_subcommand(subcommand, debug, &format)?;
} else {
eprintln!(
"Error: A subcommand is required when not generating completions"
);
std::process::exit(1);
}

Ok(())
}

fn execute_subcommand(
subcommand: SubCommand,
debug: bool,
format: &Format,
) -> anyhow::Result<()> {
match subcommand {
SubCommand::Init => init_settings_file()?,
SubCommand::Settings(action) => match action {
Settings::Init => init_settings_file()?,
},
SubCommand::Projects(action) => match action {
Projects::List(list_projects) => {
let client = init_client()?;

commands::projects::list(
debug,
list_projects.include_archived,
&format,
&client,
)?;
}
},
SubCommand::Workspaces(_action) => {
SubCommand::Settings(action) => handle_settings(action)?,
SubCommand::Projects(action) => handle_projects(action, debug, format)?,
SubCommand::Workspaces(_action) => handle_workspaces(debug, format)?,
SubCommand::TimeEntries(action) => {
handle_time_entries(action, debug, format)?;
}
SubCommand::Clients(action) => handle_clients(action, debug, format)?,
SubCommand::Reports(action) => handle_reports(action, debug)?,
}
Ok(())
}

fn handle_settings(action: Settings) -> anyhow::Result<()> {
match action {
Settings::Init => init_settings_file()?,
}
Ok(())
}

fn handle_projects(
action: Projects,
debug: bool,
format: &Format,
) -> anyhow::Result<()> {
match action {
Projects::List(list_projects) => {
let client = init_client()?;
commands::projects::list(
debug,
list_projects.include_archived,
format,
&client,
)?;
}
}
Ok(())
}

commands::workspaces::list(debug, &format, &client)?;
fn handle_workspaces(debug: bool, format: &Format) -> anyhow::Result<()> {
let client = init_client()?;
commands::workspaces::list(debug, format, &client)?;
Ok(())
}

fn handle_time_entries(
action: TimeEntries,
debug: bool,
format: &Format,
) -> anyhow::Result<()> {
let client = init_client()?;

match action {
TimeEntries::Create(time_entry) => {
commands::time_entries::create(debug, format, &time_entry, &client)?;
}
TimeEntries::List(list_time_entries) => {
commands::time_entries::list(
debug,
format,
&list_time_entries.range,
list_time_entries.missing,
&client,
)?;
}
TimeEntries::Start(time_entry) => {
commands::time_entries::start(debug, format, &time_entry, &client)?;
}
TimeEntries::Stop(time_entry) => {
commands::time_entries::stop(debug, format, &time_entry, &client)?;
}
TimeEntries::Delete(time_entry) => {
commands::time_entries::delete(debug, format, &time_entry, &client)?;
}
TimeEntries::Details(time_entry) => {
commands::time_entries::details(debug, format, &time_entry, &client)?;
}
}
Ok(())
}

fn handle_clients(
action: Clients,
debug: bool,
format: &Format,
) -> anyhow::Result<()> {
let client = init_client()?;

SubCommand::TimeEntries(action) => match action {
TimeEntries::Create(time_entry) => {
let client = init_client()?;
commands::time_entries::create(debug, &format, &time_entry, &client)?;
}
TimeEntries::List(list_time_entries) => {
let client = init_client()?;
commands::time_entries::list(
debug,
&format,
&list_time_entries.range,
list_time_entries.missing,
&client,
)?;
}
TimeEntries::Start(time_entry) => {
let client = init_client()?;
commands::time_entries::start(debug, &format, &time_entry, &client)?;
}
TimeEntries::Stop(time_entry) => {
let client = init_client()?;
commands::time_entries::stop(debug, &format, &time_entry, &client)?;
}
TimeEntries::Delete(time_entry) => {
let client = init_client()?;
commands::time_entries::delete(debug, &format, &time_entry, &client)?;
}
TimeEntries::Details(time_entry) => {
let client = init_client()?;
commands::time_entries::details(debug, &format, &time_entry, &client)?;
}
},

SubCommand::Clients(action) => match action {
Clients::Create(create_client) => {
let client = init_client()?;
commands::clients::create(debug, &format, &create_client, &client)?;
}
Clients::List(list_clients) => {
let client = init_client()?;
commands::clients::list(
debug,
list_clients.include_archived,
&format,
&client,
)?;
}
},

SubCommand::Reports(action) => match action {
Reports::Detailed(detailed) => {
let client = init_client()?;
let report_client = init_report_client()?;

commands::reports::detailed(
debug,
&client,
&detailed.range,
&report_client,
)?;
}
},
match action {
Clients::Create(create_client) => {
commands::clients::create(debug, format, &create_client, &client)?;
}
Clients::List(list_clients) => {
commands::clients::list(
debug,
list_clients.include_archived,
format,
&client,
)?;
}
}
Ok(())
}

fn handle_reports(action: Reports, debug: bool) -> anyhow::Result<()> {
match action {
Reports::Detailed(detailed) => {
let client = init_client()?;
let report_client = init_report_client()?;
commands::reports::detailed(
debug,
&client,
&detailed.range,
&report_client,
)?;
}
}
Ok(())
}