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

Combining templates with fold/match -> no two closures have the same type. #16

Closed
WillemMali opened this issue Jul 19, 2017 · 3 comments

Comments

@WillemMali
Copy link

I want to fold some Horrorshow templates, I'm using Horrorshow to make directory listings. However, I'm having a hard time understanding how to make this work, because I can't match on the output of html! and I can't fold over them either.

I'm sorry for the glorified support request, I'll try to rework your advice into a pull-request which adds examples to the documentation if you like.

Here's the code I want to write, but the only way to make it work I have found is passing around the rendered HTML by calling .into_string().unwrap() on the html! output every time.

(Sorry for the large blurb of code, the first map is largely irrelevant but I thought it would be useful for context.)

  fn list_directories_recursive(&self, path: &Path) -> Option<String> {
        let listings = path
        .read_dir().unwrap()
        .filter_map(|de| de.ok())         // remove invalid paths
        .map(|de| de.path())              // DirectoryEntry -> Path
        .filter(|p| self.visible_path(p)) // remove invisible paths
        .map(|path| {
            if let &Some(href) = &path.as_os_str().to_str() {
                if let &Some(file_name) = &path.file_name() {
                    if let Some(name) = file_name.to_str() {
                        let href = href.replacen(".", "", 1); // ./thing -> /thing
                        let name = name.replacen("/", "", 1); // ./dir/thing -> thing

                        return Some((path, href, name));
                    }
                }
            }

            None
        })
        .filter_map(|s| s)               // remove empty/non-UTF directories
        .map(|(path, href, name)| {      // Path -> String
            html! {
                @if self.list_recursively && path.is_dir() {
                    @if let Some(sub) = self.list_directories_recursive(&path) {
                        : sub
                    }
                }
                tr {
                    td {
                        a(href=&href) {
                            : name
                        }
                    }
                }
            }
        })
        .fold(html! {}, |acc, html| html! {
            : acc;
            : html;
        })
        .into_string().unwrap();

        if listings.len() != 0 {
            Some(listings)
        } else {
            None
        }
@Stebalien
Copy link
Owner

First off, I apologize for completely missing this issue; github occasionally drops email notifications and I rely on them completely 😞. In case you're still interested...

One solution is to use the html_box! macro, this will Box the resulting template so you should always get the same type out.

I wrote a template! macro for creating named templates for cases like this (where you want something with a fixed type) however, it currently borrows it's the captured variables so that won't help here either.

Personally, I recommend inverting this and not using map/fold:

fn list_directories_recursive(&self, path: &Path) -> Option<String> {
    let entries = path.read_dir().unwrap() 
        .filter_map(|de| de.ok())         // remove invalid paths
        .map(|de| de.path())              // DirectoryEntry -> Path
        .filter(|p| self.visible_path(p)) // remove invisible paths
        .map(|path| {
            let (href, name) = match (
                path.as_os_str().to_str(),
                path.file_name().and_then(|f|f.to_str())
            ) {
                (Some(href), Some(name)) => {
                    let href = href.replacen(".", "", 1); // ./thing -> /thing
                    let name = name.replacen("/", "", 1); // ./dir/thing -> thing
                    (href, name)
                }
                _ => return None,
            };
            return Some((path, href, name))
        })
        .filter_map(|s| s);

    let listings = (html! {
        @ for (path, href, name) in entries {
            @ if self.list_recursively && path.is_dir() {
                // No need to check if None. None renders as ""
                : self.list_directories_recursive(&path)
            }
            tr {
                td {
                    a(href=&href) : name
                }
            }
        }
    }).into_string().unwrap();

    if listings.len() != 0 {
        Some(listings)
    } else {
        None
    }
}

The other way to do this is an embedded closure:

fn list_directories_recursive(&self, path: &Path) -> Option<String> {
    let listings = (html! {
        |tmpl| path.read_dir().unwrap() 
            .filter_map(|de| de.ok())         // remove invalid paths
            .map(|de| de.path())              // DirectoryEntry -> Path
            .filter(|p| self.visible_path(p)) // remove invisible paths
            .map(|path| {
                let (href, name) = match (
                    path.as_os_str().to_str(),
                    path.file_name().and_then(|f|f.to_str())
                ) {
                    (Some(href), Some(name)) => {
                        let href = href.replacen(".", "", 1); // ./thing -> /thing
                        let name = name.replacen("/", "", 1); // ./dir/thing -> thing
                        (href, name)
                    }
                    _ => return None,
                };
                return Some((path, href, name))
            })
            .filter_map(|s| s)
            .for_each(|(path, href, name)| &mut *tmpl << html! {
                @ if self.list_recursively && path.is_dir() {
                    // No need to check if None. None renders as ""
                    : self.list_directories_recursive(&path)
                }
                tr {
                    td {
                        a(href=&href) : name
                    }
                }
            })
    }).into_string().unwrap();

    if listings.len() != 0 {
        Some(listings)
    } else {
        None
    }
}

but I prefer the first way.

@Stebalien
Copy link
Owner

Stebalien commented Oct 25, 2017

Actually, you could also use the following to avoid turning any intermediate templates into strings:

template! {
    // Where Context is the "Self" type.
    ListDirectories(ctx: &Context, path: &Path) {
        |tmpl| path.read_dir().unwrap() 
            .filter_map(|de| de.ok())         // remove invalid paths
            .map(|de| de.path())              // DirectoryEntry -> Path
            .filter(|p| ctx.visible_path(p))  // remove invisible paths
            .map(|path| {
                let (href, name) = match (
                    path.as_os_str().to_str(),
                    path.file_name().and_then(|f|f.to_str())
                ) {
                    (Some(href), Some(name)) => {
                        let href = href.replacen(".", "", 1); // ./thing -> /thing
                        let name = name.replacen("/", "", 1); // ./dir/thing -> thing
                        (href, name)
                    }
                    _ => return None,
                };
                return Some((path, href, name))
            })
            .filter_map(|s| s)
            .for_each(|(path, href, name)| &mut *tmpl << html! {
                @ if ctx.list_recursively && path.is_dir() {
                    : ListDirectories::new(ctx, &path)
                }
                tr {
                    td {
                        a(href=&href) : name
                    }
                }
            })
    }
}

You can use this by, e.g., calling ListDirectories::new(ctx, some_path).into_string().unwrap().

@Stebalien
Copy link
Owner

Closing as there's really nothing I can do about this.

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

No branches or pull requests

2 participants