parent
9a91ac5510
commit
1fc350d269
72
src/main.rs
72
src/main.rs
|
@ -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(), ¶ms)?;
|
let url = reqwest::Url::parse_with_params(url.as_str(), ¶ms)?;
|
||||||
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"),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue