Use leon for template in binstalk & detect malformed pkg-url/pkg-fmt early (#933)

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>
This commit is contained in:
Jiahao XU 2023-03-26 16:11:10 +11:00 committed by GitHub
parent 47d4aeaa96
commit a27d5aebf6
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
13 changed files with 512 additions and 312 deletions

View file

@ -59,7 +59,7 @@
//! # let template = Template::parse("hello {name}").unwrap();
//! assert_eq!(
//! template.render(
//! &vals(|_key| Some("marcus".into()))
//! &&vals(|_key| Some("marcus".into()))
//! ).unwrap().as_str(),
//! "hello marcus",
//! );
@ -76,7 +76,7 @@
//! let mut buf: Vec<u8> = Vec::new();
//! template.render_into(
//! &mut buf,
//! &vals(|key| if key == "name" {
//! &&vals(|key| if key == "name" {
//! Some("julius".into())
//! } else {
//! None
@ -107,7 +107,7 @@
//! name: &'static str,
//! }
//! impl Values for MyMap {
//! fn get_value<'s, 'k: 's>(&'s self, key: &'k str) -> Option<Cow<'s, str>> {
//! fn get_value(&self, key: &str) -> Option<Cow<'_, str>> {
//! if key == "name" {
//! Some(self.name.into())
//! } else {

View file

@ -1,4 +1,35 @@
/// Construct a template constant without needing to make an items constant.
#[doc(hidden)]
#[macro_export]
macro_rules! __template_item {
() => {};
({ $key:literal }) => {
$crate::Item::Key($key)
};
( $text:literal ) => {
$crate::Item::Text($text)
};
}
#[doc(hidden)]
#[macro_export]
macro_rules! __template_impl {
($( $token:tt ),* ; $default:expr) => {
$crate::Template::new(
{
const ITEMS: &'static [$crate::Item<'static>] = &[
$(
$crate::__template_item!($token)
),*
];
ITEMS
},
$default,
)
};
}
/// Construct a template constant using syntax similar to the template to be
/// passed to [`Template::parse`].
///
/// This is essentially a shorthand for:
///
@ -13,9 +44,8 @@
/// # Examples
///
/// ```
/// use leon::Item::*;
/// assert_eq!(
/// leon::template!(Text("Hello "), Key("name"))
/// leon::template!("Hello ", {"name"})
/// .render(&[("name", "Магда Нахман")])
/// .unwrap(),
/// "Hello Магда Нахман",
@ -25,9 +55,8 @@
/// With a default:
///
/// ```
/// use leon::Item::*;
/// assert_eq!(
/// leon::template!(Text("Hello "), Key("name"); "M. P. T. Acharya")
/// leon::template!("Hello ", {"name"}; "M. P. T. Acharya")
/// .render(&[("city", "Madras")])
/// .unwrap(),
/// "Hello M. P. T. Acharya",
@ -35,16 +64,93 @@
/// ```
#[macro_export]
macro_rules! template {
($($item:expr),* $(,)?) => {
$crate::Template::new({
const ITEMS: &'static [$crate::Item<'static>] = &[$($item),*];
ITEMS
}, ::core::option::Option::None)
() => {
$crate::Template::new(
{
const ITEMS: &'static [$crate::Item<'static>] = &[];
ITEMS
},
::core::option::Option::None,
)
};
($($item:expr),* $(,)? ; $default:expr) => {
$crate::Template::new({
const ITEMS: &'static [$crate::Item<'static>] = &[$($item),*];
ITEMS
}, ::core::option::Option::Some($default))
($( $token:tt ),* $(,)?) => {
$crate::__template_impl!($( $token ),* ; ::core::option::Option::None)
};
($( $token:tt ),* $(,)? ; $default:expr) => {
$crate::__template_impl!($( $token ),* ; ::core::option::Option::Some($default))
};
}
#[cfg(test)]
mod tests {
use crate::{template, Item, Template};
#[test]
fn test_template2() {
assert_eq!(template!(), Template::new(&[], None),);
// Only literals
assert_eq!(template!("1"), Template::new(&[Item::Text("1")], None));
assert_eq!(
template!("1", "2"),
Template::new(&[Item::Text("1"), Item::Text("2")], None)
);
assert_eq!(
template!("1", "2", "3"),
Template::new(&[Item::Text("1"), Item::Text("2"), Item::Text("3")], None)
);
// Only keys
assert_eq!(template!({ "k1" }), Template::new(&[Item::Key("k1")], None));
assert_eq!(
template!({ "k1" }, { "k2" }),
Template::new(&[Item::Key("k1"), Item::Key("k2")], None)
);
assert_eq!(
template!({ "k1" }, { "k2" }, { "k3" }),
Template::new(&[Item::Key("k1"), Item::Key("k2"), Item::Key("k3")], None)
);
// Mixed
assert_eq!(
template!("1", { "k1" }, "3"),
Template::new(&[Item::Text("1"), Item::Key("k1"), Item::Text("3")], None)
);
assert_eq!(
template!("1", "2", { "k1" }, "3", "4"),
Template::new(
&[
Item::Text("1"),
Item::Text("2"),
Item::Key("k1"),
Item::Text("3"),
Item::Text("4")
],
None
)
);
assert_eq!(
template!("1", "2", { "k1" }, { "k2" }, "3", "4", { "k3" }),
Template::new(
&[
Item::Text("1"),
Item::Text("2"),
Item::Key("k1"),
Item::Key("k2"),
Item::Text("3"),
Item::Text("4"),
Item::Key("k3"),
],
None
)
);
}
}

