ocs 0.1.5

A session picker for OpenCode
use super::*;

#[derive(Parser)]
#[command(about = "A fuzzy OpenCode session picker", version)]
pub(crate) struct Arguments {
  #[arg(long, help = "Show sessions from all directories")]
  pub(crate) all: bool,
  #[arg(long, value_name = "PATH", help = "OpenCode data directory")]
  pub(crate) data_dir: Option<PathBuf>,
  #[arg(
    long,
    value_name = "PATH",
    conflicts_with = "data_dir",
    help = "OpenCode database file"
  )]
  pub(crate) database: Option<PathBuf>,
  #[arg(long, help = "Print the selected session ID instead of opening it")]
  pub(crate) print: bool,
  #[arg(long, help = "Initial fuzzy-search query")]
  pub(crate) query: Option<String>,
  #[command(subcommand)]
  subcommand: Option<Subcommand>,
}

impl Arguments {
  pub(crate) fn run(self) -> Result {
    if let Some(subcommand) = self.subcommand {
      subcommand.run();
      return Ok(());
    }

    let config = Config::load()?;

    let storage = if let Some(database) = self.database {
      Storage::new(database)?
    } else if let Some(data_dir) = self.data_dir {
      Storage::new(data_dir.join("opencode.db"))?
    } else {
      Storage::default()?
    };

    storage.validate_schema()?;

    let directory = if self.all {
      None
    } else {
      Some(
        env::current_dir()
          .context("could not determine the current directory")?,
      )
    };

    let mut query = self.query;

    loop {
      let sessions = storage.sessions(directory.as_deref())?;

      let Some(selection) =
        SessionPicker::new(&storage, &sessions, query, self.all).pick()?
      else {
        return Ok(());
      };

      match selection {
        Selection::Delete {
          id,
          query: picker_query,
        } => {
          storage.delete_session(&id)?;

          if sessions.len() == 1 {
            return Ok(());
          }

          query = Some(picker_query);
        }
        Selection::Open(id) => {
          if self.print {
            println!("{id}");
            return Ok(());
          }

          let session = sessions
            .iter()
            .find(|session| session.id == id)
            .context("selected session was not indexed")?;

          return session.open(&config, &storage);
        }
      }
    }
  }
}

#[cfg(test)]
mod tests {
  use {super::*, clap::CommandFactory};

  #[test]
  fn database_and_data_dir_conflict() {
    let error = Arguments::command()
      .try_get_matches_from(["ocs", "--database", "foo", "--data-dir", "bar"])
      .err()
      .unwrap();

    assert_eq!(error.kind(), clap::error::ErrorKind::ArgumentConflict);
  }
}