tiempo 1.6.0

A command line time tracker
Documentation
use std::result;
use std::path::PathBuf;
use std::io;

use thiserror::Error;
use chrono::NaiveDateTime;
use itertools::Itertools;

fn format_paths(paths: &[PathBuf]) -> String {
    paths
        .iter()
        .map(|p| format!("    - {}", p.display()))
        .collect_vec()
        .join("\n")
}

#[derive(Debug, Error)]
pub enum Error {
    #[error("The subcommand '{0}' is not implemented")]
    UnimplementedCommand(String),

    /// Sometimes a specific variant for an error is not necessary if the error
    /// can only happen in one place in the code. This is what the generic error
    /// is for and nothing else.
    #[error("{0}")]
    GenericFailure(String),

    #[error("Sqlite error: {0}")]
    Sqlite(#[from] rusqlite::Error),

    #[error("Could not find home directory or a place where configuration can be read an written :(")]
    NoHomeDir,

    #[error("Could not read the config file at

{path}

with error:

{error}")]
    CouldntReadConfigFile {
        path: PathBuf,
        error: io::Error,
    },

    #[error("Couldn't parse toml file at: {path}\nwith error: {error}")]
    TomlError{ path: PathBuf, error: toml::de::Error},

    #[error("Couldn't parse yaml file at: {path}\nwith error: {error}")]
    YamlError{ path: PathBuf, error: serde_yaml::Error},

    #[error("Could not understand '{0}' as a date format. Some options are:

Something similar to ISO format will be parsed as a time in the computer's
timezone.

* '2021-01-13' a date
* '2019-05-03 11:13' a date with portions of a time

ISO format with offset or UTC will be parsed as a time in the specified
timezone. Use 'Z' for 'UTC' and an offset for everything else

* '2021-01-13Z'
* '2005-10-14 19:20:35+05:00'

something that looks like an hour will be parsed as a time in the current
day in the computer's timezone. Add 'Z' or an offset to specify the timezone.

* '11:30'
* '23:50:45' (with seconds)

some human times, for now restricted to time ago:

* 'an hour ago'
* 'a minute ago'
* '50 min ago'
* '1h30m ago'
* 'two hours thirty minutes ago'")]
    DateTimeParseError(String),

    #[error("IOError: {0}")]
    IOError(#[from] std::io::Error),

    #[error("CSV Error: {0}")]
    CSVError(#[from] csv::Error),

    #[error("Could not serialize to json.

{0}")]
    SerdeJsonError(#[from] serde_json::Error),

    #[error("Corrupted data found in the database:
{0}

To fix this first find where your database is located using t configure and
query it using t backend or the sqlite3 command provided by your system.")]
    CorruptedData(String),

    #[error("Trying to parse {0} as a time in your timezone led to no results")]
    NoneLocalTime(String),

    #[error("Trying to parse {orig} as a time in your timezone led to the ambiguous results {t1} and {t2}")]
    AmbiguousLocalTime {
        orig: String,
        t1: NaiveDateTime,
        t2: NaiveDateTime,
    },

    #[error("The provided regex '{0}' could not be parsed")]
    InvalidRegex(String),

    #[error("Could not understand '{0}' as a month specifier. Try 'this', 'last', or any month name like 'january' or 'nov'")]
    InvalidMonthSpec(String),

    #[error("Could not understand '{0}' as a week day. Try 'monday' or 'TuesDay'")]
    InvalidWeekDaySpec(String),

    #[error("Could not understand '{0}' as a number of hours")]
    InvalidHours(String),

    #[error("An error ocurred while trying to read entries from the database.

In the row with id {id} the data at column '{col}' has a value that is not a
valid time.

A valid time looks like this: '2021-07-12 19:35:43.645916' and must be in UTC
timezone (unless you are using a database that was created by timetrap, case in
which the times are in the local timezone).

To fix this problem you can use t backend and manually update the value using
good old SQL.")]
    InvalidTimeInDatabase {
        id: String,
        col: String,
    },

    #[error("A note wasn't provided, and the config file specifies that the note
is required, but an empty string was found in the config value for 'note_editor'.

You can fix this by running t config and specifying a note editor. Check
t config --help for more options.")]
    EditorIsEmpty,

    #[error("Running editor '{editor}' failed with error: '{error}'")]
    EditorFailed {
        editor: String,
        error: String,
    },

    #[error("A temporary file couldn't be created for you to edit. This is very
weird, but might have something to do with yout OS.

Here's the underlaying error:
{0}

If you think this is an issue with this program report it at:

https://gitlab.com/categulario/tiempo")]
    NoTmpFile(String),

    #[error("The temporary file you just created couldn't be read. This is the
underlaying error:

{0}

I didn't imagine this could ever happen, but now I'm curious. Do you mind
reporging the issue at https://gitlab.com/categulario/tiempo ?")]
    CouldntReadTmpFile(String),

    #[error("The ")]
    CouldntCreateConfigDir {
        path: PathBuf,
        error: String,
    },

    #[error("A problem ocurred while trying to edit/create the config file at

{path}

Is there a permissions issue? Here's the underlaying error:

{error}")]
    CouldntEditConfigFile {
        path: PathBuf,
        error: String,
    },

    #[error("Trying to run system command sqlite3 failed with error:

{0}")]
    Sqlite3CommandFailed(std::io::Error),

    #[error("Trying to run system command sqlite3 failed")]
    Sqlite3CommandFailedUnkown,

    #[error("The specified name for a custom formatter \"{0}\" is not valid. Only ascii
letters, numbers, dots, dashes and underscores are allowed.")]
    InvalidCustomFormatter(String),

    #[error("You have specified a custom formatter \"{0}\" but your config file doesn't say
where to look for it.

You can set a path using

t config --formatter-search-paths <path>..")]
    NoFormatterSearchPaths(String),

    #[error("Could not find a formatter with name '{name}' in any of the following paths:

{0}

which where taken from your config file located at

    {config_at}

Perhaps it is mispelled?", format_paths(.paths))]
    FormatterNotFound {
        name: String,
        paths: Vec<PathBuf>,
        config_at: PathBuf,
    },

    #[error("The custom formatter located at:

{0}

failed with error:

{1}")]
    CustomFormatterFailed(PathBuf, std::io::Error),
}

pub type Result<T> = result::Result<T, Error>;