From 6dd9c4871d2f5891aeec278c686109890a180566 Mon Sep 17 00:00:00 2001 From: emma Date: Wed, 22 Mar 2023 18:24:25 -0400 Subject: [PATCH 01/30] arg types implemented --- LICENSE => COPYING | 4 +- Cargo.lock | 1005 +++++++++++++++----------------------------- Cargo.toml | 16 +- src/api.rs | 156 ------- src/args.rs | 156 +++++++ src/client.rs | 166 -------- src/config.rs | 196 ++++----- src/main.rs | 137 ++---- 8 files changed, 609 insertions(+), 1227 deletions(-) rename LICENSE => COPYING (99%) create mode 100644 src/args.rs diff --git a/LICENSE b/COPYING similarity index 99% rename from LICENSE rename to COPYING index 0ad25db..be3f7b2 100644 --- a/LICENSE +++ b/COPYING @@ -633,8 +633,8 @@ the "copyright" line and a pointer to where the full notice is found. Copyright (C) This program is free software: you can redistribute it and/or modify - it under the terms of the GNU Affero General Public License as published - by the Free Software Foundation, either version 3 of the License, or + it under the terms of the GNU Affero General Public License as published by + the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, diff --git a/Cargo.lock b/Cargo.lock index fc39bd6..ec4fdc4 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3,29 +3,23 @@ version = 3 [[package]] -name = "aho-corasick" -version = "0.7.18" +name = "arg" +version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1e37cfd5e7657ada45f742d6e99ca5788580b5c529dc78faf11ece6dc702656f" +checksum = "568bbed0b9810cd4b43c8560ff3c5d2318b29d78fc314818d156bbed7075d394" dependencies = [ - "memchr", + "arg-derive", ] [[package]] -name = "anyhow" -version = "1.0.47" +name = "arg-derive" +version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "38d9ff5d688f1c13395289f67db01d4826b46dd694e7580accdc3e8430f2d98e" - -[[package]] -name = "atty" -version = "0.2.14" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d9b39be18770d11421cdb1b9947a45dd3f37e93092cbf377614828a319d5fee8" +checksum = "849efc162c06e51ce911bf4fe702f62a08f3b6ebbbfc5178e86c6bae449c2c60" dependencies = [ - "hermit-abi", - "libc", - "winapi", + "quote", + "syn 1.0.109", + "tabwriter", ] [[package]] @@ -35,10 +29,26 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cdb031dd78e28731d87d56cc8ffef4a8f36ca26c38fe2de700543e627f8a464a" [[package]] -name = "base64" -version = "0.13.0" +name = "bindgen" +version = "0.63.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "904dfeac50f3cdaba28fc6f57fdcddb75f49ed61346676a78c4ffe55877802fd" +checksum = "36d860121800b2a9a94f9b5604b332d5cffb234ce17609ea479d723dbc9d3885" +dependencies = [ + "bitflags", + "cexpr", + "clang-sys", + "lazy_static", + "lazycell", + "log", + "peeking_take_while", + "proc-macro2", + "quote", + "regex", + "rustc-hash", + "shlex", + "syn 1.0.109", + "which", +] [[package]] name = "bitflags" @@ -46,12 +56,6 @@ version = "1.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" -[[package]] -name = "bumpalo" -version = "3.8.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8f1e260c3a9040a7c19a12468758f4c16f31a81a1fe087482be9570ec864bb6c" - [[package]] name = "bytes" version = "1.1.0" @@ -59,16 +63,28 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c4872d67bab6358e59559027aa3b9157c53d9358c51423c17554809a8858e0f8" [[package]] -name = "cc" -version = "1.0.72" +name = "c-main" +version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "22a9137b95ea06864e018375b72adfb7db6e6f68cfc8df5a04d00288050485ee" +checksum = "797bbff8bd2bcddb7f0ee638b55398686adac15174689a86da5ffc0f51219f75" +dependencies = [ + "libc", +] [[package]] -name = "cfg-if" -version = "0.1.10" +name = "cc" +version = "1.0.79" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4785bdd1c96b2a846b2bd7cc02e86b6b3dbf14e7e53446c4f54c92a361040822" +checksum = "50d30906286121d95be3d479533b458f87493b30a4b5f79a607db8f5d11aa91f" + +[[package]] +name = "cexpr" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6fac387a98bb7c37292057cffc56d62ecb629900026402633ae9160df93a8766" +dependencies = [ + "nom", +] [[package]] name = "cfg-if" @@ -77,53 +93,14 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" [[package]] -name = "clap" -version = "3.2.20" +name = "clang-sys" +version = "1.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "23b71c3ce99b7611011217b366d923f1d0a7e07a92bb2dbf1e84508c673ca3bd" +checksum = "77ed9a53e5d4d9c573ae844bfac6872b159cb1d1585a83b29e7a64b7eef7332a" dependencies = [ - "atty", - "bitflags", - "clap_derive", - "clap_lex", - "indexmap", - "once_cell", - "strsim", - "termcolor", - "textwrap", -] - -[[package]] -name = "clap_derive" -version = "3.2.18" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ea0c8bce528c4be4da13ea6fead8965e95b6073585a2f05204bd8f4119f82a65" -dependencies = [ - "heck", - "proc-macro-error", - "proc-macro2", - "quote", - "syn", -] - -[[package]] -name = "clap_lex" -version = "0.2.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2850f2f5a82cbf437dd5af4d49848fbdfc27c157c3d010345776f952765261c5" -dependencies = [ - "os_str_bytes", -] - -[[package]] -name = "confy" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2913470204e9e8498a0f31f17f90a0de801ae92c8c5ac18c49af4819e6786697" -dependencies = [ - "directories", - "serde", - "toml", + "glob", + "libc", + "libloading", ] [[package]] @@ -142,20 +119,34 @@ dependencies = [ ] [[package]] -name = "core-foundation" -version = "0.9.2" +name = "curl" +version = "0.4.44" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6888e10551bb93e424d8df1d07f1a8b4fceb0001a3a4b048bfc47554946f47b3" +checksum = "509bd11746c7ac09ebd19f0b17782eae80aadee26237658a6b4808afb5c11a22" dependencies = [ - "core-foundation-sys", + "curl-sys", "libc", + "openssl-probe", + "openssl-sys", + "schannel", + "socket2", + "winapi", ] [[package]] -name = "core-foundation-sys" -version = "0.8.3" +name = "curl-sys" +version = "0.4.61+curl-8.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5827cebf4670468b8772dd191856768aedcb1b0278a04f989f7766351917b9dc" +checksum = "14d05c10f541ae6f3bc5b3d923c20001f47db7d5f0b2bc6ad16490133842db79" +dependencies = [ + "cc", + "libc", + "libz-sys", + "openssl-sys", + "pkg-config", + "vcpkg", + "winapi", +] [[package]] name = "dialoguer" @@ -170,94 +161,37 @@ dependencies = [ ] [[package]] -name = "directories" -version = "2.0.2" +name = "dirs" +version = "4.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "551a778172a450d7fc12e629ca3b0428d00f6afa9a43da1b630d54604e97371c" +checksum = "ca3aa72a6f96ea37bbc5aa912f6788242832f75369bdfdadcb0e38423f100059" dependencies = [ - "cfg-if 0.1.10", "dirs-sys", ] [[package]] name = "dirs-sys" -version = "0.3.6" +version = "0.3.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "03d86534ed367a67548dc68113a0f5db55432fdfbb6e6f9d77704397d95d5780" +checksum = "1b1d1d91c932ef41c0f2663aa8b0ca0342d444d842c06914aa0a7e352d0bada6" dependencies = [ "libc", "redox_users", "winapi", ] +[[package]] +name = "either" +version = "1.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7fcaabb2fef8c910e7f4c7ce9f67a1283a1715879a7c230ca9d6d1ae31f16d91" + [[package]] name = "encode_unicode" version = "0.3.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a357d28ed41a50f9c765dbfe56cbc04a64e53e5fc58ba79fbc34c10ef3df831f" -[[package]] -name = "encoding_rs" -version = "0.8.29" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a74ea89a0a1b98f6332de42c95baff457ada66d1cb4030f9ff151b2041a1c746" -dependencies = [ - "cfg-if 1.0.0", -] - -[[package]] -name = "env_logger" -version = "0.9.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0b2cf0344971ee6c64c31be0d530793fba457d322dfec2810c453d0ef228f9c3" -dependencies = [ - "atty", - "humantime", - "log", - "regex", - "termcolor", -] - -[[package]] -name = "fnv" -version = "1.0.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" - -[[package]] -name = "foreign-types" -version = "0.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f6f339eb8adc052cd2ca78910fda869aefa38d22d5cb648e6485e4d3fc06f3b1" -dependencies = [ - "foreign-types-shared", -] - -[[package]] -name = "foreign-types-shared" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "00b0228411908ca8685dba7fc2cdd70ec9990a6e753e89b6ac91a84c40fbaf4b" - -[[package]] -name = "form_urlencoded" -version = "1.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5fc25a87fa4fd2094bffb06925852034d90a17f0d1e05197d4956d3555752191" -dependencies = [ - "matches", - "percent-encoding", -] - -[[package]] -name = "futures-channel" -version = "0.3.17" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5da6ba8c3bb3c165d3c7319fc1cc8304facf1fb8db99c5de877183c08a273888" -dependencies = [ - "futures-core", -] - [[package]] name = "futures-core" version = "0.3.18" @@ -272,15 +206,9 @@ checksum = "a89f17b21645bc4ed773c69af9c9a0effd4a3f1a3876eadd453469f8854e7fdd" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 1.0.109", ] -[[package]] -name = "futures-sink" -version = "0.3.17" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "36ea153c13024fe480590b3e3d4cad89a0cfacecc24577b68f86c6ced9c2bc11" - [[package]] name = "futures-task" version = "0.3.18" @@ -307,41 +235,22 @@ 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", ] [[package]] -name = "h2" -version = "0.3.7" +name = "glob" +version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7fd819562fcebdac5afc5c113c3ec36f902840b70fd4fc458799c8ce4607ae55" -dependencies = [ - "bytes", - "fnv", - "futures-core", - "futures-sink", - "futures-util", - "http", - "indexmap", - "slab", - "tokio", - "tokio-util", - "tracing", -] +checksum = "d2fabcfbdc87f4758337ca535fb41a6d701b65693ce38287d856d1674551ec9b" [[package]] name = "hashbrown" -version = "0.11.2" +version = "0.12.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ab5ef0d4909ef3724cc8cce6ccc8572c5c817592e9285f5464f8e86f8bd3726e" - -[[package]] -name = "heck" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2540771e65fc8cb83cd6e8a237f70c319bd5c29f78ed1084ba5d50eeac86f7f9" +checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888" [[package]] name = "hermit-abi" @@ -356,114 +265,26 @@ dependencies = [ name = "hopper" version = "0.1.0" dependencies = [ - "anyhow", - "clap", - "confy", + "arg", + "c-main", "console", + "curl", "dialoguer", - "env_logger", "futures-util", "indicatif", - "log", - "reqwest", "serde", "serde_json", "tokio", -] - -[[package]] -name = "http" -version = "0.2.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1323096b05d41827dadeaee54c9981958c0f94e670bc94ed80037d1a7b8b186b" -dependencies = [ - "bytes", - "fnv", - "itoa", -] - -[[package]] -name = "http-body" -version = "0.4.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1ff4f84919677303da5f147645dbea6b1881f368d03ac84e1dc09031ebd7b2c6" -dependencies = [ - "bytes", - "http", - "pin-project-lite", -] - -[[package]] -name = "httparse" -version = "1.5.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "acd94fdbe1d4ff688b67b04eee2e17bd50995534a61539e45adfefb45e5e5503" - -[[package]] -name = "httpdate" -version = "1.0.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c4a1e36c821dbe04574f602848a19f742f4fb3c98d40449f11bcad18d6b17421" - -[[package]] -name = "humantime" -version = "2.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9a3a5bfb195931eeb336b2a7b4d761daec841b97f947d34394601737a7bba5e4" - -[[package]] -name = "hyper" -version = "0.14.15" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "436ec0091e4f20e655156a30a0df3770fe2900aa301e548e08446ec794b6953c" -dependencies = [ - "bytes", - "futures-channel", - "futures-core", - "futures-util", - "h2", - "http", - "http-body", - "httparse", - "httpdate", - "itoa", - "pin-project-lite", - "socket2", - "tokio", - "tower-service", - "tracing", - "want", -] - -[[package]] -name = "hyper-tls" -version = "0.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d6183ddfa99b85da61a140bea0efc93fdf56ceaa041b37d553518030827f9905" -dependencies = [ - "bytes", - "hyper", - "native-tls", - "tokio", - "tokio-native-tls", -] - -[[package]] -name = "idna" -version = "0.2.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "418a0a6fab821475f634efe3ccc45c013f742efe03d853e8d3355d5cb850ecf8" -dependencies = [ - "matches", - "unicode-bidi", - "unicode-normalization", + "toml", + "xdg", + "yacexits", ] [[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,30 +308,15 @@ 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]] -name = "ipnet" -version = "2.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "68f2d64f2edebec4ce84ad108148e67e1064789bee435edc5b60ad398714a3a9" - [[package]] name = "itoa" version = "0.4.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b71991ff56294aa922b450139ee08b3bfc70982c6b2c7562771375cf73542dd4" -[[package]] -name = "js-sys" -version = "0.3.55" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7cc9ffccd38c451a86bf13657df244e9c3f37493cce8e5e21e940963777acc84" -dependencies = [ - "wasm-bindgen", -] - [[package]] name = "lazy_static" version = "1.4.0" @@ -518,10 +324,38 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" [[package]] -name = "libc" -version = "0.2.108" +name = "lazycell" +version = "1.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8521a1b57e76b1ec69af7599e75e38e7b7fad6610f037db8c79b127201b5d119" +checksum = "830d08ce1d1d941e6b30645f1a0eb5643013d835ce3779a5fc208261dbe10f55" + +[[package]] +name = "libc" +version = "0.2.140" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "99227334921fae1a979cf0bfdfcc6b3e5ce376ef57e16fb6fb3ea2ed6095f80c" + +[[package]] +name = "libloading" +version = "0.7.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b67380fd3b2fbe7527a606e18729d21c6f3951633d0500574c4dc22d2d638b9f" +dependencies = [ + "cfg-if", + "winapi", +] + +[[package]] +name = "libz-sys" +version = "1.1.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9702761c3935f8cc2f101793272e202c72b99da8f4224a19ddcf1279a6450bbf" +dependencies = [ + "cc", + "libc", + "pkg-config", + "vcpkg", +] [[package]] name = "lock_api" @@ -538,15 +372,9 @@ 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]] -name = "matches" -version = "0.1.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a3e378b66a060d48947b590737b30a1be76706c8dd7b8ba0f2fe3989c68a853f" - [[package]] name = "memchr" version = "2.4.1" @@ -554,10 +382,10 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "308cc39be01b73d0d18f82a0e7b2a3df85245f84af96fdddc5d202d27e47b86a" [[package]] -name = "mime" -version = "0.3.16" +name = "minimal-lexical" +version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2a60c7ce501c71e03a9c9c0d35b861413ae925bd979cc7a4e30d060069aaac8d" +checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a" [[package]] name = "mio" @@ -582,21 +410,13 @@ dependencies = [ ] [[package]] -name = "native-tls" -version = "0.2.8" +name = "nom" +version = "7.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "48ba9f7719b5a0f42f338907614285fb5fd70e53858141f69898a1fb7203b24d" +checksum = "d273983c5a657a70a3e8f2a01329822f3b8c8172b73826411a55751e404a0a4a" dependencies = [ - "lazy_static", - "libc", - "log", - "openssl", - "openssl-probe", - "openssl-sys", - "schannel", - "security-framework", - "security-framework-sys", - "tempfile", + "memchr", + "minimal-lexical", ] [[package]] @@ -630,31 +450,17 @@ version = "1.14.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2f7254b99e31cad77da24b08ebf628882739a608578bb1bcdfc1f9c21260d7c0" -[[package]] -name = "openssl" -version = "0.10.38" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0c7ae222234c30df141154f159066c5093ff73b63204dcda7121eb082fc56a95" -dependencies = [ - "bitflags", - "cfg-if 1.0.0", - "foreign-types", - "libc", - "once_cell", - "openssl-sys", -] - [[package]] name = "openssl-probe" -version = "0.1.4" +version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "28988d872ab76095a6e6ac88d99b54fd267702734fd7ffe610ca27f533ddb95a" +checksum = "ff011a302c396a5197692431fc1948019154afc178baf7d8e37367442a4601cf" [[package]] name = "openssl-sys" -version = "0.9.71" +version = "0.9.82" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7df13d165e607909b363a4757a6f133f8a818a74e9d3a98d09c6128e15fa4c73" +checksum = "a95792af3c4e0153c3914df2261bedd30a98476f94dc892b67dfe1d89d433a04" dependencies = [ "autocfg", "cc", @@ -663,12 +469,6 @@ dependencies = [ "vcpkg", ] -[[package]] -name = "os_str_bytes" -version = "6.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9ff7415e9ae3fff1225851df9e0d9e4e5479f947619774677a63572e55e80eff" - [[package]] name = "parking_lot" version = "0.11.2" @@ -686,7 +486,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", @@ -695,10 +495,10 @@ dependencies = [ ] [[package]] -name = "percent-encoding" -version = "2.1.0" +name = "peeking_take_while" +version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d4fd5641d01c8f18a23da7b6fe29298ff4b55afcccdf78973b24cf3175fee32e" +checksum = "19b17cddbe7ec3f8bc800887bab5e717348c95ea2ca0b1bf0837fb964dc67099" [[package]] name = "pin-project-lite" @@ -714,9 +514,9 @@ checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" [[package]] name = "pkg-config" -version = "0.3.22" +version = "0.3.26" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "12295df4f294471248581bc09bef3c38a5e46f1e36d6a37353621a0c6c357e1f" +checksum = "6ac9a59f73473f1b8d852421e59e64809f025994837ef743615c6d0c5b305160" [[package]] name = "ppv-lite86" @@ -724,44 +524,20 @@ version = "0.2.15" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ed0cfbc8191465bed66e1718596ee0b0b35d5ee1f41c5df2189d0fe8bde535ba" -[[package]] -name = "proc-macro-error" -version = "1.0.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "da25490ff9892aab3fcf7c36f08cfb902dd3e71ca0f9f9517bea02a73a5ce38c" -dependencies = [ - "proc-macro-error-attr", - "proc-macro2", - "quote", - "syn", - "version_check", -] - -[[package]] -name = "proc-macro-error-attr" -version = "1.0.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a1be40180e52ecc98ad80b184934baf3d0d29f979574e439af5a55274b35f869" -dependencies = [ - "proc-macro2", - "quote", - "version_check", -] - [[package]] name = "proc-macro2" -version = "1.0.32" +version = "1.0.53" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ba508cc11742c0dc5c1659771673afbab7a0efab23aa17e854cbab0837ed0b43" +checksum = "ba466839c78239c09faf015484e5cc04860f88242cff4d03eb038f04b4699b73" dependencies = [ - "unicode-xid", + "unicode-ident", ] [[package]] name = "quote" -version = "1.0.10" +version = "1.0.26" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "38bc8cc6a5f2e3655e0899c1b848643b2562f853f114bfec7be120678e3ace05" +checksum = "4424af4bf778aae2051a77b60283332f386554255d722233d09fbfc7e30da2fc" dependencies = [ "proc-macro2", ] @@ -808,21 +584,22 @@ dependencies = [ [[package]] name = "redox_syscall" -version = "0.2.10" +version = "0.2.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8383f39639269cde97d255a32bdb68c047337295414940c68bdd30c2e13203ff" +checksum = "fb5a58c1855b4b6819d59012155603f0b22ad30cad752600aadfcb695265519a" dependencies = [ "bitflags", ] [[package]] name = "redox_users" -version = "0.4.0" +version = "0.4.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "528532f3d801c87aec9def2add9ca802fe569e44a544afe633765267840abe64" +checksum = "b033d837a7cf162d7993aded9304e30a83213c648b6e389db233191f891e5c2b" dependencies = [ "getrandom", "redox_syscall", + "thiserror", ] [[package]] @@ -831,8 +608,6 @@ version = "1.5.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d07a8629359eb56f1e2fb1652bb04212c072a87ba68546a04065d525673ac461" dependencies = [ - "aho-corasick", - "memchr", "regex-syntax", ] @@ -852,39 +627,10 @@ dependencies = [ ] [[package]] -name = "reqwest" -version = "0.11.6" +name = "rustc-hash" +version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "66d2927ca2f685faf0fc620ac4834690d29e7abb153add10f5812eef20b5e280" -dependencies = [ - "base64", - "bytes", - "encoding_rs", - "futures-core", - "futures-util", - "http", - "http-body", - "hyper", - "hyper-tls", - "ipnet", - "js-sys", - "lazy_static", - "log", - "mime", - "native-tls", - "percent-encoding", - "pin-project-lite", - "serde", - "serde_json", - "serde_urlencoded", - "tokio", - "tokio-native-tls", - "url", - "wasm-bindgen", - "wasm-bindgen-futures", - "web-sys", - "winreg", -] +checksum = "08d43f7aa6b08d49f382cde6a7982047c3426db949b1424bc4b7ec9ae12c6ce2" [[package]] name = "ryu" @@ -894,12 +640,11 @@ checksum = "71d301d4193d031abdd79ff7e3dd721168a9572ef3fe51a1517aba235bd8f86e" [[package]] name = "schannel" -version = "0.1.19" +version = "0.1.21" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8f05ba609c234e60bee0d547fe94a4c7e9da733d1c962cf6e59efa4cd9c8bc75" +checksum = "713cfb06c7059f3588fb8044c0fad1d09e3c01d225e25b9220dbfdcf16dbb1b3" dependencies = [ - "lazy_static", - "winapi", + "windows-sys", ] [[package]] @@ -908,47 +653,24 @@ version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d29ab0c6d3fc0ee92fe66e2d99f700eab17a8d57d1c1d3b748380fb20baa78cd" -[[package]] -name = "security-framework" -version = "2.4.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "525bc1abfda2e1998d152c45cf13e696f76d0a4972310b22fac1658b05df7c87" -dependencies = [ - "bitflags", - "core-foundation", - "core-foundation-sys", - "libc", - "security-framework-sys", -] - -[[package]] -name = "security-framework-sys" -version = "2.4.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a9dd14d83160b528b7bfd66439110573efcfbe281b17fc2ca9f39f550d619c7e" -dependencies = [ - "core-foundation-sys", - "libc", -] - [[package]] name = "serde" -version = "1.0.130" +version = "1.0.158" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f12d06de37cf59146fbdecab66aa99f9fe4f78722e3607577a5375d66bd0c913" +checksum = "771d4d9c4163ee138805e12c710dd365e4f44be8be0503cb1bb9eb989425d9c9" dependencies = [ "serde_derive", ] [[package]] name = "serde_derive" -version = "1.0.130" +version = "1.0.158" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d7bc1a1ab1961464eae040d96713baa5a724a8152c1222492465b54322ec508b" +checksum = "e801c1712f48475582b7696ac71e0ca34ebb30e09338425384269d9717c62cad" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.5", ] [[package]] @@ -963,17 +685,20 @@ dependencies = [ ] [[package]] -name = "serde_urlencoded" -version = "0.7.0" +name = "serde_spanned" +version = "0.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "edfa57a7f8d9c1d260a549e7224100f6c43d43f9103e06dd8b4095a9b2b43ce9" +checksum = "0efd8caf556a6cebd3b285caf480045fcc1ac04f6bd786b09a6f11af30c4fcf4" dependencies = [ - "form_urlencoded", - "itoa", - "ryu", "serde", ] +[[package]] +name = "shlex" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "43b2853a4d09f215c24cc5489c992ce46052d359b5109343cbafbf26bc62f8a3" + [[package]] name = "signal-hook-registry" version = "1.4.0" @@ -997,29 +722,43 @@ checksum = "1ecab6c735a6bb4139c0caafd0cc3635748bbb3acf4550e8138122099251f309" [[package]] name = "socket2" -version = "0.4.2" +version = "0.4.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5dc90fe6c7be1a323296982db1836d1ea9e47b6839496dde9a541bc496df3516" +checksum = "64a4a911eed85daf18834cfaa86a79b7d266ff93ff5ba14005426219480ed662" dependencies = [ "libc", "winapi", ] -[[package]] -name = "strsim" -version = "0.10.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623" - [[package]] name = "syn" -version = "1.0.81" +version = "1.0.109" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f2afee18b8beb5a596ecb4a2dce128c719b4ba399d34126b9e4396e3f9860966" +checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237" dependencies = [ "proc-macro2", "quote", - "unicode-xid", + "unicode-ident", +] + +[[package]] +name = "syn" +version = "2.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "89c2d1c76a26822187a1fbb5964e3fff108bc208f02e820ab9dac1234f6b388a" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "tabwriter" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "36205cfc997faadcc4b0b87aaef3fbedafe20d38d4959a7ca6ff803564051111" +dependencies = [ + "unicode-width", ] [[package]] @@ -1028,7 +767,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", @@ -1036,15 +775,6 @@ dependencies = [ "winapi", ] -[[package]] -name = "termcolor" -version = "1.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2dfed899f0eb03f32ee8c6a0aabdb8a7949659e3466561fc0adf54e26d88c5f4" -dependencies = [ - "winapi-util", -] - [[package]] name = "terminal_size" version = "0.1.17" @@ -1056,25 +786,24 @@ dependencies = [ ] [[package]] -name = "textwrap" -version = "0.15.0" +name = "thiserror" +version = "1.0.40" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b1141d4d61095b28419e22cb0bbf02755f5e54e0526f97f1e3d1d160e60885fb" - -[[package]] -name = "tinyvec" -version = "1.5.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2c1c1d5a42b6245520c249549ec267180beaffcc0615401ac8e31853d4b6d8d2" +checksum = "978c9a314bd8dc99be594bc3c175faaa9794be04a5a5e153caba6915336cebac" dependencies = [ - "tinyvec_macros", + "thiserror-impl", ] [[package]] -name = "tinyvec_macros" -version = "0.1.0" +name = "thiserror-impl" +version = "1.0.40" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cda74da7e1a664f795bb1f8a87ec406fb89a02522cf6e50620d016add6dbbf5c" +checksum = "f9456a42c5b0d803c8cd86e73dd7cc9edd429499f37a3550d286d5e86720569f" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.5", +] [[package]] name = "tokio" @@ -1104,88 +833,48 @@ checksum = "c9efc1aba077437943f7515666aa2b882dfabfbfdf89c819ea75a8d6e9eaba5e" dependencies = [ "proc-macro2", "quote", - "syn", -] - -[[package]] -name = "tokio-native-tls" -version = "0.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f7d995660bd2b7f8c1568414c1126076c13fbb725c40112dc0120b78eb9b717b" -dependencies = [ - "native-tls", - "tokio", -] - -[[package]] -name = "tokio-util" -version = "0.6.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9e99e1983e5d376cd8eb4b66604d2e99e79f5bd988c3055891dcd8c9e2604cc0" -dependencies = [ - "bytes", - "futures-core", - "futures-sink", - "log", - "pin-project-lite", - "tokio", + "syn 1.0.109", ] [[package]] name = "toml" -version = "0.5.8" +version = "0.7.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a31142970826733df8241ef35dc040ef98c679ab14d7c3e54d827099b3acecaa" +checksum = "b403acf6f2bb0859c93c7f0d967cb4a75a7ac552100f9322faf64dc047669b21" +dependencies = [ + "serde", + "serde_spanned", + "toml_datetime", + "toml_edit", +] + +[[package]] +name = "toml_datetime" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3ab8ed2edee10b50132aed5f331333428b011c99402b5a534154ed15746f9622" dependencies = [ "serde", ] [[package]] -name = "tower-service" -version = "0.3.1" +name = "toml_edit" +version = "0.19.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "360dfd1d6d30e05fda32ace2c8c70e9c0a9da713275777f5a4dbb8a1893930c6" - -[[package]] -name = "tracing" -version = "0.1.29" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "375a639232caf30edfc78e8d89b2d4c375515393e7af7e16f01cd96917fb2105" +checksum = "dc18466501acd8ac6a3f615dd29a3438f8ca6bb3b19537138b3106e575621274" dependencies = [ - "cfg-if 1.0.0", - "pin-project-lite", - "tracing-core", + "indexmap", + "serde", + "serde_spanned", + "toml_datetime", + "winnow", ] [[package]] -name = "tracing-core" -version = "0.1.21" +name = "unicode-ident" +version = "1.0.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1f4ed65637b8390770814083d20756f87bfa2c21bf2f110babdc5438351746e4" -dependencies = [ - "lazy_static", -] - -[[package]] -name = "try-lock" -version = "0.2.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "59547bce71d9c38b83d9c0e92b6066c4253371f15005def0c30d9657f50c7642" - -[[package]] -name = "unicode-bidi" -version = "0.3.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1a01404663e3db436ed2746d9fefef640d868edae3cceb81c3b8d5732fda678f" - -[[package]] -name = "unicode-normalization" -version = "0.1.19" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d54590932941a9e9266f0832deed84ebe1bf2e4c9e4a3554d393d18f5e854bf9" -dependencies = [ - "tinyvec", -] +checksum = "e5464a87b239f13a63a501f2701565754bae92d243d4bb7eb12f6d57d2269bf4" [[package]] name = "unicode-width" @@ -1193,46 +882,12 @@ 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" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a507c383b2d33b5fc35d1861e77e6b383d158b2da5e14fe51b83dfedf6fd578c" -dependencies = [ - "form_urlencoded", - "idna", - "matches", - "percent-encoding", -] - [[package]] name = "vcpkg" version = "0.2.15" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426" -[[package]] -name = "version_check" -version = "0.9.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5fecdca9a5291cc2b8dcf7dc02453fee791a280f3743cb0905f8822ae463b3fe" - -[[package]] -name = "want" -version = "0.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1ce8a968cb1cd110d136ff8b819a556d6fb6d919363c61534f6860c7eb172ba0" -dependencies = [ - "log", - "try-lock", -] - [[package]] name = "wasi" version = "0.10.2+wasi-snapshot-preview1" @@ -1240,79 +895,14 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fd6fbd9a79829dd1ad0cc20627bf1ed606756a7f77edff7b66b7064f9cb327c6" [[package]] -name = "wasm-bindgen" -version = "0.2.78" +name = "which" +version = "4.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "632f73e236b219150ea279196e54e610f5dbafa5d61786303d4da54f84e47fce" +checksum = "2441c784c52b289a054b7201fc93253e288f094e2f4be9058343127c4226a269" dependencies = [ - "cfg-if 1.0.0", - "wasm-bindgen-macro", -] - -[[package]] -name = "wasm-bindgen-backend" -version = "0.2.78" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a317bf8f9fba2476b4b2c85ef4c4af8ff39c3c7f0cdfeed4f82c34a880aa837b" -dependencies = [ - "bumpalo", - "lazy_static", - "log", - "proc-macro2", - "quote", - "syn", - "wasm-bindgen-shared", -] - -[[package]] -name = "wasm-bindgen-futures" -version = "0.4.28" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8e8d7523cb1f2a4c96c1317ca690031b714a51cc14e05f712446691f413f5d39" -dependencies = [ - "cfg-if 1.0.0", - "js-sys", - "wasm-bindgen", - "web-sys", -] - -[[package]] -name = "wasm-bindgen-macro" -version = "0.2.78" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d56146e7c495528bf6587663bea13a8eb588d39b36b679d83972e1a2dbbdacf9" -dependencies = [ - "quote", - "wasm-bindgen-macro-support", -] - -[[package]] -name = "wasm-bindgen-macro-support" -version = "0.2.78" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7803e0eea25835f8abdc585cd3021b3deb11543c6fe226dcd30b228857c5c5ab" -dependencies = [ - "proc-macro2", - "quote", - "syn", - "wasm-bindgen-backend", - "wasm-bindgen-shared", -] - -[[package]] -name = "wasm-bindgen-shared" -version = "0.2.78" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0237232789cf037d5480773fe568aac745bfe2afbc11a863e97901780a6b47cc" - -[[package]] -name = "web-sys" -version = "0.3.55" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "38eb105f1c59d9eaa6b5cdc92b859d85b926e82cb2e0945cd0c9259faa6fe9fb" -dependencies = [ - "js-sys", - "wasm-bindgen", + "either", + "libc", + "once_cell", ] [[package]] @@ -1331,15 +921,6 @@ version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" -[[package]] -name = "winapi-util" -version = "0.1.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "70ec6ce85bb158151cae5e5c87f95a8e97d2c0c4b001223f33a334e3ce5de178" -dependencies = [ - "winapi", -] - [[package]] name = "winapi-x86_64-pc-windows-gnu" version = "0.4.0" @@ -1347,12 +928,88 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" [[package]] -name = "winreg" -version = "0.7.0" +name = "windows-sys" +version = "0.42.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0120db82e8a1e0b9fb3345a539c478767c0048d842860994d96113d5b667bd69" +checksum = "5a3e1820f08b8513f676f7ab6c1f99ff312fb97b553d30ff4dd86f9f15728aa7" dependencies = [ - "winapi", + "windows_aarch64_gnullvm", + "windows_aarch64_msvc", + "windows_i686_gnu", + "windows_i686_msvc", + "windows_x86_64_gnu", + "windows_x86_64_gnullvm", + "windows_x86_64_msvc", +] + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "597a5118570b68bc08d8d59125332c54f1ba9d9adeedeef5b99b02ba2b0698f8" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e08e8864a60f06ef0d0ff4ba04124db8b0fb3be5776a5cd47641e942e58c4d43" + +[[package]] +name = "windows_i686_gnu" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c61d927d8da41da96a81f029489353e68739737d3beca43145c8afec9a31a84f" + +[[package]] +name = "windows_i686_msvc" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "44d840b6ec649f480a41c8d80f9c65108b92d89345dd94027bfe06ac444d1060" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8de912b8b8feb55c064867cf047dda097f92d51efad5b491dfb98f6bbb70cb36" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "26d41b46a36d453748aedef1486d5c7a85db22e56aff34643984ea85514e94a3" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9aec5da331524158c6d1a4ac0ab1541149c0b9505fde06423b02f5ef0106b9f0" + +[[package]] +name = "winnow" +version = "0.3.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "23d020b441f92996c80d94ae9166e8501e59c7bb56121189dc9eab3bd8216966" +dependencies = [ + "memchr", +] + +[[package]] +name = "xdg" +version = "2.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0c4583db5cbd4c4c0303df2d15af80f0539db703fa1c68802d4cbbd2dd0f88f6" +dependencies = [ + "dirs", +] + +[[package]] +name = "yacexits" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b3afbe270dff95fe94b3a55c7e2dce91457a89b2b0dc6013814bba9806d099be" +dependencies = [ + "bindgen", + "libc", ] [[package]] diff --git a/Cargo.toml b/Cargo.toml index be435aa..a1dc83c 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -3,18 +3,22 @@ name = "hopper" version = "0.1.0" license = "AGPL-3.0-or-later" edition = "2021" +authors = [ + "Emma Tebibyte ", + "Marceline Cramer " +] [dependencies] -anyhow = "1.0" -confy = "0.4" +arg = "0.4.1" +c-main = "1.0.1" console = "0.15.0" +curl = "0.4.44" dialoguer = "0.9.0" -env_logger = "0.9.0" futures-util = "0.3.18" indicatif = "0.15.0" -log = "0.4.14" -reqwest = { version = "0.11", features = ["json", "stream"] } serde = { version = "1", features = ["derive"] } serde_json = "1" -clap = { version = "3.2.20", features = ["derive"] } tokio = { version = "1", features = ["full"] } +toml = "0.7.3" +xdg = "2.4.1" +yacexits = "0.1.3" diff --git a/src/api.rs b/src/api.rs index 07c1cfe..e69de29 100644 --- a/src/api.rs +++ b/src/api.rs @@ -1,156 +0,0 @@ -use console::style; -use serde::Deserialize; -use std::{collections::HashMap, fmt}; - -#[derive(Deserialize, Debug)] -pub struct SearchResponse { - pub hits: Vec, - pub offset: isize, - pub limit: isize, - pub total_hits: isize, -} - -#[derive(Deserialize, Debug)] -pub struct ModResult { - pub slug: String, - pub title: String, - pub description: String, - pub categories: Vec, - pub display_categories: Vec, // NOTE this is not in the OpenAPI docs - pub client_side: String, - pub server_side: String, - pub project_type: String, // NOTE this isn't in all search results? - pub downloads: isize, - pub icon_url: String, - pub project_id: String, // TODO parse to 'local-xxxx' with reegex - pub author: String, - pub versions: Vec, - pub follows: isize, - pub date_created: String, - pub date_modified: String, - pub latest_version: String, - pub license: String, - pub gallery: Vec, -} - -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 slug: String, - pub title: String, - pub description: String, - pub categories: Vec, - pub additional_categories: Vec, // NOTE not listed in OpenAPI docs - pub client_side: String, // TODO serialize as enum - pub server_side: String, // TODO serialize as enum - pub body: String, - pub issues_url: Option, - pub source_url: Option, - pub wiki_url: Option, - pub discord_url: Option, - pub donation_urls: Option>, - pub project_type: String, - pub downloads: isize, - pub icon_url: Option, - pub id: String, // TODO serialize mod id? - pub team: String, // TODO serialize team id? - pub body_url: Option, // NOTE deprecated - pub moderator_message: Option, - pub published: String, // TODO serialize as datetime - pub updated: String, // TODO serialize as datetime - pub approved: Option, // NOTE not listed in OpenAPI docs, TODO serialize as datetime - pub followers: isize, - pub status: String, - pub license: License, - pub versions: Vec, - pub gallery: Option>, -} - -#[derive(Deserialize, Debug)] -pub struct GalleryEntry { - pub url: String, - pub featured: bool, - pub title: String, - pub description: String, - pub created: String, -} - -#[derive(Deserialize, Debug)] -pub struct License { - pub id: String, - pub name: String, - pub url: String, -} - -#[derive(Deserialize, Debug)] -pub struct DonationLink { - pub id: String, - pub platform: String, - pub url: String, -} - -#[derive(Deserialize, Debug)] -pub struct ModVersion { - pub name: String, - pub version_number: String, - pub changelog: Option, - // pub dependencies: Option>, // TODO dependency wrangling, thank you modrinth, very cool - pub game_versions: Vec, - pub version_type: String, // TODO {alpha | beta | release} - pub loaders: Vec, - pub featured: bool, - pub id: String, // version id - pub project_id: String, // mod id - pub author_id: String, // user id - pub date_published: String, // TODO serialize datetime - pub downloads: isize, - pub changelog_url: Option, // NOTE deprecated - pub files: Vec, -} - -#[derive(Deserialize, Debug)] -pub struct ModVersionFile { - pub hashes: HashMap, - pub url: String, - pub filename: String, - pub primary: bool, - pub size: isize, -} - -#[derive(Deserialize, Debug)] -pub struct Error { - pub error: String, - pub description: String, -} - -impl fmt::Display for Error { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - write!(f, "{}: {}", self.error, self.description) - } -} - -impl std::error::Error for Error {} diff --git a/src/args.rs b/src/args.rs new file mode 100644 index 0000000..840a6bc --- /dev/null +++ b/src/args.rs @@ -0,0 +1,156 @@ +/* + * Copyright (c) 2022–2023 Emma Tebibyte + * Copyright (c) 2021–2022 Marceline Cramer + * SPDX-License-Identifier: AGPL-3.0-or-later + * + * This file is part of Hopper. + * + * Hopper is free software: you can redistribute it and/or modify it under the + * terms of the GNU General Public License as published by the Free Software + * Foundation, either version 3 of the License, or (at your option) any later + * version. + * + * Hopper is distributed in the hope that it will be useful, but WITHOUT ANY + * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR + * A PARTICULAR PURPOSE. See the GNU General Public License for more details. + * You should have received a copy of the GNU General Public License along with + * Hopper. If not, see . + */ + +use core::str::FromStr; + +use arg::Args; + +#[derive(Args, Debug)] +struct Arguments { + #[arg(short = "v")] + v: bool, + + #[arg(sub)] + sub: Command, +} + +#[derive(Args, Debug)] +struct InitArgs { + #[arg(short = "d")] + dir: Option, + + #[arg(short = "f")] + template: Option, + + #[arg(short = "m")] + mc_version: Vec, + + #[arg(short = "t", required)] + package_type: PackageType, +} + +#[derive(Args, Debug)] +struct HopArgs { + #[arg(short = "f")] + hopfile: Option, + + #[arg(short = "m")] + mc_version: Vec, + + #[arg(short = "t")] + package_type: Option, +} + +#[derive(Args, Debug)] +struct SearchArgs { + package_name: String, + + /// Overrides the download directory + #[arg(short = "d")] + dir: Option, + + /// Restricts the target Minecraft version + #[arg(short = "m")] + mc_version: Vec, + + /// Type of package to use + #[arg(short = "t")] + package_type: Option, +} + +#[derive(Args, Debug)] +enum Command { + Add(SearchArgs), + Get(SearchArgs), + Init(InitArgs), + List(HopArgs), + Remove(HopArgs), + Update(HopArgs), +} + +#[derive(Debug)] +enum PackageType { + Mod(Loader), + Pack(Loader), + Plugin(Server), + ResourcePack, +} + +#[derive(Debug)] +enum Loader { + Fabric, + Forge, + Quilt, +} + +#[derive(Debug)] +enum Server { + Bukkit, + Paper, + Purpur, + Spigot, + Sponge, +} + +#[derive(Debug)] +enum PackageParseError { + Invalid(String), +} + +impl FromStr for PackageType { + type Err = PackageParseError; + fn from_str(s: &str) -> Result { + let pieces: Vec<&str> = s.split("-").collect(); + + if pieces.len() > 2 || pieces.len() == 1 { + return Err(PackageParseError::Invalid( + format!("{}: Invalid package name.", s) + )); + } + + let (prefix, postfix) = (pieces[0], pieces[1]); + + let loader = match prefix { + "bukkit" => return Ok(PackageType::Plugin(Server::Bukkit)), + "fabric" => Loader::Fabric, + "forge" => Loader::Forge, + "paper" => return Ok(PackageType::Plugin(Server::Paper)), + "purpur" => return Ok(PackageType::Plugin(Server::Purpur)), + "quilt" => Loader::Quilt, + "resource" => return Ok(PackageType::ResourcePack), + "spigot" => return Ok(PackageType::Plugin(Server::Spigot)), + "sponge" => return Ok(PackageType::Plugin(Server::Sponge)), + _ => { + return Err(PackageParseError::Invalid( + format!("{}: Invalid package type.", prefix) + )) + }, + }; + + match postfix { + "mod" => Ok(PackageType::Mod(loader)), + "pack" => Ok(PackageType::Pack(loader)), + _ => { + Err(PackageParseError::Invalid( + format!("{}: Invalid package type.", postfix) + )) + }, + } + } +} diff --git a/src/client.rs b/src/client.rs index 37019a7..e69de29 100644 --- a/src/client.rs +++ b/src/client.rs @@ -1,166 +0,0 @@ -use crate::api::{ModInfo, ModResult, ModVersion, ModVersionFile, SearchResponse, Error as APIError}; -use crate::config::{Args, Config, PackageType, SearchArgs}; -use futures_util::StreamExt; -use log::*; -use std::cmp::min; -use std::io::Write; - -pub struct HopperClient { - config: Config, - client: reqwest::Client, -} - -impl HopperClient { - pub fn new(config: Config) -> Self { - Self { - config: config, - client: reqwest::ClientBuilder::new() - .user_agent(format!("tebibytemedia/hopper/{} (tebibyte.media)", env!("CARGO_PKG_VERSION"))) - .build() - .unwrap(), - } - } - - pub async fn search_mods(&self, search_args: &SearchArgs) -> anyhow::Result { - println!("Searching with query \"{}\"...", search_args.package_name); - - let url = format!("https://{}/v2/search", self.config.upstream.server_address); - - let mut params = vec![("query", search_args.package_name.to_owned())]; - let mut facets: Vec = Vec::new(); - if let Some(versions) = &search_args.version { - let versions_facets = versions - .iter() - .map(|e| format!("[\"versions:{}\"]", e)) - .collect::>() - .join(","); - facets.push(format!("{}", versions_facets)); - } - if let Some(package_type) = &search_args.package_type { - let package_type_facet = match package_type { - PackageType::Fabric => "[\"categories:fabric\"],[\"project_type:mod\"]", - PackageType::Forge => "[\"categories:forge\"],[\"project_type:mod\"]", - PackageType::Quilt => "[\"categories:quilt\"],[\"project_type:mod\"]", - PackageType::Resource => "[\"project_type:resourcepack\"]", - PackageType::FabricPack => "[\"project_type:modpack\"],[\"categories:fabric\"]", - PackageType::ForgePack => "[\"project_type:modpack\"],[\"categories:forge\"]", - PackageType::QuiltPack => "[\"project_type:modpack\"],[\"categories:quilt\"]", - PackageType::BukkitPlugin => "[\"project_type:mod\"],[\"categories:bukkit\"]", - PackageType::PaperPlugin => "[\"project_type:mod\"],[\"categories:paper\"]", - PackageType::PurpurPlugin => "[\"project_type:mod\"],[\"categories:purpur\"]", - PackageType::SpigotPlugin => "[\"project_type:mod\"],[\"categories:spigot\"]", - PackageType::SpongePlugin => "[\"project_type:mod\"],[\"categories:sponge\"]", - } - .to_string(); - facets.push(package_type_facet); - } - - if !facets.is_empty() { - params.push(("facets", format!("[{}]", facets.join(",")))); - } - - let url = reqwest::Url::parse_with_params(url.as_str(), ¶ms)?; - info!("GET {}", url); - let response = self.client.get(url).send().await?; - - if response.status().is_success() { - Ok(response.json::().await?) - } else { - Err(response.json::().await?.into()) - } - } - - pub async fn fetch_mod_info(&self, mod_result: &ModResult) -> anyhow::Result { - let mod_id = &mod_result.project_id; - println!( - "Fetching mod info for {} (ID: {})...", - mod_result.title, mod_id - ); - - let url = format!( - "https://{}/v2/project/{}", - self.config.upstream.server_address, mod_id - ); - info!("GET {}", url); - let response = self.client.get(url).send().await?; - - if response.status().is_success() { - Ok(response.json::().await?) - } else { - Err(response.json::().await?.into()) - } - } - - pub async fn fetch_mod_version(&self, version_id: &String) -> anyhow::Result { - println!("Fetching mod version {}...", version_id); - - let url = format!( - "https://{}/v2/version/{}", - self.config.upstream.server_address, version_id - ); - info!("GET {}", url); - let response = self.client.get(url).send().await?; - - if response.status().is_success() { - Ok(response.json::().await?) - } else { - Err(response.json::().await?.into()) - } - } - - pub async fn download_version_file( - &self, - args: &Args, - file: &ModVersionFile, - ) -> anyhow::Result<()> { - // TODO replace all uses of .unwrap() with proper error codes - let filename = &file.filename; - - // TODO make confirmation skippable with flag argument - if !args.auto_accept { - use dialoguer::Confirm; - let prompt = format!("Download to {}?", filename); - let confirm = Confirm::new() - .with_prompt(prompt) - .default(true) - .interact()?; - if !confirm { - println!("Skipping downloading {}...", filename); - return Ok(()); - } - } - let url = &file.url; - info!("GET {}", url); - let response = self.client.get(url).send().await?; - - if !response.status().is_success() { - return Err(response.json::().await?.into()) - } - - let total_size = response.content_length().unwrap(); - - // TODO better colors and styling! - // TODO square colored creeper face progress indicator (from top-left clockwise spiral in) - use indicatif::{ProgressBar, ProgressStyle}; - let pb = ProgressBar::new(total_size); - pb.set_style(ProgressStyle::default_bar().template("{msg}\n{spinner:.green} [{elapsed_precise}] [{wide_bar:.cyan/blue}] {bytes}/{total_bytes} ({bytes_per_sec}, {eta})").progress_chars("#>-")); - pb.set_message(&format!("Downloading {}", url)); - - let filename = &file.filename; - let mut file = std::fs::File::create(filename)?; - let mut downloaded: u64 = 0; - let mut stream = response.bytes_stream(); - - // TODO check hashes while streaming - while let Some(item) = stream.next().await { - let chunk = &item.unwrap(); - file.write(&chunk)?; - let new = min(downloaded + (chunk.len() as u64), total_size); - downloaded = new; - pb.set_position(new); - } - - pb.finish_with_message(&format!("Downloaded {} to {}", url, filename)); - Ok(()) - } -} diff --git a/src/config.rs b/src/config.rs index 8ba1f47..4a77ab0 100644 --- a/src/config.rs +++ b/src/config.rs @@ -1,119 +1,91 @@ -use clap::{Parser, Subcommand, ValueEnum}; -use serde::{Deserialize, Serialize}; -use std::path::PathBuf; +/* + * Copyright (c) 2022–2023 Emma Tebibyte + * Copyright (c) 2021–2022 Marceline Cramer + * SPDX-License-Identifier: AGPL-3.0-or-later + * + * This file is part of Hopper. + * + * Hopper is free software: you can redistribute it and/or modify it under the + * terms of the GNU General Public License as published by the Free Software + * Foundation, either version 3 of the License, or (at your option) any later + * version. + * + * Hopper is distributed in the hope that it will be useful, but WITHOUT ANY + * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR + * A PARTICULAR PURPOSE. See the GNU General Public License for more details. + * You should have received a copy of the GNU General Public License along with + * Hopper. If not, see . + */ -// TODO parameter to restrict target Minecraft version -#[derive(clap::Args, Clone, Debug)] -pub struct SearchArgs { - pub package_name: String, +use std::{ + collections::HashMap, + fs::File, + io::Read, +}; - /// Type of package to use - #[clap(short, long, value_enum)] - pub package_type: Option, +use serde::Deserialize; +use toml::de::ValueDeserializer; +use yacexits::{ + EX_DATAERR, + EX_UNAVAILABLE, +}; - /// Restricts the target Minecraft version - #[clap(short, long)] - pub version: Option>, -} - -// TODO use ColoredHelp by default? -#[derive(Subcommand, Clone, Debug)] -pub enum Command { - /// Adds a mod to the current instance - Add(SearchArgs), - /// Removes a mod - Remove { - package_name: String, - }, - Get(SearchArgs), - Update, - Clean, -} - -#[derive(ValueEnum, Clone, Debug)] -pub enum PackageType { - Fabric, - Forge, - Quilt, - Resource, - FabricPack, - ForgePack, - QuiltPack, - BukkitPlugin, - PaperPlugin, - PurpurPlugin, - SpigotPlugin, - SpongePlugin, -} - -// TODO move main body argument fields to substruct for ease of moving? -#[derive(Parser, Clone, Debug)] -#[clap(name = "hopper")] -pub struct Args { - /// Path to configuration file - #[clap(short, long, value_parser)] - pub config: Option, - - /// Path to mod lockfile - #[clap(short, long, value_parser)] - pub lockfile: Option, - - /// Auto-accept confirmation dialogues - #[clap(short = 'y', long = "yes")] - pub auto_accept: bool, - - #[clap(subcommand)] - pub command: Command, -} - -impl Args { - pub fn load_config(&self) -> Result { - if let Some(config_path) = &self.config { - confy::load_path(config_path) - } else { - confy::load("hopper") - } - } -} - -#[derive(Deserialize, Serialize, Debug, Clone)] -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, Clone)] -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, Clone)] +#[derive(Deserialize)] pub struct Config { - /// General settings - pub options: Options, - - /// Configuration for the upstream Modrinth server - pub upstream: Upstream, + hopfiles: Vec, + sources: HashMap, } -pub struct AppContext { - pub args: Args, - pub config: Config, +pub fn get_config() -> Result<(), (String, u32)> { + let xdg_dirs = match xdg::BaseDirectories::with_prefix("hopper") { + Ok(dirs) => dirs, + Err(err) => { + return Err(( + format!("{:?}", err), + EX_UNAVAILABLE, + )); + }, + }; + Ok(()) +} + +impl Config { + pub fn read_config(config_path: String) -> Result { + let mut buf: Vec = Vec::new(); + + let mut config_file = match File::open(&config_path) { + Ok(file) => file, + Err(_) => { + return Err(( + format!("{}: Permission denied.", &config_path), + EX_UNAVAILABLE, + )); + }, + }; + + match config_file.read_to_end(&mut buf) { + Ok(_) => {}, + Err(err) => { + return Err(( + format!("{:?}", err), + EX_DATAERR, + )); + }, + }; + + let toml = match String::from_utf8(buf) { + Ok(contents) => contents, + Err(_) => { + return Err(( + format!("Invalid configuration file."), + EX_DATAERR, + )); + }, + }; + + match Config::deserialize(ValueDeserializer::new(&toml)) { + Ok(val) => Ok(val), + Err(err) => Err((format!("{:?}", err), EX_DATAERR)), + } + } } diff --git a/src/main.rs b/src/main.rs index ec7553e..ae63b44 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,121 +1,36 @@ +/* + * Copyright (c) 2022–2023 Emma Tebibyte + * Copyright (c) 2021–2022 Marceline Cramer + * SPDX-License-Identifier: AGPL-3.0-or-later + * + * This file is part of Hopper. + * + * Hopper is free software: you can redistribute it and/or modify it under the + * terms of the GNU General Public License as published by the Free Software + * Foundation, either version 3 of the License, or (at your option) any later + * version. + * + * Hopper is distributed in the hope that it will be useful, but WITHOUT ANY + * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR + * A PARTICULAR PURPOSE. See the GNU General Public License for more details. + * You should have received a copy of the GNU General Public License along with + * Hopper. If not, see . + */ + +#![no_main] + mod api; +mod args; mod client; mod config; use api::*; -use clap::Parser; +use args::*; use client::*; use config::*; -fn display_search_results(ctx: &AppContext, response: &SearchResponse) { - let iter = response.hits.iter().enumerate(); - if ctx.config.options.reverse_search { - for (i, result) in iter.rev() { - result.display(i + 1); - } - } else { - for (i, result) in iter { - result.display(i + 1); - } - } -} - -// TODO implement enum for more graceful exiting -async fn select_from_results( - _ctx: &AppContext, - response: &SearchResponse, -) -> anyhow::Result> { - let input: String = dialoguer::Input::new() - .with_prompt("Mods to install (eg: 1 2 3-5)") - .interact_text()?; - - let mut selected: Vec = Vec::new(); - for token in input.split(" ") { - let terms: Vec<&str> = token.split("-").collect(); - - match terms.len() { - 1 => selected.push(terms[0].parse().expect("Token must be an integer")), - 2 => { - let terms: Vec = terms - .iter() - .map(|term| term.parse().expect("Term must be an integer")) - .collect(); - let from = terms[0]; - let to = terms[1]; - - for index in from..=to { - selected.push(index); - } - } - _ => panic!("Invalid selection token {}", token), - } - } - - selected.dedup(); - - let selected = selected - .iter() - .map(|index| { - if *index < 1 || *index > response.hits.len() { - // TODO return useful error instead of panicking - panic!("Index {} is out of bounds", index); - } - - // input is indexed from 1, but results are indexed from 0 - let index = index - 1; - - index - }) - .collect(); - - Ok(selected) -} - -async fn cmd_get(ctx: &AppContext, search_args: SearchArgs) -> anyhow::Result<()> { - let client = HopperClient::new(ctx.config.clone()); - let response = client.search_mods(&search_args).await?; - - if response.hits.is_empty() { - // TODO formatting - println!("No results; nothing to do..."); - return Ok(()); - } - - display_search_results(ctx, &response); - let selected = select_from_results(ctx, &response).await?; - - if selected.is_empty() { - // TODO formatting - println!("No packages selected; nothing to do..."); - return Ok(()); - } - - for selection in selected.iter() { - let to_get = &response.hits[*selection]; - let mod_info = client.fetch_mod_info(to_get).await?; - - // TODO allow the user to select multiple versions - if let Some(version_id) = mod_info.versions.first() { - println!("fetching version {}", version_id); - - let version = client.fetch_mod_version(version_id).await?; - for file in version.files.iter() { - client.download_version_file(&ctx.args, file).await?; - } - } - } - - Ok(()) -} - #[tokio::main] -async fn main() -> anyhow::Result<()> { - env_logger::init(); - let args = Args::parse(); - let config = args.load_config()?; - let ctx = AppContext { args, config }; - match ctx.args.to_owned().command { - Command::Get(search_args) => cmd_get(&ctx, search_args).await, - _ => unimplemented!("unimplemented subcommand"), - } +#[no_mangle] +async fn rust_main(args: c_main::Args) { + let argv: Vec<&str> = args.into_iter().collect(); } -- 2.30.2 From 04a7f27deae5fbdee2c0e630ad226da43b2f2926 Mon Sep 17 00:00:00 2001 From: emma Date: Wed, 22 Mar 2023 18:42:20 -0400 Subject: [PATCH 02/30] oops forgot to make those pub --- .github/FUNDING.yml | 12 ------------ src/args.rs | 46 ++++++++++++++++++++++----------------------- src/main.rs | 2 +- 3 files changed, 24 insertions(+), 36 deletions(-) delete mode 100644 .github/FUNDING.yml diff --git a/.github/FUNDING.yml b/.github/FUNDING.yml deleted file mode 100644 index fa01091..0000000 --- a/.github/FUNDING.yml +++ /dev/null @@ -1,12 +0,0 @@ -# These are supported funding model platforms - -github: # Replace with up to 4 GitHub Sponsors-enabled usernames e.g., [user1, user2] -patreon: # Replace with a single Patreon username -open_collective: tebibytemedia # Replace with a single Open Collective username -ko_fi: # Replace with a single Ko-fi username -tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel -community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry -liberapay: tebibytemedia # Replace with a single Liberapay username -issuehunt: # Replace with a single IssueHunt username -otechie: # Replace with a single Otechie username -custom: # Replace with up to 4 custom sponsorship URLs e.g., ['link1', 'link2'] diff --git a/src/args.rs b/src/args.rs index 840a6bc..f334987 100644 --- a/src/args.rs +++ b/src/args.rs @@ -19,63 +19,63 @@ use core::str::FromStr; -use arg::Args; +pub use arg::Args; #[derive(Args, Debug)] -struct Arguments { +pub struct Arguments { #[arg(short = "v")] - v: bool, + pub v: bool, #[arg(sub)] - sub: Command, + pub sub: Command, } #[derive(Args, Debug)] -struct InitArgs { +pub struct InitArgs { #[arg(short = "d")] - dir: Option, + pub dir: Option, #[arg(short = "f")] - template: Option, + pub template: Option, #[arg(short = "m")] - mc_version: Vec, + pub mc_version: Vec, #[arg(short = "t", required)] - package_type: PackageType, + pub package_type: PackageType, } #[derive(Args, Debug)] -struct HopArgs { +pub struct HopArgs { #[arg(short = "f")] - hopfile: Option, + pub hopfile: Option, #[arg(short = "m")] - mc_version: Vec, + pub mc_version: Vec, #[arg(short = "t")] - package_type: Option, + pub package_type: Option, } #[derive(Args, Debug)] -struct SearchArgs { - package_name: String, +pub struct SearchArgs { + pub package_name: String, /// Overrides the download directory #[arg(short = "d")] - dir: Option, + pub dir: Option, /// Restricts the target Minecraft version #[arg(short = "m")] - mc_version: Vec, + pub mc_version: Vec, /// Type of package to use #[arg(short = "t")] - package_type: Option, + pub package_type: Option, } #[derive(Args, Debug)] -enum Command { +pub enum Command { Add(SearchArgs), Get(SearchArgs), Init(InitArgs), @@ -85,7 +85,7 @@ enum Command { } #[derive(Debug)] -enum PackageType { +pub enum PackageType { Mod(Loader), Pack(Loader), Plugin(Server), @@ -93,14 +93,14 @@ enum PackageType { } #[derive(Debug)] -enum Loader { +pub enum Loader { Fabric, Forge, Quilt, } #[derive(Debug)] -enum Server { +pub enum Server { Bukkit, Paper, Purpur, @@ -109,7 +109,7 @@ enum Server { } #[derive(Debug)] -enum PackageParseError { +pub enum PackageParseError { Invalid(String), } diff --git a/src/main.rs b/src/main.rs index ae63b44..dd94e86 100644 --- a/src/main.rs +++ b/src/main.rs @@ -32,5 +32,5 @@ use config::*; #[tokio::main] #[no_mangle] async fn rust_main(args: c_main::Args) { - let argv: Vec<&str> = args.into_iter().collect(); + let arguments = Arguments::from_args(args.into_iter()); } -- 2.30.2 From 31db7e6feaf77b83fe19204e9e98d865a73c075e Mon Sep 17 00:00:00 2001 From: emma Date: Wed, 22 Mar 2023 18:56:07 -0400 Subject: [PATCH 03/30] license header update --- Cargo.toml | 1 + src/api.rs | 176 ++++++++++++++++++++++++++++++++++++++++++++++++++ src/args.rs | 1 + src/client.rs | 19 ++++++ src/config.rs | 1 + src/main.rs | 1 + 6 files changed, 199 insertions(+) diff --git a/Cargo.toml b/Cargo.toml index a1dc83c..d51c750 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -6,6 +6,7 @@ edition = "2021" authors = [ "Emma Tebibyte ", "Marceline Cramer " + "Spookdot " ] [dependencies] diff --git a/src/api.rs b/src/api.rs index e69de29..96526de 100644 --- a/src/api.rs +++ b/src/api.rs @@ -0,0 +1,176 @@ +/* + * Copyright (c) 2022–2023 Emma Tebibyte + * Copyright (c) 2021–2022 Marceline Cramer + * Copyright (c) 2022 Spookdot + * SPDX-License-Identifier: AGPL-3.0-or-later + * + * This file is part of Hopper. + * + * Hopper is free software: you can redistribute it and/or modify it under the + * terms of the GNU General Public License as published by the Free Software + * Foundation, either version 3 of the License, or (at your option) any later + * version. + * + * Hopper is distributed in the hope that it will be useful, but WITHOUT ANY + * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR + * A PARTICULAR PURPOSE. See the GNU General Public License for more details. + * You should have received a copy of the GNU General Public License along with + * Hopper. If not, see . + */ + +use console::style; +use serde::Deserialize; +use std::{collections::HashMap, fmt}; + +#[derive(Deserialize, Debug)] +pub struct SearchResponse { + pub hits: Vec, + pub offset: isize, + pub limit: isize, + pub total_hits: isize, +} + +#[derive(Deserialize, Debug)] +pub struct ModResult { + pub slug: String, + pub title: String, + pub description: String, + pub categories: Vec, + pub display_categories: Vec, // NOTE this is not in the OpenAPI docs + pub client_side: String, + pub server_side: String, + pub project_type: String, // NOTE this isn't in all search results? + pub downloads: isize, + pub icon_url: String, + pub project_id: String, // TODO parse to 'local-xxxx' with regex + pub author: String, + pub versions: Vec, + pub follows: isize, + pub date_created: String, + pub date_modified: String, + pub latest_version: String, + pub license: String, + pub gallery: Vec, +} + +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 slug: String, + pub title: String, + pub description: String, + pub categories: Vec, + pub additional_categories: Vec, // NOTE not listed in OpenAPI docs + pub client_side: String, // TODO serialize as enum + pub server_side: String, // TODO serialize as enum + pub body: String, + pub issues_url: Option, + pub source_url: Option, + pub wiki_url: Option, + pub discord_url: Option, + pub donation_urls: Option>, + pub project_type: String, + pub downloads: isize, + pub icon_url: Option, + pub id: String, // TODO serialize mod id? + pub team: String, // TODO serialize team id? + pub body_url: Option, // NOTE deprecated + pub moderator_message: Option, + pub published: String, // TODO serialize as datetime + pub updated: String, // TODO serialize as datetime + pub approved: Option, // NOTE not listed in OpenAPI docs, TODO serialize as datetime + pub followers: isize, + pub status: String, + pub license: License, + pub versions: Vec, + pub gallery: Option>, +} + +#[derive(Deserialize, Debug)] +pub struct GalleryEntry { + pub url: String, + pub featured: bool, + pub title: String, + pub description: String, + pub created: String, +} + +#[derive(Deserialize, Debug)] +pub struct License { + pub id: String, + pub name: String, + pub url: String, +} + +#[derive(Deserialize, Debug)] +pub struct DonationLink { + pub id: String, + pub platform: String, + pub url: String, +} + +#[derive(Deserialize, Debug)] +pub struct ModVersion { + pub name: String, + pub version_number: String, + pub changelog: Option, + // pub dependencies: Option>, // TODO dependency wrangling, thank you modrinth, very cool + pub game_versions: Vec, + pub version_type: String, // TODO {alpha | beta | release} + pub loaders: Vec, + pub featured: bool, + pub id: String, // version id + pub project_id: String, // mod id + pub author_id: String, // user id + pub date_published: String, // TODO serialize datetime + pub downloads: isize, + pub changelog_url: Option, // NOTE deprecated + pub files: Vec, +} + +#[derive(Deserialize, Debug)] +pub struct ModVersionFile { + pub hashes: HashMap, + pub url: String, + pub filename: String, + pub primary: bool, + pub size: isize, +} + +#[derive(Deserialize, Debug)] +pub struct Error { + pub error: String, + pub description: String, +} + +impl fmt::Display for Error { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "{}: {}", self.error, self.description) + } +} + +impl std::error::Error for Error {} diff --git a/src/args.rs b/src/args.rs index f334987..3b90a41 100644 --- a/src/args.rs +++ b/src/args.rs @@ -1,6 +1,7 @@ /* * Copyright (c) 2022–2023 Emma Tebibyte * Copyright (c) 2021–2022 Marceline Cramer + * Copyright (c) 2022 Spookdot * SPDX-License-Identifier: AGPL-3.0-or-later * * This file is part of Hopper. diff --git a/src/client.rs b/src/client.rs index e69de29..7534c4b 100644 --- a/src/client.rs +++ b/src/client.rs @@ -0,0 +1,19 @@ +/* + * Copyright (c) 2022–2023 Emma Tebibyte + * Copyright (c) 2021–2022 Marceline Cramer + * Copyright (c) 2022 Spookdot + * SPDX-License-Identifier: AGPL-3.0-or-later + * + * This file is part of Hopper. + * + * Hopper is free software: you can redistribute it and/or modify it under the + * terms of the GNU General Public License as published by the Free Software + * Foundation, either version 3 of the License, or (at your option) any later + * version. + * + * Hopper is distributed in the hope that it will be useful, but WITHOUT ANY + * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR + * A PARTICULAR PURPOSE. See the GNU General Public License for more details. + * You should have received a copy of the GNU General Public License along with + * Hopper. If not, see . + */ diff --git a/src/config.rs b/src/config.rs index 4a77ab0..48311d6 100644 --- a/src/config.rs +++ b/src/config.rs @@ -1,6 +1,7 @@ /* * Copyright (c) 2022–2023 Emma Tebibyte * Copyright (c) 2021–2022 Marceline Cramer + * Copyright (c) 2022 Spookdot * SPDX-License-Identifier: AGPL-3.0-or-later * * This file is part of Hopper. diff --git a/src/main.rs b/src/main.rs index dd94e86..512dc60 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,6 +1,7 @@ /* * Copyright (c) 2022–2023 Emma Tebibyte * Copyright (c) 2021–2022 Marceline Cramer + * Copyright (c) 2022 Spookdot * SPDX-License-Identifier: AGPL-3.0-or-later * * This file is part of Hopper. -- 2.30.2 From ffc028adffb6ce218edd31fb944e3baa38a9f9bf Mon Sep 17 00:00:00 2001 From: emma Date: Wed, 22 Mar 2023 19:50:18 -0400 Subject: [PATCH 04/30] oops --- Cargo.toml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index d51c750..574fb4c 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -5,8 +5,8 @@ license = "AGPL-3.0-or-later" edition = "2021" authors = [ "Emma Tebibyte ", - "Marceline Cramer " - "Spookdot " + "Marceline Cramer ", + "Spookdot ", ] [dependencies] -- 2.30.2 From a22bcde459acd041c3cfa1d24f6ecfb30e9bc683 Mon Sep 17 00:00:00 2001 From: emma Date: Wed, 22 Mar 2023 23:43:53 -0400 Subject: [PATCH 05/30] added hopfile.rs from #22 --- src/hopfile.rs | 137 +++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 137 insertions(+) create mode 100644 src/hopfile.rs diff --git a/src/hopfile.rs b/src/hopfile.rs new file mode 100644 index 0000000..8edd991 --- /dev/null +++ b/src/hopfile.rs @@ -0,0 +1,137 @@ +/* + * Copyright (c) 2022 [ ] + * SPDX-License-Identifier: AGPL-3.0-or-later + * + * This file is part of Hopper. + * + * Hopper is free software: you can redistribute it and/or modify it under the + * terms of the GNU General Public License as published by the Free Software + * Foundation, either version 3 of the License, or (at your option) any later + * version. + * + * Hopper is distributed in the hope that it will be useful, but WITHOUT ANY + * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR + * A PARTICULAR PURPOSE. See the GNU General Public License for more details. + * You should have received a copy of the GNU General Public License along with + * Hopper. If not, see . + */ + +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, + // 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, version: Option) -> 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, + pub resources: Vec, +} + + +#[derive(Debug, Clone, Copy, Default)] +enum Provider { + #[default] + Modrinth, +} + +impl FromStr for Provider { + type Err = (); + + fn from_str(s: &str) -> Result { + 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 { + 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(&self, serializer: S) -> Result + 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(self, v: &str) -> Result + 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(deserializer: D) -> Result + where D: serde::Deserializer<'de> { + deserializer.deserialize_str(V) + } +} -- 2.30.2 From 70b2bb72beac6eb321fe37d83edcb28a90174410 Mon Sep 17 00:00:00 2001 From: emma Date: Thu, 23 Mar 2023 00:17:33 -0400 Subject: [PATCH 06/30] added contribution guidelines --- CONTRIBUTING.md | 104 ++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 104 insertions(+) create mode 100644 CONTRIBUTING.md diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 0000000..77c2be1 --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1,104 @@ +# Contributing + +When contributing a pull request to the main branch, please sign your commits +with a PGP key and add your name and the year to the bottom of the list of +copyright holders for the file. For example, an existing copyright header might +read: + +``` + * Copyright (c) 2022–2023 Emma Tebibyte +``` + +You would add your name below it like this: + +``` + * Copyright (c) 2022–2023 Emma Tebibyte + * Copyright (c) 20XX Your Name +``` + +Only list years in which you worked on the source file. For example: + +``` + * Copyright (c) 2020–2021, 2023 Your Name +``` + +This header shows that `Your Name` worked on this source file in 2020, 2021, and +2023. Please use the en dash (“–”) to separate the years in the copyright +notice. + +If you are contributing a new file, please add the following license header text +to it, replacing the proper text on the copyright line: + +``` +/* + * Copyright (c) 20XX [Your Name] + * SPDX-License-Identifier: AGPL-3.0-or-later + * + * This file is part of Hopper. + * + * Hopper is free software: you can redistribute it and/or modify it under the + * terms of the GNU General Public License as published by the Free Software + * Foundation, either version 3 of the License, or (at your option) any later + * version. + * + * Hopper is distributed in the hope that it will be useful, but WITHOUT ANY + * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR + * A PARTICULAR PURPOSE. See the GNU General Public License for more details. + * You should have received a copy of the GNU General Public License along with + * Hopper. If not, see . + */ +``` + +When including code provided under an AGPLv3-compatible license, please modify +the license notice. The following example contains an Expat (MIT) license +notice: + +``` +/* + * Copyright (c) 20XX [Your Name] + * SPDX-License-Identifier: AGPL-3.0-or-later + * + * This file is part of Hopper. + * + * Hopper is free software: you can redistribute it and/or modify it under the + * terms of the GNU General Public License as published by the Free Software + * Foundation, either version 3 of the License, or (at your option) any later + * version. + * + * Hopper is distributed in the hope that it will be useful, but WITHOUT ANY + * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR + * A PARTICULAR PURPOSE. See the GNU General Public License for more details. + * You should have received a copy of the GNU General Public License along with + * Hopper. If not, see . + * + * This file incorporates work covered by the following copyright and permission + * notice: + * + * MIT License + * + * Copyright (c) + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to permit + * persons to whom the Software is furnished to do so, subject to the + * following conditions: + * + * The above copyright notice and this permission notice (including the next + * paragraph) shall be included in all copies or substantial portions of the + * Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS + * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN + * NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, + * DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR + * OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE + * USE OR OTHER DEALINGS IN THE SOFTWARE. + */ +``` + +When writing code, make sure lines never exceed 80 characters in width when +using four-character-wide tabs. -- 2.30.2 From 8a0315822e9b7a402acb0454c85de79e4e7d4950 Mon Sep 17 00:00:00 2001 From: emma Date: Thu, 23 Mar 2023 00:18:42 -0400 Subject: [PATCH 07/30] fixed copyright headers, rewrote most of the original code --- src/api.rs | 4 +- src/args.rs | 2 +- src/client.rs | 212 +++++++++++++++++++++++++++++++++++++++++++++++++- src/config.rs | 36 +++++---- src/main.rs | 40 +++++++++- 5 files changed, 275 insertions(+), 19 deletions(-) diff --git a/src/api.rs b/src/api.rs index 96526de..f4408ec 100644 --- a/src/api.rs +++ b/src/api.rs @@ -1,6 +1,6 @@ /* - * Copyright (c) 2022–2023 Emma Tebibyte * Copyright (c) 2021–2022 Marceline Cramer + * Copyright (c) 2022–2023 Emma Tebibyte * Copyright (c) 2022 Spookdot * SPDX-License-Identifier: AGPL-3.0-or-later * @@ -20,7 +20,7 @@ use console::style; use serde::Deserialize; -use std::{collections::HashMap, fmt}; +use std::{ collections::HashMap, fmt }; #[derive(Deserialize, Debug)] pub struct SearchResponse { diff --git a/src/args.rs b/src/args.rs index 3b90a41..7688d13 100644 --- a/src/args.rs +++ b/src/args.rs @@ -1,6 +1,6 @@ /* - * Copyright (c) 2022–2023 Emma Tebibyte * Copyright (c) 2021–2022 Marceline Cramer + * Copyright (c) 2022–2023 Emma Tebibyte * Copyright (c) 2022 Spookdot * SPDX-License-Identifier: AGPL-3.0-or-later * diff --git a/src/client.rs b/src/client.rs index 7534c4b..b097b0c 100644 --- a/src/client.rs +++ b/src/client.rs @@ -1,6 +1,6 @@ /* - * Copyright (c) 2022–2023 Emma Tebibyte * Copyright (c) 2021–2022 Marceline Cramer + * Copyright (c) 2022–2023 Emma Tebibyte * Copyright (c) 2022 Spookdot * SPDX-License-Identifier: AGPL-3.0-or-later * @@ -17,3 +17,213 @@ * You should have received a copy of the GNU General Public License along with * Hopper. If not, see . */ + +use crate::{ + api::{ + ModInfo, + ModResult, + ModVersion, + ModVersionFile, + SearchResponse, + Error as APIError, + }, + config::{ + Config, + }, + args::{ + Arguments, + Loader, + PackageType, + Server, + SearchArgs, + }, +}; + +use std::cmp::min; +use std::io::Write; + +use curl::easy::{ Easy2, Handler }; +use futures_util::StreamExt; + +pub struct HopperClient { + config: Config, + client: Easy2, +} + +impl HopperClient { + pub fn new(config: Config) -> Self { + Self { + config: config, + client: Easy2::new() + } + } + + pub async fn search_mods( + &self, + search_args: &SearchArgs, + ) -> Result { + println!("Searching with query “{}”...", search_args.package_name); + + let urls = Vec::new(); + + for entry in self.config.sources.drain() { + let (source, domain) = entry; + urls.push(format!("{}/v2/search", domain)); + } + + let mut params = vec![("query", search_args.package_name.to_owned())]; + let mut facets: Vec = Vec::new(); + if let versions = &search_args.mc_version { + let versions_facets = versions + .iter() + .map(|e| format!("[\"versions:{}\"]", e)) + .collect::>() + .join(","); + facets.push(format!("{}", versions_facets)); + } + if let Some(package_type) = &search_args.package_type { + let project_type = match package_type { + PackageType::Mod(_) => "[\"project_type:mod\"]", + PackageType::Pack(_) => "[\"project_type:modpack\"]", + PackageType::Plugin(_) => "[\"project_type:mod\"]", + PackageType::ResourcePack => "[\"project_type:resourcepack\"]", + }; + + let project_category = match package_type { + PackageType::Mod(kind) | PackageType::Pack(kind) => { + match kind { + Loader::Fabric => "[\"categories:fabric\"]", + Loader::Forge => "[\"categories:forge\"]", + Loader::Quilt => "[\"categories:quilt\"]", + } + }, + PackageType::Plugin(kind) => { + match kind { + Server::Bukkit => "[\"categories:bukkit\"]", + Server::Paper => "[\"categories:paper\"]", + Server::Purpur => "[\"categories:purpur\"]", + Server::Spigot => "[\"categories:spigot\"]", + Server::Sponge => "[\"categories:sponge\"]", + } + }, + }; + + let package_type_facet = format!( + "{},{}", + project_type, + project_category, + ); + + facets.push(package_type_facet); + } + + if !facets.is_empty() { + params.push(("facets", format!("[{}]", facets.join(",")))); + } + + // TODO: Rewrite using curl + let url = reqwest::Url::parse_with_params(url.as_str(), ¶ms)?; + info!("GET {}", url); + let response = self.client.get(url).send().await?; + + if response.status().is_success() { + Ok(response.json::().await?) + } else { + Err(response.json::().await?.into()) + } + + } + pub async fn fetch_mod_info(&self, mod_result: &ModResult) -> anyhow::Result { + let mod_id = &mod_result.project_id; + println!( + "Fetching mod info for {} (ID: {})...", + mod_result.title, mod_id + ); + + let url = format!( + "https://{}/v2/project/{}", + self.config.upstream.server_address, mod_id + ); + info!("GET {}", url); + let response = self.client.get(url).send().await?; + + if response.status().is_success() { + Ok(response.json::().await?) + } else { + Err(response.json::().await?.into()) + } + } + + pub async fn fetch_mod_version(&self, version_id: &String) -> anyhow::Result { + println!("Fetching mod version {}...", version_id); + + let url = format!( + "https://{}/v2/version/{}", + self.config.upstream.server_address, version_id + ); + info!("GET {}", url); + let response = self.client.get(url).send().await?; + + if response.status().is_success() { + Ok(response.json::().await?) + } else { + Err(response.json::().await?.into()) + } + } + + pub async fn download_version_file( + &self, + args: &Args, + file: &ModVersionFile, + ) -> anyhow::Result<()> { + // TODO replace all uses of .unwrap() with proper error codes + let filename = &file.filename; + + // TODO make confirmation skippable with flag argument + if !args.auto_accept { + use dialoguer::Confirm; + let prompt = format!("Download to {}?", filename); + let confirm = Confirm::new() + .with_prompt(prompt) + .default(true) + .interact()?; + if !confirm { + println!("Skipping downloading {}...", filename); + return Ok(()); + } + } + let url = &file.url; + info!("GET {}", url); + let response = self.client.get(url).send().await?; + + if !response.status().is_success() { + return Err(response.json::().await?.into()) + } + + let total_size = response.content_length().unwrap(); + + // TODO better colors and styling! + // TODO square colored creeper face progress indicator (from top-left clockwise spiral in) + use indicatif::{ProgressBar, ProgressStyle}; + let pb = ProgressBar::new(total_size); + pb.set_style(ProgressStyle::default_bar().template("{msg}\n{spinner:.green} [{elapsed_precise}] [{wide_bar:.cyan/blue}] {bytes}/{total_bytes} ({bytes_per_sec}, {eta})").progress_chars("#>-")); + pb.set_message(&format!("Downloading {}", url)); + + let filename = &file.filename; + let mut file = std::fs::File::create(filename)?; + let mut downloaded: u64 = 0; + let mut stream = response.bytes_stream(); + + // TODO check hashes while streaming + while let Some(item) = stream.next().await { + let chunk = &item.unwrap(); + file.write(&chunk)?; + let new = min(downloaded + (chunk.len() as u64), total_size); + downloaded = new; + pb.set_position(new); + } + + pb.finish_with_message(&format!("Downloaded {} to {}", url, filename)); + Ok(()) + } +} diff --git a/src/config.rs b/src/config.rs index 48311d6..04e9b56 100644 --- a/src/config.rs +++ b/src/config.rs @@ -1,6 +1,6 @@ /* - * Copyright (c) 2022–2023 Emma Tebibyte * Copyright (c) 2021–2022 Marceline Cramer + * Copyright (c) 2022–2023 Emma Tebibyte * Copyright (c) 2022 Spookdot * SPDX-License-Identifier: AGPL-3.0-or-later * @@ -22,10 +22,12 @@ use std::{ collections::HashMap, fs::File, io::Read, + path::PathBuf, }; use serde::Deserialize; use toml::de::ValueDeserializer; +use xdg::BaseDirectories; use yacexits::{ EX_DATAERR, EX_UNAVAILABLE, @@ -33,32 +35,38 @@ use yacexits::{ #[derive(Deserialize)] pub struct Config { - hopfiles: Vec, - sources: HashMap, + pub hopfiles: Vec, + pub sources: HashMap, } -pub fn get_config() -> Result<(), (String, u32)> { - let xdg_dirs = match xdg::BaseDirectories::with_prefix("hopper") { - Ok(dirs) => dirs, - Err(err) => { - return Err(( - format!("{:?}", err), +pub fn get_config(dirs: BaseDirectories) -> Result { + match dirs.place_config_file("config.toml") { + Ok(file) => Ok(file), + Err(_) => { + Err(( + format!("Unable to create configuration file."), EX_UNAVAILABLE, - )); + )) }, - }; - Ok(()) + } } impl Config { - pub fn read_config(config_path: String) -> Result { + pub fn read_config(config_path: PathBuf) -> Result { let mut buf: Vec = Vec::new(); let mut config_file = match File::open(&config_path) { Ok(file) => file, Err(_) => { return Err(( - format!("{}: Permission denied.", &config_path), + format!( + "{}: Permission denied.", + config_path + .clone() + .into_os_string() + .into_string() + .unwrap() + ), EX_UNAVAILABLE, )); }, diff --git a/src/main.rs b/src/main.rs index 512dc60..e1e5a11 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,7 +1,8 @@ /* - * Copyright (c) 2022–2023 Emma Tebibyte * Copyright (c) 2021–2022 Marceline Cramer + * Copyright (c) 2022–2023 Emma Tebibyte * Copyright (c) 2022 Spookdot + * Copyright (c) 2022 [ ] * SPDX-License-Identifier: AGPL-3.0-or-later * * This file is part of Hopper. @@ -24,14 +25,51 @@ mod api; mod args; mod client; mod config; +mod hopfile; use api::*; use args::*; use client::*; use config::*; +use hopfile::*; + +use yacexits::{ + exit, + EX_UNAVAILABLE, +}; #[tokio::main] #[no_mangle] async fn rust_main(args: c_main::Args) { let arguments = Arguments::from_args(args.into_iter()); + + let xdg_dirs = match xdg::BaseDirectories::with_prefix("hopper") { + Ok(dirs) => dirs, + Err(err) => { + eprintln!("{:?}", err); + exit(EX_UNAVAILABLE); + }, + }; + + let config_path = match get_config(xdg_dirs) { + Ok(path) => path, + Err((err, code)) => { + eprintln!("{:?}", err); + exit(code); + }, + }; + + let config = match Config::read_config(config_path) { + Ok(file) => file, + Err((err, code)) => { + eprintln!("{:?}", err); + exit(code); + }, + }; + + match ctx.arguments.command { + // Command::Get(search_args) => cmd_get(&ctx, search_args).await, + // Command::Init(hopfile_args) => cmd_init(hopfile_args).await, + _ => unimplemented!("unimplemented subcommand"), + }; } -- 2.30.2 From 5274597ce02df3422972a205eb3e68900e7e0662 Mon Sep 17 00:00:00 2001 From: emma Date: Thu, 23 Mar 2023 00:19:02 -0400 Subject: [PATCH 08/30] added commands.rs --- src/commands.rs | 197 ++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 197 insertions(+) create mode 100644 src/commands.rs diff --git a/src/commands.rs b/src/commands.rs new file mode 100644 index 0000000..8ac9c87 --- /dev/null +++ b/src/commands.rs @@ -0,0 +1,197 @@ +/* + * Copyright (c) 2021–2022 Marceline Cramer + * Copyright (c) 2022 [ ] + * Copyright (c) 2023 Emma Tebibyte + * SPDX-License-Identifier: AGPL-3.0-or-later + * + * This file is part of Hopper. + * + * Hopper is free software: you can redistribute it and/or modify it under the + * terms of the GNU General Public License as published by the Free Software + * Foundation, either version 3 of the License, or (at your option) any later + * version. + * + * Hopper is distributed in the hope that it will be useful, but WITHOUT ANY + * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR + * A PARTICULAR PURPOSE. See the GNU General Public License for more details. + * You should have received a copy of the GNU General Public License along with + * Hopper. If not, see . + */ + +pub async fn cmd_get( + ctx: &AppContext, + search_args: SearchArgs +) -> anyhow::Result<()> { + let client = HopperClient::new(ctx.config.clone()); + let response = client.search_mods(&search_args).await?; + + if response.hits.is_empty() { + // TODO formatting + println!("No results; nothing to do..."); + return Ok(()); + } + + display_search_results(ctx, &response); + let selected = select_from_results(ctx, &response).await?; + + if selected.is_empty() { + // TODO formatting + println!("No packages selected; nothing to do..."); + return Ok(()); + } + + for selection in selected.iter() { + let to_get = &response.hits[*selection]; + let mod_info = client.fetch_mod_info(to_get).await?; + + // TODO allow the user to select multiple versions + if let Some(version_id) = mod_info.versions.first() { + println!("fetching version {}", version_id); + + let version = client.fetch_mod_version(version_id).await?; + for file in version.files.iter() { + client.download_version_file(&ctx.args, file).await?; + } + } + } + + Ok(()) +} + +pub 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(()) + } +} + +fn display_search_results(ctx: &AppContext, response: &SearchResponse) { + let iter = response.hits.iter().enumerate(); + if ctx.config.options.reverse_search { + for (i, result) in iter.rev() { + result.display(i + 1); + } + } else { + for (i, result) in iter { + result.display(i + 1); + } + } +} + +// TODO implement enum for more graceful exiting +async fn select_from_results( + _ctx: &AppContext, + response: &SearchResponse, +) -> anyhow::Result> { + let input: String = dialoguer::Input::new() + .with_prompt("Mods to install (eg: 1 2 3-5)") + .interact_text()?; + + let mut selected: Vec = Vec::new(); + for token in input.split(" ") { + let terms: Vec<&str> = token.split("-").collect(); + + match terms.len() { + 1 => selected.push(terms[0].parse().expect("Token must be an integer")), + 2 => { + let terms: Vec = terms + .iter() + .map(|term| term.parse().expect("Term must be an integer")) + .collect(); + let from = terms[0]; + let to = terms[1]; + + for index in from..=to { + selected.push(index); + } + } + _ => panic!("Invalid selection token {}", token), + } + } + + selected.dedup(); + + let selected = selected + .iter() + .map(|index| { + if *index < 1 || *index > response.hits.len() { + // TODO return useful error instead of panicking + panic!("Index {} is out of bounds", index); + } + + // input is indexed from 1, but results are indexed from 0 + let index = index - 1; + + index + }) + .collect(); + + Ok(selected) +} + +// TODO implement enum for more graceful exiting +async fn select_from_results( + _ctx: &AppContext, + response: &SearchResponse, +) -> anyhow::Result> { + let input: String = dialoguer::Input::new() + .with_prompt("Mods to install (eg: 1 2 3-5)") + .interact_text()?; + + let mut selected: Vec = Vec::new(); + for token in input.split(" ") { + let terms: Vec<&str> = token.split("-").collect(); + + match terms.len() { + 1 => selected.push(terms[0].parse().expect("Token must be an integer")), + 2 => { + let terms: Vec = terms + .iter() + .map(|term| term.parse().expect("Term must be an integer")) + .collect(); + let from = terms[0]; + let to = terms[1]; + + for index in from..=to { + selected.push(index); + } + } + _ => panic!("Invalid selection token {}", token), + } + } + + selected.dedup(); + + let selected = selected + .iter() + .map(|index| { + if *index < 1 || *index > response.hits.len() { + // TODO return useful error instead of panicking + panic!("Index {} is out of bounds", index); + } + + // input is indexed from 1, but results are indexed from 0 + let index = index - 1; + + index + }) + .collect(); + + Ok(selected) +} + -- 2.30.2 From 3aeae3886ae3e575b2137c810144716a379ceb5e Mon Sep 17 00:00:00 2001 From: emma Date: Thu, 23 Mar 2023 00:39:07 -0400 Subject: [PATCH 09/30] added [ ] to authors list in Cargo.toml --- Cargo.toml | 1 + 1 file changed, 1 insertion(+) diff --git a/Cargo.toml b/Cargo.toml index 574fb4c..98ef676 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -4,6 +4,7 @@ version = "0.1.0" license = "AGPL-3.0-or-later" edition = "2021" authors = [ + "[ ] " "Emma Tebibyte ", "Marceline Cramer ", "Spookdot ", -- 2.30.2 From c946310e278f96b3ff546454cd34dc831267d233 Mon Sep 17 00:00:00 2001 From: emma Date: Thu, 23 Mar 2023 01:13:28 -0400 Subject: [PATCH 10/30] updated README --- README.md | 138 ++++++++++++++++++++++++++++++++---------------------- 1 file changed, 82 insertions(+), 56 deletions(-) diff --git a/README.md b/README.md index 4408098..2bcc455 100644 --- a/README.md +++ b/README.md @@ -25,36 +25,36 @@ Hopper can automatically search, download, and update Minecraft mods, modpacks, resource packs, and plugins from [Modrinth](https://modrinth.com/) so that keeping your mods up-to-date and compatible with each other is easy. With Hopper, you won't have to manually visit [CurseForge](https://curseforge.com/) -and download each mod one-by-one every time you set up a new instance, or deal +and download each mod one by one every time you set up a new instance or deal with the hassle of swapping out different mod versions for hours while trying to get Minecraft to accept them all at once. Hopper is still very early in development, but important features are coming along smoothly, and we'll have lots of progress to show off in the coming weeks. -It's written in [Rust](https://www.rust-lang.org/) and released under the -[AGPLv3](LICENSE). +It’s written in [Rust](https://www.rust-lang.org/) and released under the +[GNU AGPLv3](https://www.gnu.org/licenses/agpl-3.0.en.html). -We're looking for people to help contribute code and write documentation. Please -reach out to us in [our Discord "server"](https://discord.gg/jJutHQjsh9) if -you're interested in helping out. If you have a taste in CLI apps like Hopper, +We’re looking for people to help contribute code and write documentation. Please +reach out to us in [our Discord “server”](https://discord.gg/jJutHQjsh9) if +you’re interested in helping out. If you have a taste in CLI apps like Hopper, your input is especially appreciated. Inspired by applications like [paru](https://github.com/morganamilo/paru), a feature-packed AUR helper and [topgrade](https://github.com/r-darwish/topgrade), -a tool to upgrade everything +a tool to upgrade everything. [![Donate using Liberapay](https://liberapay.com/assets/widgets/donate.svg)](https://liberapay.com/tebibytemedia/donate) -# High-level Goals +## High-level Goals -## Continuous +### Continuous - Small binary size - Minimal compile times -## Features +### Features -### High Priority: +High Priority: - Modrinth package searching - Modrinth package installation - Parallel package downloading @@ -62,17 +62,17 @@ Liberapay](https://liberapay.com/assets/widgets/donate.svg)](https://liberapay.c - Package updating - Listing installed packages -### Medium Priority +Medium Priority - CurseForge package searching - CurseForge package installation - A `man(1)` entry -### Low Priority: +Low Priority: - Shell autocomplete - Configurable search result display like [Starship](https://starship.rs) - Version-control system repository package management & compilation -### External-Dependent: +External-Dependent: - Conflict resolution - Dependency resolution - Integration into [Prism Launcher](https://prismlauncher.org/) and/or @@ -81,26 +81,26 @@ Liberapay](https://liberapay.com/assets/widgets/donate.svg)](https://liberapay.c - Graphical frontend with notifications [Modrinth REST API -docs](https://docs.modrinth.com/api-spec/) +documentation](https://docs.modrinth.com/api-spec/) # File Structure ``` ├── "$XDG_CONFIG_HOME"/hopper.toml -├── "$XDG_CACHE_HOME"/hopper/ +├── "$XDG_DATA_HOME"/hopper/ │ ├── 1.19.1/ │ │ └── fabric/ │ └── 1.18.2/ │ ├── forge/ │ └── plugin/ -└── "XDG_DATA_HOME"/templates/ +└── "$XDG_DATA_HOME"/templates/ └── example-template.hop -> ~/.minecraft/mods/example-template.hop ``` # Hopfile Structure Hopfiles will contain a Minecraft version number, a list of packages, and any -references to other hopfiles on which it's based, or "templates". If a hopfile +references to other hopfiles on which it’s based, or “templates”. If a hopfile is based on other template hopfiles, it inherits the packages from them. A hopfile does not inherit the package or Minecraft version from a template. @@ -115,7 +115,7 @@ resource = "alacrity" # Hopper Configuration File Structure -Hopper's configuration will be maintained with a list of all hopfiles known to +Hopper’s configuration will be maintained with a list of all hopfiles known to hopper. Its config file will also contain a list of mod hosting sites like Modrinth and CurseForge and a list of (remote or local) version-control repositories from which to compile mods. The latter will use a (potentially @@ -136,46 +136,64 @@ git = [ ] ``` -# Docs +## Documentation -## Types +### Types There are multiple types of packages hopper can manage. -### Mods +#### Mods - `fabric-mod` - `forge-mod` - `quilt-mod` -### Plugins +#### Modpacks +- `fabric-pack` +- `forge-pack` +- `quilt-pack` + +#### Plugins - `bukkit-plugin` - `paper-plugin` - `purpur-plugin` - `spigot-plugin` - `sponge-plugin` -### Other -- `data-pack` -- `fabric-pack` -- `forge-pack` +#### Other +- `data-pack` (planned) - `resource-pack` -- `quilt-pack` -These types are specified in various hopper subcommands and in its configuration. +These types are specified in various hopper subcommands and in its +configuration. -## Usage +### Usage `hopper [options...] [subcommand...]` -## OPTIONS +### Options `-v`, `--verbose`  Includes debug information in the output of `hopper` subcommands. -## SUBCOMMANDS +## Subcommands -`get [options...] [targets...]` +### `add [options...] [packages...]` + + Adds packages to the current hopfile, symlinking them to its directory. If +the package cannot be found in the package cache, `hopper get` is run first. + +OPTIONS + +  `-f`, `--hopfile [hopfiles...]` + +  Specifies hopfiles to which mods will be added. + + `-m`, `--mc-version [version...]` + +  Overrides the version of Minecraft added packages will be for. + +### `get [options...] [targets...]`  Searches for packages, displays the results, and downloads any selected packages to the local cache. If multiple targets are specified, results are @@ -186,7 +204,7 @@ OPTIONS  `-d`, `--dir [directory...]`   Specifies the directory to download to (default is -"$XDG_CACHE_HOME"/hopper/). +`"$XDG_DATA_HOME"/hopper/`).  `-m`, `--mc-version [version...]` @@ -202,7 +220,7 @@ cache. Requires `--mc-version` and `--type` be specified.   Specifies what types of packages are being queried. -`init [options...]` +### `init [options...]`  Creates a hopfile in the current directory and adds it to the global known hopfiles list. @@ -217,26 +235,16 @@ OPTIONS   Specifies templates upon which to base the new hopfile. - `-m`, `--mc-version [version]` + `-m`, `--mc-version [version...]` -  Specifies for what version of Minecraft packages are being managed. +  Specifies the version of Minecraft packages are being managed for.  `-t`, `--type [type...]`   Specifies what type of packages will be listed in this hopfile. -`install [options...] [packages...]` - Adds packages to the current hopfile, symlinking them to its directory. If -the package cannot be found in the package cache, `hopper get` is run first. - -OPTIONS - -  `-f`, `--hopfile [hopfiles...]` - -  Specifies hopfiles to which mods will be added. - -`list [options...]` +### `list [options...]`  Lists all installed packages. @@ -246,7 +254,7 @@ OPTIONS   Lists packages installed in a specified hopfile. - `-m`, `--mc-version [version]` + `-m`, `--mc-version [version...]`   Specifies for what version of Minecraft packages are being managed. @@ -254,22 +262,40 @@ OPTIONS   List all packages of a specified type. -`update [options...]` +### `remove [options...] [packages...]` - Updates installed packages and adds mods if they're missing to directories -with known hopfiles. + Uninstalls packages. OPTIONS  `-f`, `--hopfile [hopfiles...]` -  Updates only packages in the specified hopfile. Note that this -option creates a new file and symlink as it does not update the packages for -other hopfiles. +  Removes only packages in the specified hopfile.  `-m`, `--mc-version [version]` -  Specifies for what version of Minecraft packages are being updated. +  Specifies the version of Minecraft the packages are being +uninstalled for. + + `-t`, `--type [types...] [packages...]` + +  Removes only packages of a specified type. Optionally takes a list +of packages as an argument. + +### `update [options...]` + + Updates installed packages. This command also adds mods to directories +with known hopfiles if the hopfile lists a mod which is not present. + +OPTIONS + + `-f`, `--hopfile [hopfiles...]` + +  Updates only packages in the specified hopfile. + + `-m`, `--mc-version [version...]` + +  Specifies the version of Minecraft packages are being updated for.  `-t`, `--type [types...] [packages...]` -- 2.30.2 From 5f92afb375ac9e7d78f3eb8d534fe64d7e47ff5e Mon Sep 17 00:00:00 2001 From: emma Date: Thu, 23 Mar 2023 01:35:01 -0400 Subject: [PATCH 11/30] changed config file name --- src/config.rs | 2 +- src/main.rs | 7 +++++++ 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/src/config.rs b/src/config.rs index 04e9b56..1701fcc 100644 --- a/src/config.rs +++ b/src/config.rs @@ -40,7 +40,7 @@ pub struct Config { } pub fn get_config(dirs: BaseDirectories) -> Result { - match dirs.place_config_file("config.toml") { + match dirs.place_config_file("hopper.toml") { Ok(file) => Ok(file), Err(_) => { Err(( diff --git a/src/main.rs b/src/main.rs index e1e5a11..c6c75e0 100644 --- a/src/main.rs +++ b/src/main.rs @@ -38,6 +38,11 @@ use yacexits::{ EX_UNAVAILABLE, }; +struct AppContext { + args: Arguments, + config: Config, +} + #[tokio::main] #[no_mangle] async fn rust_main(args: c_main::Args) { @@ -67,6 +72,8 @@ async fn rust_main(args: c_main::Args) { }, }; + let ctx = AppContext { args, config }; + match ctx.arguments.command { // Command::Get(search_args) => cmd_get(&ctx, search_args).await, // Command::Init(hopfile_args) => cmd_init(hopfile_args).await, -- 2.30.2 From 7b7207b6ba1c49e2a2478bafc11452ab32c64244 Mon Sep 17 00:00:00 2001 From: emma Date: Thu, 23 Mar 2023 01:40:49 -0400 Subject: [PATCH 12/30] more pieces from #22 --- src/commands.rs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/commands.rs b/src/commands.rs index 8ac9c87..247b855 100644 --- a/src/commands.rs +++ b/src/commands.rs @@ -18,6 +18,9 @@ * Hopper. If not, see . */ +use tokio::fs::File; +use tokio::io::AsyncWriteExt; + pub async fn cmd_get( ctx: &AppContext, search_args: SearchArgs -- 2.30.2 From 8d0b82149da065ce9b753a3545dcf3dd8877a391 Mon Sep 17 00:00:00 2001 From: emma Date: Thu, 23 Mar 2023 02:29:48 -0400 Subject: [PATCH 13/30] oops! --- Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Cargo.toml b/Cargo.toml index 98ef676..225a24b 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -4,7 +4,7 @@ version = "0.1.0" license = "AGPL-3.0-or-later" edition = "2021" authors = [ - "[ ] " + "[ ] ", "Emma Tebibyte ", "Marceline Cramer ", "Spookdot ", -- 2.30.2 From 261b40cc0b1f079696b1ddb520c97ccec2677fda Mon Sep 17 00:00:00 2001 From: emma Date: Fri, 24 Mar 2023 14:50:10 -0400 Subject: [PATCH 14/30] client.rs: changed PackageType parsing to fix compilation and removed reqwest-based code; main.rs, args.rs: cleanup --- src/args.rs | 8 ++-- src/client.rs | 121 ++++---------------------------------------------- src/main.rs | 6 +-- 3 files changed, 16 insertions(+), 119 deletions(-) diff --git a/src/args.rs b/src/args.rs index 7688d13..8be043b 100644 --- a/src/args.rs +++ b/src/args.rs @@ -85,7 +85,7 @@ pub enum Command { Update(HopArgs), } -#[derive(Debug)] +#[derive(Clone, Copy, Debug)] pub enum PackageType { Mod(Loader), Pack(Loader), @@ -93,14 +93,14 @@ pub enum PackageType { ResourcePack, } -#[derive(Debug)] +#[derive(Clone, Copy, Debug)] pub enum Loader { Fabric, Forge, Quilt, } -#[derive(Debug)] +#[derive(Clone, Copy, Debug)] pub enum Server { Bukkit, Paper, @@ -109,7 +109,7 @@ pub enum Server { Sponge, } -#[derive(Debug)] +#[derive(Clone, Debug)] pub enum PackageParseError { Invalid(String), } diff --git a/src/client.rs b/src/client.rs index b097b0c..f00ddf8 100644 --- a/src/client.rs +++ b/src/client.rs @@ -42,29 +42,30 @@ use crate::{ use std::cmp::min; use std::io::Write; -use curl::easy::{ Easy2, Handler }; +use curl::easy::{ Easy }; use futures_util::StreamExt; pub struct HopperClient { config: Config, - client: Easy2, + client: Easy, } impl HopperClient { pub fn new(config: Config) -> Self { + curl::init(); Self { config: config, - client: Easy2::new() + client: Easy::new() } } pub async fn search_mods( - &self, + &mut self, search_args: &SearchArgs, - ) -> Result { + ) /*-> Result*/ { println!("Searching with query “{}”...", search_args.package_name); - let urls = Vec::new(); + let mut urls = Vec::new(); for entry in self.config.sources.drain() { let (source, domain) = entry; @@ -81,7 +82,7 @@ impl HopperClient { .join(","); facets.push(format!("{}", versions_facets)); } - if let Some(package_type) = &search_args.package_type { + if let Some(package_type) = search_args.package_type { let project_type = match package_type { PackageType::Mod(_) => "[\"project_type:mod\"]", PackageType::Pack(_) => "[\"project_type:modpack\"]", @@ -106,6 +107,7 @@ impl HopperClient { Server::Sponge => "[\"categories:sponge\"]", } }, + PackageType::ResourcePack => "", }; let package_type_facet = format!( @@ -120,110 +122,5 @@ impl HopperClient { if !facets.is_empty() { params.push(("facets", format!("[{}]", facets.join(",")))); } - - // TODO: Rewrite using curl - let url = reqwest::Url::parse_with_params(url.as_str(), ¶ms)?; - info!("GET {}", url); - let response = self.client.get(url).send().await?; - - if response.status().is_success() { - Ok(response.json::().await?) - } else { - Err(response.json::().await?.into()) - } - - } - pub async fn fetch_mod_info(&self, mod_result: &ModResult) -> anyhow::Result { - let mod_id = &mod_result.project_id; - println!( - "Fetching mod info for {} (ID: {})...", - mod_result.title, mod_id - ); - - let url = format!( - "https://{}/v2/project/{}", - self.config.upstream.server_address, mod_id - ); - info!("GET {}", url); - let response = self.client.get(url).send().await?; - - if response.status().is_success() { - Ok(response.json::().await?) - } else { - Err(response.json::().await?.into()) - } - } - - pub async fn fetch_mod_version(&self, version_id: &String) -> anyhow::Result { - println!("Fetching mod version {}...", version_id); - - let url = format!( - "https://{}/v2/version/{}", - self.config.upstream.server_address, version_id - ); - info!("GET {}", url); - let response = self.client.get(url).send().await?; - - if response.status().is_success() { - Ok(response.json::().await?) - } else { - Err(response.json::().await?.into()) - } - } - - pub async fn download_version_file( - &self, - args: &Args, - file: &ModVersionFile, - ) -> anyhow::Result<()> { - // TODO replace all uses of .unwrap() with proper error codes - let filename = &file.filename; - - // TODO make confirmation skippable with flag argument - if !args.auto_accept { - use dialoguer::Confirm; - let prompt = format!("Download to {}?", filename); - let confirm = Confirm::new() - .with_prompt(prompt) - .default(true) - .interact()?; - if !confirm { - println!("Skipping downloading {}...", filename); - return Ok(()); - } - } - let url = &file.url; - info!("GET {}", url); - let response = self.client.get(url).send().await?; - - if !response.status().is_success() { - return Err(response.json::().await?.into()) - } - - let total_size = response.content_length().unwrap(); - - // TODO better colors and styling! - // TODO square colored creeper face progress indicator (from top-left clockwise spiral in) - use indicatif::{ProgressBar, ProgressStyle}; - let pb = ProgressBar::new(total_size); - pb.set_style(ProgressStyle::default_bar().template("{msg}\n{spinner:.green} [{elapsed_precise}] [{wide_bar:.cyan/blue}] {bytes}/{total_bytes} ({bytes_per_sec}, {eta})").progress_chars("#>-")); - pb.set_message(&format!("Downloading {}", url)); - - let filename = &file.filename; - let mut file = std::fs::File::create(filename)?; - let mut downloaded: u64 = 0; - let mut stream = response.bytes_stream(); - - // TODO check hashes while streaming - while let Some(item) = stream.next().await { - let chunk = &item.unwrap(); - file.write(&chunk)?; - let new = min(downloaded + (chunk.len() as u64), total_size); - downloaded = new; - pb.set_position(new); - } - - pb.finish_with_message(&format!("Downloaded {} to {}", url, filename)); - Ok(()) } } diff --git a/src/main.rs b/src/main.rs index c6c75e0..59ffbc3 100644 --- a/src/main.rs +++ b/src/main.rs @@ -45,8 +45,8 @@ struct AppContext { #[tokio::main] #[no_mangle] -async fn rust_main(args: c_main::Args) { - let arguments = Arguments::from_args(args.into_iter()); +async fn rust_main(arguments: c_main::Args) { + let args = Arguments::from_args(arguments.into_iter()).unwrap(); let xdg_dirs = match xdg::BaseDirectories::with_prefix("hopper") { Ok(dirs) => dirs, @@ -74,7 +74,7 @@ async fn rust_main(args: c_main::Args) { let ctx = AppContext { args, config }; - match ctx.arguments.command { + match ctx.args.sub { // Command::Get(search_args) => cmd_get(&ctx, search_args).await, // Command::Init(hopfile_args) => cmd_init(hopfile_args).await, _ => unimplemented!("unimplemented subcommand"), -- 2.30.2 From 53bd5ce4b3042de338e18e6dfccdeb3270f835d3 Mon Sep 17 00:00:00 2001 From: emma Date: Sat, 25 Mar 2023 05:03:27 -0400 Subject: [PATCH 15/30] made usage info, fixed configuration parsing, and made a better error for unimplemented subcommands --- src/args.rs | 22 ++++++++++++++++++++-- src/client.rs | 5 ++--- src/config.rs | 49 +++++++++++++++++++++++-------------------------- src/main.rs | 49 +++++++++++++++++++++++++++++++++++++++---------- 4 files changed, 84 insertions(+), 41 deletions(-) diff --git a/src/args.rs b/src/args.rs index 8be043b..e8695ab 100644 --- a/src/args.rs +++ b/src/args.rs @@ -18,14 +18,19 @@ * Hopper. If not, see . */ -use core::str::FromStr; +use core::{ + fmt, + str::FromStr, +}; pub use arg::Args; #[derive(Args, Debug)] pub struct Arguments { + pub argv0: String, + #[arg(short = "v")] - pub v: bool, + pub v: Option, #[arg(sub)] pub sub: Command, @@ -85,6 +90,19 @@ pub enum Command { Update(HopArgs), } +impl fmt::Display for Command { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match &self { + Command::Add(_) => write!(f, "add"), + Command::Get(_) => write!(f, "get"), + Command::Init(_) => write!(f, "init"), + Command::List(_) => write!(f, "list"), + Command::Remove(_) => write!(f, "remove"), + Command::Update(_) => write!(f, "update"), + } + } +} + #[derive(Clone, Copy, Debug)] pub enum PackageType { Mod(Loader), diff --git a/src/client.rs b/src/client.rs index f00ddf8..494589c 100644 --- a/src/client.rs +++ b/src/client.rs @@ -67,9 +67,8 @@ impl HopperClient { let mut urls = Vec::new(); - for entry in self.config.sources.drain() { - let (source, domain) = entry; - urls.push(format!("{}/v2/search", domain)); + for entry in self.config.sources.modrinth.iter() { + urls.push(format!("{}/v2/search", entry)); } let mut params = vec![("query", search_args.package_name.to_owned())]; diff --git a/src/config.rs b/src/config.rs index 1701fcc..5b71de5 100644 --- a/src/config.rs +++ b/src/config.rs @@ -19,14 +19,12 @@ */ use std::{ - collections::HashMap, fs::File, io::Read, path::PathBuf, }; use serde::Deserialize; -use toml::de::ValueDeserializer; use xdg::BaseDirectories; use yacexits::{ EX_DATAERR, @@ -36,11 +34,16 @@ use yacexits::{ #[derive(Deserialize)] pub struct Config { pub hopfiles: Vec, - pub sources: HashMap, + pub sources: Sources, +} + +#[derive(Deserialize)] +pub struct Sources { + pub modrinth: Vec, } pub fn get_config(dirs: BaseDirectories) -> Result { - match dirs.place_config_file("hopper.toml") { + match dirs.place_config_file("config.toml") { Ok(file) => Ok(file), Err(_) => { Err(( @@ -59,21 +62,18 @@ impl Config { Ok(file) => file, Err(_) => { return Err(( - format!( - "{}: Permission denied.", - config_path - .clone() - .into_os_string() - .into_string() - .unwrap() - ), + format!("{}: Permission denied.", config_path.display()), EX_UNAVAILABLE, )); }, }; - match config_file.read_to_end(&mut buf) { - Ok(_) => {}, + if let Some(err) = config_file.read_to_end(&mut buf).err() { + return Err((format!("{:?}", err), EX_DATAERR)); + }; + + let toml = match String::from_utf8(buf) { + Ok(contents) => contents, Err(err) => { return Err(( format!("{:?}", err), @@ -82,19 +82,16 @@ impl Config { }, }; - let toml = match String::from_utf8(buf) { - Ok(contents) => contents, - Err(_) => { - return Err(( - format!("Invalid configuration file."), - EX_DATAERR, - )); - }, - }; - - match Config::deserialize(ValueDeserializer::new(&toml)) { + match toml::from_str(&toml) { Ok(val) => Ok(val), - Err(err) => Err((format!("{:?}", err), EX_DATAERR)), + Err(_) => { + Err(( + format!( + "{}: Invalid configuration file.", config_path.display() + ), + EX_DATAERR, + )) + }, } } } diff --git a/src/main.rs b/src/main.rs index 59ffbc3..f6ca02e 100644 --- a/src/main.rs +++ b/src/main.rs @@ -35,7 +35,9 @@ use hopfile::*; use yacexits::{ exit, + EX_SOFTWARE, EX_UNAVAILABLE, + EX_USAGE, }; struct AppContext { @@ -46,20 +48,40 @@ struct AppContext { #[tokio::main] #[no_mangle] async fn rust_main(arguments: c_main::Args) { - let args = Arguments::from_args(arguments.into_iter()).unwrap(); + let argv: Vec<&str> = arguments.into_iter().collect(); + let args: Arguments; - let xdg_dirs = match xdg::BaseDirectories::with_prefix("hopper") { - Ok(dirs) => dirs, - Err(err) => { - eprintln!("{:?}", err); + let usage_info = format!( + "Usage: {}{}", + argv[0], + " [-v] add | get | init | list | remove | update \n\n".to_owned() + + "add -f hopfiles... [-m version]\n" + + "get [-d directory] -m versions... [-n] [-t types...]\n" + + "init -d directory [-f hopfiles...] -m version -t type\n" + + "list [-f hopfiles...] [-m versions...] [-t types...]\n" + + "remove [-f hopfiles...] -m version -t type\n" + + "update [-f hopfiles...] [-m versions...] [-t types...]", + ); + + + args = Arguments::from_args(argv.clone().into_iter()).unwrap_or_else(|_| { + eprintln!("{}", usage_info); + exit(EX_USAGE); + }); + + let xdg_dirs = xdg::BaseDirectories::with_prefix("hopper") + .unwrap_or_else(|_| { + eprintln!( + "{}: Unable to open configuration file: Permission denied.", + argv[0], + ); exit(EX_UNAVAILABLE); - }, - }; + }); let config_path = match get_config(xdg_dirs) { Ok(path) => path, Err((err, code)) => { - eprintln!("{:?}", err); + eprintln!("{}: {}", argv[0], err); exit(code); }, }; @@ -67,7 +89,7 @@ async fn rust_main(arguments: c_main::Args) { let config = match Config::read_config(config_path) { Ok(file) => file, Err((err, code)) => { - eprintln!("{:?}", err); + eprintln!("{}: {}", argv[0], err); exit(code); }, }; @@ -77,6 +99,13 @@ async fn rust_main(arguments: c_main::Args) { match ctx.args.sub { // Command::Get(search_args) => cmd_get(&ctx, search_args).await, // Command::Init(hopfile_args) => cmd_init(hopfile_args).await, - _ => unimplemented!("unimplemented subcommand"), + _ => { + eprintln!( + "{}: {}: Unimplemented subcommand.", + argv[0], + ctx.args.sub + ); + exit(EX_SOFTWARE); + }, }; } -- 2.30.2 From b01064e615fc4b4c09933f17269a82e4830666c0 Mon Sep 17 00:00:00 2001 From: emma Date: Sat, 25 Mar 2023 05:58:45 -0400 Subject: [PATCH 16/30] updated usage text --- src/main.rs | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/src/main.rs b/src/main.rs index f6ca02e..e292e39 100644 --- a/src/main.rs +++ b/src/main.rs @@ -54,13 +54,13 @@ async fn rust_main(arguments: c_main::Args) { let usage_info = format!( "Usage: {}{}", argv[0], - " [-v] add | get | init | list | remove | update \n\n".to_owned() + - "add -f hopfiles... [-m version]\n" + - "get [-d directory] -m versions... [-n] [-t types...]\n" + - "init -d directory [-f hopfiles...] -m version -t type\n" + - "list [-f hopfiles...] [-m versions...] [-t types...]\n" + - "remove [-f hopfiles...] -m version -t type\n" + - "update [-f hopfiles...] [-m versions...] [-t types...]", + " [-v] add | get | init | list | remove | update\n\n".to_owned() + + "add [-m version] [-f hopfiles...] packages...\n" + + "get [-n] [-d directory] [-m versions...] [-t types...] packages\n" + + "init [-f hopfiles...] version type\n" + + "list [[-f hopfiles...] | [-m versions...] [-t types...]]\n" + + "remove [[-f hopfiles...] | type version]] packages...\n" + + "update [[-f hopfiles... | [-m versions...] [-t types...]]", ); -- 2.30.2 From 0f1561e0c4e8be1d1c8ec943832d25024511abe2 Mon Sep 17 00:00:00 2001 From: emma Date: Sat, 25 Mar 2023 06:11:43 -0400 Subject: [PATCH 17/30] args.rs, client.rs: changed to reflect changes to usage text --- src/args.rs | 96 +++++++++++++++++++++++++++++++++------------------ src/client.rs | 4 ++- 2 files changed, 65 insertions(+), 35 deletions(-) diff --git a/src/args.rs b/src/args.rs index e8695ab..38e9a6d 100644 --- a/src/args.rs +++ b/src/args.rs @@ -37,24 +37,30 @@ pub struct Arguments { } #[derive(Args, Debug)] -pub struct InitArgs { - #[arg(short = "d")] - pub dir: Option, +pub enum Command { + Add(AddArgs), + Get(SearchArgs), + Init(InitArgs), + List(HopArgs), + Remove(RmArgs), + Update(HopArgs), +} + +#[derive(Args, Debug)] +pub struct AddArgs { + #[arg(short = "m")] + pub mc_version: String, #[arg(short = "f")] - pub template: Option, + pub hopfiles: Vec, - #[arg(short = "m")] - pub mc_version: Vec, - - #[arg(short = "t", required)] - pub package_type: PackageType, + pub package_names: Vec, } #[derive(Args, Debug)] pub struct HopArgs { #[arg(short = "f")] - pub hopfile: Option, + pub hopfile: Vec, #[arg(short = "m")] pub mc_version: Vec, @@ -63,9 +69,32 @@ pub struct HopArgs { pub package_type: Option, } +#[derive(Args, Debug)] +pub struct InitArgs { + #[arg(short = "f")] + pub template: Option, + + pub mc_version: String, + + pub package_type: PackageType, +} + +#[derive(Args, Debug)] +pub struct RmArgs { + #[arg(short = "f")] + pub hopfile: Option, + + pub package_type: PackageType, + + pub mc_version: String, + + pub package_names: Vec, +} + #[derive(Args, Debug)] pub struct SearchArgs { - pub package_name: String, + #[arg(short = "n")] + pub no_confirm: bool, /// Overrides the download directory #[arg(short = "d")] @@ -77,34 +106,14 @@ pub struct SearchArgs { /// Type of package to use #[arg(short = "t")] - pub package_type: Option, -} + pub package_type: PackageType, -#[derive(Args, Debug)] -pub enum Command { - Add(SearchArgs), - Get(SearchArgs), - Init(InitArgs), - List(HopArgs), - Remove(HopArgs), - Update(HopArgs), -} - -impl fmt::Display for Command { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - match &self { - Command::Add(_) => write!(f, "add"), - Command::Get(_) => write!(f, "get"), - Command::Init(_) => write!(f, "init"), - Command::List(_) => write!(f, "list"), - Command::Remove(_) => write!(f, "remove"), - Command::Update(_) => write!(f, "update"), - } - } + pub package_name: String, } #[derive(Clone, Copy, Debug)] pub enum PackageType { + Dummy, Mod(Loader), Pack(Loader), Plugin(Server), @@ -127,11 +136,30 @@ pub enum Server { Sponge, } +impl fmt::Display for Command { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match &self { + Command::Add(_) => write!(f, "add"), + Command::Get(_) => write!(f, "get"), + Command::Init(_) => write!(f, "init"), + Command::List(_) => write!(f, "list"), + Command::Remove(_) => write!(f, "remove"), + Command::Update(_) => write!(f, "update"), + } + } +} + #[derive(Clone, Debug)] pub enum PackageParseError { Invalid(String), } +impl std::default::Default for PackageType { //TODO: Actually implement Default + fn default() -> Self { // for PackageType + PackageType::Dummy + } +} + impl FromStr for PackageType { type Err = PackageParseError; fn from_str(s: &str) -> Result { diff --git a/src/client.rs b/src/client.rs index 494589c..501e273 100644 --- a/src/client.rs +++ b/src/client.rs @@ -81,8 +81,9 @@ impl HopperClient { .join(","); facets.push(format!("{}", versions_facets)); } - if let Some(package_type) = search_args.package_type { + if let package_type = search_args.package_type { let project_type = match package_type { + PackageType::Dummy => "", PackageType::Mod(_) => "[\"project_type:mod\"]", PackageType::Pack(_) => "[\"project_type:modpack\"]", PackageType::Plugin(_) => "[\"project_type:mod\"]", @@ -90,6 +91,7 @@ impl HopperClient { }; let project_category = match package_type { + PackageType::Dummy => "", PackageType::Mod(kind) | PackageType::Pack(kind) => { match kind { Loader::Fabric => "[\"categories:fabric\"]", -- 2.30.2 From c2f6af517c74eb0dff47c466ebf2013bdc390b14 Mon Sep 17 00:00:00 2001 From: emma Date: Sat, 25 Mar 2023 06:41:14 -0400 Subject: [PATCH 18/30] main.rs: minor tweaks --- src/main.rs | 35 ++++++++++++++++++++--------------- 1 file changed, 20 insertions(+), 15 deletions(-) diff --git a/src/main.rs b/src/main.rs index e292e39..f8c159e 100644 --- a/src/main.rs +++ b/src/main.rs @@ -49,7 +49,6 @@ struct AppContext { #[no_mangle] async fn rust_main(arguments: c_main::Args) { let argv: Vec<&str> = arguments.into_iter().collect(); - let args: Arguments; let usage_info = format!( "Usage: {}{}", @@ -64,30 +63,36 @@ async fn rust_main(arguments: c_main::Args) { ); - args = Arguments::from_args(argv.clone().into_iter()).unwrap_or_else(|_| { + let args = Arguments::from_args( + argv + .clone() + .into_iter() + ).unwrap_or_else(|_| { eprintln!("{}", usage_info); exit(EX_USAGE); }); - let xdg_dirs = xdg::BaseDirectories::with_prefix("hopper") - .unwrap_or_else(|_| { + let xdg_basedirs = xdg::BaseDirectories::with_prefix("hopper"); + + // this might be cursed; I haven’t decided + let config = match get_config( + xdg_basedirs.unwrap_or_else(|_| { eprintln!( "{}: Unable to open configuration file: Permission denied.", argv[0], ); exit(EX_UNAVAILABLE); - }); - - let config_path = match get_config(xdg_dirs) { - Ok(path) => path, - Err((err, code)) => { - eprintln!("{}: {}", argv[0], err); - exit(code); + }) + ) { + Ok(path) => { + match Config::read_config(path) { + Ok(file) => file, + Err((err, code)) => { + eprintln!("{}: {}", argv[0], err); + exit(code); + }, + } }, - }; - - let config = match Config::read_config(config_path) { - Ok(file) => file, Err((err, code)) => { eprintln!("{}: {}", argv[0], err); exit(code); -- 2.30.2 From a0453f1cc30847db02634a41fe5e9096e6112862 Mon Sep 17 00:00:00 2001 From: emma Date: Sat, 25 Mar 2023 19:21:15 -0400 Subject: [PATCH 19/30] updated deps --- Cargo.lock | 372 +++++++++++++++++++++++++------------------------- src/client.rs | 2 +- 2 files changed, 190 insertions(+), 184 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index ec4fdc4..9423e02 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -24,9 +24,9 @@ dependencies = [ [[package]] name = "autocfg" -version = "1.0.1" +version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cdb031dd78e28731d87d56cc8ffef4a8f36ca26c38fe2de700543e627f8a464a" +checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" [[package]] name = "bindgen" @@ -58,9 +58,9 @@ checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" [[package]] name = "bytes" -version = "1.1.0" +version = "1.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c4872d67bab6358e59559027aa3b9157c53d9358c51423c17554809a8858e0f8" +checksum = "89b2fd2a0dcf38d7971e2194b6b6eebab45ae01067456a7fd93d5547a61b70be" [[package]] name = "c-main" @@ -105,17 +105,15 @@ dependencies = [ [[package]] name = "console" -version = "0.15.0" +version = "0.15.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a28b32d32ca44b70c3e4acd7db1babf555fa026e385fb95f18028f88848b3c31" +checksum = "c3d79fbe8970a77e3e34151cc13d3b3e248aa0faaecb9f6091fa07ebefe5ad60" dependencies = [ "encode_unicode", + "lazy_static", "libc", - "once_cell", - "regex", - "terminal_size", "unicode-width", - "winapi", + "windows-sys 0.42.0", ] [[package]] @@ -193,16 +191,46 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a357d28ed41a50f9c765dbfe56cbc04a64e53e5fc58ba79fbc34c10ef3df831f" [[package]] -name = "futures-core" -version = "0.3.18" +name = "errno" +version = "0.2.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "629316e42fe7c2a0b9a65b47d159ceaa5453ab14e8f0a3c5eedbb8cd55b4a445" +checksum = "f639046355ee4f37944e44f60642c6f3a7efa3cf6b78c78a0d989a8ce6c396a1" +dependencies = [ + "errno-dragonfly", + "libc", + "winapi", +] + +[[package]] +name = "errno-dragonfly" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aa68f1b12764fab894d2755d2518754e71b4fd80ecfb822714a1206c2aab39bf" +dependencies = [ + "cc", + "libc", +] + +[[package]] +name = "fastrand" +version = "1.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e51093e27b0797c359783294ca4f0a911c270184cb10f85783b118614a1501be" +dependencies = [ + "instant", +] + +[[package]] +name = "futures-core" +version = "0.3.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "86d7a0c1aa76363dac491de0ee99faf6941128376f1cf96f07db7603b7de69dd" [[package]] name = "futures-macro" -version = "0.3.18" +version = "0.3.27" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a89f17b21645bc4ed773c69af9c9a0effd4a3f1a3876eadd453469f8854e7fdd" +checksum = "3eb14ed937631bd8b8b8977f2c198443447a8355b6e3ca599f38c975e5a963b6" dependencies = [ "proc-macro2", "quote", @@ -211,15 +239,15 @@ dependencies = [ [[package]] name = "futures-task" -version = "0.3.18" +version = "0.3.27" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dabf1872aaab32c886832f2276d2f5399887e2bd613698a02359e4ea83f8de12" +checksum = "fd65540d33b37b16542a0438c12e6aeead10d4ac5d05bd3f805b8f35ab592879" [[package]] name = "futures-util" -version = "0.3.18" +version = "0.3.27" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "41d22213122356472061ac0f1ab2cee28d2bac8491410fd68c2af53d1cedb83e" +checksum = "3ef6b17e481503ec85211fed8f39d1970f128935ca1f814cd32ac4a6842e84ab" dependencies = [ "futures-core", "futures-macro", @@ -231,9 +259,9 @@ dependencies = [ [[package]] name = "getrandom" -version = "0.2.3" +version = "0.2.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7fcd999463524c52659517fe2cea98493cfe485d10565e7b0fb07dbba7ad2753" +checksum = "c05aeb6a22b8f62540c194aac980f2115af067bfe15a0734d7277a768d396b31" dependencies = [ "cfg-if", "libc", @@ -254,13 +282,19 @@ checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888" [[package]] name = "hermit-abi" -version = "0.1.19" +version = "0.2.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "62b467343b94ba476dcb2500d242dadbb39557df889310ac77c5d99100aaac33" +checksum = "ee512640fe35acbfb4bb779db6f0d80704c2cacfa2e39b601ef3e3f47d1ae4c7" dependencies = [ "libc", ] +[[package]] +name = "hermit-abi" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fed44880c466736ef9a5c5b5facefb5ed0785676d0c02d612db14e54f0d84286" + [[package]] name = "hopper" version = "0.1.0" @@ -282,9 +316,9 @@ dependencies = [ [[package]] name = "indexmap" -version = "1.9.2" +version = "1.9.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1885e79c1fc4b10f0e172c475f458b7f7b93061064d98c3293e98c5ba0c8b399" +checksum = "bd070e393353796e801d209ad339e89596eb4c8d430d18ede6a1cced8fafbd99" dependencies = [ "autocfg", "hashbrown", @@ -312,10 +346,21 @@ dependencies = [ ] [[package]] -name = "itoa" -version = "0.4.8" +name = "io-lifetimes" +version = "1.0.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b71991ff56294aa922b450139ee08b3bfc70982c6b2c7562771375cf73542dd4" +checksum = "09270fd4fa1111bc614ed2246c7ef56239a3063d5be0d1ec3b589c505d400aeb" +dependencies = [ + "hermit-abi 0.3.1", + "libc", + "windows-sys 0.45.0", +] + +[[package]] +name = "itoa" +version = "1.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "453ad9f582a441959e5f0d088b02ce04cfe8d51a8eaf077f12ac6d3e94164ca6" [[package]] name = "lazy_static" @@ -358,28 +403,35 @@ dependencies = [ ] [[package]] -name = "lock_api" -version = "0.4.5" +name = "linux-raw-sys" +version = "0.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "712a4d093c9976e24e7dbca41db895dabcbac38eb5f4045393d17a95bdfb1109" +checksum = "f051f77a7c8e6957c0696eac88f26b0117e54f52d3fc682ab19397a8812846a4" + +[[package]] +name = "lock_api" +version = "0.4.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "435011366fe56583b16cf956f9df0095b405b82d76425bc8981c0e22e60ec4df" dependencies = [ + "autocfg", "scopeguard", ] [[package]] name = "log" -version = "0.4.14" +version = "0.4.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "51b9bbe6c47d51fc3e1a9b945965946b4c44142ab8792c50835a980d362c2710" +checksum = "abb12e687cfb44aa40f41fc3978ef76448f9b6038cad6aef4259d3c095a2382e" dependencies = [ "cfg-if", ] [[package]] name = "memchr" -version = "2.4.1" +version = "2.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "308cc39be01b73d0d18f82a0e7b2a3df85245f84af96fdddc5d202d27e47b86a" +checksum = "2dffe52ecf27772e601905b7522cb4ef790d2cc203488bbd0e2fe85fcb74566d" [[package]] name = "minimal-lexical" @@ -389,24 +441,14 @@ checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a" [[package]] name = "mio" -version = "0.7.14" +version = "0.8.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8067b404fe97c70829f082dec8bcf4f71225d7eaea1d8645349cb76fa06205cc" +checksum = "5b9d9a46eff5b4ff64b45a9e316a6d1e0bc719ef429cbec4dc630684212bfdf9" dependencies = [ "libc", "log", - "miow", - "ntapi", - "winapi", -] - -[[package]] -name = "miow" -version = "0.3.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b9f1c5b025cda876f66ef43a113f91ebc9f4ccef34843000e0adf6ebbab84e21" -dependencies = [ - "winapi", + "wasi", + "windows-sys 0.45.0", ] [[package]] @@ -419,22 +461,13 @@ dependencies = [ "minimal-lexical", ] -[[package]] -name = "ntapi" -version = "0.3.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3f6bb902e437b6d86e03cce10a7e2af662292c5dfef23b65899ea3ac9354ad44" -dependencies = [ - "winapi", -] - [[package]] name = "num_cpus" -version = "1.13.0" +version = "1.15.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "05499f3756671c15885fee9034446956fff3f243d6077b91e5767df161f766b3" +checksum = "0fac9e2da13b5eb447a6ce3d392f23a29d8694bff781bf03a16cd9ac8697593b" dependencies = [ - "hermit-abi", + "hermit-abi 0.2.6", "libc", ] @@ -446,9 +479,9 @@ checksum = "17b02fc0ff9a9e4b35b3342880f48e896ebf69f2967921fe8646bf5b7125956a" [[package]] name = "once_cell" -version = "1.14.0" +version = "1.17.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2f7254b99e31cad77da24b08ebf628882739a608578bb1bcdfc1f9c21260d7c0" +checksum = "b7e5500299e16ebb147ae15a00a942af264cf3688f47923b8fc2cd5858f23ad3" [[package]] name = "openssl-probe" @@ -458,9 +491,9 @@ checksum = "ff011a302c396a5197692431fc1948019154afc178baf7d8e37367442a4601cf" [[package]] name = "openssl-sys" -version = "0.9.82" +version = "0.9.83" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a95792af3c4e0153c3914df2261bedd30a98476f94dc892b67dfe1d89d433a04" +checksum = "666416d899cf077260dac8698d60a60b435a46d57e82acb1be3d0dad87284e5b" dependencies = [ "autocfg", "cc", @@ -471,27 +504,25 @@ dependencies = [ [[package]] name = "parking_lot" -version = "0.11.2" +version = "0.12.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7d17b78036a60663b797adeaee46f5c9dfebb86948d1255007a1d6be0271ff99" +checksum = "3742b2c103b9f06bc9fff0a37ff4912935851bee6d36f3c02bcc755bcfec228f" dependencies = [ - "instant", "lock_api", "parking_lot_core", ] [[package]] name = "parking_lot_core" -version = "0.8.5" +version = "0.9.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d76e8e1493bcac0d2766c42737f34458f1c8c50c0d23bcb24ea953affb273216" +checksum = "9069cbb9f99e3a5083476ccb29ceb1de18b9118cafa53e90c9551235de2b9521" dependencies = [ "cfg-if", - "instant", "libc", "redox_syscall", "smallvec", - "winapi", + "windows-sys 0.45.0", ] [[package]] @@ -502,9 +533,9 @@ checksum = "19b17cddbe7ec3f8bc800887bab5e717348c95ea2ca0b1bf0837fb964dc67099" [[package]] name = "pin-project-lite" -version = "0.2.7" +version = "0.2.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8d31d11c69a6b52a174b42bdc0c30e5e11670f90788b2c471c31c1d17d449443" +checksum = "e0a7ae3ac2f1173085d398531c705756c94a4c56843785df85a60c1a0afac116" [[package]] name = "pin-utils" @@ -518,12 +549,6 @@ version = "0.3.26" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6ac9a59f73473f1b8d852421e59e64809f025994837ef743615c6d0c5b305160" -[[package]] -name = "ppv-lite86" -version = "0.2.15" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ed0cfbc8191465bed66e1718596ee0b0b35d5ee1f41c5df2189d0fe8bde535ba" - [[package]] name = "proc-macro2" version = "1.0.53" @@ -542,46 +567,6 @@ dependencies = [ "proc-macro2", ] -[[package]] -name = "rand" -version = "0.8.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2e7573632e6454cf6b99d7aac4ccca54be06da05aca2ef7423d22d27d4d4bcd8" -dependencies = [ - "libc", - "rand_chacha", - "rand_core", - "rand_hc", -] - -[[package]] -name = "rand_chacha" -version = "0.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" -dependencies = [ - "ppv-lite86", - "rand_core", -] - -[[package]] -name = "rand_core" -version = "0.6.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d34f1408f55294453790c48b2f1ebbb1c5b4b7563eb1f418bcfcfdbb06ebb4e7" -dependencies = [ - "getrandom", -] - -[[package]] -name = "rand_hc" -version = "0.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d51e9f596de227fda2ea6c84607f5558e196eeaf43c986b724ba4fb8fdf497e7" -dependencies = [ - "rand_core", -] - [[package]] name = "redox_syscall" version = "0.2.16" @@ -604,27 +589,18 @@ dependencies = [ [[package]] name = "regex" -version = "1.5.4" +version = "1.7.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d07a8629359eb56f1e2fb1652bb04212c072a87ba68546a04065d525673ac461" +checksum = "8b1f693b24f6ac912f4893ef08244d70b6067480d2f1a46e950c9691e6749d1d" dependencies = [ "regex-syntax", ] [[package]] name = "regex-syntax" -version = "0.6.25" +version = "0.6.29" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f497285884f3fcff424ffc933e56d7cbca511def0c9831a7f9b5f6153e3cc89b" - -[[package]] -name = "remove_dir_all" -version = "0.5.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3acd125665422973a33ac9d3dd2df85edad0f4ae9b00dafb1a05e43a9f5ef8e7" -dependencies = [ - "winapi", -] +checksum = "f162c6dd7b008981e4d40210aca20b4bd0f9b60ca9271061b07f78537722f2e1" [[package]] name = "rustc-hash" @@ -633,10 +609,24 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "08d43f7aa6b08d49f382cde6a7982047c3426db949b1424bc4b7ec9ae12c6ce2" [[package]] -name = "ryu" -version = "1.0.5" +name = "rustix" +version = "0.36.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "71d301d4193d031abdd79ff7e3dd721168a9572ef3fe51a1517aba235bd8f86e" +checksum = "db4165c9963ab29e422d6c26fbc1d37f15bace6b2810221f9d925023480fcf0e" +dependencies = [ + "bitflags", + "errno", + "io-lifetimes", + "libc", + "linux-raw-sys", + "windows-sys 0.45.0", +] + +[[package]] +name = "ryu" +version = "1.0.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f91339c0467de62360649f8d3e185ca8de4224ff281f66000de5eb2a77a79041" [[package]] name = "schannel" @@ -644,7 +634,7 @@ version = "0.1.21" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "713cfb06c7059f3588fb8044c0fad1d09e3c01d225e25b9220dbfdcf16dbb1b3" dependencies = [ - "windows-sys", + "windows-sys 0.42.0", ] [[package]] @@ -670,14 +660,14 @@ checksum = "e801c1712f48475582b7696ac71e0ca34ebb30e09338425384269d9717c62cad" dependencies = [ "proc-macro2", "quote", - "syn 2.0.5", + "syn 2.0.10", ] [[package]] name = "serde_json" -version = "1.0.71" +version = "1.0.94" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "063bf466a64011ac24040a49009724ee60a57da1b437617ceb32e53ad61bfb19" +checksum = "1c533a59c9d8a93a09c6ab31f0fd5e5f4dd1b8fc9434804029839884765d04ea" dependencies = [ "itoa", "ryu", @@ -701,24 +691,27 @@ checksum = "43b2853a4d09f215c24cc5489c992ce46052d359b5109343cbafbf26bc62f8a3" [[package]] name = "signal-hook-registry" -version = "1.4.0" +version = "1.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e51e73328dc4ac0c7ccbda3a494dfa03df1de2f46018127f60c693f2648455b0" +checksum = "d8229b473baa5980ac72ef434c4415e70c4b5e71b423043adb4ba059f89c99a1" dependencies = [ "libc", ] [[package]] name = "slab" -version = "0.4.5" +version = "0.4.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9def91fd1e018fe007022791f865d0ccc9b3a0d5001e01aabb8b40e46000afb5" +checksum = "6528351c9bc8ab22353f9d776db39a20288e8d6c37ef8cfe3317cf875eecfc2d" +dependencies = [ + "autocfg", +] [[package]] name = "smallvec" -version = "1.7.0" +version = "1.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1ecab6c735a6bb4139c0caafd0cc3635748bbb3acf4550e8138122099251f309" +checksum = "a507befe795404456341dfab10cef66ead4c041f62b8b11bbb92bffe5d0953e0" [[package]] name = "socket2" @@ -743,9 +736,9 @@ dependencies = [ [[package]] name = "syn" -version = "2.0.5" +version = "2.0.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "89c2d1c76a26822187a1fbb5964e3fff108bc208f02e820ab9dac1234f6b388a" +checksum = "5aad1363ed6d37b84299588d62d3a7d95b5a5c2d9aad5c85609fda12afaa1f40" dependencies = [ "proc-macro2", "quote", @@ -763,26 +756,15 @@ dependencies = [ [[package]] name = "tempfile" -version = "3.2.0" +version = "3.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dac1c663cfc93810f88aed9b8941d48cabf856a1b111c29a40439018d870eb22" +checksum = "af18f7ae1acd354b992402e9ec5864359d693cd8a79dcbef59f76891701c1e95" dependencies = [ "cfg-if", - "libc", - "rand", + "fastrand", "redox_syscall", - "remove_dir_all", - "winapi", -] - -[[package]] -name = "terminal_size" -version = "0.1.17" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "633c1a546cee861a1a6d0dc69ebeca693bf4296661ba7852b9d21d159e0506df" -dependencies = [ - "libc", - "winapi", + "rustix", + "windows-sys 0.42.0", ] [[package]] @@ -802,14 +784,14 @@ checksum = "f9456a42c5b0d803c8cd86e73dd7cc9edd429499f37a3550d286d5e86720569f" dependencies = [ "proc-macro2", "quote", - "syn 2.0.5", + "syn 2.0.10", ] [[package]] name = "tokio" -version = "1.14.0" +version = "1.26.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "70e992e41e0d2fb9f755b37446f20900f64446ef54874f40a60c78f021ac6144" +checksum = "03201d01c3c27a29c8a5cee5b55a93ddae1ccf6f08f65365c2c918f8c1b76f64" dependencies = [ "autocfg", "bytes", @@ -817,19 +799,19 @@ dependencies = [ "memchr", "mio", "num_cpus", - "once_cell", "parking_lot", "pin-project-lite", "signal-hook-registry", + "socket2", "tokio-macros", - "winapi", + "windows-sys 0.45.0", ] [[package]] name = "tokio-macros" -version = "1.6.0" +version = "1.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c9efc1aba077437943f7515666aa2b882dfabfbfdf89c819ea75a8d6e9eaba5e" +checksum = "d266c00fde287f55d3f1c3e96c500c362a2b8c695076ec180f27918820bc6df8" dependencies = [ "proc-macro2", "quote", @@ -859,9 +841,9 @@ dependencies = [ [[package]] name = "toml_edit" -version = "0.19.7" +version = "0.19.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dc18466501acd8ac6a3f615dd29a3438f8ca6bb3b19537138b3106e575621274" +checksum = "239410c8609e8125456927e6707163a3b1fdb40561e4b803bc041f466ccfdc13" dependencies = [ "indexmap", "serde", @@ -878,9 +860,9 @@ checksum = "e5464a87b239f13a63a501f2701565754bae92d243d4bb7eb12f6d57d2269bf4" [[package]] name = "unicode-width" -version = "0.1.9" +version = "0.1.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3ed742d4ea2bd1176e236172c8429aaf54486e7ac098db29ffe6529e0ce50973" +checksum = "c0edd1e5b14653f783770bce4a4dabb4a5108a5370a5f5d8cfe8710c361f6c8b" [[package]] name = "vcpkg" @@ -890,9 +872,9 @@ checksum = "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426" [[package]] name = "wasi" -version = "0.10.2+wasi-snapshot-preview1" +version = "0.11.0+wasi-snapshot-preview1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fd6fbd9a79829dd1ad0cc20627bf1ed606756a7f77edff7b66b7064f9cb327c6" +checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" [[package]] name = "which" @@ -942,6 +924,30 @@ dependencies = [ "windows_x86_64_msvc", ] +[[package]] +name = "windows-sys" +version = "0.45.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "75283be5efb2831d37ea142365f009c02ec203cd29a3ebecbc093d52315b66d0" +dependencies = [ + "windows-targets", +] + +[[package]] +name = "windows-targets" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e5180c00cd44c9b1c88adb3693291f1cd93605ded80c250a75d472756b4d071" +dependencies = [ + "windows_aarch64_gnullvm", + "windows_aarch64_msvc", + "windows_i686_gnu", + "windows_i686_msvc", + "windows_x86_64_gnu", + "windows_x86_64_gnullvm", + "windows_x86_64_msvc", +] + [[package]] name = "windows_aarch64_gnullvm" version = "0.42.2" @@ -986,9 +992,9 @@ checksum = "9aec5da331524158c6d1a4ac0ab1541149c0b9505fde06423b02f5ef0106b9f0" [[package]] name = "winnow" -version = "0.3.6" +version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "23d020b441f92996c80d94ae9166e8501e59c7bb56121189dc9eab3bd8216966" +checksum = "ae8970b36c66498d8ff1d66685dc86b91b29db0c7739899012f63a63814b4b28" dependencies = [ "memchr", ] @@ -1004,9 +1010,9 @@ dependencies = [ [[package]] name = "yacexits" -version = "0.1.3" +version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b3afbe270dff95fe94b3a55c7e2dce91457a89b2b0dc6013814bba9806d099be" +checksum = "53fe740dd05c1bbc919431e842e6c1bea30195e0518ae99cae35b7f0730ddc18" dependencies = [ "bindgen", "libc", @@ -1014,6 +1020,6 @@ dependencies = [ [[package]] name = "zeroize" -version = "1.4.3" +version = "1.5.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d68d9dcec5f9b43a30d38c49f91dfedfaac384cb8f085faca366c26207dd1619" +checksum = "c394b5bd0c6f669e7275d9c20aa90ae064cb22e75a1cad54e1b34088034b149f" diff --git a/src/client.rs b/src/client.rs index 501e273..8e1819d 100644 --- a/src/client.rs +++ b/src/client.rs @@ -55,7 +55,7 @@ impl HopperClient { curl::init(); Self { config: config, - client: Easy::new() + client: Easy::new(), } } -- 2.30.2 From 3eac2d2eab16345b1152f6ac8e299a07014b19eb Mon Sep 17 00:00:00 2001 From: "[ ]" Date: Sun, 26 Mar 2023 11:29:37 +0100 Subject: [PATCH 20/30] create custom CError trait, using it for errors The name chosen here is perhaps not ideal, and if you can think of a better one that should probably be quickly used instead. This is one option for C exit-code handling which is a lot cleaner than the existing implementation in my opinion. Much of the cleanup also comes from the enum error type in config. This enum's implementation could be a lot cleaner with something like the 'thiserror' crate: the message method could simply utilize that crate's derived Display implementation. Regarding `unwrap_or_else(|e| e.exit())`, it would have been ideal to simply handle panics with a handler so it could simply be `unwrap()`, but since panics tend to only pass the error string to the handler, an exit method seems like the best solution. Because this enforces consistency of adding the program invokation before every error message, it has an unfortunate side-effect of the usage text getting that information duplicated which may be seen as undesirable. There are some workarounds to this, but I've deferred deciding how best to do that (if it even is desirable to do so). --- src/config.rs | 102 +++++++++++++++++++++++++++----------------------- src/error.rs | 47 +++++++++++++++++++++++ src/main.rs | 54 +++++--------------------- 3 files changed, 112 insertions(+), 91 deletions(-) create mode 100644 src/error.rs diff --git a/src/config.rs b/src/config.rs index 5b71de5..6ac049f 100644 --- a/src/config.rs +++ b/src/config.rs @@ -20,7 +20,7 @@ use std::{ fs::File, - io::Read, + io::{Read, self}, path::PathBuf, }; @@ -31,6 +31,8 @@ use yacexits::{ EX_UNAVAILABLE, }; +use crate::error::CError; + #[derive(Deserialize)] pub struct Config { pub hopfiles: Vec, @@ -42,56 +44,64 @@ pub struct Sources { pub modrinth: Vec, } -pub fn get_config(dirs: BaseDirectories) -> Result { - match dirs.place_config_file("config.toml") { - Ok(file) => Ok(file), - Err(_) => { - Err(( - format!("Unable to create configuration file."), - EX_UNAVAILABLE, - )) - }, - } +pub enum ConfigError { + CreateFailed(io::Error), + OpenError(io::Error), + ReadError(io::Error), + FormatError(std::string::FromUtf8Error), + ParseError(toml::de::Error), } -impl Config { - pub fn read_config(config_path: PathBuf) -> Result { - let mut buf: Vec = Vec::new(); - - let mut config_file = match File::open(&config_path) { - Ok(file) => file, - Err(_) => { - return Err(( - format!("{}: Permission denied.", config_path.display()), - EX_UNAVAILABLE, - )); +impl CError for ConfigError { + fn message(&self) -> String { + match self { + Self::CreateFailed(err) => { + format!("Unable to create configuration file: {}", err) }, - }; - - if let Some(err) = config_file.read_to_end(&mut buf).err() { - return Err((format!("{:?}", err), EX_DATAERR)); - }; - - let toml = match String::from_utf8(buf) { - Ok(contents) => contents, - Err(err) => { - return Err(( - format!("{:?}", err), - EX_DATAERR, - )); + Self::OpenError(err) => { + format!("Unable to open configuration file: {}", err) }, - }; - - match toml::from_str(&toml) { - Ok(val) => Ok(val), - Err(_) => { - Err(( - format!( - "{}: Invalid configuration file.", config_path.display() - ), - EX_DATAERR, - )) + Self::ReadError(err) => { + format!("Error while reading configuration file: {}", err) + }, + Self::FormatError(err) => { + format!("Configuration file is not valid utf-8: {}", err) + }, + Self::ParseError(err) => { + format!("Unable to parse configuration file: {}", err) }, } } + + fn code(&self) -> u32 { + match self { + Self::CreateFailed(_) => EX_UNAVAILABLE, + Self::OpenError(_) => EX_UNAVAILABLE, + Self::ReadError(_) => EX_DATAERR, + Self::FormatError(_) => EX_DATAERR, + Self::ParseError(_) => EX_DATAERR, + } + + } +} + +pub fn get_config(dirs: BaseDirectories) -> Result { + dirs.place_config_file("config.toml").map_err(ConfigError::CreateFailed) +} + +impl Config { + pub fn read_config(config_path: PathBuf) -> Result { + let mut buf: Vec = Vec::new(); + + let mut config_file = File::open(&config_path) + .map_err(ConfigError::OpenError)?; + + config_file.read_to_end(&mut buf) + .map_err(ConfigError::ReadError)?; + + let toml = String::from_utf8(buf) + .map_err(ConfigError::FormatError)?; + + toml::from_str(&toml).map_err(ConfigError::ParseError) + } } diff --git a/src/error.rs b/src/error.rs new file mode 100644 index 0000000..35c2912 --- /dev/null +++ b/src/error.rs @@ -0,0 +1,47 @@ +use yacexits::*; + +pub trait CError { + fn code(&self) -> u32; + + fn message(&self) -> String; + + fn exit(&self) -> ! { + eprintln!("{}: {}", program_invokation(), self.message()); + exit(self.code()); + } +} + +fn program_invokation() -> String { + // TODO: ideally this would be argv[0] from main. + // This could be done with a const OnceCell, but I'm not sure I like that solution. + // Using std, we can do this though: + std::env::args().next() + // with a fallback to the program name + .unwrap_or_else(|| env!("CARGO_PKG_NAME").to_owned()) +} + +impl<'l> CError for arg::ParseKind<'l> { + fn message(&self) -> String { + format!( + "Usage: {}{}", + program_invokation(), // argv[0], + " [-v] add | get | init | list | remove | update\n\n".to_owned() + + "add [-m version] [-f hopfiles...] packages...\n" + + "get [-n] [-d directory] [-m versions...] [-t types...] packages\n" + + "init [-f hopfiles...] version type\n" + + "list [[-f hopfiles...] | [-m versions...] [-t types...]]\n" + + "remove [[-f hopfiles...] | type version]] packages...\n" + + "update [[-f hopfiles... | [-m versions...] [-t types...]]", + ) + } + + fn code(&self) -> u32 { EX_USAGE } +} + +impl CError for xdg::BaseDirectoriesError { + fn message(&self) -> String { + format!("Unable to open configuration file: {}", self) + } + + fn code(&self) -> u32 { EX_UNAVAILABLE } +} \ No newline at end of file diff --git a/src/main.rs b/src/main.rs index f8c159e..ffde991 100644 --- a/src/main.rs +++ b/src/main.rs @@ -26,18 +26,18 @@ mod args; mod client; mod config; mod hopfile; +mod error; use api::*; use args::*; use client::*; use config::*; use hopfile::*; +use error::*; use yacexits::{ exit, EX_SOFTWARE, - EX_UNAVAILABLE, - EX_USAGE, }; struct AppContext { @@ -50,55 +50,19 @@ struct AppContext { async fn rust_main(arguments: c_main::Args) { let argv: Vec<&str> = arguments.into_iter().collect(); - let usage_info = format!( - "Usage: {}{}", - argv[0], - " [-v] add | get | init | list | remove | update\n\n".to_owned() + - "add [-m version] [-f hopfiles...] packages...\n" + - "get [-n] [-d directory] [-m versions...] [-t types...] packages\n" + - "init [-f hopfiles...] version type\n" + - "list [[-f hopfiles...] | [-m versions...] [-t types...]]\n" + - "remove [[-f hopfiles...] | type version]] packages...\n" + - "update [[-f hopfiles... | [-m versions...] [-t types...]]", - ); - - let args = Arguments::from_args( argv .clone() .into_iter() - ).unwrap_or_else(|_| { - eprintln!("{}", usage_info); - exit(EX_USAGE); - }); + ).unwrap_or_else(|e| e.exit()); - let xdg_basedirs = xdg::BaseDirectories::with_prefix("hopper"); - - // this might be cursed; I haven’t decided - let config = match get_config( - xdg_basedirs.unwrap_or_else(|_| { - eprintln!( - "{}: Unable to open configuration file: Permission denied.", - argv[0], - ); - exit(EX_UNAVAILABLE); - }) - ) { - Ok(path) => { - match Config::read_config(path) { - Ok(file) => file, - Err((err, code)) => { - eprintln!("{}: {}", argv[0], err); - exit(code); - }, - } - }, - Err((err, code)) => { - eprintln!("{}: {}", argv[0], err); - exit(code); - }, - }; + let xdg_basedirs = xdg::BaseDirectories::with_prefix("hopper") + .unwrap_or_else(|e| e.exit()); + let config = get_config(xdg_basedirs) + .and_then(Config::read_config) + .unwrap_or_else(|e| e.exit()); + let ctx = AppContext { args, config }; match ctx.args.sub { -- 2.30.2 From 80fbbebc91ad3696078094ee7bf2840f57d9c275 Mon Sep 17 00:00:00 2001 From: "[ ]" Date: Sun, 26 Mar 2023 11:55:17 +0100 Subject: [PATCH 21/30] update copyright --- src/config.rs | 1 + src/error.rs | 18 ++++++++++++++++++ src/main.rs | 2 +- 3 files changed, 20 insertions(+), 1 deletion(-) diff --git a/src/config.rs b/src/config.rs index 6ac049f..2c77a21 100644 --- a/src/config.rs +++ b/src/config.rs @@ -2,6 +2,7 @@ * Copyright (c) 2021–2022 Marceline Cramer * Copyright (c) 2022–2023 Emma Tebibyte * Copyright (c) 2022 Spookdot + * Copyright (c) 2023 [ ] * SPDX-License-Identifier: AGPL-3.0-or-later * * This file is part of Hopper. diff --git a/src/error.rs b/src/error.rs index 35c2912..751a146 100644 --- a/src/error.rs +++ b/src/error.rs @@ -1,3 +1,21 @@ +/* + * Copyright (c) 2023 [ ] + * SPDX-License-Identifier: AGPL-3.0-or-later + * + * This file is part of Hopper. + * + * Hopper is free software: you can redistribute it and/or modify it under the + * terms of the GNU General Public License as published by the Free Software + * Foundation, either version 3 of the License, or (at your option) any later + * version. + * + * Hopper is distributed in the hope that it will be useful, but WITHOUT ANY + * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR + * A PARTICULAR PURPOSE. See the GNU General Public License for more details. + * You should have received a copy of the GNU General Public License along with + * Hopper. If not, see . + */ + use yacexits::*; pub trait CError { diff --git a/src/main.rs b/src/main.rs index ffde991..7c45e19 100644 --- a/src/main.rs +++ b/src/main.rs @@ -2,7 +2,7 @@ * Copyright (c) 2021–2022 Marceline Cramer * Copyright (c) 2022–2023 Emma Tebibyte * Copyright (c) 2022 Spookdot - * Copyright (c) 2022 [ ] + * Copyright (c) 2022-2023 [ ] * SPDX-License-Identifier: AGPL-3.0-or-later * * This file is part of Hopper. -- 2.30.2 From 623d65be0d83a453dd3a61d14ddf9864a8082f32 Mon Sep 17 00:00:00 2001 From: emma Date: Sun, 26 Mar 2023 19:08:28 -0400 Subject: [PATCH 22/30] added .editorconfig file --- .editorconfig | 8 ++++++++ 1 file changed, 8 insertions(+) create mode 100644 .editorconfig diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 0000000..f78e6ef --- /dev/null +++ b/.editorconfig @@ -0,0 +1,8 @@ +root = true + +[*] +charset = utf8 +end_of_line = lf +indent_style = tab +indent_size = 4 +insert_final_newline = true -- 2.30.2 From 3eedea2f9c2a32392a0855dbe377491ff2a1c1ab Mon Sep 17 00:00:00 2001 From: emma Date: Mon, 27 Mar 2023 00:01:32 -0400 Subject: [PATCH 23/30] updated error types --- Cargo.lock | 33 +++++++-------- Cargo.toml | 3 +- src/config.rs | 113 ++++++++++++++++++++++++++++++++++++-------------- src/error.rs | 54 ++++++++++-------------- src/main.rs | 37 ++++++++--------- 5 files changed, 138 insertions(+), 102 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 9423e02..c2f6328 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -62,15 +62,6 @@ version = "1.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "89b2fd2a0dcf38d7971e2194b6b6eebab45ae01067456a7fd93d5547a61b70be" -[[package]] -name = "c-main" -version = "1.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "797bbff8bd2bcddb7f0ee638b55398686adac15174689a86da5ffc0f51219f75" -dependencies = [ - "libc", -] - [[package]] name = "cc" version = "1.0.79" @@ -300,7 +291,6 @@ name = "hopper" version = "0.1.0" dependencies = [ "arg", - "c-main", "console", "curl", "dialoguer", @@ -380,6 +370,15 @@ version = "0.2.140" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "99227334921fae1a979cf0bfdfcc6b3e5ce376ef57e16fb6fb3ea2ed6095f80c" +[[package]] +name = "libc-print" +version = "0.1.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "06cea5d58bd9ba4717bbf5c6c5bb11bb6e9e76685b7fff34039b80f50ce86c11" +dependencies = [ + "libc", +] + [[package]] name = "libloading" version = "0.7.4" @@ -551,9 +550,9 @@ checksum = "6ac9a59f73473f1b8d852421e59e64809f025994837ef743615c6d0c5b305160" [[package]] name = "proc-macro2" -version = "1.0.53" +version = "1.0.54" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ba466839c78239c09faf015484e5cc04860f88242cff4d03eb038f04b4699b73" +checksum = "e472a104799c74b514a57226160104aa483546de37e839ec50e3c2e41dd87534" dependencies = [ "unicode-ident", ] @@ -1010,16 +1009,16 @@ dependencies = [ [[package]] name = "yacexits" -version = "0.1.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "53fe740dd05c1bbc919431e842e6c1bea30195e0518ae99cae35b7f0730ddc18" +version = "0.2.0" +source = "git+https://git.tebibyte.media/yac/yacexits.git?branch=c-entry#d2af983f4ad18dec2af0604d7f34b7863daa5c25" dependencies = [ "bindgen", "libc", + "libc-print", ] [[package]] name = "zeroize" -version = "1.5.7" +version = "1.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c394b5bd0c6f669e7275d9c20aa90ae064cb22e75a1cad54e1b34088034b149f" +checksum = "2a0956f1ba7c7909bfb66c2e9e4124ab6f6482560f6628b5aaeba39207c9aad9" diff --git a/Cargo.toml b/Cargo.toml index 225a24b..baca1d2 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -12,7 +12,6 @@ authors = [ [dependencies] arg = "0.4.1" -c-main = "1.0.1" console = "0.15.0" curl = "0.4.44" dialoguer = "0.9.0" @@ -23,4 +22,4 @@ serde_json = "1" tokio = { version = "1", features = ["full"] } toml = "0.7.3" xdg = "2.4.1" -yacexits = "0.1.3" +yacexits = { git = "https://git.tebibyte.media/yac/yacexits.git", branch = "c-entry", version = "0.2.0" } diff --git a/src/config.rs b/src/config.rs index 2c77a21..265143b 100644 --- a/src/config.rs +++ b/src/config.rs @@ -19,9 +19,11 @@ * Hopper. If not, see . */ +use crate::HopError; + use std::{ fs::File, - io::{Read, self}, + io::{ Read, self }, path::PathBuf, }; @@ -32,8 +34,6 @@ use yacexits::{ EX_UNAVAILABLE, }; -use crate::error::CError; - #[derive(Deserialize)] pub struct Config { pub hopfiles: Vec, @@ -46,52 +46,103 @@ pub struct Sources { } pub enum ConfigError { - CreateFailed(io::Error), + CreateError(io::Error), OpenError(io::Error), ReadError(io::Error), FormatError(std::string::FromUtf8Error), ParseError(toml::de::Error), } -impl CError for ConfigError { - fn message(&self) -> String { - match self { - Self::CreateFailed(err) => { - format!("Unable to create configuration file: {}", err) +impl From for HopError { + fn from(error: ConfigError) -> Self { + let (message, code) = match error { + ConfigError::CreateError(err) => { + ( + format!("{}: Unable to create configuration file.", err), + EX_UNAVAILABLE, + ) }, - Self::OpenError(err) => { - format!("Unable to open configuration file: {}", err) + ConfigError::OpenError(err) => { + ( + format!("{}: Unable to open configuration file.", err), + EX_UNAVAILABLE, + ) }, - Self::ReadError(err) => { - format!("Error while reading configuration file: {}", err) + ConfigError::ReadError(err) => { + ( + format!("{}: Error while reading configuration file", err), + EX_DATAERR, + ) }, - Self::FormatError(err) => { - format!("Configuration file is not valid utf-8: {}", err) + ConfigError::FormatError(err) => { + ( + format!("{}: Configuration file is not valid UTF-8.", err), + EX_DATAERR + ) }, - Self::ParseError(err) => { - format!("Unable to parse configuration file: {}", err) + ConfigError::ParseError(err) => { + ( + format!("{}: Unable to parse configuration file.", err), + EX_DATAERR, + ) }, - } - } + }; - fn code(&self) -> u32 { - match self { - Self::CreateFailed(_) => EX_UNAVAILABLE, - Self::OpenError(_) => EX_UNAVAILABLE, - Self::ReadError(_) => EX_DATAERR, - Self::FormatError(_) => EX_DATAERR, - Self::ParseError(_) => EX_DATAERR, - } - + Self { message, code } } } -pub fn get_config(dirs: BaseDirectories) -> Result { - dirs.place_config_file("config.toml").map_err(ConfigError::CreateFailed) +impl From for (String, u32) { + fn from(error: ConfigError) -> Self { + let (message, code) = match error { + ConfigError::CreateError(err) => { + ( + format!("{}: Unable to create configuration file.", err), + EX_UNAVAILABLE, + ) + }, + ConfigError::OpenError(err) => { + ( + format!("{}: Unable to open configuration file.", err), + EX_UNAVAILABLE, + ) + }, + ConfigError::ReadError(err) => { + ( + format!("{}: Error while reading configuration file", err), + EX_DATAERR, + ) + }, + ConfigError::FormatError(err) => { + ( + format!("{}: Configuration file is not valid UTF-8.", err), + EX_DATAERR + ) + }, + ConfigError::ParseError(err) => { + ( + format!("{}: Unable to parse configuration file", err), + EX_DATAERR, + ) + }, + }; + + (message, code) + } + +} + +impl From for ConfigError { + fn from(err: xdg::BaseDirectoriesError) -> Self { + ConfigError::CreateError(io::Error::from(err)) + } } impl Config { - pub fn read_config(config_path: PathBuf) -> Result { + pub fn read_config() -> Result { + let config_path = BaseDirectories::with_prefix("hopper")? + .place_config_file("config.toml") + .map_err(ConfigError::CreateError)?; let mut buf: Vec = Vec::new(); let mut config_file = File::open(&config_path) diff --git a/src/error.rs b/src/error.rs index 751a146..9e59fef 100644 --- a/src/error.rs +++ b/src/error.rs @@ -18,48 +18,38 @@ use yacexits::*; -pub trait CError { - fn code(&self) -> u32; +pub struct HopError { + pub code: u32, - fn message(&self) -> String; - - fn exit(&self) -> ! { - eprintln!("{}: {}", program_invokation(), self.message()); - exit(self.code()); - } + pub message: String, } -fn program_invokation() -> String { - // TODO: ideally this would be argv[0] from main. - // This could be done with a const OnceCell, but I'm not sure I like that solution. - // Using std, we can do this though: - std::env::args().next() - // with a fallback to the program name - .unwrap_or_else(|| env!("CARGO_PKG_NAME").to_owned()) -} - -impl<'l> CError for arg::ParseKind<'l> { - fn message(&self) -> String { - format!( - "Usage: {}{}", - program_invokation(), // argv[0], - " [-v] add | get | init | list | remove | update\n\n".to_owned() + +impl From> for HopError { + fn from(_: arg::ParseKind) -> Self { + let message = format!( + "Usage: {}", + "[-v] add | get | init | list | remove | update\n\n".to_owned() + "add [-m version] [-f hopfiles...] packages...\n" + "get [-n] [-d directory] [-m versions...] [-t types...] packages\n" + "init [-f hopfiles...] version type\n" + "list [[-f hopfiles...] | [-m versions...] [-t types...]]\n" + "remove [[-f hopfiles...] | type version]] packages...\n" + "update [[-f hopfiles... | [-m versions...] [-t types...]]", - ) + ); + Self { message, code: EX_USAGE } } - - fn code(&self) -> u32 { EX_USAGE } } -impl CError for xdg::BaseDirectoriesError { - fn message(&self) -> String { - format!("Unable to open configuration file: {}", self) - } +impl From for HopError { + fn from(err: xdg::BaseDirectoriesError) -> Self { + let message = format!("{}: Unable to open configuration file", err); - fn code(&self) -> u32 { EX_UNAVAILABLE } -} \ No newline at end of file + Self { message, code: EX_UNAVAILABLE } + } +} + +impl From for (String, u32) { + fn from(err: HopError) -> Self { + (err.message, err.code) + } +} diff --git a/src/main.rs b/src/main.rs index 7c45e19..79d3a1e 100644 --- a/src/main.rs +++ b/src/main.rs @@ -36,7 +36,8 @@ use hopfile::*; use error::*; use yacexits::{ - exit, + exit, + EX_OSERR, EX_SOFTWARE, }; @@ -47,34 +48,30 @@ struct AppContext { #[tokio::main] #[no_mangle] -async fn rust_main(arguments: c_main::Args) { +async fn rust_main(arguments: yacexits::Args) -> Result { let argv: Vec<&str> = arguments.into_iter().collect(); - let args = Arguments::from_args( - argv - .clone() - .into_iter() - ).unwrap_or_else(|e| e.exit()); + let args = match Arguments::from_args(argv.clone().into_iter()) { + Ok(args) => args, + Err(_) => { + return Err((format!("Unable to ascertain arguments."), EX_OSERR)); + } + }; - let xdg_basedirs = xdg::BaseDirectories::with_prefix("hopper") - .unwrap_or_else(|e| e.exit()); - - let config = get_config(xdg_basedirs) - .and_then(Config::read_config) - .unwrap_or_else(|e| e.exit()); - + let config = Config::read_config()?; let ctx = AppContext { args, config }; match ctx.args.sub { // Command::Get(search_args) => cmd_get(&ctx, search_args).await, // Command::Init(hopfile_args) => cmd_init(hopfile_args).await, _ => { - eprintln!( - "{}: {}: Unimplemented subcommand.", - argv[0], - ctx.args.sub - ); - exit(EX_SOFTWARE); + return Err(( + format!( + "{}: Unimplemented subcommand.", + ctx.args.sub + ), + EX_SOFTWARE, + )); }, }; } -- 2.30.2 From 875290a75ce077966bb43a81bd24d267c60f4b32 Mon Sep 17 00:00:00 2001 From: emma Date: Mon, 27 Mar 2023 00:13:39 -0400 Subject: [PATCH 24/30] config.rs: Removed HopError From trait from ConfigError and refactored --- src/config.rs | 76 ++++++++------------------------------------------- 1 file changed, 11 insertions(+), 65 deletions(-) diff --git a/src/config.rs b/src/config.rs index 265143b..a872207 100644 --- a/src/config.rs +++ b/src/config.rs @@ -53,81 +53,27 @@ pub enum ConfigError { ParseError(toml::de::Error), } -impl From for HopError { - fn from(error: ConfigError) -> Self { - let (message, code) = match error { - ConfigError::CreateError(err) => { - ( - format!("{}: Unable to create configuration file.", err), - EX_UNAVAILABLE, - ) - }, - ConfigError::OpenError(err) => { - ( - format!("{}: Unable to open configuration file.", err), - EX_UNAVAILABLE, - ) - }, - ConfigError::ReadError(err) => { - ( - format!("{}: Error while reading configuration file", err), - EX_DATAERR, - ) - }, - ConfigError::FormatError(err) => { - ( - format!("{}: Configuration file is not valid UTF-8.", err), - EX_DATAERR - ) - }, - ConfigError::ParseError(err) => { - ( - format!("{}: Unable to parse configuration file.", err), - EX_DATAERR, - ) - }, - }; - - Self { message, code } - } -} - impl From for (String, u32) { fn from(error: ConfigError) -> Self { let (message, code) = match error { - ConfigError::CreateError(err) => { - ( - format!("{}: Unable to create configuration file.", err), - EX_UNAVAILABLE, - ) + ConfigError::CreateError(_) => { + ("Unable to create configuration file.", EX_UNAVAILABLE) }, - ConfigError::OpenError(err) => { - ( - format!("{}: Unable to open configuration file.", err), - EX_UNAVAILABLE, - ) + ConfigError::OpenError(_) => { + ("Unable to open configuration file.", EX_UNAVAILABLE) }, - ConfigError::ReadError(err) => { - ( - format!("{}: Error while reading configuration file", err), - EX_DATAERR, - ) + ConfigError::ReadError(_) => { + ("Error while reading configuration file.", EX_DATAERR) }, - ConfigError::FormatError(err) => { - ( - format!("{}: Configuration file is not valid UTF-8.", err), - EX_DATAERR - ) + ConfigError::FormatError(_) => { + ("Configuration file is not valid UTF-8.", EX_DATAERR) }, - ConfigError::ParseError(err) => { - ( - format!("{}: Unable to parse configuration file", err), - EX_DATAERR, - ) + ConfigError::ParseError(_) => { + ("Unable to parse configuration file.", EX_DATAERR) }, }; - (message, code) + (message.to_string(), code) } } -- 2.30.2 From ba726358b4e276d3ccd330eb04cd16854b37e040 Mon Sep 17 00:00:00 2001 From: emma Date: Mon, 27 Mar 2023 00:32:27 -0400 Subject: [PATCH 25/30] args.rs: implemented From for (String, u32) --- src/args.rs | 11 ++++++++++- src/commands.rs | 52 ------------------------------------------------- src/config.rs | 3 +-- src/error.rs | 9 +-------- 4 files changed, 12 insertions(+), 63 deletions(-) diff --git a/src/args.rs b/src/args.rs index 38e9a6d..c2aa32f 100644 --- a/src/args.rs +++ b/src/args.rs @@ -24,6 +24,7 @@ use core::{ }; pub use arg::Args; +use yacexits::EX_DATAERR; #[derive(Args, Debug)] pub struct Arguments { @@ -160,6 +161,14 @@ impl std::default::Default for PackageType { //TODO: Actually implement Default } } +impl From for (String, u32) { + fn from(error: PackageParseError) -> Self { + match error { + PackageParseError::Invalid(err) => (err, EX_DATAERR), + } + } +} + impl FromStr for PackageType { type Err = PackageParseError; fn from_str(s: &str) -> Result { @@ -167,7 +176,7 @@ impl FromStr for PackageType { if pieces.len() > 2 || pieces.len() == 1 { return Err(PackageParseError::Invalid( - format!("{}: Invalid package name.", s) + format!("{}: Invalid package type.", s) )); } diff --git a/src/commands.rs b/src/commands.rs index 247b855..e67a3a4 100644 --- a/src/commands.rs +++ b/src/commands.rs @@ -146,55 +146,3 @@ async fn select_from_results( Ok(selected) } - -// TODO implement enum for more graceful exiting -async fn select_from_results( - _ctx: &AppContext, - response: &SearchResponse, -) -> anyhow::Result> { - let input: String = dialoguer::Input::new() - .with_prompt("Mods to install (eg: 1 2 3-5)") - .interact_text()?; - - let mut selected: Vec = Vec::new(); - for token in input.split(" ") { - let terms: Vec<&str> = token.split("-").collect(); - - match terms.len() { - 1 => selected.push(terms[0].parse().expect("Token must be an integer")), - 2 => { - let terms: Vec = terms - .iter() - .map(|term| term.parse().expect("Term must be an integer")) - .collect(); - let from = terms[0]; - let to = terms[1]; - - for index in from..=to { - selected.push(index); - } - } - _ => panic!("Invalid selection token {}", token), - } - } - - selected.dedup(); - - let selected = selected - .iter() - .map(|index| { - if *index < 1 || *index > response.hits.len() { - // TODO return useful error instead of panicking - panic!("Index {} is out of bounds", index); - } - - // input is indexed from 1, but results are indexed from 0 - let index = index - 1; - - index - }) - .collect(); - - Ok(selected) -} - diff --git a/src/config.rs b/src/config.rs index a872207..a6a45bf 100644 --- a/src/config.rs +++ b/src/config.rs @@ -19,8 +19,6 @@ * Hopper. If not, see . */ -use crate::HopError; - use std::{ fs::File, io::{ Read, self }, @@ -56,6 +54,7 @@ pub enum ConfigError { impl From for (String, u32) { fn from(error: ConfigError) -> Self { let (message, code) = match error { + // TODO: More precise matching inside these arms ConfigError::CreateError(_) => { ("Unable to create configuration file.", EX_UNAVAILABLE) }, diff --git a/src/error.rs b/src/error.rs index 9e59fef..f0346ce 100644 --- a/src/error.rs +++ b/src/error.rs @@ -24,6 +24,7 @@ pub struct HopError { pub message: String, } +// TODO: More granular matching here impl From> for HopError { fn from(_: arg::ParseKind) -> Self { let message = format!( @@ -40,14 +41,6 @@ impl From> for HopError { } } -impl From for HopError { - fn from(err: xdg::BaseDirectoriesError) -> Self { - let message = format!("{}: Unable to open configuration file", err); - - Self { message, code: EX_UNAVAILABLE } - } -} - impl From for (String, u32) { fn from(err: HopError) -> Self { (err.message, err.code) -- 2.30.2 From 7eb530c9b00eeca310a166816c9bb80475f20568 Mon Sep 17 00:00:00 2001 From: emma Date: Mon, 27 Mar 2023 00:32:59 -0400 Subject: [PATCH 26/30] main.rs: fixed license header --- src/main.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main.rs b/src/main.rs index 79d3a1e..98679a4 100644 --- a/src/main.rs +++ b/src/main.rs @@ -2,7 +2,7 @@ * Copyright (c) 2021–2022 Marceline Cramer * Copyright (c) 2022–2023 Emma Tebibyte * Copyright (c) 2022 Spookdot - * Copyright (c) 2022-2023 [ ] + * Copyright (c) 2022–2023 [ ] * SPDX-License-Identifier: AGPL-3.0-or-later * * This file is part of Hopper. -- 2.30.2 From 34993ceb13bf3759faa218ff7acd2a0b9514d05e Mon Sep 17 00:00:00 2001 From: emma Date: Mon, 27 Mar 2023 00:44:35 -0400 Subject: [PATCH 27/30] main.rs: changed command match err output to HopError --- src/api.rs | 224 +++++++++++++++++++++++++++------------------------- src/args.rs | 222 +++++++++++++++++++++++++-------------------------- src/main.rs | 38 +++++---- 3 files changed, 246 insertions(+), 238 deletions(-) diff --git a/src/api.rs b/src/api.rs index f4408ec..3e3840f 100644 --- a/src/api.rs +++ b/src/api.rs @@ -24,153 +24,163 @@ use std::{ collections::HashMap, fmt }; #[derive(Deserialize, Debug)] pub struct SearchResponse { - pub hits: Vec, - pub offset: isize, - pub limit: isize, - pub total_hits: isize, + pub hits: Vec, + pub offset: isize, + pub limit: isize, + pub total_hits: isize, } #[derive(Deserialize, Debug)] pub struct ModResult { - pub slug: String, - pub title: String, - pub description: String, - pub categories: Vec, - pub display_categories: Vec, // NOTE this is not in the OpenAPI docs - pub client_side: String, - pub server_side: String, - pub project_type: String, // NOTE this isn't in all search results? - pub downloads: isize, - pub icon_url: String, - pub project_id: String, // TODO parse to 'local-xxxx' with regex - pub author: String, - pub versions: Vec, - pub follows: isize, - pub date_created: String, - pub date_modified: String, - pub latest_version: String, - pub license: String, - pub gallery: Vec, + pub slug: String, + pub title: String, + pub description: String, + pub categories: Vec, + + // NOTE this is not in the OpenAPI docs + pub display_categories: Vec, + pub client_side: String, + pub server_side: String, + + // NOTE this isn't in all search results? + pub project_type: String, + pub downloads: isize, + pub icon_url: String, + + // TODO parse to 'local-xxxx' with regex + pub project_id: String, + pub author: String, + pub versions: Vec, + pub follows: isize, + pub date_created: String, + pub date_modified: String, + pub latest_version: String, + pub license: String, + pub gallery: Vec, } 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_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 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); - } + 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 slug: String, - pub title: String, - pub description: String, - pub categories: Vec, - pub additional_categories: Vec, // NOTE not listed in OpenAPI docs - pub client_side: String, // TODO serialize as enum - pub server_side: String, // TODO serialize as enum - pub body: String, - pub issues_url: Option, - pub source_url: Option, - pub wiki_url: Option, - pub discord_url: Option, - pub donation_urls: Option>, - pub project_type: String, - pub downloads: isize, - pub icon_url: Option, - pub id: String, // TODO serialize mod id? - pub team: String, // TODO serialize team id? - pub body_url: Option, // NOTE deprecated - pub moderator_message: Option, - pub published: String, // TODO serialize as datetime - pub updated: String, // TODO serialize as datetime - pub approved: Option, // NOTE not listed in OpenAPI docs, TODO serialize as datetime - pub followers: isize, - pub status: String, - pub license: License, - pub versions: Vec, - pub gallery: Option>, + pub slug: String, + pub title: String, + pub description: String, + pub categories: Vec, + pub additional_categories: Vec, // NOTE not listed in OpenAPI docs + pub client_side: String, // TODO serialize as enum + pub server_side: String, // TODO serialize as enum + pub body: String, + pub issues_url: Option, + pub source_url: Option, + pub wiki_url: Option, + pub discord_url: Option, + pub donation_urls: Option>, + pub project_type: String, + pub downloads: isize, + pub icon_url: Option, + pub id: String, // TODO serialize mod id? + pub team: String, // TODO serialize team id? + pub body_url: Option, // NOTE deprecated + pub moderator_message: Option, + pub published: String, // TODO serialize as datetime + pub updated: String, // TODO serialize as datetime + + // NOTE not listed in OpenAPI docs, TODO serialize as datetime + pub approved: Option, + pub followers: isize, + pub status: String, + pub license: License, + pub versions: Vec, + pub gallery: Option>, } #[derive(Deserialize, Debug)] pub struct GalleryEntry { - pub url: String, - pub featured: bool, - pub title: String, - pub description: String, - pub created: String, + pub url: String, + pub featured: bool, + pub title: String, + pub description: String, + pub created: String, } #[derive(Deserialize, Debug)] pub struct License { - pub id: String, - pub name: String, - pub url: String, + pub id: String, + pub name: String, + pub url: String, } #[derive(Deserialize, Debug)] pub struct DonationLink { - pub id: String, - pub platform: String, - pub url: String, + pub id: String, + pub platform: String, + pub url: String, } #[derive(Deserialize, Debug)] pub struct ModVersion { - pub name: String, - pub version_number: String, - pub changelog: Option, - // pub dependencies: Option>, // TODO dependency wrangling, thank you modrinth, very cool - pub game_versions: Vec, - pub version_type: String, // TODO {alpha | beta | release} - pub loaders: Vec, - pub featured: bool, - pub id: String, // version id - pub project_id: String, // mod id - pub author_id: String, // user id - pub date_published: String, // TODO serialize datetime - pub downloads: isize, - pub changelog_url: Option, // NOTE deprecated - pub files: Vec, + pub name: String, + pub version_number: String, + pub changelog: Option, + + // TODO dependency wrangling, thank you modrinth, very cool + // pub dependencies: Option>, + pub game_versions: Vec, + pub version_type: String, // TODO {alpha | beta | release} + pub loaders: Vec, + pub featured: bool, + pub id: String, // version id + pub project_id: String, // mod id + pub author_id: String, // user id + pub date_published: String, // TODO serialize datetime + pub downloads: isize, + pub changelog_url: Option, // NOTE deprecated + pub files: Vec, } #[derive(Deserialize, Debug)] pub struct ModVersionFile { - pub hashes: HashMap, - pub url: String, - pub filename: String, - pub primary: bool, - pub size: isize, + pub hashes: HashMap, + pub url: String, + pub filename: String, + pub primary: bool, + pub size: isize, } #[derive(Deserialize, Debug)] pub struct Error { - pub error: String, - pub description: String, + pub error: String, + pub description: String, } impl fmt::Display for Error { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - write!(f, "{}: {}", self.error, self.description) - } + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "{}: {}", self.error, self.description) + } } impl std::error::Error for Error {} diff --git a/src/args.rs b/src/args.rs index c2aa32f..d5ef17a 100644 --- a/src/args.rs +++ b/src/args.rs @@ -19,8 +19,8 @@ */ use core::{ - fmt, - str::FromStr, + fmt, + str::FromStr, }; pub use arg::Args; @@ -28,137 +28,137 @@ use yacexits::EX_DATAERR; #[derive(Args, Debug)] pub struct Arguments { - pub argv0: String, + pub argv0: String, - #[arg(short = "v")] - pub v: Option, - - #[arg(sub)] - pub sub: Command, + #[arg(short = "v")] + pub v: Option, + + #[arg(sub)] + pub sub: Command, } #[derive(Args, Debug)] pub enum Command { - Add(AddArgs), - Get(SearchArgs), - Init(InitArgs), - List(HopArgs), - Remove(RmArgs), - Update(HopArgs), + Add(AddArgs), + Get(SearchArgs), + Init(InitArgs), + List(HopArgs), + Remove(RmArgs), + Update(HopArgs), } #[derive(Args, Debug)] pub struct AddArgs { - #[arg(short = "m")] - pub mc_version: String, + #[arg(short = "m")] + pub mc_version: String, - #[arg(short = "f")] - pub hopfiles: Vec, + #[arg(short = "f")] + pub hopfiles: Vec, - pub package_names: Vec, + pub package_names: Vec, } #[derive(Args, Debug)] pub struct HopArgs { - #[arg(short = "f")] - pub hopfile: Vec, + #[arg(short = "f")] + pub hopfile: Vec, - #[arg(short = "m")] - pub mc_version: Vec, + #[arg(short = "m")] + pub mc_version: Vec, - #[arg(short = "t")] - pub package_type: Option, + #[arg(short = "t")] + pub package_type: Option, } #[derive(Args, Debug)] pub struct InitArgs { - #[arg(short = "f")] - pub template: Option, + #[arg(short = "f")] + pub template: Option, - pub mc_version: String, - - pub package_type: PackageType, + pub mc_version: String, + + pub package_type: PackageType, } #[derive(Args, Debug)] pub struct RmArgs { - #[arg(short = "f")] - pub hopfile: Option, + #[arg(short = "f")] + pub hopfile: Option, - pub package_type: PackageType, + pub package_type: PackageType, - pub mc_version: String, - - pub package_names: Vec, + pub mc_version: String, + + pub package_names: Vec, } #[derive(Args, Debug)] pub struct SearchArgs { - #[arg(short = "n")] - pub no_confirm: bool, + #[arg(short = "n")] + pub no_confirm: bool, - /// Overrides the download directory - #[arg(short = "d")] - pub dir: Option, + /// Overrides the download directory + #[arg(short = "d")] + pub dir: Option, - /// Restricts the target Minecraft version - #[arg(short = "m")] - pub mc_version: Vec, + /// Restricts the target Minecraft version + #[arg(short = "m")] + pub mc_version: Vec, - /// Type of package to use - #[arg(short = "t")] - pub package_type: PackageType, + /// Type of package to use + #[arg(short = "t")] + pub package_type: PackageType, - pub package_name: String, + pub package_name: String, } #[derive(Clone, Copy, Debug)] pub enum PackageType { - Dummy, - Mod(Loader), - Pack(Loader), - Plugin(Server), - ResourcePack, + Dummy, + Mod(Loader), + Pack(Loader), + Plugin(Server), + ResourcePack, } #[derive(Clone, Copy, Debug)] pub enum Loader { - Fabric, - Forge, - Quilt, + Fabric, + Forge, + Quilt, } #[derive(Clone, Copy, Debug)] pub enum Server { - Bukkit, - Paper, - Purpur, - Spigot, - Sponge, + Bukkit, + Paper, + Purpur, + Spigot, + Sponge, } impl fmt::Display for Command { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - match &self { - Command::Add(_) => write!(f, "add"), - Command::Get(_) => write!(f, "get"), - Command::Init(_) => write!(f, "init"), - Command::List(_) => write!(f, "list"), - Command::Remove(_) => write!(f, "remove"), - Command::Update(_) => write!(f, "update"), - } - } + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match &self { + Command::Add(_) => write!(f, "add"), + Command::Get(_) => write!(f, "get"), + Command::Init(_) => write!(f, "init"), + Command::List(_) => write!(f, "list"), + Command::Remove(_) => write!(f, "remove"), + Command::Update(_) => write!(f, "update"), + } + } } #[derive(Clone, Debug)] pub enum PackageParseError { - Invalid(String), + Invalid(String), } -impl std::default::Default for PackageType { //TODO: Actually implement Default - fn default() -> Self { // for PackageType - PackageType::Dummy - } +impl std::default::Default for PackageType { // TODO: Actually implement Default + fn default() -> Self { // for PackageType + PackageType::Dummy + } } impl From for (String, u32) { @@ -170,43 +170,43 @@ impl From for (String, u32) { } impl FromStr for PackageType { - type Err = PackageParseError; - fn from_str(s: &str) -> Result { - let pieces: Vec<&str> = s.split("-").collect(); + type Err = PackageParseError; + fn from_str(s: &str) -> Result { + let pieces: Vec<&str> = s.split("-").collect(); - if pieces.len() > 2 || pieces.len() == 1 { - return Err(PackageParseError::Invalid( - format!("{}: Invalid package type.", s) - )); - } + if pieces.len() > 2 || pieces.len() == 1 { + return Err(PackageParseError::Invalid( + format!("{}: Invalid package type.", s) + )); + } - let (prefix, postfix) = (pieces[0], pieces[1]); + let (prefix, postfix) = (pieces[0], pieces[1]); - let loader = match prefix { - "bukkit" => return Ok(PackageType::Plugin(Server::Bukkit)), - "fabric" => Loader::Fabric, - "forge" => Loader::Forge, - "paper" => return Ok(PackageType::Plugin(Server::Paper)), - "purpur" => return Ok(PackageType::Plugin(Server::Purpur)), - "quilt" => Loader::Quilt, - "resource" => return Ok(PackageType::ResourcePack), - "spigot" => return Ok(PackageType::Plugin(Server::Spigot)), - "sponge" => return Ok(PackageType::Plugin(Server::Sponge)), - _ => { - return Err(PackageParseError::Invalid( - format!("{}: Invalid package type.", prefix) - )) - }, - }; + let loader = match prefix { + "bukkit" => return Ok(PackageType::Plugin(Server::Bukkit)), + "fabric" => Loader::Fabric, + "forge" => Loader::Forge, + "paper" => return Ok(PackageType::Plugin(Server::Paper)), + "purpur" => return Ok(PackageType::Plugin(Server::Purpur)), + "quilt" => Loader::Quilt, + "resource" => return Ok(PackageType::ResourcePack), + "spigot" => return Ok(PackageType::Plugin(Server::Spigot)), + "sponge" => return Ok(PackageType::Plugin(Server::Sponge)), + _ => { + return Err(PackageParseError::Invalid( + format!("{}: Invalid package type.", prefix) + )) + }, + }; - match postfix { - "mod" => Ok(PackageType::Mod(loader)), - "pack" => Ok(PackageType::Pack(loader)), - _ => { - Err(PackageParseError::Invalid( - format!("{}: Invalid package type.", postfix) - )) - }, - } - } + match postfix { + "mod" => Ok(PackageType::Mod(loader)), + "pack" => Ok(PackageType::Pack(loader)), + _ => { + Err(PackageParseError::Invalid( + format!("{}: Invalid package type.", postfix) + )) + }, + } + } } diff --git a/src/main.rs b/src/main.rs index 98679a4..000b851 100644 --- a/src/main.rs +++ b/src/main.rs @@ -38,40 +38,38 @@ use error::*; use yacexits::{ exit, EX_OSERR, - EX_SOFTWARE, + EX_SOFTWARE, }; struct AppContext { - args: Arguments, - config: Config, + args: Arguments, + config: Config, } #[tokio::main] #[no_mangle] async fn rust_main(arguments: yacexits::Args) -> Result { - let argv: Vec<&str> = arguments.into_iter().collect(); + let argv: Vec<&str> = arguments.into_iter().collect(); - let args = match Arguments::from_args(argv.clone().into_iter()) { + let args = match Arguments::from_args(argv.clone().into_iter()) { Ok(args) => args, Err(_) => { return Err((format!("Unable to ascertain arguments."), EX_OSERR)); } }; - let config = Config::read_config()?; - let ctx = AppContext { args, config }; + let config = Config::read_config()?; + let ctx = AppContext { args, config }; - match ctx.args.sub { - // Command::Get(search_args) => cmd_get(&ctx, search_args).await, - // Command::Init(hopfile_args) => cmd_init(hopfile_args).await, - _ => { - return Err(( - format!( - "{}: Unimplemented subcommand.", - ctx.args.sub - ), - EX_SOFTWARE, - )); - }, - }; + match ctx.args.sub { + // Command::Get(search_args) => cmd_get(&ctx, search_args).await, + // Command::Init(hopfile_args) => cmd_init(hopfile_args).await, + _ => { + let message = format!( + "{}: Unimplemented subcommand.", ctx.args.sub + ); + let code = EX_SOFTWARE; + Err(HopError { message, code }) + }, + }? } -- 2.30.2 From 844809738788daf76b65f2d6fe5fb40054897e45 Mon Sep 17 00:00:00 2001 From: emma Date: Mon, 27 Mar 2023 00:54:00 -0400 Subject: [PATCH 28/30] main.rs: minor change to subcommand matching --- src/main.rs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/main.rs b/src/main.rs index 000b851..839cdab 100644 --- a/src/main.rs +++ b/src/main.rs @@ -68,8 +68,7 @@ async fn rust_main(arguments: yacexits::Args) -> Result { let message = format!( "{}: Unimplemented subcommand.", ctx.args.sub ); - let code = EX_SOFTWARE; - Err(HopError { message, code }) + Err(HopError { message, code: EX_SOFTWARE }) }, }? } -- 2.30.2 From 570adfc17df7d7764df39463a4a280b4c09af0f7 Mon Sep 17 00:00:00 2001 From: emma Date: Tue, 28 Mar 2023 18:27:22 -0400 Subject: [PATCH 29/30] more changes to error types --- src/args.rs | 31 ++++++++++++++++++++++++++++++- src/error.rs | 48 ------------------------------------------------ src/main.rs | 13 +++---------- 3 files changed, 33 insertions(+), 59 deletions(-) delete mode 100644 src/error.rs diff --git a/src/args.rs b/src/args.rs index d5ef17a..827b13c 100644 --- a/src/args.rs +++ b/src/args.rs @@ -24,7 +24,7 @@ use core::{ }; pub use arg::Args; -use yacexits::EX_DATAERR; +use yacexits::{ EX_DATAERR, EX_USAGE }; #[derive(Args, Debug)] pub struct Arguments { @@ -210,3 +210,32 @@ impl FromStr for PackageType { } } } + +// TODO: Make this an enum for this for matching specific error kinds +pub struct ArgsError { + message: String, + code: u32, +} + +// TODO: More granular matching here with an enum +impl From> for ArgsError { + fn from(_: arg::ParseKind) -> Self { + let message = format!( + "{}", + "[-v] add | get | init | list | remove | update\n\n".to_owned() + + "add [-m version] [-f hopfiles...] packages...\n" + + "get [-n] [-d directory] [-m versions...] [-t types...] packages\n" + + "init [-f hopfiles...] version type\n" + + "list [[-f hopfiles...] | [-m versions...] [-t types...]]\n" + + "remove [[-f hopfiles...] | type version]] packages...\n" + + "update [[-f hopfiles... | [-m versions...] [-t types...]]", + ); + ArgsError { message, code: EX_USAGE } + } +} + +impl From for (String, u32) { + fn from(err: ArgsError) -> Self { + (err.message, err.code) + } +} diff --git a/src/error.rs b/src/error.rs deleted file mode 100644 index f0346ce..0000000 --- a/src/error.rs +++ /dev/null @@ -1,48 +0,0 @@ -/* - * Copyright (c) 2023 [ ] - * SPDX-License-Identifier: AGPL-3.0-or-later - * - * This file is part of Hopper. - * - * Hopper is free software: you can redistribute it and/or modify it under the - * terms of the GNU General Public License as published by the Free Software - * Foundation, either version 3 of the License, or (at your option) any later - * version. - * - * Hopper is distributed in the hope that it will be useful, but WITHOUT ANY - * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR - * A PARTICULAR PURPOSE. See the GNU General Public License for more details. - * You should have received a copy of the GNU General Public License along with - * Hopper. If not, see . - */ - -use yacexits::*; - -pub struct HopError { - pub code: u32, - - pub message: String, -} - -// TODO: More granular matching here -impl From> for HopError { - fn from(_: arg::ParseKind) -> Self { - let message = format!( - "Usage: {}", - "[-v] add | get | init | list | remove | update\n\n".to_owned() + - "add [-m version] [-f hopfiles...] packages...\n" + - "get [-n] [-d directory] [-m versions...] [-t types...] packages\n" + - "init [-f hopfiles...] version type\n" + - "list [[-f hopfiles...] | [-m versions...] [-t types...]]\n" + - "remove [[-f hopfiles...] | type version]] packages...\n" + - "update [[-f hopfiles... | [-m versions...] [-t types...]]", - ); - Self { message, code: EX_USAGE } - } -} - -impl From for (String, u32) { - fn from(err: HopError) -> Self { - (err.message, err.code) - } -} diff --git a/src/main.rs b/src/main.rs index 839cdab..bb600b3 100644 --- a/src/main.rs +++ b/src/main.rs @@ -26,17 +26,14 @@ mod args; mod client; mod config; mod hopfile; -mod error; use api::*; use args::*; use client::*; use config::*; use hopfile::*; -use error::*; use yacexits::{ - exit, EX_OSERR, EX_SOFTWARE, }; @@ -51,12 +48,8 @@ struct AppContext { async fn rust_main(arguments: yacexits::Args) -> Result { let argv: Vec<&str> = arguments.into_iter().collect(); - let args = match Arguments::from_args(argv.clone().into_iter()) { - Ok(args) => args, - Err(_) => { - return Err((format!("Unable to ascertain arguments."), EX_OSERR)); - } - }; + let args = Arguments::from_args(argv.clone().into_iter()) + .map_err(|err| { ArgsError::from(err) })?; let config = Config::read_config()?; let ctx = AppContext { args, config }; @@ -68,7 +61,7 @@ async fn rust_main(arguments: yacexits::Args) -> Result { let message = format!( "{}: Unimplemented subcommand.", ctx.args.sub ); - Err(HopError { message, code: EX_SOFTWARE }) + Err((message, EX_SOFTWARE)) }, }? } -- 2.30.2 From 49691bd68c6463a1c827dddd75102a8f39a2ffa7 Mon Sep 17 00:00:00 2001 From: emma Date: Wed, 19 Apr 2023 01:03:28 -0400 Subject: [PATCH 30/30] updated main() --- Cargo.lock | 15 +++------------ Cargo.toml | 2 +- src/main.rs | 30 +++++++++++++++++++++++------- 3 files changed, 27 insertions(+), 20 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index c2f6328..3e1f16d 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -370,15 +370,6 @@ version = "0.2.140" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "99227334921fae1a979cf0bfdfcc6b3e5ce376ef57e16fb6fb3ea2ed6095f80c" -[[package]] -name = "libc-print" -version = "0.1.21" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "06cea5d58bd9ba4717bbf5c6c5bb11bb6e9e76685b7fff34039b80f50ce86c11" -dependencies = [ - "libc", -] - [[package]] name = "libloading" version = "0.7.4" @@ -1009,12 +1000,12 @@ dependencies = [ [[package]] name = "yacexits" -version = "0.2.0" -source = "git+https://git.tebibyte.media/yac/yacexits.git?branch=c-entry#d2af983f4ad18dec2af0604d7f34b7863daa5c25" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "53fe740dd05c1bbc919431e842e6c1bea30195e0518ae99cae35b7f0730ddc18" dependencies = [ "bindgen", "libc", - "libc-print", ] [[package]] diff --git a/Cargo.toml b/Cargo.toml index baca1d2..cb69453 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -22,4 +22,4 @@ serde_json = "1" tokio = { version = "1", features = ["full"] } toml = "0.7.3" xdg = "2.4.1" -yacexits = { git = "https://git.tebibyte.media/yac/yacexits.git", branch = "c-entry", version = "0.2.0" } +yacexits = "0.1.5" diff --git a/src/main.rs b/src/main.rs index bb600b3..6deafc1 100644 --- a/src/main.rs +++ b/src/main.rs @@ -19,8 +19,6 @@ * Hopper. If not, see . */ -#![no_main] - mod api; mod args; mod client; @@ -33,9 +31,13 @@ use client::*; use config::*; use hopfile::*; +use std::env::args; + use yacexits::{ + exit, EX_OSERR, EX_SOFTWARE, + EX_USAGE, }; struct AppContext { @@ -43,12 +45,26 @@ struct AppContext { config: Config, } -#[tokio::main] -#[no_mangle] -async fn rust_main(arguments: yacexits::Args) -> Result { - let argv: Vec<&str> = arguments.into_iter().collect(); - let args = Arguments::from_args(argv.clone().into_iter()) +fn main() { + let argv = args().collect::>(); + match rust_main(argv.clone()) { + Ok(code) => exit(code), + Err((message, code)) => { + if code == EX_USAGE { + eprintln!("Usage: {} {}", argv[0], message); + } else { + eprintln!("{}: {}", argv[0], message); + } + exit(code); + }, + }; +} + +#[tokio::main] +async fn rust_main(argv: Vec) -> Result { + + let args = Arguments::from_args(argv.clone().iter().map(|s| s.as_str())) .map_err(|err| { ArgsError::from(err) })?; let config = Config::read_config()?; -- 2.30.2