oxen-cli 0.50.1

Oxen is a fast, unstructured data version control, to help version large machine learning datasets written in Rust.
use async_trait::async_trait;
use clap::{Arg, Command};

use liboxen::command;
use liboxen::config::{AuthConfig, UserConfig};
use liboxen::error::OxenError;
use liboxen::model::LocalRepository;

use crate::cmd::RunCmd;
pub const NAME: &str = "config";
pub struct ConfigCmd;

#[async_trait]
impl RunCmd for ConfigCmd {
    fn name(&self) -> &str {
        NAME
    }

    fn args(&self) -> Command {
        // Setups the CLI args for the command
        Command::new(NAME)
            .about("Sets the user configuration in ~/.oxen/user_config.toml")
            .arg(
                Arg::new("name")
                    .long("name")
                    .short('n')
                    .help("Set the name you want your commits to be saved as.")
                    .action(clap::ArgAction::Set),
            )
            .arg(
                Arg::new("email")
                    .long("email")
                    .short('e')
                    .help("Set the email you want your commits to be saved as.")
                    .action(clap::ArgAction::Set),
            )
            // Note: we differ from git here
            .arg(
                Arg::new("set-remote")
                    .long("set-remote")
                    .number_of_values(2)
                    .value_names(["NAME", "URL"])
                    .help("Set a remote for your current working repository.")
                    .action(clap::ArgAction::Set),
            )
            // "delete-remote" is easier to read than "remove-remote"
            .arg(
                Arg::new("delete-remote")
                    .long("delete-remote")
                    .value_name("REMOTE_NAME")
                    .help("Delete a remote from the current working repository.")
                    .action(clap::ArgAction::Set),
            )
            .arg(
                Arg::new("auth-token")
                    .long("auth")
                    .short('a')
                    .number_of_values(2)
                    .value_names(["HOST", "TOKEN"])
                    .help("Set the authentication token for a specific oxen-server host.")
                    .action(clap::ArgAction::Set),
            )
            .arg(
                Arg::new("default-host")
                    .long("default-host")
                    .help("Sets the default host used to check version numbers. If empty, the CLI will not do a version check.")
                    .action(clap::ArgAction::Set),
            )
            .arg(
                Arg::new("editor")
                    .long("editor")
                    .help("Set the default text editor for commit messages (e.g. vim, nano, code --wait).")
                    .action(clap::ArgAction::Set),
            )
            .arg_required_else_help(true)
    }

    async fn run(&self, args: &clap::ArgMatches) -> Result<(), OxenError> {
        // Non-Repo Dependent
        if let Some(name) = args.get_one::<String>("name") {
            self.set_user_name(name)?;
        }

        if let Some(email) = args.get_one::<String>("email") {
            self.set_user_email(email)?;
        }

        if let Some(auth) = args.get_many::<String>("auth-token") {
            let values: Vec<_> = auth.collect();
            // clap enforces number_of_values(2), so this is guaranteed
            self.set_auth_token(values[0], values[1])?;
        }

        if let Some(default_host) = args.get_one::<String>("default-host") {
            self.set_default_host(default_host)?;
        }

        if let Some(editor) = args.get_one::<String>("editor") {
            self.set_editor(editor)?;
        }

        // Repo Dependent
        if let Some(remote) = args.get_many::<String>("set-remote") {
            let mut repo = LocalRepository::from_current_dir()?;
            let values: Vec<_> = remote.collect();
            // clap enforces number_of_values(2), so this is guaranteed
            self.set_remote(&mut repo, values[0], values[1])?;
        }

        if let Some(name) = args.get_one::<String>("delete-remote") {
            let mut repo = LocalRepository::from_current_dir()?;
            self.delete_remote(&mut repo, name)?;
        }

        Ok(())
    }
}

impl ConfigCmd {
    fn strip_host(host: &str) -> Result<String, OxenError> {
        if host.contains("://") {
            Ok(url::Url::parse(host)?
                .host_str()
                .ok_or_else(|| OxenError::basic_str("Unable to parse host."))?
                .to_string())
        } else {
            Ok(host.to_string())
        }
    }

    pub fn set_remote(
        &self,
        repo: &mut LocalRepository,
        name: &str,
        url: &str,
    ) -> Result<(), OxenError> {
        command::config::set_remote(repo, name, url)?;

        Ok(())
    }

    pub fn delete_remote(&self, repo: &mut LocalRepository, name: &str) -> Result<(), OxenError> {
        command::config::delete_remote(repo, name)?;

        Ok(())
    }

    pub fn set_auth_token(&self, host: &str, token: &str) -> Result<(), OxenError> {
        let host = Self::strip_host(host)?;
        let mut config = AuthConfig::get_or_create()?;
        config.add_host_auth_token(host.as_ref(), token);
        config.save_default()?;
        println!("Authentication token set for host: {host}");
        Ok(())
    }

    pub fn set_default_host(&self, host: &str) -> Result<(), OxenError> {
        let host = Self::strip_host(host)?;
        let mut config = AuthConfig::get_or_create()?;
        if host.is_empty() {
            config.default_host = None;
        } else {
            config.default_host = Some(host.clone());
        }
        config.save_default()?;
        println!("Default host set to: {host}");
        Ok(())
    }

    pub fn set_user_name(&self, name: &str) -> Result<(), OxenError> {
        let mut config = UserConfig::get_or_create()?;
        config.name = String::from(name);
        config.save_default()?;
        Ok(())
    }

    pub fn set_user_email(&self, email: &str) -> Result<(), OxenError> {
        let mut config = UserConfig::get_or_create()?;
        config.email = String::from(email);
        config.save_default()?;
        Ok(())
    }

    pub fn set_editor(&self, editor: &str) -> Result<(), OxenError> {
        let mut config = UserConfig::get_or_create()?;
        config.editor = Some(String::from(editor));
        config.save_default()?;
        Ok(())
    }
}