From 2ad733d7a48967468f616e14ba7c1c11cea1b649 Mon Sep 17 00:00:00 2001 From: dark0dave Date: Sun, 25 Aug 2024 17:34:32 +0100 Subject: [PATCH] feat(config): Add config parser file for mod installer Signed-off-by: dark0dave --- .gitignore | 1 - Cargo.lock | 318 ++++++++++++++++++++++++++++++++++--- Cargo.toml | 18 +-- README.md | 2 +- check-version.sh | 30 ++-- example_parser_config.toml | 29 ++++ src/args.rs | 9 +- src/component.rs | 13 +- src/log_file.rs | 8 +- src/main.rs | 14 +- src/parser_config.rs | 181 +++++++++++++++++++++ src/utils.rs | 22 +-- src/weidu.rs | 22 ++- src/weidu_parser.rs | 142 +---------------- 14 files changed, 609 insertions(+), 200 deletions(-) create mode 100644 example_parser_config.toml create mode 100644 src/parser_config.rs diff --git a/.gitignore b/.gitignore index f52a7ba..d0d25b1 100644 --- a/.gitignore +++ b/.gitignore @@ -3,4 +3,3 @@ .swp target/ dist/ -Cargo.toml.test diff --git a/Cargo.lock b/Cargo.lock index 317cccd..d99ddb3 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -60,11 +60,23 @@ dependencies = [ "windows-sys 0.52.0", ] +[[package]] +name = "bitflags" +version = "2.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b048fb63fd8b5923fc5aa7b340d8e156aec7ec02f0c78fa8a6ddc2613f6f71de" + +[[package]] +name = "cfg-if" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" + [[package]] name = "clap" -version = "4.5.15" +version = "4.5.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "11d8838454fda655dafd3accb2b6e2bea645b9e4078abe84a22ceb947235c5cc" +checksum = "3e5a21b8495e732f1b3c364c9949b201ca7bae518c502c80256c96ad79eaf6ac" dependencies = [ "clap_builder", "clap_derive", @@ -72,9 +84,9 @@ dependencies = [ [[package]] name = "clap_builder" -version = "4.5.15" +version = "4.5.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "216aec2b177652e3846684cbfe25c9964d18ec45234f0f5da5157b207ed1aab6" +checksum = "8cf2dd12af7a047ad9d6da2b6b249759a22a7abc0f474c1dae1777afa4b21a73" dependencies = [ "anstream", "anstyle", @@ -106,12 +118,45 @@ version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d3fd119d74b830634cea2a0f58bbd0d54540518a14397557951e79340abc28c0" +[[package]] +name = "confy" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "45b1f4c00870f07dc34adcac82bb6a72cc5aabca8536ba1797e01df51d2ce9a0" +dependencies = [ + "directories", + "serde", + "thiserror", + "toml", +] + [[package]] name = "diff" version = "0.1.13" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "56254986775e3233ffa9c4d7d3faaf6d36a2c09d30b20687e9f88bc8bafc16c8" +[[package]] +name = "directories" +version = "5.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a49173b84e034382284f27f1af4dcbbd231ffa358c0fe316541a7337f376a35" +dependencies = [ + "dirs-sys", +] + +[[package]] +name = "dirs-sys" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "520f05a5cbd335fae5a99ff7a6ab8627577660ee5cfd6a94a6a929b52ff0321c" +dependencies = [ + "libc", + "option-ext", + "redox_users", + "windows-sys 0.48.0", +] + [[package]] name = "env_filter" version = "0.1.2" @@ -135,12 +180,35 @@ dependencies = [ "log", ] +[[package]] +name = "equivalent" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5" + [[package]] name = "fs_extra" version = "1.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "42703706b716c37f96a77aea830392ad231f44c9e9a67872fa5548707e11b11c" +[[package]] +name = "getrandom" +version = "0.2.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c4567c8db10ae91089c99af84c68c38da3ec2f087c3f82960bcdbf3656b6f4d7" +dependencies = [ + "cfg-if", + "libc", + "wasi", +] + +[[package]] +name = "hashbrown" +version = "0.14.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e5274423e17b7c9fc20b6e7e208532f9b19825d82dfd615708b70edd83df41f1" + [[package]] name = "heck" version = "0.5.0" @@ -153,12 +221,38 @@ version = "2.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9a3a5bfb195931eeb336b2a7b4d761daec841b97f947d34394601737a7bba5e4" +[[package]] +name = "indexmap" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "68b900aa2f7301e21c36462b170ee99994de34dff39a4a6a528e80e7376d07e5" +dependencies = [ + "equivalent", + "hashbrown", +] + [[package]] name = "is_terminal_polyfill" version = "1.70.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7943c866cc5cd64cbc25b2e01621d07fa8eb2a1a23160ee81ce38704e97b8ecf" +[[package]] +name = "libc" +version = "0.2.158" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d8adc4bb1803a324070e64a98ae98f38934d91957a99cfb3a43dcbc01bc56439" + +[[package]] +name = "libredox" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c0ff37bd590ca25063e35af745c343cb7a0271906fb7b37e4813e8f79f00268d" +dependencies = [ + "bitflags", + "libc", +] + [[package]] name = "log" version = "0.4.22" @@ -173,16 +267,25 @@ checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3" [[package]] name = "mod_installer" -version = "8.1.0" +version = "9.0.0" dependencies = [ "clap", + "confy", "env_logger", "fs_extra", "log", "pretty_assertions", + "serde", + "serde_derive", "walkdir", ] +[[package]] +name = "option-ext" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "04744f49eae99ab78e0d5c0b603ab218f515ea8cfe5a456d7629ad883a3b6e7d" + [[package]] name = "pretty_assertions" version = "1.4.0" @@ -204,13 +307,24 @@ dependencies = [ [[package]] name = "quote" -version = "1.0.36" +version = "1.0.37" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0fa76aaf39101c457836aec0ce2316dbdc3ab723cdda1c6bd4e6ad4208acaca7" +checksum = "b5b9d34b8991d19d98081b46eacdd8eb58c6f2b201139f7c5f643cc155a633af" dependencies = [ "proc-macro2", ] +[[package]] +name = "redox_users" +version = "0.4.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba009ff324d1fc1b900bd1fdb31564febe58a8ccc8a6fdbb93b543d33b13ca43" +dependencies = [ + "getrandom", + "libredox", + "thiserror", +] + [[package]] name = "regex" version = "1.10.6" @@ -249,6 +363,35 @@ dependencies = [ "winapi-util", ] +[[package]] +name = "serde" +version = "1.0.210" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c8e3592472072e6e22e0a54d5904d9febf8508f65fb8552499a1abc7d1078c3a" +dependencies = [ + "serde_derive", +] + +[[package]] +name = "serde_derive" +version = "1.0.210" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "243902eda00fad750862fc144cea25caca5e20d615af0a81bee94ca738f1df1f" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "serde_spanned" +version = "0.6.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eb5b1b31579f3811bf615c144393417496f152e12ac8b7663bf664f4a815306d" +dependencies = [ + "serde", +] + [[package]] name = "strsim" version = "0.11.1" @@ -257,20 +400,74 @@ checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f" [[package]] name = "syn" -version = "2.0.74" +version = "2.0.77" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1fceb41e3d546d0bd83421d3409b1460cc7444cd389341a4c880fe7a042cb3d7" +checksum = "9f35bcdf61fd8e7be6caf75f429fdca8beb3ed76584befb503b1569faee373ed" dependencies = [ "proc-macro2", "quote", "unicode-ident", ] +[[package]] +name = "thiserror" +version = "1.0.63" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c0342370b38b6a11b6cc11d6a805569958d54cfa061a29969c3b5ce2ea405724" +dependencies = [ + "thiserror-impl", +] + +[[package]] +name = "thiserror-impl" +version = "1.0.63" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a4558b58466b9ad7ca0f102865eccc95938dca1a74a856f2b57b6629050da261" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "toml" +version = "0.8.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a1ed1f98e3fdc28d6d910e6737ae6ab1a93bf1985935a1193e68f93eeb68d24e" +dependencies = [ + "serde", + "serde_spanned", + "toml_datetime", + "toml_edit", +] + +[[package]] +name = "toml_datetime" +version = "0.6.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0dd7358ecb8fc2f8d014bf86f6f638ce72ba252a2c3a2572f2a795f1d23efb41" +dependencies = [ + "serde", +] + +[[package]] +name = "toml_edit" +version = "0.22.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "583c44c02ad26b0c3f3066fe629275e50627026c51ac2e595cca4c230ce1ce1d" +dependencies = [ + "indexmap", + "serde", + "serde_spanned", + "toml_datetime", + "winnow", +] + [[package]] name = "unicode-ident" -version = "1.0.12" +version = "1.0.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b" +checksum = "e91b56cd4cadaeb79bbf1a5645f6b4f8dc5bde8834ad5894a8db35fda9efa1fe" [[package]] name = "utf8parse" @@ -288,6 +485,12 @@ dependencies = [ "winapi-util", ] +[[package]] +name = "wasi" +version = "0.11.0+wasi-snapshot-preview1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" + [[package]] name = "winapi-util" version = "0.1.9" @@ -297,13 +500,22 @@ dependencies = [ "windows-sys 0.59.0", ] +[[package]] +name = "windows-sys" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9" +dependencies = [ + "windows-targets 0.48.5", +] + [[package]] name = "windows-sys" version = "0.52.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" dependencies = [ - "windows-targets", + "windows-targets 0.52.6", ] [[package]] @@ -312,7 +524,22 @@ version = "0.59.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b" dependencies = [ - "windows-targets", + "windows-targets 0.52.6", +] + +[[package]] +name = "windows-targets" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a2fa6e2155d7247be68c096456083145c183cbbbc2764150dda45a87197940c" +dependencies = [ + "windows_aarch64_gnullvm 0.48.5", + "windows_aarch64_msvc 0.48.5", + "windows_i686_gnu 0.48.5", + "windows_i686_msvc 0.48.5", + "windows_x86_64_gnu 0.48.5", + "windows_x86_64_gnullvm 0.48.5", + "windows_x86_64_msvc 0.48.5", ] [[package]] @@ -321,28 +548,46 @@ version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973" dependencies = [ - "windows_aarch64_gnullvm", - "windows_aarch64_msvc", - "windows_i686_gnu", + "windows_aarch64_gnullvm 0.52.6", + "windows_aarch64_msvc 0.52.6", + "windows_i686_gnu 0.52.6", "windows_i686_gnullvm", - "windows_i686_msvc", - "windows_x86_64_gnu", - "windows_x86_64_gnullvm", - "windows_x86_64_msvc", + "windows_i686_msvc 0.52.6", + "windows_x86_64_gnu 0.52.6", + "windows_x86_64_gnullvm 0.52.6", + "windows_x86_64_msvc 0.52.6", ] +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8" + [[package]] name = "windows_aarch64_gnullvm" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" +[[package]] +name = "windows_aarch64_msvc" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc" + [[package]] name = "windows_aarch64_msvc" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" +[[package]] +name = "windows_i686_gnu" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e" + [[package]] name = "windows_i686_gnu" version = "0.52.6" @@ -355,30 +600,63 @@ version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" +[[package]] +name = "windows_i686_msvc" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406" + [[package]] name = "windows_i686_msvc" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" +[[package]] +name = "windows_x86_64_gnu" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e" + [[package]] name = "windows_x86_64_gnu" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc" + [[package]] name = "windows_x86_64_gnullvm" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" +[[package]] +name = "windows_x86_64_msvc" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538" + [[package]] name = "windows_x86_64_msvc" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" +[[package]] +name = "winnow" +version = "0.6.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "68a9bda4691f099d435ad181000724da8e5899daa10713c2d432552b9ccd3a6f" +dependencies = [ + "memchr", +] + [[package]] name = "yansi" version = "0.5.1" diff --git a/Cargo.toml b/Cargo.toml index cdb4284..e715259 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "mod_installer" -version = "8.1.0" +version = "9.0.0" edition = "2021" authors = [ "dark0dave" ] documentation = "https://raw.githubusercontent.com/dark0dave/mod_installer/main/README.md" @@ -19,14 +19,14 @@ keywords = [ ] [dependencies] -env_logger = "0.11.1" -fs_extra = "1.3.0" -log = "0.4.22" -walkdir = "2.3.2" - - [dependencies.clap] - version = "4.5.9" - features = [ "derive", "env" ] +confy = "^0.6" +clap = { version = "^4.5.9", features = ["derive", "env"] } +env_logger = "^0.11.1" +fs_extra = "^1.3.0" +log = "^0.4.22" +serde = { version = "^1.0.152", features = ["derive"] } +serde_derive = "^1.0.152" +walkdir = "^2.3.2" [dev-dependencies] pretty_assertions = "1.3.0" diff --git a/README.md b/README.md index 1122325..3557444 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -# Infinity Engine Mod Installer + # Infinity Engine Mod Installer [![](./docs/rust.svg)](https://www.rust-lang.org/tools/install) [![](https://img.shields.io/badge/Linux-FCC624?style=for-the-badge&logo=linux&logoColor=black)](https://github.com/dark0dave/mod_installer/releases/latest) [![](https://img.shields.io/badge/Windows-0078D6?&style=for-the-badge&logoColor=white&logo=git-for-windows)](https://github.com/dark0dave/mod_installer/releases/latest) diff --git a/check-version.sh b/check-version.sh index ee8a78b..c46b5d0 100755 --- a/check-version.sh +++ b/check-version.sh @@ -1,13 +1,25 @@ #!/usr/bin/env bash set -euo pipefail -release_tag=$(git describe --tags --abbrev=0); -trap "rm -f Cargo.toml.test" SIGINT; -sed "s/^version \= .*/version = \"${release_tag/v/}\"/" Cargo.toml > Cargo.toml.test; -diff -sd Cargo.toml.test Cargo.toml; -if [ $? -eq 0 ]; then - rm -f Cargo.toml.test; - exit 0; -else +function version_gt() { test "$(echo "$@" | tr " " "\n" | sort -V | head -n 1)" != "$1"; } + +function main() { + local tag_version=$(git describe --tags --abbrev=0); + echo "Git tag version: ${tag_version}" + local current_version=$(head -3 Cargo.toml | tail -1 | sed -E "s/^version \= \"(.*)\"/\1/") + echo "Cargo.toml version: v${current_version}" + if [ "${tag_version}" == "v${current_version}" ]; then + echo "Versions are the same" + exit 0; + fi + + if version_gt "v${current_version}" "${tag_version}"; then + echo "Current version greater than tag version" + exit 0; + fi + + echo "Failed, tag version: ${tag_version} is greater than Cargo,toml: v${current_version}" exit 1; -fi +} + +main diff --git a/example_parser_config.toml b/example_parser_config.toml new file mode 100644 index 0000000..59ff6c0 --- /dev/null +++ b/example_parser_config.toml @@ -0,0 +1,29 @@ +in_progress_words = [ + "installing", + "creating", +] +useful_status_words = [ + "copied", + "copying", + "creating", + "installed", + "installing", + "patched", + "patching", + "processed", + "processing", +] +choice_words = [ + "choice", + "choose", + "select", + "enter", +] +choice_phrase = [ + "do you want", + "would you like", +] +completed_with_warnings = "installed with warnings" +failed_with_error = "not installed due to errors" +finished = "successfully installed" +eet_finished = "process ended" diff --git a/src/args.rs b/src/args.rs index d438184..ed70db9 100644 --- a/src/args.rs +++ b/src/args.rs @@ -2,7 +2,9 @@ use std::path::PathBuf; use clap::{ArgAction, Parser}; -const WEIDU_LOG_MODE_ERROR: &str = r" +pub(crate) const CARGO_PKG_NAME: &str = env!("CARGO_PKG_NAME"); + +pub(crate) const WEIDU_LOG_MODE_ERROR: &str = r" Please provide a valid weidu logging setting, options are: --weidu-log-mode log X log output and details to X --weidu-log-mode autolog log output and details to WSETUP.DEBUG @@ -101,11 +103,13 @@ fn parse_absolute_path(arg: &str) -> Result { #[cfg(test)] mod tests { + use super::*; use pretty_assertions::assert_eq; + use std::error::Error; #[test] - fn test_parse_weidu_log_mode() { + fn test_parse_weidu_log_mode() -> Result<(), Box> { let tests = vec![ ("autolog", Ok("--autolog".to_string())), ("log /home", Ok("--log /home".to_string())), @@ -134,5 +138,6 @@ mod tests { "Result {result:?} didn't match Expected {expected:?}", ); } + Ok(()) } } diff --git a/src/component.rs b/src/component.rs index 76dc67a..14017d2 100644 --- a/src/component.rs +++ b/src/component.rs @@ -139,13 +139,14 @@ impl TryFrom for Component { #[cfg(test)] mod tests { + use super::*; use pretty_assertions::assert_eq; #[test] - fn test_parse_windows() { + fn test_parse_windows() -> Result<(), Box> { let mod_string = r"~TOBEX\TOBEX.TP2~ #0 #100 // TobEx - Core: v28"; - let mod_component = Component::try_from(mod_string.to_string()).unwrap(); + let mod_component = Component::try_from(mod_string.to_string())?; let expected = Component { tp_file: "TOBEX.TP2".to_string(), name: "tobex".to_string(), @@ -155,11 +156,12 @@ mod tests { sub_component: "".to_string(), version: "v28".to_string(), }; - assert_eq!(mod_component, expected) + assert_eq!(mod_component, expected); + Ok(()) } #[test] - fn test_strict_match() { + fn test_strict_match() -> Result<(), Box> { let non_strict_match_1 = Component { tp_file: "TOBEX.TP2".to_string(), name: "tobex".to_string(), @@ -183,6 +185,7 @@ mod tests { assert_eq!( non_strict_match_1.strict_matching(&non_strict_match_2), false - ) + ); + Ok(()) } } diff --git a/src/log_file.rs b/src/log_file.rs index 881da9e..5fcdbe4 100644 --- a/src/log_file.rs +++ b/src/log_file.rs @@ -53,14 +53,15 @@ impl TryFrom for LogFile { #[cfg(test)] mod tests { + use super::*; use pretty_assertions::assert_eq; use std::path::PathBuf; #[test] - fn test_parse_weidu_log() { + fn test_parse_weidu_log() -> Result<(), Box> { let test_log = PathBuf::from("fixtures/test.log"); - let result = LogFile::try_from(test_log).unwrap(); + let result = LogFile::try_from(test_log)?; let expected = LogFile(vec![ Component { tp_file: "TEST.TP2".to_string(), @@ -109,6 +110,7 @@ mod tests { version: "v16".to_string(), }, ]); - assert_eq!(expected, result) + assert_eq!(expected, result); + Ok(()) } } diff --git a/src/main.rs b/src/main.rs index 86ac299..f485691 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,8 +1,9 @@ -use std::{collections::HashMap, process::ExitCode}; +use std::{collections::HashMap, process::ExitCode, sync::Arc}; -use args::Args; +use args::{Args, CARGO_PKG_NAME}; use clap::Parser; use env_logger::Env; +use parser_config::ParserConfig; use utils::find_mods; use crate::{ @@ -13,6 +14,7 @@ use crate::{ mod args; mod component; mod log_file; +mod parser_config; mod state; mod utils; mod weidu; @@ -29,6 +31,13 @@ fn main() -> ExitCode { " ); let args = Args::parse(); + let parser_config: Arc = match confy::load(CARGO_PKG_NAME, None) { + Ok(config) => Arc::new(config), + Err(err) => { + log::error!("Internal error with config crate, {:?}", err); + return ExitCode::FAILURE; + } + }; let mods_to_be_installed = match find_mods( args.log_file, @@ -63,6 +72,7 @@ fn main() -> ExitCode { match install( &args.weidu_binary, &args.game_directory, + parser_config.clone(), weidu_mod, &args.language, &args.weidu_log_mode, diff --git a/src/parser_config.rs b/src/parser_config.rs new file mode 100644 index 0000000..9003e71 --- /dev/null +++ b/src/parser_config.rs @@ -0,0 +1,181 @@ +use crate::state::State; + +use serde_derive::{Deserialize, Serialize}; + +#[derive(Debug, PartialEq, Serialize, Deserialize)] +pub(crate) struct ParserConfig { + pub(crate) in_progress_words: Vec, + pub(crate) useful_status_words: Vec, + pub(crate) choice_words: Vec, + pub(crate) choice_phrase: Vec, + pub(crate) completed_with_warnings: String, + pub(crate) failed_with_error: String, + pub(crate) finished: String, + pub(crate) eet_finished: String, +} + +impl Default for ParserConfig { + fn default() -> Self { + Self { + in_progress_words: vec!["installing".to_string(), "creating".to_string()], + useful_status_words: vec![ + "copied".to_string(), + "copying".to_string(), + "creating".to_string(), + "installed".to_string(), + "installing".to_string(), + "patched".to_string(), + "patching".to_string(), + "processed".to_string(), + "processing".to_string(), + ], + choice_words: vec![ + "choice".to_string(), + "choose".to_string(), + "select".to_string(), + "enter".to_string(), + ], + choice_phrase: vec!["do you want".to_string(), "would you like".to_string()], + completed_with_warnings: "installed with warnings".to_string(), + failed_with_error: "not installed due to errors".to_string(), + finished: "successfully installed".to_string(), + eet_finished: "process ended".to_string(), + } + } +} + +impl ParserConfig { + pub fn string_looks_like_question(&self, weidu_output: &str) -> bool { + let comparable_output = weidu_output.trim().to_ascii_lowercase(); + // installing|creating + for progress_word in self.in_progress_words.iter() { + if comparable_output.contains(progress_word) { + return false; + } + } + + for question in self.choice_phrase.iter() { + if comparable_output.contains(question) { + return true; + } + } + + for question in self.choice_words.iter() { + for word in comparable_output.split_whitespace() { + if word + .chars() + .filter(|c| c.is_alphabetic()) + .collect::() + == *question + { + return true; + } + } + } + + false + } + + pub fn detect_weidu_finished_state(&self, weidu_output: &str) -> Option { + let comparable_output = weidu_output.trim().to_lowercase(); + if comparable_output.contains(&self.failed_with_error) { + Some(State::CompletedWithErrors { + error_details: comparable_output, + }) + } else if comparable_output.contains(&self.completed_with_warnings) { + Some(State::CompletedWithWarnings) + } else if comparable_output.contains(&self.finished) + || comparable_output.contains(&self.eet_finished) + { + Some(State::Completed) + } else { + None + } + } +} + +#[cfg(test)] +mod tests { + + use super::*; + use pretty_assertions::assert_eq; + use std::{error::Error, path::Path, result::Result}; + + #[test] + fn test_exit_warnings() -> Result<(), Box> { + let config = ParserConfig::default(); + let test = "INSTALLED WITH WARNINGS Additional equipment for Thieves and Bards"; + assert_eq!(config.string_looks_like_question(test), false); + assert_eq!( + config.detect_weidu_finished_state(test), + Some(State::CompletedWithWarnings) + ); + Ok(()) + } + + #[test] + fn test_exit_success() -> Result<(), Box> { + let config = ParserConfig::default(); + let test = "SUCCESSFULLY INSTALLED Jan's Extended Quest"; + assert_eq!(config.string_looks_like_question(test), false); + assert_eq!( + config.detect_weidu_finished_state(test), + Some(State::Completed) + ); + Ok(()) + } + + #[test] + fn is_not_question() -> Result<(), Box> { + let config = ParserConfig::default(); + let test = "Creating epilogues. Too many epilogues... Why are there so many options here?"; + assert_eq!(config.string_looks_like_question(test), false); + let test = "Including file(s) spellchoices_defensive/vanilla/ENCHANTER.TPH"; + assert_eq!(config.string_looks_like_question(test), false); + Ok(()) + } + + #[test] + fn is_a_question() -> Result<(), Box> { + let config = ParserConfig::default(); + let tests = vec!["Enter the full path to your Baldur's Gate installation then press Enter.", "Enter the full path to your BG:EE+SoD installation then press Enter.\ +Example: C:\\Program Files (x86)\\BeamDog\\Games\\00806", "[N]o, [Q]uit or choose one:", "Please enter the chance for items to randomly not be randomised as a integet number (e.g. 10 for 10%)"]; + for question in tests { + assert_eq!( + config.string_looks_like_question(question), + true, + "String {} doesn't look like a question", + question + ); + } + Ok(()) + } + + #[test] + fn is_not_a_question() -> Result<(), Box> { + let config = ParserConfig::default(); + let tests = vec![ + "FAILURE:", + "NOT INSTALLED DUE TO ERRORS The BG1 NPC Project: Required Modifications", + ]; + for question in tests { + assert_eq!( + config.string_looks_like_question(question), + false, + "String {} does look like a question", + question + ); + } + Ok(()) + } + + #[test] + fn load_config() -> Result<(), Box> { + let root = std::env::current_dir()?; + let config_path = Path::join(&root, Path::new("example_parser_config.toml")); + let config: ParserConfig = confy::load_path(config_path)?; + let expected = ParserConfig::default(); + assert_eq!(expected, config); + Ok(()) + } +} diff --git a/src/utils.rs b/src/utils.rs index fcd0cc2..9738035 100644 --- a/src/utils.rs +++ b/src/utils.rs @@ -103,11 +103,12 @@ pub fn sleep(millis: u64) { #[cfg(test)] mod tests { + use super::*; use pretty_assertions::assert_eq; #[test] - fn finds_mod_folder() { + fn finds_mod_folder() -> Result<(), Box> { let mod_component = Component { tp_file: "TEST.TP2".to_string(), name: "test_mod_name_1".to_string(), @@ -121,25 +122,27 @@ mod tests { let expected = Path::new(&format!("fixtures/mods/mod_a/{}", mod_component.name)).to_path_buf(); - assert_eq!(mod_folder, Some(expected)) + assert_eq!(mod_folder, Some(expected)); + Ok(()) } #[test] - fn test_find_mods() { + fn test_find_mods() -> Result<(), Box> { let log_file = PathBuf::from("./fixtures/test.log"); let skip_installed = false; let game_directory = PathBuf::from("./fixtures"); - let result = find_mods(log_file.clone(), skip_installed, game_directory, false); - let expected = LogFile::try_from(log_file); - assert_eq!(expected.ok(), result.ok()) + let result = find_mods(log_file.clone(), skip_installed, game_directory, false)?; + let expected = LogFile::try_from(log_file)?; + assert_eq!(expected, result); + Ok(()) } #[test] - fn test_find_mods_skip_installed() { + fn test_find_mods_skip_installed() -> Result<(), Box> { let log_file = PathBuf::from("./fixtures/test.log"); let skip_installed = true; let game_directory = PathBuf::from("./fixtures"); - let result = find_mods(log_file, skip_installed, game_directory, false).unwrap(); + let result = find_mods(log_file, skip_installed, game_directory, false)?; let expected = LogFile(vec![ Component { tp_file: "TEST.TP2".to_string(), @@ -160,6 +163,7 @@ mod tests { version: "1.02".to_string(), }, ]); - assert_eq!(expected, result) + assert_eq!(expected, result); + Ok(()) } } diff --git a/src/weidu.rs b/src/weidu.rs index f21f147..3de63d3 100644 --- a/src/weidu.rs +++ b/src/weidu.rs @@ -10,7 +10,10 @@ use std::{ thread, }; -use crate::{component::Component, state::State, utils::sleep, weidu_parser::parse_raw_output}; +use crate::{ + component::Component, parser_config::ParserConfig, state::State, utils::sleep, + weidu_parser::parse_raw_output, +}; pub(crate) fn get_user_input() -> String { let stdin = io::stdin(); @@ -42,6 +45,7 @@ pub(crate) enum InstallationResult { pub(crate) fn install( weidu_binary: &PathBuf, game_directory: &PathBuf, + parser_config: Arc, weidu_mod: &Component, language: &str, weidu_log_mode: &str, @@ -58,15 +62,25 @@ pub(crate) fn install( .spawn() .expect("Failed to spawn weidu process"); - handle_io(child, timeout) + handle_io(child, parser_config, timeout) } -pub(crate) fn handle_io(mut child: Child, timeout: usize) -> InstallationResult { +pub(crate) fn handle_io( + mut child: Child, + parser_config: Arc, + timeout: usize, +) -> InstallationResult { let mut weidu_stdin = child.stdin.take().unwrap(); let wait_counter = Arc::new(AtomicUsize::new(0)); let raw_output_receiver = create_output_reader(child.stdout.take().unwrap()); let (sender, parsed_output_receiver) = mpsc::channel::(); - parse_raw_output(sender, raw_output_receiver, wait_counter.clone(), timeout); + parse_raw_output( + sender, + raw_output_receiver, + parser_config, + wait_counter.clone(), + timeout, + ); loop { match parsed_output_receiver.try_recv() { diff --git a/src/weidu_parser.rs b/src/weidu_parser.rs index 9298945..61e2b73 100644 --- a/src/weidu_parser.rs +++ b/src/weidu_parser.rs @@ -7,31 +7,7 @@ use std::{ thread, }; -use crate::{state::State, utils::sleep}; - -const WEIDU_USEFUL_STATUS: [&str; 9] = [ - "copied", - "copying", - "creating", - "installed", - "installing", - "patched", - "patching", - "processed", - "processing", -]; - -const WEIDU_CHOICE_WORDS: [&str; 4] = ["choice", "choose", "select", "enter"]; - -const WEIDU_CHOICE_PHRASE: [&str; 2] = ["do you want", "would you like"]; - -const WEIDU_COMPLETED_WITH_WARNINGS: &str = "installed with warnings"; - -const WEIDU_FAILED_WITH_ERROR: &str = "not installed due to errors"; - -const WEIDU_FINISHED: &str = "successfully installed"; - -const EET_FINISHED: &str = "Process ended"; +use crate::{parser_config::ParserConfig, state::State, utils::sleep}; #[derive(Debug)] enum ParserState { @@ -43,6 +19,7 @@ enum ParserState { pub(crate) fn parse_raw_output( sender: Sender, receiver: Receiver, + parser_config: Arc, wait_count: Arc, timeout: usize, ) { @@ -55,7 +32,7 @@ pub(crate) fn parse_raw_output( match receiver.try_recv() { Ok(string) => match current_state { ParserState::CollectingQuestion | ParserState::WaitingForMoreQuestionContent => { - if WEIDU_USEFUL_STATUS.contains(&string.as_str()) { + if parser_config.useful_status_words.contains(&string) { log::debug!( "Weidu seems to know an answer for the last question, ignoring it" ); @@ -69,12 +46,14 @@ pub(crate) fn parse_raw_output( } ParserState::LookingForInterestingOutput => { log::trace!("{}", string); - if let Some(weidu_finished_state) = detect_weidu_finished_state(&string) { + if let Some(weidu_finished_state) = + parser_config.detect_weidu_finished_state(&string) + { sender .send(weidu_finished_state) .expect("Failed to send process error event"); break; - } else if string_looks_like_question(&string) { + } else if parser_config.string_looks_like_question(&string) { log::debug!( "Changing parser state to '{:?}' due to line {}", ParserState::CollectingQuestion, @@ -125,110 +104,3 @@ pub(crate) fn parse_raw_output( } }); } - -fn string_looks_like_question(weidu_output: &str) -> bool { - let comparable_output = weidu_output.trim().to_ascii_lowercase(); - // installing|creating - if comparable_output.contains(WEIDU_USEFUL_STATUS[2]) - || comparable_output.contains(WEIDU_USEFUL_STATUS[4]) - { - return false; - } - - for question in WEIDU_CHOICE_PHRASE { - if comparable_output.contains(question) { - return true; - } - } - - for question in WEIDU_CHOICE_WORDS { - for word in comparable_output.split_whitespace() { - if word - .chars() - .filter(|c| c.is_alphabetic()) - .collect::() - == question - { - return true; - } - } - } - - false -} - -fn detect_weidu_finished_state(weidu_output: &str) -> Option { - let comparable_output = weidu_output.trim().to_lowercase(); - if comparable_output.contains(WEIDU_FAILED_WITH_ERROR) { - Some(State::CompletedWithErrors { - error_details: comparable_output, - }) - } else if comparable_output.contains(WEIDU_COMPLETED_WITH_WARNINGS) { - Some(State::CompletedWithWarnings) - } else if comparable_output.contains(WEIDU_FINISHED) || comparable_output.contains(EET_FINISHED) - { - Some(State::Completed) - } else { - None - } -} - -#[cfg(test)] -mod tests { - use super::*; - use pretty_assertions::assert_eq; - - #[test] - fn test_exit_warnings() { - let test = "INSTALLED WITH WARNINGS Additional equipment for Thieves and Bards"; - assert_eq!(string_looks_like_question(test), false); - assert_eq!( - detect_weidu_finished_state(test), - Some(State::CompletedWithWarnings) - ) - } - #[test] - fn test_exit_success() { - let test = "SUCCESSFULLY INSTALLED Jan's Extended Quest"; - assert_eq!(string_looks_like_question(test), false); - assert_eq!(detect_weidu_finished_state(test), Some(State::Completed)) - } - - #[test] - fn is_not_question() { - let test = "Creating epilogues. Too many epilogues... Why are there so many options here?"; - assert_eq!(string_looks_like_question(test), false); - let test = "Including file(s) spellchoices_defensive/vanilla/ENCHANTER.TPH"; - assert_eq!(string_looks_like_question(test), false); - } - - #[test] - fn is_a_question() { - let tests = vec!["Enter the full path to your Baldur's Gate installation then press Enter.", "Enter the full path to your BG:EE+SoD installation then press Enter.\ -Example: C:\\Program Files (x86)\\BeamDog\\Games\\00806", "[N]o, [Q]uit or choose one:", "Please enter the chance for items to randomly not be randomised as a integet number (e.g. 10 for 10%)"]; - for question in tests { - assert_eq!( - string_looks_like_question(question), - true, - "String {} doesn't look like a question", - question - ); - } - } - - #[test] - fn is_not_a_question() { - let tests = vec![ - "FAILURE:", - "NOT INSTALLED DUE TO ERRORS The BG1 NPC Project: Required Modifications", - ]; - for question in tests { - assert_eq!( - string_looks_like_question(question), - false, - "String {} does look like a question", - question - ); - } - } -}