summaryrefslogtreecommitdiffstats
path: root/src
diff options
context:
space:
mode:
authorLibravatar bigfoot547 <[email protected]>2025-01-20 00:53:39 -0600
committerLibravatar bigfoot547 <[email protected]>2025-01-20 00:53:39 -0600
commit1782b86d68ec923df965386acd98f80ef6bcaa46 (patch)
tree81f9891806e2bf6438f338489ae3a9f2ede23bd3 /src
parentupdate strsub (diff)
get rid of wacky closure business
Diffstat (limited to 'src')
-rw-r--r--src/launcher.rs86
-rw-r--r--src/launcher/rules.rs2
-rw-r--r--src/launcher/runner.rs105
-rw-r--r--src/launcher/strsub.rs83
-rw-r--r--src/version.rs45
5 files changed, 187 insertions, 134 deletions
diff --git a/src/launcher.rs b/src/launcher.rs
index 626bac9..a21a422 100644
--- a/src/launcher.rs
+++ b/src/launcher.rs
@@ -12,14 +12,13 @@ use std::borrow::Cow;
use std::cmp::min;
use std::env::consts::{ARCH, OS};
use std::error::Error;
-use std::ffi::{OsStr, OsString};
+use std::ffi::OsStr;
use std::fmt::{Display, Formatter};
use std::io::ErrorKind;
use std::io::ErrorKind::AlreadyExists;
use std::path::{Component, Path, PathBuf};
use std::{env, process};
use std::env::JoinPathsError;
-use std::process::Command;
use std::time::{SystemTime, UNIX_EPOCH};
use const_format::formatcp;
use futures::{StreamExt, TryStreamExt};
@@ -34,13 +33,15 @@ use tokio_stream::wrappers::ReadDirStream;
use download::{MultiDownloader, VerifiedDownload};
use rules::{CompatCheck, IncompatibleError};
use version::{VersionList, VersionResolveError, VersionResult};
-use crate::version::{Logging, Library, OSRestriction, OperatingSystem, DownloadType, DownloadInfo, LibraryExtractRule};
+use crate::version::{Logging, Library, OSRestriction, OperatingSystem, DownloadType, DownloadInfo, LibraryExtractRule, CompleteVersion, FeatureMatcher};
use assets::{AssetError, AssetRepository};
use crate::util::{self, FileVerifyError, IntegrityError};
pub use settings::*;
use crate::assets::AssetIndex;
+use crate::launcher::runner::ArgumentType;
+use crate::launcher::strsub::SubFunc;
use crate::version::manifest::VersionType;
#[derive(Debug)]
@@ -182,8 +183,10 @@ impl Error for LaunchError {
}
}
-pub struct Launch<'l> {
+struct LaunchInfo<'l, F: FeatureMatcher> {
launcher: &'l Launcher,
+ feature_matcher: &'l F,
+
asset_index_name: Option<String>,
classpath: String,
virtual_assets_path: Option<PathBuf>,
@@ -192,10 +195,14 @@ pub struct Launch<'l> {
client_jar: Option<PathBuf>,
version_id: String,
version_type: Option<VersionType>,
- asset_index: Option<AssetIndex>,
+ asset_index: Option<AssetIndex>
+}
- jvm_arguments: Vec<OsString>,
- game_arguments: Vec<OsString>,
+struct FMTrivial;
+impl FeatureMatcher for FMTrivial {
+ fn matches(&self, _feature: &str) -> bool {
+ false
+ }
}
impl Launcher {
@@ -239,6 +246,10 @@ impl Launcher {
lib.natives.as_ref().map_or(None, |n| n.get(&self.system_info.os)).map(|s| s.as_str())
}
+ fn get_feature_matcher(&self) -> &impl FeatureMatcher {
+ &FMTrivial
+ }
+
async fn ensure_file(&self, path: &Path, dlinfo: &DownloadInfo) -> Result<(), LaunchError> {
// verify the file
match util::verify_file(path, dlinfo.size, dlinfo.sha1).await {
@@ -350,13 +361,20 @@ impl Launcher {
self.ensure_file(&path, dlinfo).await?;
- Ok(strsub::replace_string(config.client.argument.as_str(), |key| match key {
- "path" => Some(path.to_string_lossy()),
- _ => None
- }).to_string())
+ struct PathSub<'a>(&'a Path);
+ impl<'a> SubFunc<'a> for PathSub<'a> {
+ fn substitute(&self, key: &str) -> Option<Cow<'a, str>> {
+ match key {
+ "path" => Some(self.0.to_string_lossy()),
+ _ => None
+ }
+ }
+ }
+
+ Ok(strsub::replace_string(config.client.argument.as_str(), &PathSub(path.as_ref())).to_string())
}
- pub async fn prepare_launch(&self, version_id: &ProfileVersion, instance: &Instance) -> Result<Launch<'_>, LaunchError> {
+ pub async fn prepare_launch(&self, version_id: &ProfileVersion, instance: &Instance) -> Result<(), LaunchError> {
/* tasks 2 l;aunch the gayme!!!! :3
* - java runtime
* - normal process (good research, past figboot :3)
@@ -411,7 +429,7 @@ impl Launcher {
};
let ver = self.versions.resolve_version(ver.as_ref()).await.map_err(|e| LaunchError::ResolveVersion(e))?;
- ver.rules_apply(&self.system_info, |_| false).map_err(|e| LaunchError::IncompatibleVersion(e))?;
+ ver.rules_apply(&self.system_info, self.get_feature_matcher()).map_err(|e| LaunchError::IncompatibleVersion(e))?;
info!("Resolved launch version {}!", ver.id);
@@ -420,7 +438,7 @@ impl Launcher {
let mut downloads = Vec::new();
for lib in ver.libraries.values() {
- if lib.rules_apply(&self.system_info, |_| false).is_err() {
+ if lib.rules_apply(&self.system_info, self.get_feature_matcher()).is_err() {
continue;
}
@@ -527,8 +545,11 @@ impl Launcher {
info!("Classpath: {classpath}");
- Ok(Launch {
+ info!("Deriving launch arguments");
+ let info = LaunchInfo {
launcher: self,
+ feature_matcher: self.get_feature_matcher(),
+
asset_index_name: asset_idx_name.map(|s| s.to_owned()),
classpath,
virtual_assets_path: game_assets,
@@ -537,11 +558,13 @@ impl Launcher {
client_jar: client_jar_path,
version_id: ver.id.to_string(),
version_type: ver.version_type.clone(),
- asset_index: asset_idx,
+ asset_index: asset_idx
+ };
- jvm_arguments: Vec::new(), // TODO
- game_arguments: Vec::new(), // TODO
- })
+ let jvm_args = runner::build_arguments(&info, ver.as_ref(), ArgumentType::JVM);
+ let game_args = runner::build_arguments(&info, ver.as_ref(), ArgumentType::Game);
+
+ todo!()
}
}
@@ -578,13 +601,6 @@ struct LibraryExtractJob {
const ARCH_BITS: &'static str = formatcp!("{}", usize::BITS);
impl LibraryRepository {
- fn lib_replace(key: &str) -> Option<Cow<'static, str>> {
- match key {
- "arch" => Some(Cow::Borrowed(ARCH_BITS)),
- _ => None
- }
- }
-
fn get_artifact_base_dir(name: &str) -> Option<PathBuf> {
let end_of_gid = name.find(':')?;
@@ -594,16 +610,26 @@ impl LibraryRepository {
fn get_artifact_filename(name: &str, classifier: Option<&str>) -> Option<PathBuf> {
let n: Vec<&str> = name.splitn(4, ':').skip(1).collect();
+ struct LibReplace;
+ impl SubFunc<'static> for LibReplace {
+ fn substitute(&self, key: &str) -> Option<Cow<'static, str>> {
+ match key {
+ "arch" => Some(Cow::Borrowed(ARCH_BITS)),
+ _ => None
+ }
+ }
+ }
+
if let Some(classifier) = classifier {
match n.len() {
- 2 => Some(PathBuf::from(strsub::replace_string(format!("{}-{}-{}.jar", n[0], n[1], classifier).as_str(), Self::lib_replace).as_ref())),
- 3 => Some(PathBuf::from(strsub::replace_string(format!("{}-{}-{}-{}.jar", n[0], n[1], classifier, n[2]).as_str(), Self::lib_replace).as_ref())),
+ 2 => Some(PathBuf::from(strsub::replace_string(format!("{}-{}-{}.jar", n[0], n[1], classifier).as_str(), &LibReplace).as_ref())),
+ 3 => Some(PathBuf::from(strsub::replace_string(format!("{}-{}-{}-{}.jar", n[0], n[1], classifier, n[2]).as_str(), &LibReplace).as_ref())),
_ => None
}
} else {
match n.len() {
- 2 => Some(PathBuf::from(strsub::replace_string(format!("{}-{}.jar", n[0], n[1]).as_str(), Self::lib_replace).as_ref())),
- 3 => Some(PathBuf::from(strsub::replace_string(format!("{}-{}-{}.jar", n[0], n[1], n[2]).as_str(), Self::lib_replace).as_ref())),
+ 2 => Some(PathBuf::from(strsub::replace_string(format!("{}-{}.jar", n[0], n[1]).as_str(), &LibReplace).as_ref())),
+ 3 => Some(PathBuf::from(strsub::replace_string(format!("{}-{}-{}.jar", n[0], n[1], n[2]).as_str(), &LibReplace).as_ref())),
_ => None
}
}
diff --git a/src/launcher/rules.rs b/src/launcher/rules.rs
index 29da8a2..69c967d 100644
--- a/src/launcher/rules.rs
+++ b/src/launcher/rules.rs
@@ -31,7 +31,7 @@ mod seal {
}
pub trait CompatCheck: seal::CompatCheckInner {
- fn rules_apply<'a>(&'a self, system: &SystemInfo, feature_matcher: impl FeatureMatcher<'a>) -> Result<(), IncompatibleError> {
+ fn rules_apply(&self, system: &SystemInfo, feature_matcher: &impl FeatureMatcher) -> Result<(), IncompatibleError> {
let Some(rules) = self.get_rules() else { return Ok(()) };
let mut action = RuleAction::Disallow;
diff --git a/src/launcher/runner.rs b/src/launcher/runner.rs
index 8d12197..4d07f20 100644
--- a/src/launcher/runner.rs
+++ b/src/launcher/runner.rs
@@ -1,27 +1,34 @@
use std::borrow::Cow;
-use crate::launcher::Launch;
-use crate::launcher::strsub::SubFunc;
+use std::ffi::OsString;
+use std::iter;
+use crate::version::{CompleteVersion, FeatureMatcher};
+use super::rules::CompatCheck;
+use super::strsub::{self, SubFunc};
+use super::LaunchInfo;
-impl<'k, 'rep, 'l: 'rep> SubFunc<'k, 'rep> for &'rep Launch<'l> {
- fn substitute(self, key: &'k str) -> Option<Cow<'rep, str>> {
+#[derive(Clone, Copy)]
+struct LaunchArgSub<'a: 'l, 'l, F: FeatureMatcher>(&'a LaunchInfo<'l, F>);
+
+impl<'rep, 'l, F: FeatureMatcher> SubFunc<'rep> for LaunchArgSub<'rep, 'l, F> {
+ fn substitute(&self, key: &str) -> Option<Cow<'rep, str>> {
match key {
- "assets_index_name" => self.asset_index_name.as_ref().map(|s| Cow::Borrowed(s.as_str())),
- "assets_root" => Some(self.launcher.assets.get_home().to_string_lossy()),
+ "assets_index_name" => self.0.asset_index_name.as_ref().map(|s| Cow::Borrowed(s.as_str())),
+ "assets_root" => Some(self.0.launcher.assets.get_home().to_string_lossy()),
"auth_access_token" => Some(Cow::Borrowed("-")), // TODO
"auth_player_name" => Some(Cow::Borrowed("Player")), // TODO
"auth_session" => Some(Cow::Borrowed("-")), // TODO
"auth_uuid" => Some(Cow::Borrowed("00000000-0000-0000-0000-000000000000")), // TODO
"auth_xuid" => Some(Cow::Borrowed("00000000-0000-0000-0000-000000000000")), // TODO
- "classpath" => Some(Cow::Borrowed(self.classpath.as_str())), // TODO
+ "classpath" => Some(Cow::Borrowed(self.0.classpath.as_str())), // TODO
"classpath_separator" => None, // FIXME
- "game_assets" => self.virtual_assets_path.as_ref().map(|s| s.to_string_lossy()),
- "game_directory" => Some(self.instance_home.to_string_lossy()),
+ "game_assets" => self.0.virtual_assets_path.as_ref().map(|s| s.to_string_lossy()),
+ "game_directory" => Some(self.0.instance_home.to_string_lossy()),
"language" => Some(Cow::Borrowed("en-us")), // ???
"launcher_name" => Some(Cow::Borrowed("ozone (olauncher 3)")), // TODO
"launcher_version" => Some(Cow::Borrowed("yeah")), // TODO
- "library_directory" => Some(self.launcher.libraries.home.to_string_lossy()),
- "natives_directory" => Some(self.natives_path.to_string_lossy()),
- "primary_jar" => self.client_jar.as_ref().map(|p| p.to_string_lossy()),
+ "library_directory" => Some(self.0.launcher.libraries.home.to_string_lossy()),
+ "natives_directory" => Some(self.0.natives_path.to_string_lossy()),
+ "primary_jar" => self.0.client_jar.as_ref().map(|p| p.to_string_lossy()),
"quickPlayMultiplayer" => None, // TODO
"quickPlayPath" => None, // TODO
"quickPlayRealms" => None, // TODO
@@ -31,17 +38,81 @@ impl<'k, 'rep, 'l: 'rep> SubFunc<'k, 'rep> for &'rep Launch<'l> {
"user_properties" => Some(Cow::Borrowed("{}")), // TODO
"user_property_map" => Some(Cow::Borrowed("[]")), // TODO
"user_type" => Some(Cow::Borrowed("legacy")), // TODO
- "version_name" => Some(Cow::Borrowed(&self.version_id.as_ref())),
- "version_type" => self.version_type.as_ref().map(|s| Cow::Borrowed(s.to_str())),
+ "version_name" => Some(Cow::Borrowed(&self.0.version_id.as_ref())),
+ "version_type" => self.0.version_type.as_ref().map(|s| Cow::Borrowed(s.to_str())),
_ => {
if let Some(asset_key) = key.strip_prefix("asset=") {
- return self.asset_index.as_ref()
+ return self.0.asset_index.as_ref()
.map_or(None, |idx| idx.objects.get(asset_key))
- .map(|obj| Cow::Owned(self.launcher.assets.get_object_path(obj).to_string_lossy().into_owned()))
+ .map(|obj| Cow::Owned(self.0.launcher.assets.get_object_path(obj).to_string_lossy().into_owned()))
}
None
}
}
}
-} \ No newline at end of file
+}
+
+#[derive(Clone, Copy)]
+pub enum ArgumentType {
+ JVM,
+ Game
+}
+
+struct OptionalIterator<I>
+where
+ I: Iterator
+{
+ opt: Option<I>
+}
+
+impl<I: Iterator> Iterator for OptionalIterator<I> {
+ type Item = I::Item;
+
+ fn next(&mut self) -> Option<Self::Item> {
+ match self.opt {
+ Some(ref mut i) => i.next(),
+ None => None
+ }
+ }
+}
+
+impl<II: IntoIterator> From<Option<II>> for OptionalIterator<II::IntoIter> {
+ fn from(opt: Option<II>) -> Self {
+ OptionalIterator {
+ opt: opt.map(IntoIterator::into_iter)
+ }
+ }
+}
+
+pub fn build_arguments<'l, F: FeatureMatcher>(launch: &LaunchInfo<'l, F>, version: &CompleteVersion, arg_type: ArgumentType) -> Vec<OsString> {
+ let sub = LaunchArgSub(launch);
+ let system_info = &launch.launcher.system_info;
+
+ if let Some(arguments) = version.arguments.as_ref().map_or(None, |args| match arg_type {
+ ArgumentType::JVM => args.jvm.as_ref(),
+ ArgumentType::Game => args.game.as_ref()
+ }) {
+ arguments.iter()
+ .flat_map(|wa| OptionalIterator::from(wa.rules_apply(system_info, launch.feature_matcher).ok().map(|_| &wa.value)))
+ .map(|s| OsString::from(strsub::replace_string(s, &sub).into_owned())).collect()
+ } else if let Some(arguments) = version.minecraft_arguments.as_ref() {
+ match arg_type {
+ ArgumentType::JVM => {
+ todo!()
+ }
+ ArgumentType::Game => {
+ arguments.split(' ')
+ .chain(iter::once("--demo")
+ .take_while(|_| launch.feature_matcher.matches("is_demo_user")))
+ .chain(iter::once(["--width", "${resolution_width}", "--height", "${resolution_height}"])
+ .take_while(|_| launch.feature_matcher.matches("has_custom_resolution"))
+ .flatten())
+ .map(|s| OsString::from(strsub::replace_string(s, &sub).into_owned()))
+ .collect()
+ }
+ }
+ } else {
+ Vec::default()
+ }
+}
diff --git a/src/launcher/strsub.rs b/src/launcher/strsub.rs
index c89681e..0d2357d 100644
--- a/src/launcher/strsub.rs
+++ b/src/launcher/strsub.rs
@@ -8,17 +8,8 @@ const VAR_BEGIN: &str = "${";
const VAR_END: &str = "}";
const VAR_DEFAULT: &str = ":-";
-pub trait SubFunc<'k, 'rep>: Copy {
- fn substitute(self, key: &'k str) -> Option<Cow<'rep, str>>;
-}
-
-impl<'k, 'rep, F> SubFunc<'k, 'rep> for F
-where
- F: Fn(&'k str) -> Option<Cow<'rep, str>> + Copy
-{
- fn substitute(self, key: &'k str) -> Option<Cow<'rep, str>> {
- self(key)
- }
+pub trait SubFunc<'rep> {
+ fn substitute(&self, key: &str) -> Option<Cow<'rep, str>>;
}
/* NOTE: the in-place implementation has been replaced for the following reasons:
@@ -69,7 +60,7 @@ where
}
}*/
-pub fn replace_string<'k, 'rep>(input: &'k str, sub: impl SubFunc<'k, 'rep>) -> Cow<'k, str> {
+pub fn replace_string<'inp, 'rep>(input: &'inp str, sub: &impl SubFunc<'rep>) -> Cow<'inp, str> {
let mut ret: Option<String> = None;
let mut cursor = 0usize;
@@ -125,73 +116,77 @@ pub fn replace_string<'k, 'rep>(input: &'k str, sub: impl SubFunc<'k, 'rep>) ->
mod tests {
use super::*;
- fn replace_fun(key: &str) -> Option<Cow<'static, str>> {
- match key {
- "exists" => Some(Cow::Borrowed("value123")),
- "empty" => None,
- "borger" => Some(Cow::Borrowed("\u{1f354}")),
- _ => panic!("replace_fun called with unexpected key: {}", key)
+ #[derive(Clone, Copy)]
+ struct TestSub;
+ impl SubFunc<'static> for TestSub {
+ fn substitute(&self, key: &str) -> Option<Cow<'static, str>> {
+ match key {
+ "exists" => Some(Cow::Borrowed("value123")),
+ "empty" => None,
+ "borger" => Some(Cow::Borrowed("\u{1f354}")),
+ _ => panic!("replace_fun called with unexpected key: {}", key)
+ }
}
}
#[test]
fn test_standard_replace() {
- assert_eq!(replace_string("this has ${exists} and more", replace_fun), "this has value123 and more");
- assert_eq!(replace_string("multiple ${exists} repl${exists}ace", replace_fun), "multiple value123 replvalue123ace");
- assert_eq!(replace_string("${exists}${exists}", replace_fun), "value123value123");
+ assert_eq!(replace_string("this has ${exists} and more", &TestSub), "this has value123 and more");
+ assert_eq!(replace_string("multiple ${exists} repl${exists}ace", &TestSub), "multiple value123 replvalue123ace");
+ assert_eq!(replace_string("${exists}${exists}", &TestSub), "value123value123");
}
#[test]
fn test_empty_replace() {
- assert_eq!(replace_string("this has ${empty} and more", replace_fun), "this has ${empty} and more");
- assert_eq!(replace_string("multiple ${empty} repl${empty}ace", replace_fun), "multiple ${empty} repl${empty}ace");
- assert_eq!(replace_string("${empty}${empty}", replace_fun), "${empty}${empty}");
+ assert_eq!(replace_string("this has ${empty} and more", &TestSub), "this has ${empty} and more");
+ assert_eq!(replace_string("multiple ${empty} repl${empty}ace", &TestSub), "multiple ${empty} repl${empty}ace");
+ assert_eq!(replace_string("${empty}${empty}", &TestSub), "${empty}${empty}");
}
#[test]
fn test_homogenous_replace() {
- assert_eq!(replace_string("some ${exists} and ${empty} ...", replace_fun), "some value123 and ${empty} ...");
- assert_eq!(replace_string("some ${empty} and ${exists} ...", replace_fun), "some ${empty} and value123 ...");
- assert_eq!(replace_string("${exists}${empty}", replace_fun), "value123${empty}");
- assert_eq!(replace_string("${empty}${exists}", replace_fun), "${empty}value123");
+ assert_eq!(replace_string("some ${exists} and ${empty} ...", &TestSub), "some value123 and ${empty} ...");
+ assert_eq!(replace_string("some ${empty} and ${exists} ...", &TestSub), "some ${empty} and value123 ...");
+ assert_eq!(replace_string("${exists}${empty}", &TestSub), "value123${empty}");
+ assert_eq!(replace_string("${empty}${exists}", &TestSub), "${empty}value123");
}
#[test]
fn test_default_replace() {
- assert_eq!(replace_string("some ${exists:-def1} and ${empty:-def2} ...", replace_fun), "some value123 and def2 ...");
- assert_eq!(replace_string("some ${empty:-def1} and ${exists:-def2} ...", replace_fun), "some def1 and value123 ...");
- assert_eq!(replace_string("abc${empty:-}def", replace_fun), "abcdef");
- assert_eq!(replace_string("${empty:-}${empty:-}", replace_fun), "");
+ assert_eq!(replace_string("some ${exists:-def1} and ${empty:-def2} ...", &TestSub), "some value123 and def2 ...");
+ assert_eq!(replace_string("some ${empty:-def1} and ${exists:-def2} ...", &TestSub), "some def1 and value123 ...");
+ assert_eq!(replace_string("abc${empty:-}def", &TestSub), "abcdef");
+ assert_eq!(replace_string("${empty:-}${empty:-}", &TestSub), "");
}
#[test]
fn test_escape() {
- assert_eq!(replace_string("an $${escaped} replacement (${exists})", replace_fun), "an ${escaped} replacement (value123)");
- assert_eq!(replace_string("${exists}$${escaped}${exists}", replace_fun), "value123${escaped}value123");
+ assert_eq!(replace_string("an $${escaped} replacement (${exists})", &TestSub), "an ${escaped} replacement (value123)");
+ assert_eq!(replace_string("${exists}$${escaped}${exists}", &TestSub), "value123${escaped}value123");
// make sure this weird behavior is preserved... (the original code seemed to show it)
- assert_eq!(replace_string("some $${ else", replace_fun), "some ${ else");
+ assert_eq!(replace_string("some $${ else", &TestSub), "some ${ else");
}
#[test]
fn test_weird() {
- assert_eq!(replace_string("${exists}", replace_fun), "value123");
- assert_eq!(replace_string("$${empty}", replace_fun), "${empty}");
- assert_eq!(replace_string("${empty:-a}", replace_fun), "a");
- assert_eq!(replace_string("${empty:-}", replace_fun), "");
+ assert_eq!(replace_string("${exists}", &TestSub), "value123");
+ assert_eq!(replace_string("$${empty}", &TestSub), "${empty}");
+ assert_eq!(replace_string("${empty:-a}", &TestSub), "a");
+ assert_eq!(replace_string("${empty:-}", &TestSub), "");
}
// these make sure it doesn't chop up multibyte characters illegally
#[test]
fn test_multibyte_surround() {
- assert_eq!(replace_string("\u{1f354}$${}\u{1f354}", replace_fun), "\u{1f354}${}\u{1f354}");
- assert_eq!(replace_string("\u{1f354}${exists}\u{1f354}${empty:-}\u{1f354}", replace_fun), "\u{1f354}value123\u{1f354}\u{1f354}");
+ assert_eq!(replace_string("\u{1f354}$${}\u{1f354}", &TestSub), "\u{1f354}${}\u{1f354}");
+ assert_eq!(replace_string("\u{1f354}${exists}\u{1f354}${empty:-}\u{1f354}", &TestSub), "\u{1f354}value123\u{1f354}\u{1f354}");
}
#[test]
fn test_multibyte_replace() {
- assert_eq!(replace_string("borger ${borger}", replace_fun), "borger \u{1f354}");
- assert_eq!(replace_string("${exists:-\u{1f354}}${empty:-\u{1f354}}", replace_fun), "value123\u{1f354}");
- assert_eq!(replace_string("${borger}$${}${borger}", replace_fun), "\u{1f354}${}\u{1f354}");
+ assert_eq!(replace_string("borger ${borger}", &TestSub), "borger \u{1f354}");
+ assert_eq!(replace_string("${exists:-\u{1f354}}${empty:-\u{1f354}}", &TestSub), "value123\u{1f354}");
+ assert_eq!(replace_string("${borger}$${}${borger}", &TestSub), "\u{1f354}${}\u{1f354}");
}
}
diff --git a/src/version.rs b/src/version.rs
index 1424b18..5371669 100644
--- a/src/version.rs
+++ b/src/version.rs
@@ -81,51 +81,12 @@ pub struct CompatibilityRule {
pub os: Option<OSRestriction>
}
-
-/// ## Details
-/// ### Why is the lifetime `'f` specified instead of elided?
-/// This trait has a blanket implementation for all `Fn(&str) -> bool`, with the intent of allowing
-/// one to specify trivial feature matcher implementations using closures (maybe `|_| false` or
-/// `|f| f == "something"`). If the lifetime is left unbounded, the following error is produced by
-/// the compiler:
-///
-/// ```text
-/// Implementation of `Fn` is not general enough
-/// Note: closure with signature `fn(&'2 str) -> bool` must implement `Fn<(&'1 str,)>`, for any lifetime `'1`...
-/// Note: ...but it actually implements `Fn<(&'2 str,)>`, for some specific lifetime `'2`
-/// ```
-///
-/// ### How do I implement this type for non-`Copy` custom feature matchers?
-/// The short answer is: implement the trait for the reference type. Here's an example.
-/// ```rust
-/// use o3launcher::version::FeatureMatcher;
-///
-/// #[derive(Clone)] // can't derive Copy here
-/// struct FM {
-/// our_feature: String // heap-owned data!
-/// }
-///
-/// impl<'f> FeatureMatcher<'f> for &FM {
-/// fn matches(self, feature: &'f str) -> bool {
-/// self.our_feature == feature
-/// }
-/// }
-/// ```
-pub trait FeatureMatcher<'f>: Copy {
- fn matches(self, feature: &'f str) -> bool;
-}
-
-impl<'f, F> FeatureMatcher<'f> for F
-where
- F: Fn(&'f str) -> bool + Copy
-{
- fn matches(self, feature: &'f str) -> bool {
- self(feature)
- }
+pub trait FeatureMatcher {
+ fn matches(&self, feature: &str) -> bool;
}
impl CompatibilityRule {
- pub fn features_match<'a>(&'a self, checker: impl FeatureMatcher<'a>) -> bool {
+ pub fn features_match(&self, checker: &impl FeatureMatcher) -> bool {
if let Some(m) = self.features.as_ref() {
for (feat, expect) in m {
if checker.matches(feat) != *expect {