mapm-cli 6.1.0

The command-line implementation of mapm
use crate::utils;
use crate::utils::dirs::master_problem_dir;
use clap::{Parser, Subcommand};
use std::{fs, path::PathBuf};

#[derive(Parser)]
#[clap(version, disable_help_subcommand = true)]
struct Cli {
    #[clap(subcommand)]
    subcommand: Command,
    #[clap(short, long)]
    /// Sets the effective problem directory for the command, bypassing the profile entirely.
    ///
    /// Useful for one-time manipulations, like building past contests from an archive without
    /// interfering with active development of a contest.
    problem_dir: Option<String>,
}

#[derive(Debug, Subcommand)]
enum Command {
    /// Prints the key-val pairs associated with each problem passed in
    ///
    /// Use the --hide and --show flags to filter out specific key-val pairs (they are mutually exclusive)
    View {
        problems: Vec<String>,
        #[clap(long, multiple_values = true, default_missing_value = "")]
        /// Hides the following keys in the terminal output
        ///
        /// Usage:
        ///
        /// mapm view alexander-balls --hide solutions
        ///
        /// will prevent the terminal from displaying the solutions to the printed problems
        hide: Option<Vec<String>>,
        #[clap(
            long,
            conflicts_with = "hide",
            multiple_values = true,
            default_missing_value = ""
        )]
        /// Shows ONLY the following keys in the terminal output
        ///
        /// Usage:
        ///
        /// mapm view alexander-balls --show problem author
        ///
        /// will only output the problem text and author in the terminal output
        show: Option<Vec<String>>,
    },
    /// Prints the key-val pairs associated with each problem that passes the filters
    ///
    /// Use the --hide and --show flags to filter out specific key-val pairs (they are mutually exclusive)
    Find {
        filters: Vec<String>,
        #[clap(long, multiple_values = true, default_missing_value = "")]
        /// Hides the following keys in the terminal output
        ///
        /// Usage:
        ///
        /// mapm find --hide solutions
        ///
        /// will prevent the terminal from displaying the solutions to the printed problems
        hide: Option<Vec<String>>,
        #[clap(
            long,
            conflicts_with = "hide",
            multiple_values = true,
            default_missing_value = ""
        )]
        /// Shows ONLY the following keys in the terminal output
        ///
        /// Usage:
        ///
        /// mapm find --show problem author
        ///
        /// will only output the problem text and author in the terminal output
        show: Option<Vec<String>>,
    },
    /// Generates a preview PDF for the problems passed in
    ///
    /// The preview command uses the "preview" template. Visit
    ///
    /// https://mapm.mathadvance.org/contests/template/
    ///
    /// for general instructions on creating a template, and
    ///
    /// https://mapm.mathadvance.org/preview/
    ///
    /// for specific considerations for the "preview" template.
    ///
    /// The --hide and --show flags cannot be used together
    Preview {
        problems: Vec<String>,
        #[clap(long, multiple_values = true, default_missing_value = "")]
        /// Hides the following keys in the generated PDF
        ///
        /// Usage:
        ///
        /// mapm preview <PROBLEMS...> --hide solutions
        ///
        /// will prevent the PDF from displaying the solutions to the printed problems
        hide: Option<Vec<String>>,
        #[clap(
            long,
            conflicts_with = "hide",
            multiple_values = true,
            default_missing_value = ""
        )]
        /// Shows ONLY the following keys in the generated PDF
        ///
        /// Usage:
        ///
        /// mapm preview <PROBLEMS...> --show problem author
        ///
        /// will only output the problem text and author in the generated PDF
        show: Option<Vec<String>>,
    },
    /// Generates a PDF with every problem
    ///
    /// The preview-all command uses the "preview-all" template. Visit
    ///
    /// https://mapm.mathadvance.org/contests/template/
    ///
    /// for general instructions on creating a template, and
    ///
    /// https://mapm.mathadvane.org/preview-all/
    ///
    /// The --hide and --show flags cannot be used together
    ///
    /// Filters can be used in tandem with the --hide and --show flags
    PreviewAll {
        /// Filters for problems which do or do not have a certain key or key-value pair
        ///
        /// Usage:
        ///
        /// mapm preview-all difficulty=5 subject !solutions
        ///
        /// will output every problem with a difficulty of 5, the subject key set, and no solutions onto the compiled PDF.
        filters: Vec<String>,
        #[clap(long, multiple_values = true, default_missing_value = "")]
        /// Hides the following keys in the generated PDF
        ///
        /// Usage:
        ///
        /// mapm preview-all --hide solutions
        ///
        /// will prevent the PDF from displaying the solutions to the printed problems
        hide: Option<Vec<String>>,
        #[clap(
            long,
            conflicts_with = "hide",
            multiple_values = true,
            default_missing_value = ""
        )]
        /// Shows ONLY the following keys in the generated PDF
        ///
        /// Usage:
        ///
        /// mapm preview-all --show problem author
        ///
        /// will only output the problem text and author in the generated PDF
        show: Option<Vec<String>>,
    },
    /// Edits problems
    ///
    /// Do not append the .yml extension to the names of the problems you want to edit
    Edit { problems: Vec<String> },
    /// Builds contests from yml files passed in
    Build { contests: Vec<String> },
    /// Manipulate the mapm profile
    Profile {
        #[clap(subcommand)]
        subcommand: ProfileSubcommand,
    },
    /// Renames a problem
    Rename { target: String, destination: String },
    /// Deletes a problem
    Delete {
        /// Names of problems to delete
        names: Vec<String>,
    },
}

