mirror of
https://github.com/cargo-bins/cargo-binstall.git
synced 2025-04-20 20:48:43 +00:00

Fixed #851 * Add new dep leon to crate binstalk * Add new variant `BinstallError::Template{Parse, Render}Error` * Use `leon::Template` in mod `bins` * Use `leon::Template` in mod `fetchers::gh_crate_meta` * Refactor mod `bins`: Rm unused associated fn & fields from `Context` * Simplify unit testing in mod `fetchers::gh_crate_meta` * Rm soft-deprecated field `fetchers::gh_crate_meta::Context::format` and change the `match` to resolve `archive` => `self.archive_format`. * Make macro_rules `leon::template!` far easier to use * Construct `leon::Template<'_>` as constant in `gh_crate_meta::hosting` * Simplify `leon::Values` trait Change its method `get_value` signature to ```rust fn get_value(&self, key: &str) -> Option<Cow<'_, str>>; ``` Now, `ValuesFn` also accepts non-`'static` function, but now `leon::Values` is only implemented for `&ValuesFn<F>` now. This makes it a little bit more cumbersome to use but I think it's a reasonable drawback. * Rm `Send` bound req from `ValuesFn` * Impl new fn `leon::Template::cast` for casting `Template<'s>` to `Template<'t>` where `'s: 't` * Rename `leon::Template::has_keys` => `has_any_of_keys` * Make checking whether format related keys are present more robust * Optimize `GhCrateMeta::launch_baseline_find_tasks`: Skip checking all fmt ext if none of the format related keys ("format", "archive-format", "archive-suffix") are present. * Only ret `.exe` in `PkgFmt::extensions` on windows by adding a new param `is_windows: bool` * Improve debug msg in `GhCrateMeta::fetch_and_extract` * Add warnings to `GhCrateMeta::find` * Rm dep tinytemplate * `impl<'s, 'rhs: 's> ops::AddAssign<&Template<'rhs>> for Template<'s>` * `impl<'s, 'rhs: 's> ops::AddAssign<Template<'rhs>> for Template<'s>` * `impl<'s, 'item: 's> ops::AddAssign<Item<'item>> for Template<'s>` * `impl<'s, 'item: 's> ops::AddAssign<&Item<'item>> for Template<'s>` * `impl<'s, 'rhs: 's> ops::Add<Template<'rhs>> for Template<'s>` (improved existing `Add` impl) * `impl<'s, 'rhs: 's> ops::Add<&Template<'rhs>> for Template<'s>` * `impl<'s, 'item: 's> ops::Add<Item<'item>> for Template<'s>` * `impl<'s, 'item: 's> ops::Add<&Item<'item>> for Template<'s>` Signed-off-by: Jiahao XU <Jiahao_XU@outlook.com> Co-authored-by: Félix Saparelli <felix@passcod.name>
334 lines
9.8 KiB
Rust
334 lines
9.8 KiB
Rust
use crate::{Item, ParseError, Template};
|
|
|
|
impl<'s> Template<'s> {
|
|
pub(crate) fn parse_items(source: &'s str) -> Result<Vec<Item<'s>>, ParseError> {
|
|
let mut items = Vec::new();
|
|
|
|
let mut start = 0;
|
|
let mut s = source;
|
|
|
|
loop {
|
|
if let Some(index) = s.find(['\\', '{', '}']) {
|
|
if index != 0 {
|
|
let (first, last) = s.split_at(index);
|
|
items.push(Item::Text(first));
|
|
|
|
// Move cursor forward
|
|
start += index;
|
|
s = last;
|
|
}
|
|
} else {
|
|
if !s.is_empty() {
|
|
items.push(Item::Text(s));
|
|
}
|
|
|
|
break Ok(items);
|
|
};
|
|
|
|
let mut chars = s.chars();
|
|
let ch = chars.next().unwrap();
|
|
|
|
match ch {
|
|
'\\' => {
|
|
match chars.next() {
|
|
Some('\\' | '{' | '}') => {
|
|
let t = s.get(1..2).unwrap();
|
|
debug_assert!(["\\", "{", "}"].contains(&t), "{}", t);
|
|
items.push(Item::Text(t));
|
|
|
|
// Move cursor forward
|
|
start += 2;
|
|
s = s.get(2..).unwrap();
|
|
}
|
|
_ => {
|
|
return Err(ParseError::escape(source, start, start + 1));
|
|
}
|
|
}
|
|
}
|
|
'{' => {
|
|
let Some((key, rest)) = s[1..].split_once('}') else {
|
|
return Err(ParseError::unbalanced(source, start, start + s.len()));
|
|
};
|
|
if let Some(index) = key.find('\\') {
|
|
return Err(ParseError::key_escape(source, start, start + 1 + index));
|
|
}
|
|
|
|
let k = key.trim();
|
|
if k.is_empty() {
|
|
return Err(ParseError::key_empty(source, start, start + key.len() + 1));
|
|
}
|
|
items.push(Item::Key(k));
|
|
|
|
// Move cursor forward
|
|
// for the '{'
|
|
// | for the '}'
|
|
// | |
|
|
start += 1 + key.len() + 1;
|
|
s = rest;
|
|
}
|
|
'}' => {
|
|
return Err(ParseError::unbalanced(source, start, start));
|
|
}
|
|
_ => unreachable!(),
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
#[cfg(test)]
|
|
mod test_valid {
|
|
use crate::{template, Template};
|
|
|
|
#[test]
|
|
fn empty() {
|
|
let template = Template::parse("").unwrap();
|
|
assert_eq!(template, Template::default());
|
|
}
|
|
|
|
#[test]
|
|
fn no_keys() {
|
|
let template = Template::parse("hello world").unwrap();
|
|
assert_eq!(template, template!("hello world"));
|
|
}
|
|
|
|
#[test]
|
|
fn leading_key() {
|
|
let template = Template::parse("{salutation} world").unwrap();
|
|
assert_eq!(template, template!({ "salutation" }, " world"));
|
|
}
|
|
|
|
#[test]
|
|
fn trailing_key() {
|
|
let template = Template::parse("hello {name}").unwrap();
|
|
assert_eq!(template, template!("hello ", { "name" }));
|
|
}
|
|
|
|
#[test]
|
|
fn middle_key() {
|
|
let template = Template::parse("hello {name}!").unwrap();
|
|
assert_eq!(template, template!("hello ", { "name" }, "!"));
|
|
}
|
|
|
|
#[test]
|
|
fn middle_text() {
|
|
let template = Template::parse("{salutation} good {title}").unwrap();
|
|
assert_eq!(template, template!({ "salutation" }, " good ", { "title" }));
|
|
}
|
|
|
|
#[test]
|
|
fn multiline() {
|
|
let template = Template::parse(
|
|
"
|
|
And if thy native country was { ancient civilisation },
|
|
What need to slight thee? Came not {hero} thence,
|
|
Who gave to { country } her books and art of writing?
|
|
",
|
|
)
|
|
.unwrap();
|
|
assert_eq!(
|
|
template,
|
|
template!(
|
|
"\n And if thy native country was ",
|
|
{ "ancient civilisation" },
|
|
",\n What need to slight thee? Came not ",
|
|
{ "hero" },
|
|
" thence,\n Who gave to ",
|
|
{ "country" },
|
|
" her books and art of writing?\n ",
|
|
)
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn key_no_whitespace() {
|
|
let template = Template::parse("{word}").unwrap();
|
|
assert_eq!(template, template!({ "word" }));
|
|
}
|
|
|
|
#[test]
|
|
fn key_leading_whitespace() {
|
|
let template = Template::parse("{ word}").unwrap();
|
|
assert_eq!(template, template!({ "word" }));
|
|
}
|
|
|
|
#[test]
|
|
fn key_trailing_whitespace() {
|
|
let template = Template::parse("{word\n}").unwrap();
|
|
assert_eq!(template, template!({ "word" }));
|
|
}
|
|
|
|
#[test]
|
|
fn key_both_whitespace() {
|
|
let template = Template::parse(
|
|
"{
|
|
\tword
|
|
}",
|
|
)
|
|
.unwrap();
|
|
assert_eq!(template, template!({ "word" }));
|
|
}
|
|
|
|
#[test]
|
|
fn key_inner_whitespace() {
|
|
let template = Template::parse("{ a word }").unwrap();
|
|
assert_eq!(template, template!({ "a word" }));
|
|
}
|
|
|
|
#[test]
|
|
fn escape_left() {
|
|
let template = Template::parse(r"this \{ single left brace").unwrap();
|
|
assert_eq!(template, template!("this ", "{", " single left brace"));
|
|
}
|
|
|
|
#[test]
|
|
fn escape_right() {
|
|
let template = Template::parse(r"this \} single right brace").unwrap();
|
|
assert_eq!(template, template!("this ", "}", " single right brace"));
|
|
}
|
|
|
|
#[test]
|
|
fn escape_both() {
|
|
let template = Template::parse(r"these \{ two \} braces").unwrap();
|
|
assert_eq!(template, template!("these ", "{", " two ", "}", " braces"));
|
|
}
|
|
|
|
#[test]
|
|
fn escape_doubled() {
|
|
let template = Template::parse(r"these \{\{ four \}\} braces").unwrap();
|
|
assert_eq!(
|
|
template,
|
|
template!("these ", "{", "{", " four ", "}", "}", " braces")
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn escape_escape() {
|
|
let template = Template::parse(r"these \\ backslashes \\\\").unwrap();
|
|
assert_eq!(
|
|
template,
|
|
template!("these ", r"\", " backslashes ", r"\", r"\",)
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn escape_before_key() {
|
|
let template = Template::parse(r"\\{ a } \{{ b } \}{ c }").unwrap();
|
|
assert_eq!(
|
|
template,
|
|
template!(r"\", { "a" }, " ", r"{", { "b" }, " ", r"}", { "c" })
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn escape_after_key() {
|
|
let template = Template::parse(r"{ a }\\ { b }\{ { c }\}").unwrap();
|
|
assert_eq!(
|
|
template,
|
|
template!({ "a" }, r"\", " ", { "b" }, r"{", " ", { "c" }, r"}")
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn multibyte_texts() {
|
|
let template = Template::parse("幸徳 {particle} 秋水").unwrap();
|
|
assert_eq!(template, template!("幸徳 ", { "particle" }, " 秋水"));
|
|
}
|
|
|
|
#[test]
|
|
fn multibyte_key() {
|
|
let template = Template::parse("The { 連盟 }").unwrap();
|
|
assert_eq!(template, template!("The ", { "連盟" }));
|
|
}
|
|
|
|
#[test]
|
|
fn multibyte_both() {
|
|
let template = Template::parse("大杉 {栄}").unwrap();
|
|
assert_eq!(template, template!("大杉 ", { "栄" }));
|
|
}
|
|
|
|
#[test]
|
|
fn multibyte_whitespace() {
|
|
let template = Template::parse("岩佐 作{ 太 }郎").unwrap();
|
|
assert_eq!(template, template!("岩佐 作", { "太" }, "郎"));
|
|
}
|
|
|
|
#[test]
|
|
fn multibyte_with_escapes() {
|
|
let template = Template::parse(r"日本\{アナキスト\}連盟").unwrap();
|
|
assert_eq!(
|
|
template,
|
|
template!("日本", r"{", "アナキスト", r"}", "連盟")
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn multibyte_rtl_text() {
|
|
let template = Template::parse("محمد صايل").unwrap();
|
|
assert_eq!(template, template!("محمد صايل"));
|
|
}
|
|
|
|
#[test]
|
|
fn multibyte_rtl_key() {
|
|
let template = Template::parse("محمد {ريشة}").unwrap();
|
|
assert_eq!(template, template!("محمد ", { "ريشة" }));
|
|
}
|
|
}
|
|
|
|
#[cfg(test)]
|
|
mod test_error {
|
|
use crate::{ParseError, Template};
|
|
|
|
#[test]
|
|
fn key_left_half() {
|
|
let template = Template::parse("{ open").unwrap_err();
|
|
assert_eq!(template, ParseError::unbalanced("{ open", 0, 6));
|
|
}
|
|
|
|
#[test]
|
|
fn key_right_half() {
|
|
let template = Template::parse("open }").unwrap_err();
|
|
assert_eq!(template, ParseError::unbalanced("open }", 5, 5));
|
|
}
|
|
|
|
#[test]
|
|
fn key_with_half_escape() {
|
|
let template = Template::parse(r"this is { not \ allowed }").unwrap_err();
|
|
assert_eq!(
|
|
template,
|
|
ParseError::key_escape(r"this is { not \ allowed }", 8, 14)
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn key_with_full_escape() {
|
|
let template = Template::parse(r"{ not \} allowed }").unwrap_err();
|
|
assert_eq!(
|
|
template,
|
|
ParseError::key_escape(r"{ not \} allowed }", 0, 6)
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn key_empty() {
|
|
let template = Template::parse(r"void: {}").unwrap_err();
|
|
assert_eq!(template, ParseError::key_empty(r"void: {}", 6, 7));
|
|
}
|
|
|
|
#[test]
|
|
fn key_only_whitespace() {
|
|
let template = Template::parse(r"nothing: { }").unwrap_err();
|
|
assert_eq!(template, ParseError::key_empty(r"nothing: { }", 9, 11));
|
|
}
|
|
|
|
#[test]
|
|
fn bad_escape() {
|
|
let template = Template::parse(r"not \a thing").unwrap_err();
|
|
assert_eq!(template, ParseError::escape(r"not \a thing", 4, 5));
|
|
}
|
|
|
|
#[test]
|
|
fn end_escape() {
|
|
let template = Template::parse(r"forget me not \").unwrap_err();
|
|
assert_eq!(template, ParseError::escape(r"forget me not \", 14, 15));
|
|
}
|
|
}
|