refactor(core): M126 r1 返工:词表改由宏单表生成,安装侧解析收口
复审 r1 的三条: P1(CI 会红):`const MARKERS: [(LoaderKind, &'static str); 6]` 触发 clippy::redundant_static_lifetimes(`error: constants have by default a 'static lifetime`)。返工直接**删掉 MARKERS**:标记串就取 `LoaderKind::as_str()`,候选顺序取 `LoaderKind::ALL`,认不认标记看 `has_marker()`——解析表既没有独立字面量,也没有独立清单。 P2-1:「漏加 ALL 不会被任何东西拦住」是真话,之前的 doc 覆盖声明是假的。返工用 `loader_kind_vocab!` 宏从**一张表**一次生成枚举 / `ALL` / `as_str` / `has_marker`: 「加了一支却忘了进清单」在物理上不可能(没有第二份清单可写)。尺寸锚随之删除(`[Self; N]` 的宽度本来就由表决定),`parse` 留在宏外的独立 impl 里。 P2-2:「全仓唯一一份 id 字面量」原先不成立——安装侧 `LoaderKind::parse` 还手写了一份反向 匹配表,并带一个只进不出的 `neo-forge` 别名。返工让 install 侧 `parse` **委派** `core::game::LoaderKind::parse`,只额外收 `neo-forge`(Modrinth 拼法)这一个输入别名, 并新增 `addon::loader::tests::vocabulary` 覆盖「委派没接错 + 别名只进不出 + 装不了的几家 返回 None」。 测试:core 的 `markers_cover_every_kind_that_has_one` 换成 `every_stable_id_parses_back_to_its_kind`(往返 + 顺序优先级),仍是「改名不可见、只有契约 测试能抓」的分工,doc 里写明。 门禁:fmt OK;clippy -p unlucky-core -p unlucky-presenter -p unlucky-cli --all-targets -- -D warnings 干净;三 crate 测试 core lib 401 / presenter 220 / cli 41 全 ok。 文件行数:loader.rs 397(≤400)。
This commit is contained in:
1 parent
be37c17962
commit
a322cfcbfe
4 files changed
+219
-138
No files matched your search
@@ -62,15 +62,33 @@ pub enum LoaderKind {
|
||||
}
|
||||
|
||||
impl LoaderKind {
|
||||
/// 解析命令行里的加载器名(大小写不敏感;`neo-forge` 是 Modrinth 的另一种拼法)。
|
||||
/// 解析命令行 / 整合包清单里的加载器名(大小写不敏感)。
|
||||
///
|
||||
/// 七个 id 的匹配**委派**给 [`crate::game::LoaderKind::parse`](词表唯一真相源,M126),
|
||||
/// 本方法只多收一个**输入别名** `neo-forge`:那是 Modrinth 的拼法(mrpack
|
||||
/// `dependencies` 的键、CF 包里也有这种写法),**只进不出**——[`Self::as_str`] 绝不产出
|
||||
/// 它。识别得出但本启动器装不了的几家(原版 / OptiFine / LiteLoader)返回 `None`。
|
||||
#[must_use]
|
||||
pub fn parse(raw: &str) -> Option<Self> {
|
||||
match raw.trim().to_ascii_lowercase().as_str() {
|
||||
"fabric" => Some(Self::Fabric),
|
||||
"quilt" => Some(Self::Quilt),
|
||||
"neoforge" | "neo-forge" => Some(Self::NeoForge),
|
||||
"forge" => Some(Self::Forge),
|
||||
_ => None,
|
||||
let wanted = raw.trim();
|
||||
if wanted.eq_ignore_ascii_case("neo-forge") {
|
||||
return Some(Self::NeoForge);
|
||||
}
|
||||
Self::from_core(crate::game::LoaderKind::parse(wanted)?)
|
||||
}
|
||||
|
||||
/// core 的宽种类 → 本模块的四家(`None` = 识别得出但本启动器装不了)。
|
||||
///
|
||||
/// 穷尽 match:core 加一支就编不过,逼作者决定「它能不能装」。
|
||||
const fn from_core(kind: crate::game::LoaderKind) -> Option<Self> {
|
||||
match kind {
|
||||
crate::game::LoaderKind::Fabric => Some(Self::Fabric),
|
||||
crate::game::LoaderKind::Quilt => Some(Self::Quilt),
|
||||
crate::game::LoaderKind::NeoForge => Some(Self::NeoForge),
|
||||
crate::game::LoaderKind::Forge => Some(Self::Forge),
|
||||
crate::game::LoaderKind::Vanilla
|
||||
| crate::game::LoaderKind::OptiFine
|
||||
| crate::game::LoaderKind::LiteLoader => None,
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -7,6 +7,7 @@ mod fabric;
|
||||
mod forge;
|
||||
mod maven;
|
||||
mod plan;
|
||||
mod vocabulary;
|
||||
|
||||
/// 裁剪过的 Fabric `/profile/json`(1.20.1 + fabric-loader 0.15.11)。
|
||||
pub(crate) const FABRIC_PROFILE: &str = r#"{
|
||||
|
||||
@@ -0,0 +1,63 @@
|
||||
//! 安装侧词表与 `crate::game` 词表的对接:委派有没有接错、别名有没有漏进产出。
|
||||
//!
|
||||
//! 词表本体(七个对外 id)在 `crate::game::loader`,这里只验**安装侧这一半**:
|
||||
//! [`LoaderKind::as_str`] 委派给 core,[`LoaderKind::parse`] 委派 core 的解析并只多收一个
|
||||
//! Modrinth 输入别名。为什么放在安装侧的测试目录:这一半的存在理由是「装出来的版本 id 得
|
||||
//! 能被认回来」,那正好是生成端与解析端的接缝。
|
||||
|
||||
use crate::addon::loader::LoaderKind;
|
||||
use crate::game::{InstanceLoader, LoaderKind as Wide};
|
||||
|
||||
/// `as_str` 是 core 词表的委派:四家装得上的一一对应,且绝不产出别名。
|
||||
///
|
||||
/// 抓得住:委派接错支(`Fabric` 映到 `Quilt`);有人把 `as_str` 改回手写字面量并写错;
|
||||
/// 有人让 `as_str` 产出 `neo-forge`(别名只进不出);生成端 `<mc>-<kind>-<loader>` 与解析端
|
||||
/// 脱节(装出来的 id 认不回同一家)。
|
||||
/// 抓不住:core 词表的字面值(那是 `game::loader` 的往返测试与 presenter 契约测试的职责)。
|
||||
#[test]
|
||||
fn install_side_ids_are_delegated_to_the_core_vocabulary() {
|
||||
let cases = [
|
||||
(LoaderKind::Fabric, Wide::Fabric),
|
||||
(LoaderKind::Quilt, Wide::Quilt),
|
||||
(LoaderKind::NeoForge, Wide::NeoForge),
|
||||
(LoaderKind::Forge, Wide::Forge),
|
||||
];
|
||||
for (install, wide) in cases {
|
||||
assert_eq!(
|
||||
install.as_str(),
|
||||
wide.as_str(),
|
||||
"{install:?} 的 id 没走 core 词表"
|
||||
);
|
||||
assert_ne!(install.as_str(), "neo-forge", "别名只进不出");
|
||||
assert_eq!(LoaderKind::parse(install.as_str()), Some(install), "反查");
|
||||
|
||||
let id = install.version_id("1.20.4", "0.16.5");
|
||||
let parsed = InstanceLoader::parse(&id);
|
||||
assert_eq!(parsed.kind.as_str(), install.as_str(), "`{id}` 认错了家");
|
||||
assert_eq!(parsed.version, "0.16.5", "`{id}` 的版本段");
|
||||
}
|
||||
}
|
||||
|
||||
/// 别名策略:`neo-forge` 只作为**输入**被接受(Modrinth 的拼法),产出永远是 `neoforge`。
|
||||
///
|
||||
/// 抓得住:别名被搬进词表(`as_str` 开始产出 `neo-forge`);别名只认小写;安装侧顺手接受了
|
||||
/// 本启动器装不了的几家(原版 / OptiFine / LiteLoader)。
|
||||
/// 抓不住:`neo-forge` 之外 Modrinth 以后可能再发明的拼法——没有样本可写。
|
||||
#[test]
|
||||
fn modrinth_alias_is_input_only() {
|
||||
for raw in ["neo-forge", "NEO-FORGE", " neo-forge "] {
|
||||
assert_eq!(
|
||||
LoaderKind::parse(raw),
|
||||
Some(LoaderKind::NeoForge),
|
||||
"`{raw}`"
|
||||
);
|
||||
}
|
||||
assert_eq!(
|
||||
LoaderKind::NeoForge.as_str(),
|
||||
"neoforge",
|
||||
"产出不许带连字符"
|
||||
);
|
||||
for raw in ["vanilla", "optifine", "liteloader", "", "rift"] {
|
||||
assert_eq!(LoaderKind::parse(raw), None, "`{raw}` 不该被安装侧接受");
|
||||
}
|
||||
}
|
||||
+130
-131
@@ -3,30 +3,36 @@
|
||||
//!
|
||||
//! # 为什么要这一个模块
|
||||
//!
|
||||
//! 「这个实例装的什么加载器」在 UI 上是一句话,在代码里却有两个来源:版本 JSON 的
|
||||
//! `libraries[]`(权威但要多读一次盘)与**版本 id 的字符串**(磁贴/列表最省事的那条)。
|
||||
//! 只要有人按 id 猜就必须只有**一处**在猜——两处各算一遍迟早一边先漂,用户看到的就是
|
||||
//! 「我刚装的 Fabric 显示成原版」(M120 复审的老 bug:解析端只认 `fabric-loader-…`,认不出
|
||||
//! 自家生成的 `1.20.4-fabric-0.16.5`)。所以 presenter 的实例页、CLI 的实例总览、以后任何
|
||||
//! 「按 id 认加载器」的地方一律调 [`InstanceLoader::parse`],**不许**再写一份标记匹配。
|
||||
//! 生成端([`crate::addon::loader::LoaderKind::version_id`] 拼 `<mc>-<kind>-<loader>`)与
|
||||
//! 解析端的一致性由 [`tests::own_version_ids_parse_back_to_their_loader`] 钉住——那条测试
|
||||
//! **从生成端取 id**,不手抄。OptiFine / LiteLoader 本启动器不能装(见
|
||||
//! `presenter::host::install`),只能从别的启动器继承进来。
|
||||
//! 「这个实例装的什么加载器」在 UI 上是一句话,代码里却有两个来源:版本 JSON 的 `libraries[]`
|
||||
//! (权威但要多读一次盘)与**版本 id 的字符串**(磁贴/列表最省事的那条)。只要有人按 id 猜就
|
||||
//! 必须只有**一处**在猜——两处各算一遍迟早一边先漂,用户看到的就是「我刚装的 Fabric 显示成
|
||||
//! 原版」(M120 复审的老 bug:解析端只认 `fabric-loader-…`,认不出自家生成的
|
||||
//! `1.20.4-fabric-0.16.5`)。所以 presenter 的实例页、CLI 的实例总览、以后任何「按 id 认加载
|
||||
//! 器」的地方一律调 [`InstanceLoader::parse`],**不许**再写一份标记匹配。生成端
|
||||
//! ([`crate::addon::loader::LoaderKind::version_id`] 拼 `<mc>-<kind>-<loader>`)与解析端的一致
|
||||
//! 性由 [`tests::own_version_ids_parse_back_to_their_loader`] 钉住(**从生成端取 id**,不手抄);
|
||||
//! OptiFine / LiteLoader 本启动器不能装(见 `presenter::host::install`),只能继承进来。
|
||||
//!
|
||||
//! # 命名词表只有一份字面量(M126)
|
||||
//! # 命名词表只有一张表(M126)
|
||||
//!
|
||||
//! 契约(0.2.0 已发布形状,**不许改名**):`vanilla` / `fabric` / `quilt` / `forge` /
|
||||
//! `neoforge` / `optifine` / `liteloader`。其中只有 [`LoaderKind::as_str`] 是字面量;安装侧与
|
||||
//! presenter 的 `as_str` 是**委派**(结构映射,写不进串),CLI 直接调,`MARKERS` 的标记串也
|
||||
//! 取自它。加一家加载器要动:
|
||||
//! `neoforge` / `optifine` / `liteloader`。生产代码里这七个串只有**一处**:下面那次
|
||||
//! `loader_kind_vocab!` 展开——枚举、`ALL`、`as_str`、`has_marker` 全从同一张表长出来,
|
||||
//! 所以「加了一支却漏进清单」**物理上做不到**(手写枚举 + 手写清单时它既不报错也不会被测试
|
||||
//! 抓住:测试同样从 `ALL` 出发,新加载器被**静默**当成「认不出」)。安装侧与 presenter 的
|
||||
//! `as_str` / `parse` 都是**委派**,CLI 直接调;唯一不在词表里的字面量是安装侧收的输入别名
|
||||
//! `neo-forge`(Modrinth 拼法,只进不出)。
|
||||
//!
|
||||
//! 1. `LoaderKind` 加一支 → **[`LoaderKind::as_str`] / [`LoaderKind::has_marker`] /
|
||||
//! presenter 的 `to_core`·`from_core` 编不过**(能装的话再加安装侧 `as_str` 一支);
|
||||
//! 2. [`LoaderKind::ALL`] 加一格、`MARKERS` 加一行、尺寸锚改成 8 → **只能靠锚与测试**
|
||||
//! (Rust 没有「枚举有哪几支」的编译期反射),漏了是「有 id 但认不出」,由
|
||||
//! [`tests::markers_cover_every_kind_that_has_one`] 抓;
|
||||
//! 3. `ull help instances` 的词表行与 CLI 的 `json_rows_expose_loader_kind_and_version` 一起红。
|
||||
//! 加一家加载器要动:① `loader_kind_vocab!` 表里加一行(id + 认不认标记)→ 枚举 / `ALL` /
|
||||
//! `as_str` / `has_marker` / 解析顺序一起长出来,漏不了;② presenter 的 `to_core`·`from_core`
|
||||
//! **编不过**(逼你决定 DTO 怎么显示它),能装的话安装侧 `as_str`·`from_core` 也各差一支;
|
||||
//! ③ 契约测试(presenter 的 `loader_kind_ids_are_the_released_contract`、CLI 的
|
||||
//! `json_rows_expose_loader_kind_and_version`、`ull help instances` 的词表行)**会红**——那
|
||||
//! 是逼你确认「对外词表真的要多一个 id」,因为 0.2.0 已经发出去过。
|
||||
//!
|
||||
//! 分工:**改名**(`neoforge` → `neo_forge`)不会被往返测试抓住(词表与解析同源、改名自洽),
|
||||
//! 只有第 ③ 步的契约测试会红——那里**故意**再抄一遍七个串,那是唯一抓得住改名的东西;往返
|
||||
//! 测试抓的是「规则把 id 认错」。
|
||||
//!
|
||||
//! # 解析规则(按整段前缀匹配,大小写不敏感,**最靠左的标记胜**)
|
||||
//!
|
||||
@@ -42,78 +48,90 @@
|
||||
//! 4. 一个标记都没找到 ⇒ 原版(版本段空串)。只把加载器写进版本 JSON、id 里没痕迹的实例
|
||||
//! (整包导入时被重命名成 `我的整合包` 那种)会**诚实**返回「原版」——那不等于没装加载器。
|
||||
|
||||
/// 实例面向的加载器种类(比 [`crate::addon::loader::LoaderKind`] 宽)。
|
||||
/// 一张表生成整份词表:枚举、[`LoaderKind::ALL`]、`as_str`、`has_marker` 一起长出来。
|
||||
///
|
||||
/// 与那个类型的区别:`addon::loader::LoaderKind` 只描述**本启动器能装**的四家(装不了就
|
||||
/// 没有下载路线,放进枚举里会逼出一堆 `unreachable!`);本类型面向**识别与展示**,所以
|
||||
/// 多出「原版」这一支,以及两家只能继承进来的第三方加载器。
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
|
||||
pub enum LoaderKind {
|
||||
/// 原版(id 里没有任何加载器标记)
|
||||
Vanilla,
|
||||
/// Fabric
|
||||
Fabric,
|
||||
/// Quilt
|
||||
Quilt,
|
||||
/// Forge
|
||||
Forge,
|
||||
/// NeoForge
|
||||
NeoForge,
|
||||
/// OptiFine(第三方,本启动器不装,只继承)
|
||||
OptiFine,
|
||||
/// LiteLoader(第三方,本启动器不装,只继承)
|
||||
LiteLoader,
|
||||
/// 为什么非要用宏:Rust 没有「枚举有哪几支」的编译期反射,手写枚举 + 手写清单时「加了一支
|
||||
/// 却忘了进清单」既不报错也不会被测试抓住(测试同样从清单出发),新加载器被**静默**当成
|
||||
/// 「认不出」。让两者从同一张表展开,这件事在物理上不可能发生。
|
||||
///
|
||||
/// 表里的顺序就是标记的匹配优先级;带 `marker` 的条目参与 id 识别,不带的(原版)不参与。
|
||||
macro_rules! loader_kind_vocab {
|
||||
(
|
||||
$(#[$meta:meta])*
|
||||
$vis:vis enum $name:ident {
|
||||
$(
|
||||
$(#[$variant_doc:meta])*
|
||||
$variant:ident => $id:literal $(, $marker:ident)?;
|
||||
)+
|
||||
}
|
||||
) => {
|
||||
$(#[$meta])*
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
|
||||
$vis enum $name {
|
||||
$(
|
||||
$(#[$variant_doc])*
|
||||
$variant,
|
||||
)+
|
||||
}
|
||||
|
||||
impl $name {
|
||||
/// 全部种类,**顺序即标记的匹配优先级**;原版没有标记(由 `has_marker` 跳过)。
|
||||
pub const ALL: [Self; loader_kind_vocab!(@count $($variant)+)] =
|
||||
[$(Self::$variant,)+];
|
||||
|
||||
/// 对外稳定 id(`--json` 的 `loader.kind`、GUI 词表、`--loader` 的取值)。
|
||||
///
|
||||
/// **生产代码里唯一的一份 id 字面量**(0.2.0 已发布契约);穷尽 match:加一支就编不过。
|
||||
#[must_use]
|
||||
pub const fn as_str(self) -> &'static str {
|
||||
match self {
|
||||
$(Self::$variant => $id,)+
|
||||
}
|
||||
}
|
||||
|
||||
/// 这一支会不会从版本 id 的标记里认出来(原版不会)。穷尽 match:加一支必须表态。
|
||||
#[must_use]
|
||||
pub const fn has_marker(self) -> bool {
|
||||
match self {
|
||||
$(Self::$variant => loader_kind_vocab!(@marker $($marker)?),)+
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
(@marker marker) => { true };
|
||||
(@marker) => { false };
|
||||
(@count) => { 0 };
|
||||
(@count $head:ident $($tail:ident)*) => { 1 + loader_kind_vocab!(@count $($tail)*) };
|
||||
}
|
||||
|
||||
loader_kind_vocab! {
|
||||
/// 实例面向的加载器种类(比 [`crate::addon::loader::LoaderKind`] 宽)。
|
||||
///
|
||||
/// 那个类型只描述**本启动器能装**的四家(装不了就没有下载路线);本类型面向**识别与展示**,
|
||||
/// 多出「原版」这一支,以及两家只能继承进来的第三方加载器。
|
||||
pub enum LoaderKind {
|
||||
/// 原版(id 里没有任何加载器标记)
|
||||
Vanilla => "vanilla";
|
||||
/// NeoForge(必须排在 `Forge` 前:两者都是段前缀匹配,顺序写死能防以后宽成「包含」)
|
||||
NeoForge => "neoforge", marker;
|
||||
/// Fabric
|
||||
Fabric => "fabric", marker;
|
||||
/// Quilt
|
||||
Quilt => "quilt", marker;
|
||||
/// Forge
|
||||
Forge => "forge", marker;
|
||||
/// OptiFine(第三方,本启动器不装,只继承)
|
||||
OptiFine => "optifine", marker;
|
||||
/// LiteLoader(第三方,本启动器不装,只继承)
|
||||
LiteLoader => "liteloader", marker;
|
||||
}
|
||||
}
|
||||
|
||||
impl LoaderKind {
|
||||
/// 全部种类,**顺序即标记的匹配优先级**;原版没有标记,排最后。
|
||||
/// 稳定 id → 种类([`Self::as_str`] 的反查;整串全等、大小写不敏感、不认前缀与别名)。
|
||||
///
|
||||
/// `NeoForge` 必须在 `Forge` 前(两者都是段前缀匹配,今天互不干扰,但顺序写死能防以后
|
||||
/// 有人把匹配放宽成「包含」)。这张表同时是枚举的清单,漏掉一格编译器抓不到。
|
||||
pub const ALL: [Self; 7] = [
|
||||
Self::NeoForge,
|
||||
Self::Fabric,
|
||||
Self::Quilt,
|
||||
Self::Forge,
|
||||
Self::OptiFine,
|
||||
Self::LiteLoader,
|
||||
Self::Vanilla,
|
||||
];
|
||||
|
||||
/// 对外稳定 id(`--json` 的 `loader.kind`、GUI 词表、`--loader` 的取值)。
|
||||
///
|
||||
/// **全仓唯一的一份 id 字面量**,也是 0.2.0 已发布契约。穷尽 match:加一支枚举就编不过。
|
||||
#[must_use]
|
||||
pub const fn as_str(self) -> &'static str {
|
||||
match self {
|
||||
Self::Vanilla => "vanilla",
|
||||
Self::Fabric => "fabric",
|
||||
Self::Quilt => "quilt",
|
||||
Self::Forge => "forge",
|
||||
Self::NeoForge => "neoforge",
|
||||
Self::OptiFine => "optifine",
|
||||
Self::LiteLoader => "liteloader",
|
||||
}
|
||||
}
|
||||
|
||||
/// 这一支会不会从版本 id 的标记里认出来(原版不会)。穷尽 match:加一支必须表态。
|
||||
#[must_use]
|
||||
pub const fn has_marker(self) -> bool {
|
||||
match self {
|
||||
Self::Vanilla => false,
|
||||
Self::Fabric
|
||||
| Self::Quilt
|
||||
| Self::Forge
|
||||
| Self::NeoForge
|
||||
| Self::OptiFine
|
||||
| Self::LiteLoader => true,
|
||||
}
|
||||
}
|
||||
|
||||
/// 稳定 id → 种类([`Self::as_str`] 的反查;整串全等,大小写不敏感,不认前缀与别名)。
|
||||
///
|
||||
/// 别和 [`InstanceLoader::parse`] 搞混:那个认的是**版本 id**(切段、认前缀、给版本段),
|
||||
/// 这个认的是**词表里的一个 id**。
|
||||
/// 别和 [`InstanceLoader::parse`] 搞混:那个认的是**版本 id**(切段、认前缀、给版本段)。
|
||||
/// 别名 `neo-forge` 只有安装侧收,见 [`crate::addon::loader::LoaderKind::parse`]。
|
||||
#[must_use]
|
||||
pub fn parse(raw: &str) -> Option<Self> {
|
||||
let wanted = raw.trim();
|
||||
@@ -123,24 +141,6 @@ impl LoaderKind {
|
||||
}
|
||||
}
|
||||
|
||||
/// 词表尺寸是契约的一部分:加一支枚举并放进 [`LoaderKind::ALL`] 时这行会红,逼作者确认
|
||||
/// 「对外词表真的要多一个 id 了」。
|
||||
const _: () = assert!(LoaderKind::ALL.len() == 7);
|
||||
|
||||
/// 标记 → 种类,**顺序即优先级**(同一段里先命中的算数)。
|
||||
///
|
||||
/// 标记串取自 [`LoaderKind::as_str`](没有第二份字面量);成员与
|
||||
/// [`LoaderKind::has_marker`] 是否一致由 [`tests::markers_cover_every_kind_that_has_one`]
|
||||
/// 钉住——加一支枚举而漏加一行,那条会红。
|
||||
const MARKERS: [(LoaderKind, &'static str); 6] = [
|
||||
(LoaderKind::NeoForge, LoaderKind::NeoForge.as_str()),
|
||||
(LoaderKind::Fabric, LoaderKind::Fabric.as_str()),
|
||||
(LoaderKind::Quilt, LoaderKind::Quilt.as_str()),
|
||||
(LoaderKind::Forge, LoaderKind::Forge.as_str()),
|
||||
(LoaderKind::OptiFine, LoaderKind::OptiFine.as_str()),
|
||||
(LoaderKind::LiteLoader, LoaderKind::LiteLoader.as_str()),
|
||||
];
|
||||
|
||||
/// 一个版本 id 解析出来的加载器:种类 + 版本段(原版为空串)。
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub struct InstanceLoader {
|
||||
@@ -186,12 +186,14 @@ impl InstanceLoader {
|
||||
}
|
||||
|
||||
/// 这一段的**前缀**是哪个加载器标记;命中时连它后面剩下的部分一起给出。
|
||||
///
|
||||
/// 标记串 = [`LoaderKind::as_str`],顺序 = [`LoaderKind::ALL`](即优先级),认不认标记由
|
||||
/// [`LoaderKind::has_marker`] 表决:**解析表没有独立字面量,也没有独立清单**。
|
||||
fn marker_of(segment: &str) -> Option<(LoaderKind, &str)> {
|
||||
for (kind, marker) in MARKERS {
|
||||
let Some(rest) = strip_prefix_ascii_ci(segment, marker) else {
|
||||
continue;
|
||||
};
|
||||
return Some((kind, strip_loader_suffix(rest)));
|
||||
for kind in LoaderKind::ALL.into_iter().filter(|kind| kind.has_marker()) {
|
||||
if let Some(rest) = strip_prefix_ascii_ci(segment, kind.as_str()) {
|
||||
return Some((kind, strip_loader_suffix(rest)));
|
||||
}
|
||||
}
|
||||
None
|
||||
}
|
||||
@@ -371,29 +373,26 @@ mod tests {
|
||||
}
|
||||
}
|
||||
|
||||
/// 词表 ↔ 解析表:`MARKERS` 必须与 [`LoaderKind::ALL`] / [`has_marker`] / [`as_str`]
|
||||
/// 逐格对齐,并且按标记串能 round-trip 回同一支。
|
||||
/// 往返锚:**每个对外 id 喂回解析都要认成它自己**,标记顺序也得留优先级。
|
||||
///
|
||||
/// 抓得住:加一支枚举漏加 `MARKERS` 一行(`Vec` 逐项比对,顺序也算,所以 `NeoForge`
|
||||
/// 被排到 `Forge` 后面同样红);`MARKERS` 被改回手写字面量并写错串。抓不住:词表字面值
|
||||
/// 本身(改名时两侧同源不会红)——那是对外契约测试的职责,见
|
||||
/// `cli::commands::instances::tests::json_rows_expose_loader_kind_and_version`。
|
||||
///
|
||||
/// [`has_marker`]: LoaderKind::has_marker
|
||||
/// [`as_str`]: LoaderKind::as_str
|
||||
/// 抓得住:标记匹配退化(段前缀变整段全等、`loader` 后缀/MC 尾巴规则被改坏);标记串与
|
||||
/// 词表脱节;`neoforge` 被排到 `forge` 后面。
|
||||
/// 抓不住:**改名**(`neoforge` → `neo_forge`)——词表与解析同源、改名自洽,本条是绿的;
|
||||
/// 抓改名是对外契约测试的职责(`cli::commands::instances::tests::
|
||||
/// json_rows_expose_loader_kind_and_version`)。
|
||||
#[test]
|
||||
fn markers_cover_every_kind_that_has_one() {
|
||||
let listed: Vec<LoaderKind> = MARKERS.iter().map(|(kind, _)| *kind).collect();
|
||||
let expected: Vec<LoaderKind> = LoaderKind::ALL
|
||||
.into_iter()
|
||||
.filter(|kind| kind.has_marker())
|
||||
.collect();
|
||||
assert_eq!(listed, expected, "`MARKERS` 必须正好是「认标记」的那几支");
|
||||
for (kind, marker) in MARKERS {
|
||||
assert_eq!(marker, kind.as_str(), "标记串必须就是稳定 id");
|
||||
let parsed = InstanceLoader::parse(&format!("1.20.4-{marker}-0.16.5"));
|
||||
assert_eq!(parsed.kind, kind, "标记 `{marker}` 的版本 id");
|
||||
assert_eq!(parsed.version, "0.16.5", "标记 `{marker}` 的版本段");
|
||||
fn every_stable_id_parses_back_to_its_kind() {
|
||||
for kind in LoaderKind::ALL {
|
||||
let id = kind.as_str();
|
||||
assert_eq!(LoaderKind::parse(id), Some(kind), "反查 `{id}`");
|
||||
let parsed = InstanceLoader::parse(&format!("1.20.4-{id}-0.16.5"));
|
||||
assert_eq!(parsed.kind, kind, "`{id}` 认成了 {:?}", parsed.kind);
|
||||
let expected = if kind.has_marker() { "0.16.5" } else { "" };
|
||||
assert_eq!(parsed.version, expected, "`{id}` 的版本段");
|
||||
}
|
||||
let order: Vec<LoaderKind> = LoaderKind::ALL.into_iter().collect();
|
||||
let neoforge = order.iter().position(|kind| *kind == LoaderKind::NeoForge);
|
||||
let forge = order.iter().position(|kind| *kind == LoaderKind::Forge);
|
||||
assert!(neoforge < forge, "`neoforge` 要在 `forge` 前:{order:?}");
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user