#[derive(Parser, Debug)]
enum ProfileSubcommand {
    Get,
    Set {
        /// If no profile is passed in, an interactive prompt will be presented
        profile: Option<String>,
    },
    List,
}

pub fn parse_args() {
    let args = Cli::parse();
    // We handle subcommand before looking for setup prompt. Rationale:
    // If no profile is found, only force interactive prompt if the command wasn't going to set the profile anyway
    match args.subcommand {
        Command::Profile { subcommand } => match subcommand {
            ProfileSubcommand::Get => {
                println!("{}", utils::profile::get_profile().expect("No profile set"));
            }
            ProfileSubcommand::Set { profile } => match profile {
                Some(string) => utils::profile::setup(&string),
                None => utils::profile::setup_prompt(),
            },
            ProfileSubcommand::List => {
                for path in fs::read_dir(master_problem_dir()).unwrap() {
                    println!(
                        "{}",
                        path.unwrap().path().file_name().unwrap().to_str().unwrap()
                    );
                }
            }
        },
        _ => {
            let problem_dir = &match args.problem_dir {
                Some(dir) => PathBuf::from(&dir),
                None => match utils::profile::get_profile() {
                    Some(dir) => master_problem_dir().join(dir),
                    None => {
                        utils::profile::setup_prompt();
                        quit::with_code(exitcode::OK);
                    }
                },
            };
            match args.subcommand {
                Command::View {
                    problems,
                    hide,
                    show,
                } => crate::commands::view::view(problem_dir, problems, hide, show),
                Command::Find {
                    filters,
                    hide,
                    show,
                } => crate::commands::find::find(problem_dir, filters, hide, show),
                Command::Build { contests } => crate::commands::build::build(problem_dir, contests),
                Command::Preview {
                    problems,
                    hide,
                    show,
                } => crate::commands::build::preview(problem_dir, problems, hide, show),
                Command::PreviewAll {
                    filters,
                    hide,
                    show,
                } => crate::commands::build::preview_all(problem_dir, filters, hide, show),
                Command::Edit { problems } => crate::commands::edit::edit(problem_dir, problems),
                Command::Rename {
                    target,
                    destination,
                } => crate::commands::rename::rename(problem_dir, target, destination),
                Command::Delete { names } => crate::commands::delete::delete(problem_dir, names),
                // This will never happen, it's just so the Profile case is covered by the compiler
                _ => {}
            }
        }
    }
}