Add -y flag

Refactor using AppContext (needs more work)
This commit is contained in:
marceline-cramer 2021-11-30 12:31:47 -07:00
parent 9a91ac5510
commit 1fc350d269
1 changed files with 42 additions and 30 deletions

View File

@ -8,7 +8,7 @@ use std::path::PathBuf;
use structopt::StructOpt; use structopt::StructOpt;
// TODO use ColoredHelp by default? // TODO use ColoredHelp by default?
#[derive(StructOpt, Debug)] #[derive(StructOpt, Clone, Debug)]
enum Command { enum Command {
/// Adds a mod to the current instance /// Adds a mod to the current instance
#[structopt(setting = structopt::clap::AppSettings::ColoredHelp)] #[structopt(setting = structopt::clap::AppSettings::ColoredHelp)]
@ -24,7 +24,7 @@ enum Command {
Clean, Clean,
} }
#[derive(StructOpt, Debug)] #[derive(StructOpt, Clone, Debug)]
#[structopt(name = "hopper", setting = structopt::clap::AppSettings::ColoredHelp)] #[structopt(name = "hopper", setting = structopt::clap::AppSettings::ColoredHelp)]
struct Args { struct Args {
/// Path to configuration file /// Path to configuration file
@ -35,6 +35,10 @@ struct Args {
#[structopt(short, long, parse(from_os_str))] #[structopt(short, long, parse(from_os_str))]
lockfile: Option<PathBuf>, lockfile: Option<PathBuf>,
/// Auto-accept confirmation dialogues
#[structopt(short = "y", long = "yes")]
auto_accept: bool,
#[structopt(subcommand)] #[structopt(subcommand)]
command: Command, command: Command,
} }
@ -86,6 +90,11 @@ struct Config {
upstream: Upstream, upstream: Upstream,
} }
struct AppContext {
pub args: Args,
pub config: Config,
}
#[derive(Deserialize, Debug)] #[derive(Deserialize, Debug)]
struct SearchResponse { struct SearchResponse {
hits: Vec<ModResult>, hits: Vec<ModResult>,
@ -195,9 +204,9 @@ struct ModVersionFile {
filename: String, filename: String,
} }
async fn search_mods(config: &Config, query: String) -> anyhow::Result<SearchResponse> { async fn search_mods(ctx: &AppContext, query: String) -> anyhow::Result<SearchResponse> {
let client = reqwest::Client::new(); let client = reqwest::Client::new();
let url = format!("https://{}/api/v1/mod", config.upstream.server_address); let url = format!("https://{}/api/v1/mod", ctx.config.upstream.server_address);
let params = [("query", query.as_str())]; let params = [("query", query.as_str())];
let url = reqwest::Url::parse_with_params(url.as_str(), &params)?; let url = reqwest::Url::parse_with_params(url.as_str(), &params)?;
let response = client let response = client
@ -210,9 +219,9 @@ async fn search_mods(config: &Config, query: String) -> anyhow::Result<SearchRes
} }
// TODO config flag to reverse search results order // TODO config flag to reverse search results order
fn display_search_results(config: &Config, response: &SearchResponse) { fn display_search_results(ctx: &AppContext, response: &SearchResponse) {
let iter = response.hits.iter().enumerate(); let iter = response.hits.iter().enumerate();
if config.options.reverse_search { if ctx.config.options.reverse_search {
for (i, result) in iter.rev() { for (i, result) in iter.rev() {
result.display(i + 1); result.display(i + 1);
} }
@ -225,7 +234,7 @@ fn display_search_results(config: &Config, response: &SearchResponse) {
// TODO implement enum for more graceful exiting // TODO implement enum for more graceful exiting
async fn select_from_results<'a>( async fn select_from_results<'a>(
_config: &Config, _ctx: &AppContext,
response: &'a SearchResponse, response: &'a SearchResponse,
) -> anyhow::Result<Vec<&'a ModResult>> { ) -> anyhow::Result<Vec<&'a ModResult>> {
let input: String = dialoguer::Input::new() let input: String = dialoguer::Input::new()
@ -255,44 +264,46 @@ async fn select_from_results<'a>(
Ok(selected.iter().map(|i| &response.hits[*i]).collect()) Ok(selected.iter().map(|i| &response.hits[*i]).collect())
} }
async fn fetch_mod_info(config: &Config, mod_result: &ModResult) -> anyhow::Result<ModInfo> { async fn fetch_mod_info(ctx: &AppContext, mod_result: &ModResult) -> anyhow::Result<ModInfo> {
let client = reqwest::Client::new(); let client = reqwest::Client::new();
let mod_id = &mod_result.mod_id; let mod_id = &mod_result.mod_id;
let mod_id = mod_id[6..].to_owned(); // Remove "local-" prefix let mod_id = mod_id[6..].to_owned(); // Remove "local-" prefix
let url = format!( let url = format!(
"https://{}/api/v1/mod/{}", "https://{}/api/v1/mod/{}",
config.upstream.server_address, mod_id ctx.config.upstream.server_address, mod_id
); );
let response = client.get(url).send().await?; let response = client.get(url).send().await?;
let response = response.json::<ModInfo>().await?; let response = response.json::<ModInfo>().await?;
Ok(response) Ok(response)
} }
async fn fetch_mod_version(config: &Config, version_id: &String) -> anyhow::Result<ModVersion> { async fn fetch_mod_version(ctx: &AppContext, version_id: &String) -> anyhow::Result<ModVersion> {
let client = reqwest::Client::new(); let client = reqwest::Client::new();
let url = format!( let url = format!(
"https://{}/api/v1/version/{}", "https://{}/api/v1/version/{}",
config.upstream.server_address, version_id ctx.config.upstream.server_address, version_id
); );
let response = client.get(url).send().await?; let response = client.get(url).send().await?;
let response = response.json::<ModVersion>().await?; let response = response.json::<ModVersion>().await?;
Ok(response) Ok(response)
} }
async fn download_version_file(_config: &Config, file: &ModVersionFile) -> anyhow::Result<()> { async fn download_version_file(ctx: &AppContext, file: &ModVersionFile) -> anyhow::Result<()> {
// TODO replace all uses of .unwrap() with proper error codes // TODO replace all uses of .unwrap() with proper error codes
let filename = &file.filename; let filename = &file.filename;
// TODO make confirmation skippable with flag argument // TODO make confirmation skippable with flag argument
use dialoguer::Confirm; if !ctx.args.auto_accept {
let prompt = format!("Download to {}?", filename); use dialoguer::Confirm;
let confirm = Confirm::new() let prompt = format!("Download to {}?", filename);
.with_prompt(prompt) let confirm = Confirm::new()
.default(true) .with_prompt(prompt)
.interact()?; .default(true)
if !confirm { .interact()?;
println!("Skipping downloading {}...", filename); if !confirm {
return Ok(()); println!("Skipping downloading {}...", filename);
return Ok(());
}
} }
// TODO check hashes while streaming // TODO check hashes while streaming
@ -332,8 +343,8 @@ async fn download_version_file(_config: &Config, file: &ModVersionFile) -> anyho
Ok(()) Ok(())
} }
async fn cmd_get(config: &Config, package_name: String) -> anyhow::Result<()> { async fn cmd_get(ctx: &AppContext, package_name: String) -> anyhow::Result<()> {
let response = search_mods(config, package_name).await?; let response = search_mods(ctx, package_name).await?;
if response.hits.is_empty() { if response.hits.is_empty() {
// TODO formatting // TODO formatting
@ -341,8 +352,8 @@ async fn cmd_get(config: &Config, package_name: String) -> anyhow::Result<()> {
return Ok(()); return Ok(());
} }
display_search_results(config, &response); display_search_results(ctx, &response);
let selected = select_from_results(config, &response).await?; let selected = select_from_results(ctx, &response).await?;
if selected.is_empty() { if selected.is_empty() {
// TODO formatting // TODO formatting
@ -351,18 +362,18 @@ async fn cmd_get(config: &Config, package_name: String) -> anyhow::Result<()> {
} }
for to_get in selected.iter() { for to_get in selected.iter() {
let mod_info = fetch_mod_info(config, to_get).await?; let mod_info = fetch_mod_info(ctx, to_get).await?;
println!("mod: {:#?}", mod_info); println!("mod: {:#?}", mod_info);
// TODO allow the user to select multiple versions // TODO allow the user to select multiple versions
if let Some(version_id) = mod_info.versions.first() { if let Some(version_id) = mod_info.versions.first() {
println!("fetching version {}", version_id); println!("fetching version {}", version_id);
let version = fetch_mod_version(config, version_id).await?; let version = fetch_mod_version(ctx, version_id).await?;
println!("version: {:#?}", version); println!("version: {:#?}", version);
for file in version.files.iter() { for file in version.files.iter() {
download_version_file(config, file).await?; download_version_file(ctx, file).await?;
} }
} }
} }
@ -374,8 +385,9 @@ async fn cmd_get(config: &Config, package_name: String) -> anyhow::Result<()> {
async fn main() -> anyhow::Result<()> { async fn main() -> anyhow::Result<()> {
let args = Args::from_args(); let args = Args::from_args();
let config = args.load_config()?; let config = args.load_config()?;
match args.command { let ctx = AppContext { args, config };
Command::Get { package_name } => cmd_get(&config, package_name).await, match ctx.args.to_owned().command {
Command::Get { package_name } => cmd_get(&ctx, package_name).await,
_ => unimplemented!("unimplemented subcommand"), _ => unimplemented!("unimplemented subcommand"),
} }
} }