gohugoio / hugo

The world’s fastest framework for building websites.
https://gohugo.io
Apache License 2.0
75.86k stars 7.54k forks source link

add site.GetPages, .GetPages #8273

Open regisphilibert opened 3 years ago

regisphilibert commented 3 years ago

Current status

This is very common for a Front Matter fields to hold a list of file paths to content files in order to establish a one to many relationships between pages or settings.

Currently one has to define its own solution using range and site.GetPage and append it to a slice. This might not be very efficient when dealing with hundreds of pages.

Solution

If easily implemented and more efficient than the user defined solution Hugo could sport its own methods to retrieve a collection based on a slice of file paths with site.GetPages and .Page.GetPages.

Example

title: Famous blog post
related_posts:
- great-article.md
- another-good-idea.md
- keep-going.md
related_pages:
- /product/reminder.md
- /contact.md
{{ with .Params.related_posts }}
  {{ with $.Parent.GetPages . }}
    <h3> Related Posts </h3>
    {{ range . }}
    <a href="{{ .RelPermalink" }}>{{ .Title }}</a>
    {{ end }}
  {{ end }}
{{ end }}

{{ with $.Params.related_pages }}
  {{ with site.GetPages . }}
    <h3> See also </h3>
    {{ range . }}
    <a href="{{ .RelPermalink" }}>{{ .Title }}</a>
    {{ end }}
  {{ end }}
{{ end }}
bep commented 3 years ago

So, while I do agree that we need something like this, I don't think we want to add yet another thing that we need to teach people.

Starting from a page, we already have some built-in one-to-many relations:

If you were to treat the above as named relationships (or named joins, naming is hard ...) then the above would be the same as:

Note that the naming above is just a first suggestion ...

So, if we can figure a way to express new relationships, and possibly also extend existing relationships (add to the default .Resources collection), so you would do:

{{ $seeAlso := .PagesByRelation "see_also" }}

We could probably also overload the existing .Pages method:

{{ $seeAlso := .Pages "see_also" }}

That way, both of the below would be the same:

{{ $pages1 := .Pages "_pages" }}
{{ $pages2 := .Pages }}
onedrawingperday commented 3 years ago

{{ $seeAlso := .Pages "see_also" }}

I prefer the addition of an extra argument to the existing .Pages method.

regisphilibert commented 3 years ago

So, if we can figure a way to express new relationships

From a CMS standpoint it has to be a reference of either a Front Matter value or the file. Most common usage is to use an array of paths (which also allows control of the sorting of the passed pages). Hence me wanting something people can associate with .GetPage as its path argument is well known among Hugo users.

I don't think we want to add yet another thing that we need to teach people.

It actually very easy to teach. They will intuitively understand that a method named .GetPages is expecting paths like its singular counterpart .GetPage. It's used exaclty as the .GetPage with the exception of taking a slice instead of a string. Also we wouldn't need to figure a new way to express relationships. We'll use the solution already in place on many Hugo Projects, but deliver a better way of retrieving those pages.

regisphilibert commented 2 years ago

{{ $seeAlso := .Pages "see_also" }}

I prefer the addition of an extra argument to the existing .Pages method.

But then you would need to teach user what that second argument is used for. I guess it matches the key which stores the paths... but it's not clear to me.

Documentation-wise, adding .GetPages with an example to the page/site methods after .GetPage and its example requires no teaching at all. One take a single string parameter, the other one an array of strings.

For me .Pages, .RegularPages is retrieving the children pages of the object (.Page or .Site), I don't really think it should be changed in any way which would look limited to "establishing relationships".

Yes what I'm proposing could consequentially become a great solution for handling one to many relationships. But the main goal of this ticket is to add a built-in solution to retrieve multiple pages in a given order, relationships or otherwise.

As this is currently cumbersome and resourceful as illustrated below

{{ $related := slice }}
{{ with .Params.related_posts }}
  {{ range . }}
    {{ with site.GetPage }}
      {{ $related = $related | append . }}
     {{ end}} 
  {{ end }}
{{ end }}
{{ return $related }}

VS:

{{ $related := slice }}
{{ with .Params.related_posts }}
  {{ with site.GetPages . }}
    {{ $related = . }}
  {{ end }}
{{ end }}
{{ return $related }}
jmooring commented 2 years ago

@regisphilibert

With this front matter:

+++
title = 'Foo'
date = 2022-08-31T14:32:30-07:00
draft = false
related_pages = ['post-3', 'post-1', 'post-5']
+++

You can do this:

{{ range apply .Params.related_pages "site.GetPage" "." }}
  <a href="{{ .RelPermalink }}">{{ .LinkTitle }}</a>
{{ end }}

To produce this:

<a href="/post/post-3/">Post 3</a>
<a href="/post/post-1/">Post 1</a>
<a href="/post/post-5/">Post 5</a>

Note that the original slice order is retained.

regisphilibert commented 2 years ago

yes of course... Thanks for pointing that out. I wonder if it fares better "build time"-wise.

I'll try it out!

jmooring commented 2 years ago

I wonder if it fares better "build time"-wise.

With .Params.related_pages containing 999 elements:

{{ range .Params.related_pages }}
  {{ with site.GetPage . }}
    <a href="{{ .RelPermalink }}">{{ .LinkTitle }}</a>
  {{ end }}
{{ end }}

392 ms (average of 5 runs)

{{ range apply .Params.related_pages "site.GetPage" "." }}
  <a href="{{ .RelPermalink }}">{{ .LinkTitle }}</a>
{{ end }}

403 ms (average of 5 runs)

The 3% difference is meaningless, within the margin of measurement error. I ran the same tests again, and this time the second method was faster.