Split up source into several files
This commit is contained in:
parent
30e8c782df
commit
b4ab1bf1d4
|
@ -0,0 +1,118 @@
|
|||
use console::style;
|
||||
use serde::Deserialize;
|
||||
use std::collections::HashMap;
|
||||
|
||||
#[derive(Deserialize, Debug)]
|
||||
pub struct SearchResponse {
|
||||
pub hits: Vec<ModResult>,
|
||||
pub offset: isize,
|
||||
pub limit: isize,
|
||||
pub total_hits: isize,
|
||||
}
|
||||
|
||||
#[derive(Deserialize, Debug)]
|
||||
pub struct ModResult {
|
||||
pub mod_id: String, // TODO parse to `local-xxxxx` with regex
|
||||
pub project_type: Option<String>, // NOTE this isn't in all search results?
|
||||
pub author: String,
|
||||
pub title: String,
|
||||
pub description: String,
|
||||
pub categories: Vec<String>,
|
||||
pub versions: Vec<String>,
|
||||
pub downloads: isize,
|
||||
pub page_url: String,
|
||||
pub icon_url: String,
|
||||
pub author_url: String,
|
||||
pub date_created: String,
|
||||
pub date_modified: String,
|
||||
pub latest_version: String,
|
||||
pub license: String,
|
||||
pub client_side: String,
|
||||
pub server_side: String,
|
||||
pub host: String,
|
||||
}
|
||||
|
||||
impl ModResult {
|
||||
pub fn format_info(&self) -> String {
|
||||
let title = style(self.title.clone()).bold();
|
||||
let downloads = style(self.downloads.clone()).bold().green();
|
||||
if let Some(latest_release) = self.versions.last() {
|
||||
// TODO fetch version numbers to display
|
||||
let latest_release = style(latest_release).bold().blue();
|
||||
format!("{} [{}] ({} downloads)", title, latest_release, downloads)
|
||||
} else {
|
||||
format!("{} [no releases]", title)
|
||||
}
|
||||
}
|
||||
|
||||
pub fn format_description(&self) -> String {
|
||||
self.description.to_owned()
|
||||
}
|
||||
|
||||
pub fn display(&self, index: usize) {
|
||||
let index = style(index).magenta();
|
||||
let info = self.format_info();
|
||||
let description = self.format_description();
|
||||
println!("{:>2} {}\n {}", index, info, description);
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Deserialize, Debug)]
|
||||
pub struct ModInfo {
|
||||
pub id: String, // TODO serialize mod id?
|
||||
pub slug: String,
|
||||
pub team: String, // TODO serialize team id?
|
||||
pub title: String,
|
||||
pub description: String,
|
||||
pub body: String,
|
||||
pub published: String, // TODO serialize datetime
|
||||
pub updated: String, // TODO serialize datetime
|
||||
pub status: String,
|
||||
pub license: License,
|
||||
pub client_side: String, // TODO serialize as enum
|
||||
pub server_side: String, // TODO serialize as enum
|
||||
pub downloads: isize,
|
||||
pub followers: isize,
|
||||
pub categories: Vec<String>,
|
||||
pub versions: Vec<String>,
|
||||
pub icon_url: Option<String>,
|
||||
pub issues_url: Option<String>,
|
||||
pub source_url: Option<String>,
|
||||
pub wiki_url: Option<String>,
|
||||
pub discord_url: Option<String>,
|
||||
pub donation_urls: Vec<String>,
|
||||
}
|
||||
|
||||
#[derive(Deserialize, Debug)]
|
||||
pub struct License {
|
||||
pub id: String,
|
||||
pub name: String,
|
||||
pub url: String,
|
||||
}
|
||||
|
||||
#[derive(Deserialize, Debug)]
|
||||
pub struct ModVersion {
|
||||
pub id: String, // version id
|
||||
pub mod_id: String, // mod id
|
||||
pub author_id: String, // user id
|
||||
// NOTE modrinth docs list this as a String, but is actually a bool?
|
||||
// featured: String, // user id
|
||||
pub name: String,
|
||||
pub version_number: String,
|
||||
pub changelog: Option<String>,
|
||||
pub changelog_url: Option<String>,
|
||||
pub date_published: String, // TODO serialize datetime
|
||||
pub downloads: isize,
|
||||
pub version_type: String, // TODO {alpha | beta | release}
|
||||
pub files: Vec<ModVersionFile>,
|
||||
pub dependencies: Vec<String>, // TODO dependency wrangling, thank you modrinth, very cool
|
||||
pub game_versions: Vec<String>,
|
||||
pub loaders: Vec<String>,
|
||||
}
|
||||
|
||||
#[derive(Deserialize, Debug)]
|
||||
pub struct ModVersionFile {
|
||||
pub hashes: HashMap<String, String>,
|
||||
pub url: String,
|
||||
pub filename: String,
|
||||
}
|
|
@ -0,0 +1,102 @@
|
|||
use serde::{Deserialize, Serialize};
|
||||
use std::path::PathBuf;
|
||||
use structopt::StructOpt;
|
||||
|
||||
// TODO parameter to restrict target Minecraft version
|
||||
#[derive(StructOpt, Clone, Debug)]
|
||||
pub struct SearchArgs {
|
||||
pub package_name: String,
|
||||
|
||||
/// Restricts the target Minecraft version
|
||||
#[structopt(short, long)]
|
||||
pub version: Option<Vec<String>>,
|
||||
}
|
||||
|
||||
// TODO use ColoredHelp by default?
|
||||
#[derive(StructOpt, Clone, Debug)]
|
||||
pub enum Command {
|
||||
/// Adds a mod to the current instance
|
||||
#[structopt(setting = structopt::clap::AppSettings::ColoredHelp)]
|
||||
Add(SearchArgs),
|
||||
/// Removes a mod
|
||||
#[structopt(setting = structopt::clap::AppSettings::ColoredHelp)]
|
||||
Remove { package_name: String },
|
||||
#[structopt(setting = structopt::clap::AppSettings::ColoredHelp)]
|
||||
Get(SearchArgs),
|
||||
#[structopt(setting = structopt::clap::AppSettings::ColoredHelp)]
|
||||
Update,
|
||||
#[structopt(setting = structopt::clap::AppSettings::ColoredHelp)]
|
||||
Clean,
|
||||
}
|
||||
|
||||
// TODO move main body argument fields to substruct for ease of moving?
|
||||
#[derive(StructOpt, Clone, Debug)]
|
||||
#[structopt(name = "hopper", setting = structopt::clap::AppSettings::ColoredHelp)]
|
||||
pub struct Args {
|
||||
/// Path to configuration file
|
||||
#[structopt(short, long, parse(from_os_str))]
|
||||
pub config: Option<PathBuf>,
|
||||
|
||||
/// Path to mod lockfile
|
||||
#[structopt(short, long, parse(from_os_str))]
|
||||
pub lockfile: Option<PathBuf>,
|
||||
|
||||
/// Auto-accept confirmation dialogues
|
||||
#[structopt(short = "y", long = "yes")]
|
||||
pub auto_accept: bool,
|
||||
|
||||
#[structopt(subcommand)]
|
||||
pub command: Command,
|
||||
}
|
||||
|
||||
impl Args {
|
||||
pub fn load_config(&self) -> Result<Config, confy::ConfyError> {
|
||||
if let Some(config_path) = &self.config {
|
||||
confy::load_path(config_path)
|
||||
} else {
|
||||
confy::load("hopper")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Deserialize, Serialize, Debug)]
|
||||
pub struct Upstream {
|
||||
/// Modrinth main server address
|
||||
pub server_address: String,
|
||||
}
|
||||
|
||||
impl Default for Upstream {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
server_address: "api.modrinth.com".into(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Deserialize, Serialize, Debug)]
|
||||
pub struct Options {
|
||||
/// Whether to reverse search results
|
||||
pub reverse_search: bool,
|
||||
}
|
||||
|
||||
impl Default for Options {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
reverse_search: true,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Deserialize, Serialize, Debug, Default)]
|
||||
pub struct Config {
|
||||
/// General settings
|
||||
pub options: Options,
|
||||
|
||||
/// Configuration for the upstream Modrinth server
|
||||
pub upstream: Upstream,
|
||||
}
|
||||
|
||||
pub struct AppContext {
|
||||
pub args: Args,
|
||||
pub config: Config,
|
||||
}
|
222
src/main.rs
222
src/main.rs
|
@ -1,226 +1,14 @@
|
|||
use console::style;
|
||||
use log::*;
|
||||
use futures_util::StreamExt;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use log::*;
|
||||
use std::cmp::min;
|
||||
use std::collections::HashMap;
|
||||
use std::io::Write;
|
||||
use std::path::PathBuf;
|
||||
use structopt::StructOpt;
|
||||
|
||||
// TODO parameter to restrict target Minecraft version
|
||||
#[derive(StructOpt, Clone, Debug)]
|
||||
struct SearchArgs {
|
||||
package_name: String,
|
||||
mod api;
|
||||
mod config;
|
||||
|
||||
/// Restricts the target Minecraft version
|
||||
#[structopt(short, long)]
|
||||
version: Option<Vec<String>>,
|
||||
}
|
||||
|
||||
// TODO use ColoredHelp by default?
|
||||
#[derive(StructOpt, Clone, Debug)]
|
||||
enum Command {
|
||||
/// Adds a mod to the current instance
|
||||
#[structopt(setting = structopt::clap::AppSettings::ColoredHelp)]
|
||||
Add(SearchArgs),
|
||||
/// Removes a mod
|
||||
#[structopt(setting = structopt::clap::AppSettings::ColoredHelp)]
|
||||
Remove { package_name: String },
|
||||
#[structopt(setting = structopt::clap::AppSettings::ColoredHelp)]
|
||||
Get(SearchArgs),
|
||||
#[structopt(setting = structopt::clap::AppSettings::ColoredHelp)]
|
||||
Update,
|
||||
#[structopt(setting = structopt::clap::AppSettings::ColoredHelp)]
|
||||
Clean,
|
||||
}
|
||||
|
||||
// TODO move main body argument fields to substruct for ease of moving?
|
||||
#[derive(StructOpt, Clone, Debug)]
|
||||
#[structopt(name = "hopper", setting = structopt::clap::AppSettings::ColoredHelp)]
|
||||
struct Args {
|
||||
/// Path to configuration file
|
||||
#[structopt(short, long, parse(from_os_str))]
|
||||
config: Option<PathBuf>,
|
||||
|
||||
/// Path to mod lockfile
|
||||
#[structopt(short, long, parse(from_os_str))]
|
||||
lockfile: Option<PathBuf>,
|
||||
|
||||
/// Auto-accept confirmation dialogues
|
||||
#[structopt(short = "y", long = "yes")]
|
||||
auto_accept: bool,
|
||||
|
||||
#[structopt(subcommand)]
|
||||
command: Command,
|
||||
}
|
||||
|
||||
impl Args {
|
||||
fn load_config(&self) -> Result<Config, confy::ConfyError> {
|
||||
if let Some(config_path) = &self.config {
|
||||
confy::load_path(config_path)
|
||||
} else {
|
||||
confy::load("hopper")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Deserialize, Serialize, Debug)]
|
||||
struct Upstream {
|
||||
/// Modrinth main server address
|
||||
server_address: String,
|
||||
}
|
||||
|
||||
impl Default for Upstream {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
server_address: "api.modrinth.com".into(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Deserialize, Serialize, Debug)]
|
||||
struct Options {
|
||||
/// Whether to reverse search results
|
||||
reverse_search: bool,
|
||||
}
|
||||
|
||||
impl Default for Options {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
reverse_search: true,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Deserialize, Serialize, Debug, Default)]
|
||||
struct Config {
|
||||
/// General settings
|
||||
options: Options,
|
||||
|
||||
/// Configuration for the upstream Modrinth server
|
||||
upstream: Upstream,
|
||||
}
|
||||
|
||||
struct AppContext {
|
||||
pub args: Args,
|
||||
pub config: Config,
|
||||
}
|
||||
|
||||
#[derive(Deserialize, Debug)]
|
||||
struct SearchResponse {
|
||||
hits: Vec<ModResult>,
|
||||
offset: isize,
|
||||
limit: isize,
|
||||
total_hits: isize,
|
||||
}
|
||||
|
||||
#[derive(Deserialize, Debug)]
|
||||
struct ModResult {
|
||||
mod_id: String, // TODO parse to `local-xxxxx` with regex
|
||||
project_type: Option<String>, // NOTE this isn't in all search results?
|
||||
author: String,
|
||||
title: String,
|
||||
description: String,
|
||||
categories: Vec<String>,
|
||||
versions: Vec<String>,
|
||||
downloads: isize,
|
||||
page_url: String,
|
||||
icon_url: String,
|
||||
author_url: String,
|
||||
date_created: String,
|
||||
date_modified: String,
|
||||
latest_version: String,
|
||||
license: String,
|
||||
client_side: String,
|
||||
server_side: String,
|
||||
host: String,
|
||||
}
|
||||
|
||||
impl ModResult {
|
||||
fn format_info(&self) -> String {
|
||||
let title = style(self.title.clone()).bold();
|
||||
let downloads = style(self.downloads.clone()).bold().green();
|
||||
if let Some(latest_release) = self.versions.last() {
|
||||
// TODO fetch version numbers to display
|
||||
let latest_release = style(latest_release).bold().blue();
|
||||
format!("{} [{}] ({} downloads)", title, latest_release, downloads)
|
||||
} else {
|
||||
format!("{} [no releases]", title)
|
||||
}
|
||||
}
|
||||
|
||||
fn format_description(&self) -> String {
|
||||
self.description.to_owned()
|
||||
}
|
||||
|
||||
fn display(&self, index: usize) {
|
||||
let index = style(index).magenta();
|
||||
let info = self.format_info();
|
||||
let description = self.format_description();
|
||||
println!("{:>2} {}\n {}", index, info, description);
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Deserialize, Debug)]
|
||||
struct ModInfo {
|
||||
id: String, // TODO serialize mod id?
|
||||
slug: String,
|
||||
team: String, // TODO serialize team id?
|
||||
title: String,
|
||||
description: String,
|
||||
body: String,
|
||||
published: String, // TODO serialize datetime
|
||||
updated: String, // TODO serialize datetime
|
||||
status: String,
|
||||
license: License,
|
||||
client_side: String, // TODO serialize as enum
|
||||
server_side: String, // TODO serialize as enum
|
||||
downloads: isize,
|
||||
followers: isize,
|
||||
categories: Vec<String>,
|
||||
versions: Vec<String>,
|
||||
icon_url: Option<String>,
|
||||
issues_url: Option<String>,
|
||||
source_url: Option<String>,
|
||||
wiki_url: Option<String>,
|
||||
discord_url: Option<String>,
|
||||
donation_urls: Vec<String>,
|
||||
}
|
||||
|
||||
#[derive(Deserialize, Debug)]
|
||||
struct License {
|
||||
id: String,
|
||||
name: String,
|
||||
url: String,
|
||||
}
|
||||
|
||||
#[derive(Deserialize, Debug)]
|
||||
struct ModVersion {
|
||||
id: String, // version id
|
||||
mod_id: String, // mod id
|
||||
author_id: String, // user id
|
||||
// NOTE modrinth docs list this as a String, but is actually a bool?
|
||||
// featured: String, // user id
|
||||
name: String,
|
||||
version_number: String,
|
||||
changelog: Option<String>,
|
||||
changelog_url: Option<String>,
|
||||
date_published: String, // TODO serialize datetime
|
||||
downloads: isize,
|
||||
version_type: String, // TODO {alpha | beta | release}
|
||||
files: Vec<ModVersionFile>,
|
||||
dependencies: Vec<String>, // TODO dependency wrangling, thank you modrinth, very cool
|
||||
game_versions: Vec<String>,
|
||||
loaders: Vec<String>,
|
||||
}
|
||||
|
||||
#[derive(Deserialize, Debug)]
|
||||
struct ModVersionFile {
|
||||
hashes: HashMap<String, String>,
|
||||
url: String,
|
||||
filename: String,
|
||||
}
|
||||
use api::*;
|
||||
use config::*;
|
||||
|
||||
async fn search_mods(ctx: &AppContext, search_args: &SearchArgs) -> anyhow::Result<SearchResponse> {
|
||||
let client = reqwest::Client::new();
|
||||
|
|
Loading…
Reference in New Issue