Generating a menu

230 views
Skip to first unread message

Vincent Beffara

unread,
Jan 28, 2014, 2:10:43 PM1/28/14
to hak...@googlegroups.com

Hi,

Still converting my page to hakyll, I would like to have a menu in each
page, listing all the pages. I cooked up a solution but it feels ugly:
first pass to list the pages:

> match "pages/*.md" $ version "raw" $ do
> route $ gsubRoute "pages/" (const "") `composeRoutes` setExtension ""
> compile getResourceBody

Build a context with the list in it:

> let postCtx = dateField "date" "%B %e, %Y" `mappend`
> listField "pages" defaultContext (loadAll $ "pages/*.md" .&&. hasVersion "raw") `mappend`
> defaultContext

Then compile the pages a second time with this context, this time with
extension html, and use the list in the template:

> <ul>
> $for(pages)$
> <li><a href="$url$.html">$title$</a></li>
> $endfor$
> </ul>

while adding the .html extension by hand. That works but obviously I
also end up with extension-less files in _site, created by the first
pass. I tried with snapshots but I get the complaint about circular
dependencies; I looked at http://sigkill.dk/writings/guides/hakyll.html
which is nice but uses Hakyll 3 and a "group" feature which doesn't
exist anymore.

Am I missing something? What is the right way to do that?

Thanks,

/vincent

Dan Frumin

