diff options
| -rw-r--r-- | Cargo.lock | 11 | ||||
| -rw-r--r-- | Cargo.toml | 1 | ||||
| -rw-r--r-- | src/launcher.rs | 25 | ||||
| -rw-r--r-- | src/launcher/instance.rs | 9 | ||||
| -rw-r--r-- | src/launcher/profile.rs | 37 | ||||
| -rw-r--r-- | src/launcher/strsub.rs | 157 |
6 files changed, 207 insertions, 33 deletions
@@ -2335,7 +2335,6 @@ dependencies = [ "serde_json", "sha1_smol", "tokio", - "uuid", ] [[package]] @@ -4032,16 +4031,6 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b6c140620e7ffbb22c2dee59cafe6084a59b5ffc27a8859a5f0d494b5d52b6be" [[package]] -name = "uuid" -version = "1.11.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f8c5f0a0af699448548ad1a2fbf920fb4bee257eae39953ba95cb84891a0446a" -dependencies = [ - "getrandom", - "serde", -] - -[[package]] name = "vcpkg" version = "0.2.15" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -12,7 +12,6 @@ serde = { version = "1.0.216", features = ["derive"] } serde_json = "1.0.133" sha1_smol = { version = "1.0.1", features = ["alloc", "std", "serde"] } tokio = { version = "1.42.0", features = ["fs"] } -uuid = { version = "1.11.0", features = ["rng", "serde"] } [workspace] members = [ diff --git a/src/launcher.rs b/src/launcher.rs index 70c313c..06a5d5b 100644 --- a/src/launcher.rs +++ b/src/launcher.rs @@ -1,18 +1,31 @@ mod constants;
mod version;
-mod instance;
mod profile;
+mod strsub;
-use std::error::Error;
+use std::collections::HashMap;
+use serde::{Deserialize, Serialize};
use version::VersionList;
+use crate::launcher::profile::{Instance, Profile};
+
+#[derive(Debug, Clone, Serialize, Deserialize)]
+pub struct Settings {
+ profiles: HashMap<String, Profile>,
+ instances: HashMap<String, Instance>
+}
pub struct Launcher {
- pub versions: VersionList
-
+ versions: VersionList,
+ settings: Settings,
}
impl Launcher {
- pub async fn new() -> Result<Launcher, Box<dyn Error>> {
- todo!()
+ pub fn new(versions: VersionList, settings: Settings) -> Launcher {
+ Launcher {
+ versions,
+ settings
+ }
}
+
+
}
\ No newline at end of file diff --git a/src/launcher/instance.rs b/src/launcher/instance.rs deleted file mode 100644 index 3e62990..0000000 --- a/src/launcher/instance.rs +++ /dev/null @@ -1,9 +0,0 @@ -use std::path::PathBuf; -use serde::Deserialize; -use uuid::Uuid; - -#[derive(Deserialize, Debug, Clone)] -pub struct Instance { - pub uuid: Uuid, - pub path: PathBuf -} diff --git a/src/launcher/profile.rs b/src/launcher/profile.rs index 835d912..104faef 100644 --- a/src/launcher/profile.rs +++ b/src/launcher/profile.rs @@ -1,8 +1,33 @@ -use super::instance::Instance; +use std::path::{Path, PathBuf}; +use serde::{Deserialize, Serialize}; -struct Profile<'l> { - name: String, - version_id: String, - java_runtime: Option<String>, - instance: &'l Instance +#[derive(Deserialize, Serialize, Debug, Clone)] +pub struct Instance { + pub name: String, + pub path: Option<PathBuf> // relative to launcher home (or absolute) +} + +#[derive(Deserialize, Serialize, Debug, Clone)] +pub struct Profile { + pub version_id: String, + pub java_runtime: Option<String>, + pub instance: String // ugly that this is a string instead of reference to an Instance but whatever I'm lazy +} + +impl Instance { + fn instance_dir(home: impl AsRef<Path>, name: impl AsRef<Path>) -> PathBuf { + let mut out = home.as_ref().join("instances"); + out.push(name); + out + } + + pub fn get_path(&self, home: impl AsRef<Path>) -> PathBuf { + self.path.as_ref().map(|p| { + if p.is_relative() { + Self::instance_dir(home.as_ref(), p) + } else { + p.to_owned() + } + }).unwrap_or_else(|| Self::instance_dir(home, &self.name)) + } }
\ No newline at end of file diff --git a/src/launcher/strsub.rs b/src/launcher/strsub.rs new file mode 100644 index 0000000..fba449a --- /dev/null +++ b/src/launcher/strsub.rs @@ -0,0 +1,157 @@ +// a cheap-o implementation of StrSubstitutor from apache commons +// (does not need to support recursive evaluation or preserving escapes, it was never enabled in + +const ESCAPE: char = '$'; +const VAR_BEGIN: &str = "${"; +const VAR_END: &str = "}"; +const VAR_DEFAULT: &str = ":-"; + +fn prev_char(slice: &str, mut idx: usize) -> Option<(usize, char)> { + if idx == 0 || idx >= slice.len() { + return None; + } + + loop { + // will never panic because the condition always succeeds for idx == 0 + // (the precondition will handle cases where the slice is empty) + idx -= 1; + + if slice.is_char_boundary(idx) { + return Some((idx, slice[idx..].chars().next().unwrap())) + } + } +} + +// basically the same thing as replace_string, but it creates the String itself and returns it. +pub fn replace_str<T>(input: &str, sub: T) -> String +where + T: Fn(/*key: */ &str) -> Option<String> +{ + let mut input = String::from(input); + replace_string(&mut input, sub); + input +} + +// handles ${replacements} on this string IN-PLACE. Calls the "sub" function for each key it receives. +// if "sub" returns None, it will use a default value or ignore the ${substitution}. +// There are no "invalid inputs" and this function should never panic. +pub fn replace_string<T>(input: &mut String, sub: T) +where + T: Fn(/*key: */ &str) -> Option<String> +{ + let mut cursor = input.len(); + while let Some(idx) = input[..cursor].rfind(VAR_BEGIN) { + // note: for some reason, apache processes escapes BEFORE checking if it's even a valid + // replacement expression. strange behavior IMO. + if let Some((pidx, pc)) = prev_char(input.as_ref(), idx) { + if pc == ESCAPE { + // this "replacement" is escaped. remove the escape marker and continue. + input.remove(pidx); + cursor = pidx; + continue; + } + } + + let Some(endidx) = input[idx..cursor].find(VAR_END).map(|v| v + idx) else { + // unclosed replacement expression. ignore. + cursor = idx; + continue; + }; + + let spec = &input[(idx + VAR_BEGIN.len())..endidx]; + let name; + let def_opt; + + if let Some(def) = spec.find(VAR_DEFAULT) { + name = &spec[..def]; + def_opt = Some(&spec[(def + VAR_DEFAULT.len())..]); + } else { + name = spec; + def_opt = None; + } + + if let Some(sub_val) = sub(name).map_or_else(|| def_opt.map(|d| d.to_owned()), |v| Some(v)) { + input.replace_range(idx..(endidx + VAR_END.len()), sub_val.as_ref()); + } + + cursor = idx; + } +} + +#[cfg(test)] +mod tests { + use super::*; + + fn replace_fun(key: &str) -> Option<String> { + match key { + "exists" => Some("value123".into()), + "empty" => None, + "borger" => Some("\u{1f354}".into()), + _ => panic!("replace_fun called with unexpected key: {}", key) + } + } + + #[test] + fn test_standard_replace() { + assert_eq!(replace_str("this has ${exists} and more", replace_fun), "this has value123 and more"); + assert_eq!(replace_str("multiple ${exists} repl${exists}ace", replace_fun), "multiple value123 replvalue123ace"); + assert_eq!(replace_str("${exists}${exists}", replace_fun), "value123value123"); + } + + #[test] + fn test_empty_replace() { + assert_eq!(replace_str("this has ${empty} and more", replace_fun), "this has ${empty} and more"); + assert_eq!(replace_str("multiple ${empty} repl${empty}ace", replace_fun), "multiple ${empty} repl${empty}ace"); + assert_eq!(replace_str("${empty}${empty}", replace_fun), "${empty}${empty}"); + } + + #[test] + fn test_homogenous_replace() { + assert_eq!(replace_str("some ${exists} and ${empty} ...", replace_fun), "some value123 and ${empty} ..."); + assert_eq!(replace_str("some ${empty} and ${exists} ...", replace_fun), "some ${empty} and value123 ..."); + assert_eq!(replace_str("${exists}${empty}", replace_fun), "value123${empty}"); + assert_eq!(replace_str("${empty}${exists}", replace_fun), "${empty}value123"); + } + + #[test] + fn test_default_replace() { + assert_eq!(replace_str("some ${exists:-def1} and ${empty:-def2} ...", replace_fun), "some value123 and def2 ..."); + assert_eq!(replace_str("some ${empty:-def1} and ${exists:-def2} ...", replace_fun), "some def1 and value123 ..."); + assert_eq!(replace_str("abc${empty:-}def", replace_fun), "abcdef"); + assert_eq!(replace_str("${empty:-}${empty:-}", replace_fun), ""); + } + + #[test] + fn test_escape() { + assert_eq!(replace_str("an $${escaped} replacement (${exists})", replace_fun), "an ${escaped} replacement (value123)"); + assert_eq!(replace_str("${exists}$${escaped}${exists}", replace_fun), "value123${escaped}value123"); + + // make sure this weird behavior is preserved... (the original code seemed to show it) + assert_eq!(replace_str("some $${ else", replace_fun), "some ${ else"); + } + + #[test] + fn test_weird() { + assert_eq!(replace_str("${exists}", replace_fun), "value123"); + assert_eq!(replace_str("$${empty}", replace_fun), "${empty}"); + assert_eq!(replace_str("${empty:-a}", replace_fun), "a"); + assert_eq!(replace_str("${empty:-}", replace_fun), ""); + + // there is no nested evaluation, but the algorithm does proceed through the string backwards + assert_eq!(replace_str("${exists:-${exists}}", replace_fun), "${exists:-value123}"); + } + + // these make sure it doesn't chop up multibyte characters illegally + #[test] + fn test_multibyte_surround() { + assert_eq!(replace_str("\u{1f354}$${}\u{1f354}", replace_fun), "\u{1f354}${}\u{1f354}"); + assert_eq!(replace_str("\u{1f354}${exists}\u{1f354}${empty:-}\u{1f354}", replace_fun), "\u{1f354}value123\u{1f354}\u{1f354}"); + } + + #[test] + fn test_multibyte_replace() { + assert_eq!(replace_str("borger ${borger}", replace_fun), "borger \u{1f354}"); + assert_eq!(replace_str("${exists:-\u{1f354}}${empty:-\u{1f354}}", replace_fun), "value123\u{1f354}"); + assert_eq!(replace_str("${borger}$${}${borger}", replace_fun), "\u{1f354}${}\u{1f354}"); + } +}
\ No newline at end of file |
