Implement init command

Which comes with basic hopfile support. I went back and forth a lot on
the best layout but eventually stuck mostly to how it was described in
the README. There are still a lot of questions surrounding the best
format for this file and what info it should and should not store.

For example: should currently installed versions be stored here? what
about project IDs rather than names? Regarding sources, I settled on a
simple string format of "provider:modname" where provider can currenly
only be "modrinth" with the expectation that other recognised sources
may exist in future.

Regarding the init command itself, I decided to avoid updating any
existing file since that seemed like a predicatable and non-destructive
implementation. I also briefly considered dropping the output location
and having the file be written to stdout so that it could written by the
shell, but stuck with the README once again.
This commit is contained in:
[ ] 2022-12-25 19:26:21 +00:00
parent 5c05436d02
commit c12be7b126
5 changed files with 271 additions and 42 deletions

150
Cargo.lock generated
View File

@ -64,12 +64,6 @@ version = "1.0.72"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "22a9137b95ea06864e018375b72adfb7db6e6f68cfc8df5a04d00288050485ee"
[[package]]
name = "cfg-if"
version = "0.1.10"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4785bdd1c96b2a846b2bd7cc02e86b6b3dbf14e7e53446c4f54c92a361040822"
[[package]]
name = "cfg-if"
version = "1.0.0"
@ -117,12 +111,13 @@ dependencies = [
[[package]]
name = "confy"
version = "0.4.0"
version = "0.5.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2913470204e9e8498a0f31f17f90a0de801ae92c8c5ac18c49af4819e6786697"
checksum = "e37668cb35145dcfaa1931a5f37fde375eeae8068b4c0d2f289da28a270b2d2c"
dependencies = [
"directories",
"serde",
"thiserror",
"toml",
]
@ -171,11 +166,10 @@ dependencies = [
[[package]]
name = "directories"
version = "2.0.2"
version = "4.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "551a778172a450d7fc12e629ca3b0428d00f6afa9a43da1b630d54604e97371c"
checksum = "f51c5d4ddabd36886dd3e1438cb358cdcb0d7c499cb99cb4ac2e38e18b5cb210"
dependencies = [
"cfg-if 0.1.10",
"dirs-sys",
]
@ -190,6 +184,12 @@ dependencies = [
"winapi",
]
[[package]]
name = "either"
version = "1.8.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "90e5c1c8368803113bf0c9584fc495a58b86dc8a29edbf8fe877d21d9507e797"
[[package]]
name = "encode_unicode"
version = "0.3.6"
@ -202,7 +202,7 @@ version = "0.8.29"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a74ea89a0a1b98f6332de42c95baff457ada66d1cb4030f9ff151b2041a1c746"
dependencies = [
"cfg-if 1.0.0",
"cfg-if",
]
[[package]]
@ -307,7 +307,7 @@ version = "0.2.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7fcd999463524c52659517fe2cea98493cfe485d10565e7b0fb07dbba7ad2753"
dependencies = [
"cfg-if 1.0.0",
"cfg-if",
"libc",
"wasi",
]
@ -333,9 +333,9 @@ dependencies = [
[[package]]
name = "hashbrown"
version = "0.11.2"
version = "0.12.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ab5ef0d4909ef3724cc8cce6ccc8572c5c817592e9285f5464f8e86f8bd3726e"
checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888"
[[package]]
name = "heck"
@ -369,6 +369,7 @@ dependencies = [
"serde",
"serde_json",
"tokio",
"toml_edit",
]
[[package]]
@ -461,9 +462,9 @@ dependencies = [
[[package]]
name = "indexmap"
version = "1.7.0"
version = "1.9.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bc633605454125dec4b66843673f01c7df2b89479b32e0ed634e43a91cff62a5"
checksum = "1885e79c1fc4b10f0e172c475f458b7f7b93061064d98c3293e98c5ba0c8b399"
dependencies = [
"autocfg",
"hashbrown",
@ -487,7 +488,7 @@ version = "0.1.12"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7a5bbe824c507c5da5956355e86a746d82e0e1464f65d862cc5e71da70e94b2c"
dependencies = [
"cfg-if 1.0.0",
"cfg-if",
]
[[package]]
@ -496,6 +497,15 @@ version = "2.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "68f2d64f2edebec4ce84ad108148e67e1064789bee435edc5b60ad398714a3a9"
[[package]]
name = "itertools"
version = "0.10.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b0fd2260e829bddf4cb6ea802289de2f86d6a7a690192fbe91b3f46e0f2c8473"
dependencies = [
"either",
]
[[package]]
name = "itoa"
version = "0.4.8"
@ -538,7 +548,7 @@ version = "0.4.14"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "51b9bbe6c47d51fc3e1a9b945965946b4c44142ab8792c50835a980d362c2710"
dependencies = [
"cfg-if 1.0.0",
"cfg-if",
]
[[package]]
@ -559,6 +569,12 @@ version = "0.3.16"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2a60c7ce501c71e03a9c9c0d35b861413ae925bd979cc7a4e30d060069aaac8d"
[[package]]
name = "minimal-lexical"
version = "0.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a"
[[package]]
name = "mio"
version = "0.7.14"
@ -599,6 +615,16 @@ dependencies = [
"tempfile",
]
[[package]]
name = "nom8"
version = "0.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "75d908f0297c3526d34e478d438b07eefe3d7b0416494d7ffccb17f1c7f7262c"
dependencies = [
"memchr",
"minimal-lexical",
]
[[package]]
name = "ntapi"
version = "0.3.6"
@ -637,7 +663,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0c7ae222234c30df141154f159066c5093ff73b63204dcda7121eb082fc56a95"
dependencies = [
"bitflags",
"cfg-if 1.0.0",
"cfg-if",
"foreign-types",
"libc",
"once_cell",
@ -686,7 +712,7 @@ version = "0.8.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d76e8e1493bcac0d2766c42737f34458f1c8c50c0d23bcb24ea953affb273216"
dependencies = [
"cfg-if 1.0.0",
"cfg-if",
"instant",
"libc",
"redox_syscall",
@ -750,11 +776,11 @@ dependencies = [
[[package]]
name = "proc-macro2"
version = "1.0.32"
version = "1.0.49"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ba508cc11742c0dc5c1659771673afbab7a0efab23aa17e854cbab0837ed0b43"
checksum = "57a8eca9f9c4ffde41714334dee777596264c7825420f521abc92b5b5deb63a5"
dependencies = [
"unicode-xid",
"unicode-ident",
]
[[package]]
@ -933,18 +959,18 @@ dependencies = [
[[package]]
name = "serde"
version = "1.0.130"
version = "1.0.151"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f12d06de37cf59146fbdecab66aa99f9fe4f78722e3607577a5375d66bd0c913"
checksum = "97fed41fc1a24994d044e6db6935e69511a1153b52c15eb42493b26fa87feba0"
dependencies = [
"serde_derive",
]
[[package]]
name = "serde_derive"
version = "1.0.130"
version = "1.0.151"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d7bc1a1ab1961464eae040d96713baa5a724a8152c1222492465b54322ec508b"
checksum = "255abe9a125a985c05190d687b320c12f9b1f0b99445e608c21ba0782c719ad8"
dependencies = [
"proc-macro2",
"quote",
@ -1013,13 +1039,13 @@ checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623"
[[package]]
name = "syn"
version = "1.0.81"
version = "1.0.107"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f2afee18b8beb5a596ecb4a2dce128c719b4ba399d34126b9e4396e3f9860966"
checksum = "1f4064b5b16e03ae50984a5a8ed5d4f8803e6bc1fd170a3cda91a1be4b18e3f5"
dependencies = [
"proc-macro2",
"quote",
"unicode-xid",
"unicode-ident",
]
[[package]]
@ -1028,7 +1054,7 @@ version = "3.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "dac1c663cfc93810f88aed9b8941d48cabf856a1b111c29a40439018d870eb22"
dependencies = [
"cfg-if 1.0.0",
"cfg-if",
"libc",
"rand",
"redox_syscall",
@ -1061,6 +1087,26 @@ version = "0.15.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b1141d4d61095b28419e22cb0bbf02755f5e54e0526f97f1e3d1d160e60885fb"
[[package]]
name = "thiserror"
version = "1.0.38"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6a9cd18aa97d5c45c6603caea1da6628790b37f7a34b6ca89522331c5180fed0"
dependencies = [
"thiserror-impl",
]
[[package]]
name = "thiserror-impl"
version = "1.0.38"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1fb327af4685e4d03fa8cbcf1716380da910eeb2bb8be417e7f9fd3fb164f36f"
dependencies = [
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "tinyvec"
version = "1.5.1"
@ -1140,6 +1186,28 @@ dependencies = [
"serde",
]
[[package]]
name = "toml_datetime"
version = "0.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "808b51e57d0ef8f71115d8f3a01e7d3750d01c79cac4b3eda910f4389fdf92fd"
dependencies = [
"serde",
]
[[package]]
name = "toml_edit"
version = "0.16.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "dcd65b83c7473af53e3fd994eb2888dfddfeb28cac9a82825ec5803c233c882c"
dependencies = [
"indexmap",
"itertools",
"nom8",
"serde",
"toml_datetime",
]
[[package]]
name = "tower-service"
version = "0.3.1"
@ -1152,7 +1220,7 @@ version = "0.1.29"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "375a639232caf30edfc78e8d89b2d4c375515393e7af7e16f01cd96917fb2105"
dependencies = [
"cfg-if 1.0.0",
"cfg-if",
"pin-project-lite",
"tracing-core",
]
@ -1178,6 +1246,12 @@ version = "0.3.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1a01404663e3db436ed2746d9fefef640d868edae3cceb81c3b8d5732fda678f"
[[package]]
name = "unicode-ident"
version = "1.0.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "84a22b9f218b40614adcb3f4ff08b703773ad44fa9423e4e0d346d5db86e4ebc"
[[package]]
name = "unicode-normalization"
version = "0.1.19"
@ -1193,12 +1267,6 @@ version = "0.1.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3ed742d4ea2bd1176e236172c8429aaf54486e7ac098db29ffe6529e0ce50973"
[[package]]
name = "unicode-xid"
version = "0.2.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8ccb82d61f80a663efe1f787a51b16b5a51e3314d6ac365b08639f52387b33f3"
[[package]]
name = "url"
version = "2.2.2"
@ -1245,7 +1313,7 @@ version = "0.2.78"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "632f73e236b219150ea279196e54e610f5dbafa5d61786303d4da54f84e47fce"
dependencies = [
"cfg-if 1.0.0",
"cfg-if",
"wasm-bindgen-macro",
]
@ -1270,7 +1338,7 @@ version = "0.4.28"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8e8d7523cb1f2a4c96c1317ca690031b714a51cc14e05f712446691f413f5d39"
dependencies = [
"cfg-if 1.0.0",
"cfg-if",
"js-sys",
"wasm-bindgen",
"web-sys",

View File

@ -18,3 +18,4 @@ serde = { version = "1", features = ["derive"] }
serde_json = "1"
clap = { version = "3.2.20", features = ["derive"] }
tokio = { version = "1", features = ["full"] }
toml_edit = { version = "0.16.0", features = ["easy"] }

View File

@ -16,6 +16,21 @@ pub struct SearchArgs {
pub version: Option<Vec<String>>,
}
#[derive(clap::Args, Clone, Debug)]
pub struct HopfileArgs {
/// The directory in which the hopfile is being created
#[clap(short, long)]
pub dir: Option<PathBuf>,
/// The template file to base the hopfile on
#[clap(short, long)]
pub template: Option<String>,
/// The version of Minecraft packages are being managed
#[clap(short, long)]
pub version: Option<String>,
}
// TODO use ColoredHelp by default?
#[derive(Subcommand, Clone, Debug)]
pub enum Command {
@ -27,6 +42,7 @@ pub enum Command {
},
Get(SearchArgs),
Update,
Init(HopfileArgs),
Clean,
}

119
src/hopfile.rs Normal file
View File

@ -0,0 +1,119 @@
use std::str::FromStr;
use serde::{Deserialize, Serialize, de::Visitor};
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "kebab-case")]
pub struct Hopfile {
pub template: Option<String>,
// TODO: possibly parse this into a more specific format (enum maybe?)
pub mc_version: String,
pub packages: Packages,
}
impl Hopfile {
pub fn new(template: Option<String>, version: Option<String>) -> Self {
Self {
template,
mc_version: version.unwrap_or_else(|| String::from("1.19.1")),
packages: Packages::default(),
}
}
}
#[derive(Debug, Clone, Serialize, Deserialize, Default)]
pub struct Packages {
pub mods: Vec<Resource>,
pub resources: Vec<Resource>,
}
#[derive(Debug, Clone, Copy, Default)]
enum Provider {
#[default]
Modrinth,
}
impl FromStr for Provider {
type Err = ();
fn from_str(s: &str) -> Result<Self, Self::Err> {
match s {
"modrinth" => Ok(Self::Modrinth),
_ => Err(()),
}
}
}
impl ToString for Provider {
fn to_string(&self) -> String {
String::from(match self {
Self::Modrinth => "modrinth",
})
}
}
#[derive(Debug, Clone)]
pub struct Resource {
provider: Provider,
name: String,
}
impl FromStr for Resource {
type Err = ();
fn from_str(s: &str) -> Result<Self, Self::Err> {
if let Some((provider, name)) = s.split_once(':') {
Ok(Resource {
provider: Provider::from_str(provider)?,
name: name.to_string(),
})
} else if !s.is_empty() {
Ok(Resource {
provider: Provider::default(),
name: s.to_string(),
})
} else {
Err(())
}
}
}
impl ToString for Resource {
fn to_string(&self) -> String {
[self.provider.to_string().as_str(), self.name.as_str()].join(":")
}
}
impl Serialize for Resource {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where S: serde::Serializer {
serializer.serialize_str(&self.to_string())
}
}
struct V;
impl<'de> Visitor<'de> for V {
type Value = Resource;
fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result {
write!(formatter, "a string")
}
fn visit_str<E>(self, v: &str) -> Result<Self::Value, E>
where E: serde::de::Error, {
Resource::from_str(v).map_err(|_| serde::de::Error::custom(
format!("Failed to parse mod/resource: '{}'", v)
))
}
}
impl<'de> Deserialize<'de> for Resource {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where D: serde::Deserializer<'de> {
deserializer.deserialize_str(V)
}
}

View File

@ -1,11 +1,16 @@
mod api;
mod client;
mod config;
mod hopfile;
use clap::Parser;
use tokio::fs::File;
use tokio::io::AsyncWriteExt;
use api::*;
use clap::Parser;
use client::*;
use config::*;
use hopfile::*;
fn display_search_results(ctx: &AppContext, response: &SearchResponse) {
let iter = response.hits.iter().enumerate();
@ -108,6 +113,25 @@ async fn cmd_get(ctx: &AppContext, search_args: SearchArgs) -> anyhow::Result<()
Ok(())
}
async fn cmd_init(args: HopfileArgs) -> anyhow::Result<()> {
let mut path = args.dir.unwrap_or_default();
path.push("info.hop");
if path.try_exists().expect("Invalid dir") {
let message = format!("hopfile already exists: {}", path.to_str().unwrap());
Err(anyhow::Error::msg(message))
} else {
let mut file = File::create(&path).await?;
let doc = Hopfile::new(args.template, args.version);
let output = toml_edit::easy::to_string_pretty(&doc).unwrap();
file.write_all(output.as_bytes()).await?;
println!("Saved {}", path.to_str().unwrap());
Ok(())
}
}
#[tokio::main]
async fn main() -> anyhow::Result<()> {
env_logger::init();
@ -116,6 +140,7 @@ async fn main() -> anyhow::Result<()> {
let ctx = AppContext { args, config };
match ctx.args.to_owned().command {
Command::Get(search_args) => cmd_get(&ctx, search_args).await,
Command::Init(hopfile_args) => cmd_init(hopfile_args).await,
_ => unimplemented!("unimplemented subcommand"),
}
}