rpk 0.2.2

A lightweight, cross-platform cli package manager.
use std::path::PathBuf;

use clap::{
    builder::{
        styling::{AnsiColor, Effects},
        Styles,
    },
    Parser,
};
use clap_complete::Shell;
use url::Url;

use crate::{context::Verbosity, util};

pub const ENV_CONFIG_DIR: &str = "RPK_CONFIG_DIR";
pub const ENV_DATA_DIR: &str = "RPK_DATA_DIR";
pub const ENV_CACHE_DIR: &str = "RPK_CACHE_DIR";
pub const ENV_BIN_DIR: &str = "RPK_BIN_DIR";

/// Resolved command line options.
#[derive(Debug, PartialEq, Eq, Parser)]
#[clap(author, about)]
#[clap(version = util::CRATE_VERSION)]
#[clap(long_version = util::CRATE_LONG_VERSION)]
#[clap(
    styles(Styles::styled()
        .header(AnsiColor::Yellow.on_default() | Effects::BOLD)
        .usage(AnsiColor::Yellow.on_default() | Effects::BOLD)
        .literal(AnsiColor::Green.on_default() | Effects::BOLD)
        .placeholder(AnsiColor::Cyan.on_default())
    )
)]
pub struct Opt {
    /// Suppress any informational output.
    #[clap(long, short, global = true)]
    pub quiet: bool,

    /// Use verbose output.
    #[clap(long, short, global = true)]
    pub verbose: bool,

    /// The configuration directory.
    #[clap(long, value_name = "PATH", env = ENV_CONFIG_DIR)]
    pub config_dir: Option<PathBuf>,

    /// The directory to store package data.
    #[clap(long, value_name = "PATH", env = ENV_DATA_DIR)]
    pub data_dir: Option<PathBuf>,

    /// The directory to store downloaded packages.
    #[clap(long, value_name = "PATH", env = ENV_CACHE_DIR)]
    pub cache_dir: Option<PathBuf>,

    /// The directory installed binaries linked to.
    #[clap(long, value_name = "PATH", env = ENV_BIN_DIR)]
    pub bin_dir: Option<PathBuf>,

    /// The subcommand to run.
    #[clap(subcommand)]
    pub command: SubCommand,
}

/// The resolved sub command.
#[derive(Debug, PartialEq, Eq, Parser)]
pub enum SubCommand {
    /// Initialize a configuration file.
    Init {
        /// The config file URL to initialize from.
        #[clap(short, long, value_name = "URL")]
        from: Option<Url>,
    },

    /// List all installed packages.
    List,

    /// Install any missing packages, re-generating the lock file.
    Sync,

    /// Add a new plugin to the config file.
    Add {
        /// The github repository hosting the package
        ///
        /// Example: `sharkdp/fd`
        #[clap(value_name = "REPO")]
        #[arg(value_parser = repo_parser)]
        repo: (String, String),

        /// A unique name for the package. Defaults to the repo name.
        #[clap(long, value_name = "NAME")]
        name: Option<String>,

        /// The binaries to install. Defaults to the package name.
        #[clap(long, value_name = "BIN", num_args = 0..)]
        binary: Vec<String>,

        /// The version of the package.
        #[clap(long, value_name = "VERSION")]
        version: Option<String>,

        /// A description of the package.
        #[clap(long, value_name = "DESC", long)]
        desc: Option<String>,
    },

    /// Restore packages to the state in the lockfile.
    Restore {
        /// The packages to restore.
        #[clap(value_name = "PKG")]
        package: Option<String>,
    },

    /// Update packages and re-generate the lock file.
    Update {
        /// The packages to update.
        #[clap(value_name = "PKG")]
        package: Option<String>,
    },

    /// Search packages matching the given query.
    Search {
        /// The query to search for.
        #[clap(value_name = "QUERY")]
        query: String,

        /// The number of results to display.
        #[clap(long, value_name = "NUM", default_value = "10")]
        top: u8,
    },

    /// Remove packages which are not listed in the lock file.
    Cleanup {
        /// Remove all cached data as well.
        #[clap(long)]
        cache: bool,
    },

    /// Prints the environment variables for rpk.
    Env,

    /// Generate completions for the given shell.
    Completions {
        /// The shell to generate completions for.
        #[clap(value_name = "SHELL", value_enum, required_unless_present = "list")]
        shell: Option<Shell>,

        /// The directory to write the completions to.
        ///
        /// Defaults output to stdout.
        #[clap(short, long, value_name = "DIR")]
        dir: Option<PathBuf>,

        /// List all available shells.
        #[clap(short, long, exclusive = true)]
        list: bool,
    },

    /// Prints detailed version information.
    Version,
}

impl Opt {
    pub fn verbosity(&self) -> Verbosity {
        if self.quiet {
            Verbosity::Quiet
        } else if self.verbose {
            Verbosity::Verbose
        } else {
            Verbosity::Normal
        }
    }
}

fn repo_parser(repo: &str) -> Result<(String, String), String> {
    match repo.split_once('/') {
        Some((owner, repo)) => Ok((owner.to_owned(), repo.to_owned())),
        None => Err("invalid repo format, should be: 'owner/repo'".into()),
    }
}