use std::sync::LazyLock;
use crate::shared::i18n::Lang;
pub const APP_NAME: &str = "mindfork";
pub const AUTHOR: &str = "Vladimir Shylov";
pub const SITE_URL: &str = "https://mindfork.io";
pub const REPO_URL: &str = "https://github.com/vshylov/mindfork-rs";
pub const CRATE_URL: &str = "https://crates.io/crates/mindfork";
pub const LICENSE_ID: &str = env!("CARGO_PKG_LICENSE");
pub fn platform() -> String {
format!("{} {}", std::env::consts::OS, std::env::consts::ARCH)
}
pub fn build_date() -> Option<&'static str> {
static DATE: LazyLock<Option<String>> = LazyLock::new(|| {
if cfg!(debug_assertions) {
return None;
}
let secs: i64 = env!("MINDFORK_BUILD_EPOCH").parse().ok()?;
let stamp = chrono::DateTime::from_timestamp(secs, 0)?;
Some(stamp.format("%Y-%m-%d").to_string())
});
DATE.as_deref()
}
pub const LICENSE_TEXT: &str = include_str!("../../LICENSE");
pub const LICENSE_TEXT_RU: &str = include_str!("../../docs/legal/LICENSE.ru.txt");
pub const DISCLAIMER_TEXT: &str = include_str!("../../DISCLAIMER.md");
pub const DISCLAIMER_TEXT_RU: &str = include_str!("../../docs/legal/DISCLAIMER.ru.md");
pub const PRIVACY_TEXT: &str = include_str!("../../PRIVACY.md");
pub const PRIVACY_TEXT_RU: &str = include_str!("../../docs/legal/PRIVACY.ru.md");
pub fn license_text(lang: Lang) -> &'static str {
match lang {
Lang::Ru => LICENSE_TEXT_RU,
_ => LICENSE_TEXT,
}
}
pub fn disclaimer_text(lang: Lang) -> &'static str {
match lang {
Lang::Ru => DISCLAIMER_TEXT_RU,
_ => DISCLAIMER_TEXT,
}
}
pub fn privacy_text(lang: Lang) -> &'static str {
match lang {
Lang::Ru => PRIVACY_TEXT_RU,
_ => PRIVACY_TEXT,
}
}
pub const COMPONENTS: &[(&str, &str, &str)] = &[
("ansi-to-tui", "8.0.1", "MIT"),
("anyhow", "1.0.104", "MIT OR Apache-2.0"),
("arboard", "3.6.1", "MIT OR Apache-2.0"),
("async-stream", "0.3.6", "MIT"),
("async-trait", "0.1.92", "MIT OR Apache-2.0"),
("base64", "0.23.1", "MIT OR Apache-2.0"),
("bytemuck", "1.25.2", "Zlib OR Apache-2.0 OR MIT"),
("chacha20poly1305", "0.10.1", "Apache-2.0 OR MIT"),
("chardetng", "1.0.0", "Apache-2.0 OR MIT"),
("chrono", "0.4.45", "MIT OR Apache-2.0"),
("crossterm", "0.29.0", "MIT"),
("directories", "6.0.0", "MIT OR Apache-2.0"),
(
"encoding_rs",
"0.8.41",
"(Apache-2.0 OR MIT) AND BSD-3-Clause",
),
("eventsource-stream", "0.2.3", "MIT OR Apache-2.0"),
("flate2", "1.1.10", "MIT OR Apache-2.0"),
("futures-util", "0.3.34", "MIT OR Apache-2.0"),
("hkdf", "0.13.0", "MIT OR Apache-2.0"),
("ignore", "0.4.33", "MIT OR Unlicense"),
("image", "0.25.10", "MIT OR Apache-2.0"),
("mermaid-text", "0.57.0", "MIT"),
("pdf-extract", "0.12.0", "MIT"),
("percent-encoding", "2.3.2", "MIT OR Apache-2.0"),
("pulldown-cmark", "0.13.4", "MIT"),
("quick-xml", "0.42.0", "MIT"),
("ratatui", "0.30.2", "MIT"),
("regex", "1.13.1", "MIT OR Apache-2.0"),
("reqwest", "0.13.5", "MIT OR Apache-2.0"),
("rodio", "0.22.2", "MIT OR Apache-2.0"),
("rusqlite", "0.40.2", "MIT"),
("scraper", "0.27.0", "ISC"),
("serde", "1.0.229", "MIT OR Apache-2.0"),
("serde_json", "1.0.151", "MIT OR Apache-2.0"),
("sha2", "0.11.0", "MIT OR Apache-2.0"),
("similar", "3.2.0", "Apache-2.0"),
("single-instance", "0.3.3", "MIT"),
("spellbook", "0.4.2", "MPL-2.0"),
("sqlite-vec", "0.1.9", "MIT/Apache-2.0"),
("syntect", "5.3.0", "MIT"),
("sys-locale", "0.3.2", "MIT OR Apache-2.0"),
("tar", "0.4.46", "MIT OR Apache-2.0"),
("thiserror", "2.0.20", "MIT OR Apache-2.0"),
("tokio", "1.53.1", "MIT"),
("tokio-util", "0.7.19", "MIT"),
("tracing", "0.1.44", "MIT"),
("tracing-appender", "0.2.5", "MIT"),
("tracing-subscriber", "0.3.23", "MIT"),
("tui-scrollview", "0.6.7", "MIT OR Apache-2.0"),
("unicode-segmentation", "1.13.3", "MIT OR Apache-2.0"),
("unicode-width", "0.2.2", "MIT OR Apache-2.0"),
("uuid", "1.26.1", "Apache-2.0 OR MIT"),
("windows-sys", "0.61.2", "MIT OR Apache-2.0"),
("zip", "2.4.2", "MIT"),
];
pub type Grammar = (&'static str, &'static str, &'static str);
const SYNTAX_MANIFEST: &str = include_str!("../../syntaxes/SOURCES.md");
pub static GRAMMARS: std::sync::LazyLock<Vec<Grammar>> = std::sync::LazyLock::new(|| {
SYNTAX_MANIFEST
.lines()
.map(str::trim)
.filter(|l| l.starts_with('|'))
.filter_map(|line| {
let cells: Vec<&str> = line.trim_matches('|').split('|').map(str::trim).collect();
let [grammar, _file, repo, _path, _commit, licence, _lic_path] = cells[..] else {
return None;
};
let head_or_rule =
grammar == "Grammar" || grammar.chars().all(|c| c == '-' || c == ':');
(!head_or_rule).then_some((grammar, repo, licence))
})
.collect()
});
#[cfg(test)]
mod tests {
use super::*;
use std::collections::BTreeSet;
fn cargo_runtime_deps() -> BTreeSet<String> {
let path = concat!(env!("CARGO_MANIFEST_DIR"), "/Cargo.toml");
let toml = std::fs::read_to_string(path).expect("Cargo.toml is present");
const WANTED: [&str; 2] = ["[dependencies]", "[target.'cfg(windows)'.dependencies]"];
let mut section = "";
let mut deps = BTreeSet::new();
for line in toml.lines() {
let trimmed = line.trim();
if trimmed.starts_with('[') {
section = trimmed;
continue;
}
if !WANTED.contains(§ion) || trimmed.is_empty() || trimmed.starts_with('#') {
continue;
}
if let Some((name, _)) = trimmed.split_once('=') {
let name = name.trim();
if !name.is_empty()
&& name
.chars()
.all(|c| c.is_ascii_alphanumeric() || "-_".contains(c))
{
deps.insert(name.to_string());
}
}
}
deps
}
fn cargo_lock_versions() -> std::collections::BTreeMap<String, BTreeSet<String>> {
let path = concat!(env!("CARGO_MANIFEST_DIR"), "/Cargo.lock");
let lock = std::fs::read_to_string(path).expect("Cargo.lock is present");
let mut map: std::collections::BTreeMap<String, BTreeSet<String>> = Default::default();
let mut name: Option<String> = None;
for line in lock.lines() {
let t = line.trim();
if t == "[[package]]" {
name = None;
} else if let Some(v) = t.strip_prefix("name = \"") {
name = v.strip_suffix('"').map(str::to_string);
} else if let Some(v) = t.strip_prefix("version = \"")
&& let Some(v) = v.strip_suffix('"')
&& let Some(n) = &name
{
map.entry(n.clone()).or_default().insert(v.to_string());
}
}
map
}
#[test]
fn components_cover_direct_dependencies() {
let manifest = cargo_runtime_deps();
let listed: BTreeSet<String> = COMPONENTS.iter().map(|(n, ..)| n.to_string()).collect();
let missing: Vec<_> = manifest.difference(&listed).collect();
let extra: Vec<_> = listed.difference(&manifest).collect();
assert!(
missing.is_empty() && extra.is_empty(),
"COMPONENTS has drifted from Cargo.toml — missing from the list: {missing:?}; extra: {extra:?}"
);
}
#[test]
fn the_binary_is_named_after_the_brand() {
assert_eq!(env!("CARGO_PKG_NAME"), APP_NAME);
let path = concat!(env!("CARGO_MANIFEST_DIR"), "/Cargo.toml");
let toml = std::fs::read_to_string(path).expect("Cargo.toml is present");
let mut in_bin = false;
let mut bin_name = None;
for line in toml.lines() {
let t = line.trim();
if t.starts_with('[') {
in_bin = t == "[[bin]]";
continue;
}
if in_bin && let Some(v) = t.strip_prefix("name = \"") {
bin_name = v.strip_suffix('"');
break;
}
}
assert_eq!(bin_name, Some(APP_NAME), "[[bin]] name ≠ credits::APP_NAME");
}
#[test]
fn grammar_manifest_matches_the_vendored_files() {
let dir = std::path::Path::new(concat!(env!("CARGO_MANIFEST_DIR"), "/syntaxes"));
let on_disk: BTreeSet<String> = std::fs::read_dir(dir)
.expect("the syntaxes/ directory")
.filter_map(|e| e.ok().map(|e| e.path()))
.filter(|p| p.extension().is_some_and(|e| e == "sublime-syntax"))
.map(|p| p.file_name().unwrap().to_string_lossy().into_owned())
.collect();
assert!(!on_disk.is_empty(), "no vendored grammars found in {dir:?}");
let listed: BTreeSet<String> = SYNTAX_MANIFEST
.lines()
.map(str::trim)
.filter(|l| l.starts_with('|'))
.filter_map(|l| l.trim_matches('|').split('|').nth(1).map(str::trim))
.filter(|f| f.ends_with(".sublime-syntax"))
.map(str::to_string)
.collect();
assert_eq!(
listed, on_disk,
"syntaxes/SOURCES.md has drifted from the files in syntaxes/"
);
for (grammar, repo, licence) in GRAMMARS.iter() {
assert!(
!repo.is_empty() && !licence.is_empty(),
"{grammar}: the manifest row must carry a repository and a licence"
);
let text = dir.join("licenses").join(format!("{grammar}.txt"));
assert!(
text.is_file(),
"{grammar}: the licence text is not vendored ({text:?}) — \
run `python tools/fetch_syntaxes.py`"
);
}
}
#[test]
fn component_versions_match_cargo_lock() {
let locked = cargo_lock_versions();
for (name, version, _) in COMPONENTS {
let versions = locked
.get(*name)
.unwrap_or_else(|| panic!("{name} is missing from Cargo.lock"));
assert!(
versions.contains(*version),
"version {name} {version} not found in Cargo.lock: {versions:?}"
);
}
}
#[test]
fn a_duplicated_crate_is_listed_at_the_version_this_crate_uses() {
let path = concat!(env!("CARGO_MANIFEST_DIR"), "/Cargo.lock");
let lock = std::fs::read_to_string(path).expect("Cargo.lock is present");
let ours = lock
.split("[[package]]")
.find(|block| block.contains(concat!("name = \"", env!("CARGO_PKG_NAME"), "\"")))
.expect("this crate is a package in its own lock file");
let listed: std::collections::BTreeMap<&str, &str> =
COMPONENTS.iter().map(|(n, v, _)| (*n, *v)).collect();
for line in ours.lines() {
let Some((name, version)) = line
.trim()
.trim_matches(|c| c == '"' || c == ',')
.split_once(' ')
else {
continue;
};
if let Some(shown) = listed.get(name) {
assert_eq!(
*shown, version,
"{name} is in Cargo.lock more than once and this crate uses \
{version}, but the About dialog names {shown}"
);
}
}
}
#[test]
fn components_are_sorted_and_licensed() {
for (name, version, license) in COMPONENTS {
assert!(!license.is_empty(), "{name} has no license");
assert!(!version.is_empty(), "{name} has no version");
}
let names: Vec<_> = COMPONENTS.iter().map(|(n, ..)| *n).collect();
let mut sorted = names.clone();
sorted.sort_unstable();
assert_eq!(names, sorted, "COMPONENTS is not sorted by name");
}
#[test]
fn the_build_stamp_is_a_valid_iso_date() {
let secs: i64 = env!("MINDFORK_BUILD_EPOCH")
.parse()
.expect("MINDFORK_BUILD_EPOCH is not a number");
let rendered = chrono::DateTime::from_timestamp(secs, 0)
.expect("the build stamp is not a timestamp")
.format("%Y-%m-%d")
.to_string();
assert_eq!(rendered.len(), 10, "not an ISO date: {rendered}");
assert!(
rendered.starts_with("20") && rendered.matches('-').count() == 2,
"not an ISO date: {rendered}"
);
assert_eq!(build_date().is_some(), !cfg!(debug_assertions));
}
#[test]
fn license_text_is_embedded_mit() {
assert!(LICENSE_TEXT.contains("MIT License"));
assert!(LICENSE_TEXT.contains(AUTHOR), "LICENSE copyright ≠ AUTHOR");
}
#[test]
fn license_file_carries_nothing_but_the_mit_text() {
let last = LICENSE_TEXT.trim_end().lines().last().unwrap_or("").trim();
assert_eq!(last, "SOFTWARE.", "LICENSE has content after the MIT text");
assert!(
!LICENSE_TEXT.contains('#'),
"LICENSE has markdown headings — an addendum crept in"
);
}
#[test]
fn the_russian_texts_declare_themselves_unofficial_translations() {
for (name, text) in [
("LICENSE.ru.txt", LICENSE_TEXT_RU),
("DISCLAIMER.ru.md", DISCLAIMER_TEXT_RU),
] {
assert!(text.len() > 500, "{name} looks empty");
assert!(
text.contains("неофициальный перевод") || text.contains("Неофициальный перевод"),
"{name} does not call itself an unofficial translation"
);
assert!(
text.contains("английский"),
"{name} does not name the English original as the governing text"
);
}
}
#[test]
fn the_legal_texts_follow_the_interface_language() {
assert_eq!(license_text(Lang::Ru), LICENSE_TEXT_RU);
assert_eq!(disclaimer_text(Lang::Ru), DISCLAIMER_TEXT_RU);
for lang in [Lang::En, Lang::from_code("de")] {
assert_eq!(license_text(lang), LICENSE_TEXT);
assert_eq!(disclaimer_text(lang), DISCLAIMER_TEXT);
}
}
#[test]
fn the_russian_disclaimer_mirrors_the_originals_structure() {
let shape = |text: &str| {
let levels: Vec<usize> = text
.lines()
.filter(|l| l.starts_with('#'))
.map(|l| l.chars().take_while(|c| *c == '#').count())
.collect();
let bullets = text.lines().filter(|l| l.starts_with("- ")).count();
let rules = text.lines().filter(|l| l.trim() == "---").count();
(levels, bullets, rules)
};
assert_eq!(
shape(DISCLAIMER_TEXT),
shape(DISCLAIMER_TEXT_RU),
"the ru disclaimer has drifted from DISCLAIMER.md (headings/bullets/rules)"
);
}
#[test]
fn the_russian_license_is_paragraphs_only() {
for (i, line) in LICENSE_TEXT_RU.lines().enumerate() {
let l = line.trim_start();
assert!(
!l.starts_with('#') && !l.starts_with("- ") && !l.starts_with('>'),
"docs/legal/LICENSE.ru.txt:{}: markdown in a plain-text file",
i + 1
);
assert!(
!l.contains("]("),
"docs/legal/LICENSE.ru.txt:{}: a markdown link would print verbatim",
i + 1
);
}
assert!(
LICENSE_TEXT_RU.contains(AUTHOR),
"the ru license lost the copyright holder"
);
assert!(LICENSE_TEXT_RU.contains("MIT"));
}
#[test]
fn disclaimer_text_is_embedded_and_supplements_the_license() {
assert!(DISCLAIMER_TEXT.starts_with("# Disclaimer"));
assert!(
DISCLAIMER_TEXT.contains("(LICENSE)"),
"the disclaimer does not link back to LICENSE"
);
for topic in ["no warranty", "model", "Limitation of liability"] {
assert!(
DISCLAIMER_TEXT.contains(topic),
"the disclaimer no longer mentions {topic:?}"
);
}
}
fn repo_file(relative: &str) -> String {
let path = std::path::Path::new(env!("CARGO_MANIFEST_DIR")).join(relative);
std::fs::read_to_string(&path).unwrap_or_else(|e| panic!("{relative} is present: {e}"))
}
fn directive(script: &str, name: &str) -> String {
let prefix = format!("{name}=");
script
.lines()
.map(str::trim)
.find(|line| line.starts_with(&prefix))
.unwrap_or_else(|| panic!("mindfork.iss has no {name} directive"))[prefix.len()..]
.trim()
.to_string()
}
#[test]
fn the_installer_and_the_binary_declare_the_same_product() {
let iss = repo_file("packaging/windows/mindfork.iss");
let build_rs = repo_file("build.rs");
let product = build_rs
.lines()
.find_map(|line| {
line.trim()
.strip_prefix("const PRODUCT_NAME: &str = \"")?
.split_once('"')
.map(|(name, _)| name.to_string())
})
.expect("build.rs declares PRODUCT_NAME");
assert_eq!(
product, APP_NAME,
"build.rs and credits disagree on the brand"
);
assert_eq!(directive(&iss, "AppName"), product);
assert_eq!(directive(&iss, "VersionInfoProductName"), product);
assert_eq!(
directive(&iss, "VersionInfoCompany"),
directive(&iss, "AppPublisher"),
);
assert_eq!(
directive(&iss, "VersionInfoDescription"),
format!("{product} Setup"),
);
let expected = LICENSE_TEXT
.lines()
.map(str::trim)
.find(|line| line.starts_with("Copyright (c)"))
.expect("LICENSE carries a copyright line");
assert_eq!(
directive(&iss, "VersionInfoCopyright"),
expected,
"the installer's copyright drifted from LICENSE — build.rs reads that file, \
this one cannot"
);
for name in ["VersionInfoVersion", "VersionInfoProductVersion"] {
assert_eq!(directive(&iss, name), "{#NumericVersion}", "{name}");
}
assert!(
iss.contains("#define NumericVersion Copy(AppVersion, 1, Dash - 1)")
&& iss.contains("#define NumericVersion AppVersion"),
"mindfork.iss must derive NumericVersion from AppVersion, both ways",
);
}
}