jobber 0.1.0-alpha

Minimalistic console work time tracker
use clap::Parser;
use color_print::cprintln;

use crate::{
    commands::{Cli, Config, Error, Result, current_job, from_end},
    jobber::{JobberGet, JobberWork, Mod},
};

/// End work
///
/// End running work at the specified time (or now).
/// If a subject was not set within the previous start command it must be set
/// at this point.
/// If a subject was already set within the start command, it might be
/// replaced (`-r`) or appended (`-a`) explicitly.
///
/// This function can be undone with `jobber undo`.
#[derive(Parser, Debug)]
pub(crate) struct End {
    /// Time when work ended (or current time if not given)
    ///
    /// Available formats:
    ///
    /// | Example            | Description          |
    /// |--------------------|----------------------|
    /// | `now`              | current time         |
    /// | `2026-05-31 13:15` | date/time            |
    /// | `13:15`            | time behind start    |
    /// | `15m`              | work in minutes      |
    /// | `8h`               | work in hours        |
    #[clap(default_value = "now")]
    #[clap(verbatim_doc_comment)]
    end_time: String,
    /// Subject of the done work
    subject: Option<String>,
    /// If subject already was set replace with the given one
    #[clap(short, long, conflicts_with = "append_subject", requires = "subject")]
    replace_subject: bool,
    /// If subject already was set append the given one to it
    #[clap(short, long, conflicts_with = "replace_subject", requires = "subject")]
    append_subject: bool,
    /// Comma-separated list of tags (no spaces allowed e.g. support,test)
    #[clap(short, long, value_delimiter = ',')]
    tags: Vec<String>,
    /// ID of the job to end work for (default is current job).
    #[clap(short, long)]
    job: Option<usize>,
}

impl End {
    pub(crate) fn run(&self, cli: &Cli) -> Result<()> {
        let config = Config::load()?;
        let mut database = cli.open_database()?;
        let subject = match (&self.subject, self.replace_subject, self.append_subject) {
            (_, true, true) => unreachable!("conflict must be checked before"),
            (Some(subject), true, false) => Mod::Replace(subject),
            (Some(subject), false, true) => Mod::Append(subject),
            (Some(subject), false, false) => Mod::Set(subject),
            (None, _, _) => Mod::None,
        };
        let job_id = self.job.unwrap_or(current_job(&config, &database)?);
        if let Some(start) = database.get_job(job_id)?.running.as_ref().map(|r| r.start) {
            database.end_work(
                job_id,
                from_end(&self.end_time, start)?,
                subject,
                self.tags.clone(),
            )?;
            cli.close_database(database)?;
            cprintln!();
            cprintln!("Successfully ended work in job <s>{job_id}</>");
            Ok(())
        } else {
            Err(Error::NoCurrentJob)
        }
    }
}