View file

@ -77,7 +77,7 @@ impl<'s> Template<'s> {
#[cfg(test)]
mod test_valid {
use crate::{template, Item::*, Template};
use crate::{template, Template};
#[test]
fn empty() {
@ -88,34 +88,31 @@ mod test_valid {
#[test]
fn no_keys() {
let template = Template::parse("hello world").unwrap();
assert_eq!(template, template!(Text("hello world")));
assert_eq!(template, template!("hello world"));
}
#[test]
fn leading_key() {
let template = Template::parse("{salutation} world").unwrap();
assert_eq!(template, template!(Key("salutation"), Text(" world")));
assert_eq!(template, template!({ "salutation" }, " world"));
}
#[test]
fn trailing_key() {
let template = Template::parse("hello {name}").unwrap();
assert_eq!(template, template!(Text("hello "), Key("name")));
assert_eq!(template, template!("hello ", { "name" }));
}
#[test]
fn middle_key() {
let template = Template::parse("hello {name}!").unwrap();
assert_eq!(template, template!(Text("hello "), Key("name"), Text("!")));
assert_eq!(template, template!("hello ", { "name" }, "!"));
}
#[test]
fn middle_text() {
let template = Template::parse("{salutation} good {title}").unwrap();
assert_eq!(
template,
template!(Key("salutation"), Text(" good "), Key("title"))
);
assert_eq!(template, template!({ "salutation" }, " good ", { "title" }));
}
#[test]
@ -131,13 +128,13 @@ mod test_valid {
assert_eq!(
template,
template!(
Text("\n And if thy native country was "),
Key("ancient civilisation"),
Text(",\n What need to slight thee? Came not "),
Key("hero"),
Text(" thence,\n Who gave to "),
Key("country"),
Text(" her books and art of writing?\n "),
"\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 ",
)
);
}
@ -145,19 +142,19 @@ mod test_valid {
#[test]
fn key_no_whitespace() {
let template = Template::parse("{word}").unwrap();
assert_eq!(template, template!(Key("word")));
assert_eq!(template, template!({ "word" }));
}
#[test]
fn key_leading_whitespace() {
let template = Template::parse("{ word}").unwrap();
assert_eq!(template, template!(Key("word")));
assert_eq!(template, template!({ "word" }));
}
#[test]
fn key_trailing_whitespace() {
let template = Template::parse("{word\n}").unwrap();
assert_eq!(template, template!(Key("word")));
assert_eq!(template, template!({ "word" }));
}
#[test]
@ -168,46 +165,31 @@ mod test_valid {
}",
)
.unwrap();
assert_eq!(template, template!(Key("word")));
assert_eq!(template, template!({ "word" }));
}
#[test]
fn key_inner_whitespace() {
let template = Template::parse("{ a word }").unwrap();
assert_eq!(template, template!(Key("a word")));
assert_eq!(template, template!({ "a word" }));
}
#[test]
fn escape_left() {
let template = Template::parse(r"this \{ single left brace").unwrap();
assert_eq!(
template,
template!(Text("this "), Text("{"), Text(" single left brace"))
);
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!(Text("this "), Text("}"), Text(" single right brace"))
);
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!(
Text("these "),
Text("{"),
Text(" two "),
Text("}"),
Text(" braces")
)
);
assert_eq!(template, template!("these ", "{", " two ", "}", " braces"));
}
#[test]
@ -215,15 +197,7 @@ mod test_valid {
let template = Template::parse(r"these \{\{ four \}\} braces").unwrap();
assert_eq!(
template,
template!(
Text("these "),
Text("{"),
Text("{"),
Text(" four "),
Text("}"),
Text("}"),
Text(" braces")
)
template!("these ", "{", "{", " four ", "}", "}", " braces")
);
}
@ -232,13 +206,7 @@ mod test_valid {
let template = Template::parse(r"these \\ backslashes \\\\").unwrap();
assert_eq!(
template,
template!(
Text("these "),
Text(r"\"),
Text(" backslashes "),
Text(r"\"),
Text(r"\"),
)
template!("these ", r"\", " backslashes ", r"\", r"\",)
);
}
@ -247,16 +215,7 @@ mod test_valid {
let template = Template::parse(r"\\{ a } \{{ b } \}{ c }").unwrap();
assert_eq!(
template,
template!(
Text(r"\"),
Key("a"),
Text(" "),
Text(r"{"),
Key("b"),
Text(" "),
Text(r"}"),
Key("c"),
)
template!(r"\", { "a" }, " ", r"{", { "b" }, " ", r"}", { "c" })
);
}
@ -265,44 +224,32 @@ mod test_valid {
let template = Template::parse(r"{ a }\\ { b }\{ { c }\}").unwrap();
assert_eq!(
template,
template!(
Key("a"),
Text(r"\"),
Text(" "),
Key("b"),
Text(r"{"),
Text(" "),
Key("c"),
Text(r"}"),
)
template!({ "a" }, r"\", " ", { "b" }, r"{", " ", { "c" }, r"}")
);
}
#[test]
fn multibyte_texts() {
let template = Template::parse("幸徳 {particle} 秋水").unwrap();
assert_eq!(
template,
template!(Text("幸徳 "), Key("particle"), Text(" 秋水"))
);
assert_eq!(template, template!("幸徳 ", { "particle" }, " 秋水"));
}
#[test]
fn multibyte_key() {
let template = Template::parse("The { 連盟 }").unwrap();
assert_eq!(template, template!(Text("The "), Key("連盟")));
assert_eq!(template, template!("The ", { "連盟" }));
}
#[test]
fn multibyte_both() {
let template = Template::parse("大杉 {栄}").unwrap();
assert_eq!(template, template!(Text("大杉 "), Key("")));
assert_eq!(template, template!("大杉 ", { "" }));
}
#[test]
fn multibyte_whitespace() {
let template = Template::parse("岩佐 作{ 太 }郎").unwrap();
assert_eq!(template, template!(Text("岩佐 作"), Key(""), Text("")));
assert_eq!(template, template!("岩佐 作", { "" }, ""));
}
#[test]
@ -310,26 +257,20 @@ mod test_valid {
let template = Template::parse(r"日本\{アナキスト\}連盟").unwrap();
assert_eq!(
template,
template!(
Text("日本"),
Text(r"{"),
Text("アナキスト"),
Text(r"}"),
Text("連盟")
)
template!("日本", r"{", "アナキスト", r"}", "連盟")
);
}
#[test]
fn multibyte_rtl_text() {
let template = Template::parse("محمد صايل").unwrap();
assert_eq!(template, template!(Text("محمد صايل")));
assert_eq!(template, template!("محمد صايل"));
}
#[test]
fn multibyte_rtl_key() {
let template = Template::parse("محمد {ريشة}").unwrap();
assert_eq!(template, template!(Text("محمد "), Key("ريشة")));
assert_eq!(template, template!("محمد ", { "ريشة" }));
}
}

View file

@ -1,4 +1,4 @@
use std::{borrow::Cow, fmt::Display, io::Write, ops::Add};
use std::{borrow::Cow, fmt::Display, io::Write, ops};
use crate::{ParseError, RenderError, Values};
@ -138,17 +138,20 @@ impl<'s> Template<'s> {
Ok(String::from_utf8(buf).unwrap())
}
/// If the template contains key `key`.
pub fn has_key(&self, key: &str) -> bool {
self.has_keys(&[key])
self.has_any_of_keys(&[key])
}
pub fn has_keys(&self, keys: &[&str]) -> bool {
/// If the template contains any one of the `keys`.
pub fn has_any_of_keys(&self, keys: &[&str]) -> bool {
self.items.iter().any(|token| match token {
Item::Key(k) => keys.contains(k),
_ => false,
})
}
/// Returns all keys in this template.
pub fn keys(&self) -> impl Iterator<Item = &&str> {
self.items.iter().filter_map(|token| match token {
Item::Key(k) => Some(k),
@ -160,39 +163,116 @@ impl<'s> Template<'s> {
pub fn set_default(&mut self, default: &dyn Display) {
self.default = Some(Cow::Owned(default.to_string()));
}
/// Cast `Template<'s>` to `Template<'t>` where `'s` is a subtype of `'t`,
/// meaning that `Template<'s>` outlives `Template<'t>`.
pub fn cast<'t>(self) -> Template<'t>
where
's: 't,
{
Template {
items: match self.items {
Cow::Owned(vec) => Cow::Owned(vec),
Cow::Borrowed(slice) => Cow::Borrowed(slice as &'t [Item<'t>]),
},
default: self.default.map(|default| default as Cow<'t, str>),
}
}
}
impl<'s> Add for Template<'s> {
type Output = Self;
fn add(mut self, rhs: Self) -> Self::Output {
impl<'s, 'rhs: 's> ops::AddAssign<&Template<'rhs>> for Template<'s> {
fn add_assign(&mut self, rhs: &Template<'rhs>) {
self.items
.to_mut()
.extend(rhs.items.as_ref().iter().cloned());
if let Some(default) = &rhs.default {
self.default = Some(default.clone());
}
}
}
impl<'s, 'rhs: 's> ops::AddAssign<Template<'rhs>> for Template<'s> {
fn add_assign(&mut self, rhs: Template<'rhs>) {
match rhs.items {
Cow::Borrowed(items) => self.items.to_mut().extend(items.iter().cloned()),
Cow::Owned(items) => self.items.to_mut().extend(items.into_iter()),
}
if let Some(default) = rhs.default {
self.default = Some(default);
}
}
}
impl<'s, 'item: 's> ops::AddAssign<Item<'item>> for Template<'s> {
fn add_assign(&mut self, item: Item<'item>) {
self.items.to_mut().push(item);
}
}
impl<'s, 'item: 's> ops::AddAssign<&Item<'item>> for Template<'s> {
fn add_assign(&mut self, item: &Item<'item>) {
self.add_assign(item.clone())
}
}
impl<'s, 'rhs: 's> ops::Add<Template<'rhs>> for Template<'s> {
type Output = Self;
fn add(mut self, rhs: Template<'rhs>) -> Self::Output {
self += rhs;
self
}
}
impl<'s, 'rhs: 's> ops::Add<&Template<'rhs>> for Template<'s> {
type Output = Self;
fn add(mut self, rhs: &Template<'rhs>) -> Self::Output {
self += rhs;
self
}
}
impl<'s, 'item: 's> ops::Add<Item<'item>> for Template<'s> {
type Output = Self;
fn add(mut self, item: Item<'item>) -> Self::Output {
self += item;
self
}
}
impl<'s, 'item: 's> ops::Add<&Item<'item>> for Template<'s> {
type Output = Self;
fn add(mut self, item: &Item<'item>) -> Self::Output {
self += item;
self
}
}
#[cfg(test)]
mod test {
use crate::Item::{Key, Text};
use crate::Template;
#[test]
fn concat_templates() {
let t1 = crate::template!(Text("Hello"), Key("name"));
let t2 = crate::template!(Text("have a"), Key("adjective"), Text("day"));
let t1 = crate::template!("Hello", { "name" });
let t2 = crate::template!("have a", { "adjective" }, "day");
assert_eq!(
t1 + t2,
crate::template!(
Text("Hello"),
Key("name"),
Text("have a"),
Key("adjective"),
Text("day")
),
crate::template!("Hello", { "name" }, "have a", { "adjective" }, "day"),
);
}
#[test]
fn test_cast() {
fn inner<'a>(_: &'a u32, _: Template<'a>) {}
let template: Template<'static> = crate::template!("hello");
let i = 1;
inner(&i, template.cast());
}
}

View file

@ -5,14 +5,14 @@ use std::{
};
pub trait Values {
fn get_value<'s, 'k: 's>(&'s self, key: &'k str) -> Option<Cow<'s, str>>;
fn get_value(&self, key: &str) -> Option<Cow<'_, str>>;
}
impl<T> Values for &T
where
T: Values,
{
fn get_value<'s, 'k: 's>(&'s self, key: &'k str) -> Option<Cow<'s, str>> {
fn get_value(&self, key: &str) -> Option<Cow<'_, str>> {
T::get_value(self, key)
}
}
@ -22,7 +22,7 @@ where
K: AsRef<str>,
V: AsRef<str>,
{
fn get_value<'s, 'k: 's>(&'s self, key: &'k str) -> Option<Cow<'s, str>> {
fn get_value(&self, key: &str) -> Option<Cow<'_, str>> {
self.iter().find_map(|(k, v)| {
if k.as_ref() == key {
Some(Cow::Borrowed(v.as_ref()))
@ -38,7 +38,7 @@ where
K: AsRef<str>,
V: AsRef<str>,
{
fn get_value<'s, 'k: 's>(&'s self, key: &'k str) -> Option<Cow<'s, str>> {
fn get_value(&self, key: &str) -> Option<Cow<'_, str>> {
(*self).get_value(key)
}
}
@ -48,7 +48,7 @@ where
K: AsRef<str>,
V: AsRef<str>,
{
fn get_value<'s, 'k: 's>(&'s self, key: &'k str) -> Option<Cow<'s, str>> {
fn get_value(&self, key: &str) -> Option<Cow<'_, str>> {
self.as_slice().get_value(key)
}
}
@ -58,7 +58,7 @@ where
K: AsRef<str>,
V: AsRef<str>,
{
fn get_value<'s, 'k: 's>(&'s self, key: &'k str) -> Option<Cow<'s, str>> {
fn get_value(&self, key: &str) -> Option<Cow<'_, str>> {
self.as_slice().get_value(key)
}
}
@ -69,7 +69,7 @@ where
V: AsRef<str>,
S: BuildHasher,
{
fn get_value<'s, 'k: 's>(&'s self, key: &'k str) -> Option<Cow<'s, str>> {
fn get_value(&self, key: &str) -> Option<Cow<'_, str>> {
self.get(key).map(|v| Cow::Borrowed(v.as_ref()))
}
}
@ -79,7 +79,7 @@ where
K: Borrow<str> + Ord,
V: AsRef<str>,
{
fn get_value<'s, 'k: 's>(&'s self, key: &'k str) -> Option<Cow<'s, str>> {
fn get_value(&self, key: &str) -> Option<Cow<'_, str>> {
self.get(key).map(|v| Cow::Borrowed(v.as_ref()))
}
}
@ -87,25 +87,22 @@ where
/// Workaround to allow using functions as [`Values`].
///
/// As this isn't constructible you'll want to use [`vals()`] instead.
pub struct ValuesFn<F>
where
F: for<'s> Fn(&'s str) -> Option<Cow<'s, str>> + Send + 'static,
{
pub struct ValuesFn<F> {
inner: F,
}
impl<F> Values for ValuesFn<F>
impl<'s, F> Values for &'s ValuesFn<F>
where
F: for<'s> Fn(&'s str) -> Option<Cow<'s, str>> + Send + 'static,
F: Fn(&str) -> Option<Cow<'s, str>> + 's,
{
fn get_value<'s, 'k: 's>(&'s self, key: &'k str) -> Option<Cow<'s, str>> {
fn get_value(&self, key: &str) -> Option<Cow<'_, str>> {
(self.inner)(key)
}
}
impl<F> From<F> for ValuesFn<F>
impl<'f, F> From<F> for ValuesFn<F>
where
F: for<'s> Fn(&'s str) -> Option<Cow<'s, str>> + Send + 'static,
F: Fn(&str) -> Option<Cow<'f, str>> + 'f,
{
fn from(inner: F) -> Self {
Self { inner }
@ -123,11 +120,11 @@ where
///
/// fn use_values(_values: impl Values) {}
///
/// use_values(vals(|_| Some("hello".into())));
/// use_values(&vals(|_| Some("hello".into())));
/// ```
pub const fn vals<F>(func: F) -> ValuesFn<F>
pub const fn vals<'f, F>(func: F) -> ValuesFn<F>
where
F: for<'s> Fn(&'s str) -> Option<Cow<'s, str>> + Send + 'static,
F: Fn(&str) -> Option<Cow<'f, str>> + 'f,
{
ValuesFn { inner: func }
}