summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorLibravatar bigfoot547 <[email protected]>2025-01-01 03:06:56 -0600
committerLibravatar bigfoot547 <[email protected]>2025-01-01 03:06:56 -0600
commitc21df3c73ae0dd18999f72504f96930f2c69b4eb (patch)
tree61568d7c065312953572e4e3fb98f60f329f3c32
parentsome stuff has changed (diff)
add string sub algorithm
-rw-r--r--Cargo.lock11
-rw-r--r--Cargo.toml1
-rw-r--r--src/launcher.rs25
-rw-r--r--src/launcher/instance.rs9
-rw-r--r--src/launcher/profile.rs37
-rw-r--r--src/launcher/strsub.rs157
6 files changed, 207 insertions, 33 deletions
diff --git a/Cargo.lock b/Cargo.lock
index ced08f9..a3adb30 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -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"
diff --git a/Cargo.toml b/Cargo.toml
index 1c6cf9c..8ee9cfd 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -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