Supporting "Published: false"

48 views
Skip to first unread message

Florian Hars

unread,
Oct 3, 2011, 2:38:15 PM10/3/11
to hak...@googlegroups.com
Since www.thewml.org is getting long in the tooth, I decided
to evaluate some other options (and try to see if I remember
any haskell while doing so). So I thought the the recent question
on this list about support for a "published: false" mechanism
might be a nice way to get a feeling for hakyll, since it
ought to be relatively straight forward. For the experiments, I
started with the simpleblog from the examples. Sorry if the
indentation is not idiomatic, I haven't written any haskell in
years.

Checking if a page has a published attribute is of course
trivial:

> isPublished :: Page a -> Bool
> isPublished p =
> let published = getField "published" p in
> published /= "" && published /= "false"

Getting rid of non-published pages from the overviews (index.html
and posts.html) is then easy, we only have to filter them out
of the lists of pages produced by requireAllA with a custom
compiler:

> filterPublished :: Compiler (Page String, [Page String]) (Page String, [Page String])
> filterPublished = id *** arr (filter isPublished)

All that is then needed is to chain that compiler with addPostList,
for posts.html the line changes to

> >>> requireAllA "posts/*" (filterPublished >>> addPostList)

and for index.html:

> >>> requireAllA "posts/*" (filterPublished >>> (id *** arr (take 3 . reverse . sortByBaseName)) >>> addPostList)

This will suppress the links to the unpublished pages, but
will still generate the pages themselves. So we have to somehow
tag the pages we do not want to generate. "Either" sounds like
an appropriate choice:

> isPublishedPage :: Compiler (Page String) (Either (Page String) (Page String))
> isPublishedPage = arr (\p -> if isPublished p then Right p else Left p)

Of course, just tacking

> >>> isPublishedPage

at the end of the match "posts/*" $ do ... compiler leads to a type
error, as Either (Page String) (Page String) is not an instance of
Writable. But that is easily amended:

> instance Writable b => Writable (Either a b) where
> write p (Right b) = write p b
> write _ _ = return ()

Problem solved, except that I now get a runtime type error (!?)
when generating index.html and posts.html. That is solved by changing
filterPublished:

> import Data.Either (rights)
...
> filterPublished :: Compiler (Page String, [Either (Page String) (Page String)]) (Page String, [Page String])
> filterPublished = id *** arr rights

Phew.

Of course, this is still slightly inefficient, as it builds
the whole page before discarding it at the end. That can be overcome
by importing a few more methods from Control.Arrow. This will
not apply templates to unpublished pages:

> match "posts/*" $ do
> route $ setExtension ".html"
> compile $ pageCompiler
> >>> isPublishedPage
> >>> (id +++ (applyTemplateCompiler "templates/post.html"
> >>> applyTemplateCompiler "templates/default.html"
> >>> relativizeUrlsCompiler))

If you keep the original definition of filterPublished, you might instead
think about doing

> match "posts/*" $ do
> route $ setExtension ".html"
> compile $ pageCompiler
> >>> isPublishedPage
> >>> (applyTemplateCompiler "templates/embargo-post.html" ||| applyTemplateCompiler "templates/post.html")
> >>> applyTemplateCompiler "templates/default.html"
> >>> relativizeUrlsCompiler

so that the unpublished articles will be generated with another
compiler but not included in the lists. Or whatever you want.
Jay for compositionality.

- Florian.

Jasper Van der Jeugt

unread,
Oct 4, 2011, 7:15:14 AM10/4/11
to hak...@googlegroups.com
Awesome, thanks for sharing this, Florian! Would you mind if I turned
this into a tutorial for the hakyll website? Or would you like to do
this yourself?

Cheers,
Jasper

Florian Hars

unread,
Oct 4, 2011, 12:04:58 PM10/4/11
to hak...@googlegroups.com
On Tue, Oct 04, 2011 at 01:15:14PM +0200, Jasper Van der Jeugt wrote:
> Would you mind if I turned this into a tutorial for the hakyll
> website? Or would you like to do this yourself?

Yes, I think I could do that, just fork and send a pull
request?
I have some additional ideas, like timed releases, is there a
canonical way to get at the start time of the current build job,
or should I just use

unsafeCompiler (\_ -> getCurrentTime)

- Florian

Jasper Van der Jeugt

unread,
Oct 5, 2011, 10:27:29 AM10/5/11
to hak...@googlegroups.com
Hey,

Yes, pull requests are always welcome! :-)

Your code for getting the start of the current build job will work,
but I agree that it's a bit ugly, and it might not perfectly indicate
the start of the build job, due to laziness. I could add a canonical
way to get the time, if you have a use case which requires it.

Cheers,
Jasper

Reply all
Reply to author
Forward
0 new messages