Split up source into several files

This commit is contained in:
marceline-cramer 2021-12-10 21:20:26 -07:00
parent 30e8c782df
commit b4ab1bf1d4
3 changed files with 225 additions and 217 deletions

118
src/api.rs Normal file
View File

@ -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,
}

102
src/config.rs Normal file
View File

@ -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,
}

View File

@ -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();