Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Join Vector<Html<T>> with separator #85

Open
OskarPersson opened this issue Jul 14, 2020 · 3 comments
Open

Join Vector<Html<T>> with separator #85

OskarPersson opened this issue Jul 14, 2020 · 3 comments

Comments

@OskarPersson
Copy link

How can I join a vector of html strings with a custom separator? With for exampe Vec<String> I would use join but this isn't available for Html. This is what I've tried:

   |
16 | (users.into_iter().map(|u| Html(format!("<a href=\"example.com/{}\">{}</a>", u.name, u.name))).collect::<Vec<Html<String>>>().join(",")).to_html(&mut _ructe_out_)?;
   |                                                                                                                               ^^^^ method not found in `std::vec::Vec<templates::Html<std::string::String>>`
   |
   = note: the method `join` exists but the following trait bounds were not satisfied:
           `<[templates::Html<std::string::String>] as std::slice::Join<_>>::Output = _`
@OskarPersson
Copy link
Author

OskarPersson commented Jul 14, 2020

Extracting the Html call outside the map and operate on the complete string instead works as long as none of the variables contains data that could be interpreted as html tags

@(Html(users.into_iter().map(|u| format!("<a href=\"example.com/{}\">{}</a>", u.name, u.name)).collect::<Vec<String>>().join(", ")))

@kaj
Copy link
Owner

kaj commented Jul 15, 2020

Here's a bunch of code that may be useful.

I'm thinking about adding something like this HtmlJoiner helper struct to ructe, but I'm not sure about the api.

use crate::templates::{Html, ToHtml};

struct HtmlJoiner<
    Item: ToHtml,
    Items: Iterator<Item = Item> + Clone,
    Sep: ToHtml,
> {
    items: Items,
    sep: Sep,
}

impl<Item: ToHtml, Items: Iterator<Item = Item> + Clone, Sep: ToHtml> ToHtml
    for HtmlJoiner<Item, Items, Sep>
{
    fn to_html(&self, out: &mut dyn Write) -> io::Result<()> {
        let mut iter = self.items.clone();
        if let Some(first) = iter.next() {
            first.to_html(out)?;
        } else {
            return Ok(());
        }
        for item in iter {
            self.sep.to_html(out)?;
            item.to_html(out)?;
        }
        Ok(())
    }
}

fn to_buffer(content: impl ToHtml) -> Result<Vec<u8>, io::Error> {
    let mut buf = Vec::new();
    content.to_html(&mut buf)?;
    Ok(buf)
}

#[test]
fn test_html_joiner() {
    assert_eq!(
        to_buffer(HtmlJoiner {
            items: ["foo", "b<a", "baz"].iter(),
            sep: ", ",
        })
        .unwrap(),
        b"foo, b&lt;a, baz"
    )
}

#[test]
fn test_html_joiner_empty() {
    use std::iter::empty;
    assert_eq!(
        to_buffer(HtmlJoiner {
            items: empty::<&str>(),
            sep: ", ",
        })
        .unwrap(),
        b""
    )
}

#[test]
fn test_html_joiner_mark() {
    assert_eq!(
        to_buffer(HtmlJoiner {
            items: ["foo", "b<a", "baz"].iter(),
            sep: Html("<br/>"),
        })
        .unwrap(),
        b"foo<br/>b&lt;a<br/>baz"
    )
}

In theory, it should be possible to provide a function join_html on a trait implemented for iterators over ToHtml, returning something like the HtmlJoiner above. That would make code like the following possible. But I have to think about how to provide the join_html function. Also, this would still allocate a String for each user in the format! macro.

    users.into_iter()
        .map(|u| Html(format!("<a href=\"example.com/{}\">{}</a>", u.name, u.name)))
        .join_html(", ")
        .to_html(&mut out)?;

If the html snippet is written inside a separate user_template.rs.html, then something like this would be possible:

    users.into_iter()
        .join_html(user_template, ", ")
        .to_html(&mut out)?;

Ideally, the syntax used should also be convenient in a template, where it should be possible to provide user_template inline, though I'm not sure about the parameter passing then.

In summary; yes, this is an area where ructe could use some improvement. :-)

@kaj
Copy link
Owner

kaj commented Jul 16, 2020

I went ahead and wrote a PR. Could you, @OskarPersson , check if it (the branch join-html) is usable in your context? Do you have any input on the questions on the PR?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

2 participants