const SUBSTITUTIONS: &[(&str, &str)] = &[
("@ANY_VERSION@", r"[-_]?(\d[\-+\.:\~\da-zA-Z]*)"),
(
"@SEMANTIC_VERSION@",
r"[-_]?[Vv]?((?:0|[1-9]\d*)\.(?:0|[1-9]\d*)\.(?:0|[1-9]\d*)(?:-(?:(?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*)(?:\.(?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*))*))?)",
),
("@STABLE_VERSION@", r"[-_]?[Vv]?((?:[1-9]\d*)(?:\.\d+){2})"),
(
"@ARCHIVE_EXT@",
r"(?i)\.(?:tar\.xz|tar\.bz2|tar\.gz|zip|tgz|tbz|txz)",
),
(
"@SIGNATURE_EXT@",
r"(?i)\.(?:tar\.xz|tar\.bz2|tar\.gz|zip|tgz|tbz|txz)\.(?:asc|pgp|gpg|sig|sign)",
),
("@DEB_EXT@", r"[\+~](debian|dfsg|ds|deb)(\.)?(\d+)?$"),
];
pub fn subst(
text: &str,
package: impl FnOnce() -> String,
component: impl FnOnce() -> String,
) -> String {
if !text.contains('@') {
return text.to_string();
}
let result = SUBSTITUTIONS
.iter()
.fold(text.to_string(), |acc, (pattern, replacement)| {
acc.replace(pattern, replacement)
});
let result = if result.contains("@PACKAGE@") {
let package_name = package();
result.replace("@PACKAGE@", &package_name)
} else {
result
};
if result.contains("@COMPONENT@") {
let component_name = component();
result.replace("@COMPONENT@", &component_name)
} else {
result
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_subst_any_version() {
assert_eq!(
subst("@ANY_VERSION@", || unreachable!(), || unreachable!()),
r"[-_]?(\d[\-+\.:\~\da-zA-Z]*)"
);
}
#[test]
fn test_subst_package() {
assert_eq!(
subst("@PACKAGE@", || "foo".to_string(), || unreachable!()),
"foo"
);
assert_eq!(
subst(
"https://github.com/@PACKAGE@/releases",
|| "mypackage".to_string(),
|| unreachable!()
),
"https://github.com/mypackage/releases"
);
}
#[test]
fn test_subst_component() {
assert_eq!(
subst("@COMPONENT@", || unreachable!(), || "bar".to_string()),
"bar"
);
assert_eq!(
subst("@COMPONENT@", || unreachable!(), || String::new()),
""
);
assert_eq!(
subst(
"https://example.com/@COMPONENT@/files",
|| unreachable!(),
|| "upstream".to_string()
),
"https://example.com/upstream/files"
);
}
#[test]
fn test_subst_semantic_version() {
let pattern = subst("@SEMANTIC_VERSION@", || unreachable!(), || unreachable!());
assert_eq!(
pattern,
r"[-_]?[Vv]?((?:0|[1-9]\d*)\.(?:0|[1-9]\d*)\.(?:0|[1-9]\d*)(?:-(?:(?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*)(?:\.(?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*))*))?)"
);
let re = regex::Regex::new(&pattern).unwrap();
assert!(re.is_match("1.2.3"));
assert!(re.is_match("v1.2.3"));
assert!(re.is_match("0.0.0"));
assert!(re.is_match("1.2.3-alpha"));
assert!(re.is_match("1.2.3-alpha.1"));
}
#[test]
fn test_subst_stable_version() {
let pattern = subst("@STABLE_VERSION@", || unreachable!(), || unreachable!());
assert_eq!(pattern, r"[-_]?[Vv]?((?:[1-9]\d*)(?:\.\d+){2})");
let re = regex::Regex::new(&pattern).unwrap();
assert!(re.is_match("1.2.3"));
assert!(re.is_match("v1.2.3"));
assert!(re.is_match("10.20.30"));
assert!(!re.is_match("0.2.3"));
}
#[test]
fn test_subst_archive_ext() {
let pattern = subst("@ARCHIVE_EXT@", || unreachable!(), || unreachable!());
assert_eq!(
pattern,
r"(?i)\.(?:tar\.xz|tar\.bz2|tar\.gz|zip|tgz|tbz|txz)"
);
}
#[test]
fn test_subst_signature_ext() {
let pattern = subst("@SIGNATURE_EXT@", || unreachable!(), || unreachable!());
assert_eq!(
pattern,
r"(?i)\.(?:tar\.xz|tar\.bz2|tar\.gz|zip|tgz|tbz|txz)\.(?:asc|pgp|gpg|sig|sign)"
);
}
#[test]
fn test_subst_deb_ext() {
let pattern = subst("@DEB_EXT@", || unreachable!(), || unreachable!());
assert_eq!(pattern, r"[\+~](debian|dfsg|ds|deb)(\.)?(\d+)?$");
}
#[test]
fn test_subst_multiple_templates() {
assert_eq!(
subst(
"https://github.com/@PACKAGE@/releases/@COMPONENT@/file@ARCHIVE_EXT@",
|| "myapp".to_string(),
|| "core".to_string(),
),
"https://github.com/myapp/releases/core/file(?i)\\.(?:tar\\.xz|tar\\.bz2|tar\\.gz|zip|tgz|tbz|txz)"
);
}
#[test]
fn test_subst_no_templates() {
assert_eq!(
subst(
"https://example.com/releases",
|| unreachable!(),
|| unreachable!(),
),
"https://example.com/releases"
);
}
}