unread,
Jan 29, 2014, 6:10:48 AM1/29/14
to hak...@googlegroups.com
Hi! I use a similar trick for my site, I overwrite the "raw" version though (it's called "forListing" in my code)

http://hub.darcs.net/co-dan/website/browse/site.hs#80
> --
> You received this message because you are subscribed to the Google Groups "hakyll" group.
> To unsubscribe from this group and stop receiving emails from it, send an email to hakyll+un...@googlegroups.com.
> For more options, visit https://groups.google.com/groups/opt_out.

Vincent Beffara

unread,
Jan 30, 2014, 8:12:12 AM1/30/14
to hak...@googlegroups.com

Hello,

> I use a similar trick for my site, I overwrite the "raw" version
> though (it's called "forListing" in my code)

It would be nice to have a function like getResourceMetadata that gives
access to just the meta data without counting as a dependency (only
$body$ really introduces cycles). Or perhaps rather loadAllMetadata.
Would there be any way to implement that without changing the library
too deeply?

Dummy targets feel too much like a hack ...

/v
--
| | UMPA - ENS Lyon | Mél: vbef...@ens-lyon.fr |
| Vincent Beffara | 46 allée d'Italie | Tél: (+33) 4 72 72 85 25 |
| | 69364 Lyon Cedex 07 | Fax: (+33) 4 72 72 84 80 |

Jasper Van der Jeugt

unread,
Feb 3, 2014, 5:42:55 AM2/3/14
to hak...@googlegroups.com
Hey Vincent,

What function are you using to access the metadata? `getMetadata` introduces a dependency as in that it causes additional recompilation, but it should never create a cycle.

You can have file A and B both relying on the other file's metadata. This means that when you change A, both A and B will be rebuild. However, as long as you don't use `load`, `loadBody`, or other functions like that, there should not be a cyclic dependency error. If there is, it might be a bug in Hakyll at which I'll have to look.

Peace,
Jasper

Vincent Beffara

unread,
Feb 3, 2014, 8:12:33 AM2/3/14
to hak...@googlegroups.com

Hi,

> What function are you using to access the metadata? `getMetadata`
> introduces a dependency as in that it causes additional recompilation,
> but it should never create a cycle.

I am building a list without looking at the contents at all ... The code
is at

https://github.com/vbeffara/web-hakyll

(site.hs around "pages/*" $ version "map"). One advantage to the
ugliness it to have access to the basename automatically.

BTW I have changed a few things since my previous e-mail, mainly using
makeItem "" so the problem might have vanished, I will try again at some
point.

Hakyll is really enjoyable to hack with :-)

Best,

/v

vbef...@gmail.com

unread,
May 21, 2014, 9:46:42 AM5/21/14
to hak...@googlegroups.com
(A few months later ...)

Hi,

Coming back to the question - with your solution, how sure are you that the forListing version is run first and the actual one second, overwriting it, and not the opposite ? Is the fact that the "forListing" versions are used in the main ones enough to guarantee that the overwriting happens in the correct order ?

   /vincent

Jasper Van der Jeugt

unread,
Jun 13, 2014, 5:01:12 AM6/13/14
to hak...@googlegroups.com
Hey Vincent,

Yes, if page A depends on page B, page B will always be generated first.

However, looking at the source code of the site, I think:

-- Version for providing only metadata
match "pages/*.md" $ version "forListing" $ do
route $ composeRoutes (gsubRoute "pages/" (const ""))
(setExtension ".html")
compile $ getResourceString

Can be changed to:

-- Version for providing only metadata
match "pages/*.md" $ version "forListing" $ do
compile $ getResourceString

You don't need to route it somewhere if you're going to overwrite it
anyway?

Peace,
Jasper

On Wed, May 21, 2014 at 06:46:42AM -0700, vbef...@gmail.com wrote:
> (A few months later ...)
>
> Hi,
>
> Coming back to the question - with your solution, how sure are you that the
> forListing version is run first and the actual one second, overwriting it,
> and not the opposite ? Is the fact that the "forListing" versions are used
> in the main ones enough to guarantee that the overwriting happens in the
> correct order ?
>
> /vincent
>
> On Wednesday, January 29, 2014 12:10:48 PM UTC+1, Daniel F wrote:
> >
> > Hi! I use a similar trick for my site, I overwrite the "raw" version
> > though (it's called "forListing" in my code)
> >
> > http://hub.darcs.net/co-dan/website/browse/site.hs#80
> >
> > > On 28 Jan 2014, at 23:10, Vincent Beffara <vbef...@ens-lyon.fr<javascript:>>
> > an email to hakyll+un...@googlegroups.com <javascript:>.
> > > For more options, visit https://groups.google.com/groups/opt_out.
> >
>
> --
> You received this message because you are subscribed to the Google Groups "hakyll" group.
> To unsubscribe from this group and stop receiving emails from it, send an email to hakyll+un...@googlegroups.com.
> For more options, visit https://groups.google.com/d/optout.

Vincent Beffara

unread,
Jun 29, 2014, 4:26:56 AM6/29/14
to hak...@googlegroups.com

Hi,

Thanks for your reply!

> Yes, if page A depends on page B, page B will always be generated first.

Nice to know, although I am still a bit worried by the fact that on
update, B would be regenerated but then A wouldn't because it would not
be seen as out of date, and if this happens then the output would be
wrong. Trying it out anyway ...

> However, looking at the source code of the site, I think:
>
> -- Version for providing only metadata
> match "pages/*.md" $ version "forListing" $ do
> route $ composeRoutes (gsubRoute "pages/" (const ""))
> (setExtension ".html")
> compile $ getResourceString
>
> Can be changed to:
>
> -- Version for providing only metadata
> match "pages/*.md" $ version "forListing" $ do
> compile $ getResourceString
>
> You don't need to route it somewhere if you're going to overwrite it
> anyway?

The use case it that I want to generate a navigation menu, so even if I
am going to overwrite the generated page I still need to know what to
put in the link target. In fact I would almost rather route the page
without compiling it. Here is the code, in case I am missing another
solution:

https://github.com/vbeffara/web-hakyll/blob/master/site.hs

Best,

/vincent

Игорь Пашев

unread,
Oct 8, 2019, 10:26:08 AM10/8/19
to hakyll
Hello there. Here is my approach:

Let's have `01-foo.html`, `02-bar.html`, and so on.

Template:
```
      <nav><ul>
          $for(menu)$<li $if(active)$class="current"$endif$"><a href="$url$">$title$</a></li>$endfor$
      </ul></nav>
```

Essential rules:

```haskell
rules :: Rules ()
rules = do
  match "*.html" $
    version "menu" $ do
      route (gsubRoute "[0-9]+-" (const ""))
      compile $ makeItem ("" :: String)
  match "*.html" $ do
    route (gsubRoute "[0-9]+-" (const ""))
    compile $ do
      pages <- loadAll ("*.html" .&&. hasVersion "menu")
      this <- toFilePath <$> getUnderlying
      let active = boolField "active" ((== this) . toFilePath . itemIdentifier)
          pageContext =
            listField "menu" (active <> defaultContext) (return pages) <>
            defaultContext
      getResourceBody >>=
        loadAndApplyTemplate "templates/default.html" pageContext >>=
        relativizeUrls
  match "templates/*" $ compile templateBodyCompiler
```

Reply all
Reply to author
Forward
0 new messages