diff options
Diffstat (limited to 'src')
| -rw-r--r-- | src/launcher.rs | 86 | ||||
| -rw-r--r-- | src/launcher/rules.rs | 2 | ||||
| -rw-r--r-- | src/launcher/runner.rs | 105 | ||||
| -rw-r--r-- | src/launcher/strsub.rs | 83 | ||||
| -rw-r--r-- | src/version.rs | 45 |
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 { |
