pencil 1.0.0 → 1.0.1
raw patch · 47 files changed
+1564/−2 lines, 47 filesdep ~hashablebinary-addedPVP ok
version bump matches the API change (PVP)
Dependency ranges changed: hashable
API changes (from Hackage documentation)
Files
- CHANGELOG.md +7/−1
- examples/Blog/site/assets/style.scss +10/−0
- examples/Blog/site/rss.xml +18/−0
- examples/Complex/site/assets/conversions/convert.markdown +5/−0
- examples/Complex/site/assets/conversions/convert.scss +5/−0
- examples/Complex/site/assets/conversions/giraffe.jpg binary
- examples/Complex/site/assets/example.markdown +3/−0
- examples/Complex/site/assets/fun/fun.markdown +5/−0
- examples/Complex/site/assets/fun/test.pdf binary
- examples/Complex/site/assets/fun/zanzibar.jpg binary
- examples/Complex/site/assets/giraffe.jpg binary
- examples/Complex/site/assets/passthroughs/giraffe.jpg binary
- examples/Complex/site/assets/passthroughs/pass.markdown +3/−0
- examples/Complex/site/assets/passthroughs/pass.scss +5/−0
- examples/Complex/site/assets/passthroughs/test.pdf binary
- examples/Complex/site/assets/style.scss +21/−0
- examples/Complex/site/blog/2018-01-30-code-related-stuff.markdown +19/−0
- examples/Complex/site/blog/2018-01-31-crazy-markdown.markdown +13/−0
- examples/Complex/site/blog/2018-02-01-variables.markdown +25/−0
- examples/Complex/site/deep/deep1.md +7/−0
- examples/Complex/site/deep/deep2.html +7/−0
- examples/Complex/site/deep/deep3.markdown +7/−0
- examples/Complex/site/deep/deep4a.markdown +8/−0
- examples/Complex/site/deep/deep4b.md +5/−0
- examples/Complex/site/deep/deep5.html +5/−0
- examples/Complex/site/index.html +9/−0
- examples/Complex/site/layout.html +19/−0
- examples/Complex/site/post-bullet.html +3/−0
- examples/Complex/site/post-layout.html +19/−0
- examples/Complex/site/post-list.html +5/−0
- examples/Complex/site/rss.xml +18/−0
- examples/Complex/site/tag-list.html +3/−0
- examples/Docs/site/default.scss +145/−0
- examples/Docs/site/guides/environment.markdown +69/−0
- examples/Docs/site/guides/images/structure-collection.png binary
- examples/Docs/site/guides/images/structure.png binary
- examples/Docs/site/guides/pages-and-structures.markdown +144/−0
- examples/Docs/site/guides/static-assets.markdown +30/−0
- examples/Docs/site/guides/templates.markdown +93/−0
- examples/Docs/site/index.markdown +29/−0
- examples/Docs/site/layout.html +72/−0
- examples/Docs/site/tutorials/01-getting-started.markdown +285/−0
- examples/Docs/site/tutorials/02-deploying-to-github-pages-using-circle.markdown +111/−0
- examples/Docs/site/tutorials/03-blogging.markdown +303/−0
- examples/Docs/site/tutorials/images/tutorial-1-github-base-branch.png binary
- examples/Docs/site/tutorials/images/tutorial-1-github-settings.png binary
- pencil.cabal +29/−1
CHANGELOG.md view
@@ -4,6 +4,11 @@ ## [Unreleased] +## [1.0.1]++Tests were failing from the sdist distribution due to missing files in+`extra-source-files`. Add them.+ ## [1.0.0] This is a milestone release! Version 1.0.0. Several breaking changes, but if you@@ -94,7 +99,8 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/). -[Unreleased]: https://github.com/elben/pencil/compare/v1.0.0...HEAD+[Unreleased]: https://github.com/elben/pencil/compare/v1.0.1...HEAD+[1.0.1]: https://github.com/elben/pencil/compare/v1.0.0...v1.0.1 [1.0.0]: https://github.com/elben/pencil/compare/v0.1.3...v1.0.0 [0.1.3]: https://github.com/elben/pencil/compare/v0.1.2...v0.1.3 [0.1.2]: https://github.com/elben/pencil/compare/v0.1.1...v0.1.2
+ examples/Blog/site/assets/style.scss view
@@ -0,0 +1,10 @@+body {+ font-family: sans-serif;+ font-size: 16px;+}++.structure {+ margin-left: auto;+ margin-right: auto;+ width: 600px;+}
+ examples/Blog/site/rss.xml view
@@ -0,0 +1,18 @@+<?xml version="1.0" encoding="UTF-8"?>+<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">+<channel>+ <title>Example RSS Feed</title>+ <link>http://example.com/rss.xml</link>+ <description>An example RSS feed.</description>++ ${for(posts)}+ <item>+ <link>https://example.com${this.url}</link>+ <guid>https://example.com${this.url}</guid>+ <pubDate>${date}</pubDate>+ <title>${postTitle}</title>+ <description>${this.content}</description>+ </item>+ ${end}+</channel>+</rss>
+ examples/Complex/site/assets/conversions/convert.markdown view
@@ -0,0 +1,5 @@+# Convert++This is an example Markdown file.++We want this one to be converted to an HTML file.
+ examples/Complex/site/assets/conversions/convert.scss view
@@ -0,0 +1,5 @@+$red: #ff0000;++.red {+ color: $red;+}
+ examples/Complex/site/assets/conversions/giraffe.jpg view
binary file changed (absent → 18541 bytes)
+ examples/Complex/site/assets/example.markdown view
@@ -0,0 +1,3 @@+# Example Markdown++This is an example Markdown file. It **should** not be converted to HTML.
+ examples/Complex/site/assets/fun/fun.markdown view
@@ -0,0 +1,5 @@+# Fun++This is an example Markdown file.++We want this one to be converted to an HTML file.
+ examples/Complex/site/assets/fun/test.pdf view
binary file changed (absent → 6844 bytes)
+ examples/Complex/site/assets/fun/zanzibar.jpg view
binary file changed (absent → 113104 bytes)
+ examples/Complex/site/assets/giraffe.jpg view
binary file changed (absent → 18541 bytes)
+ examples/Complex/site/assets/passthroughs/giraffe.jpg view
binary file changed (absent → 18541 bytes)
+ examples/Complex/site/assets/passthroughs/pass.markdown view
@@ -0,0 +1,3 @@+# Passthrough++This is an example Markdown file. It **should** not be converted to HTML.
+ examples/Complex/site/assets/passthroughs/pass.scss view
@@ -0,0 +1,5 @@+$red: #ff0000;++.red {+ color: $red;+}
+ examples/Complex/site/assets/passthroughs/test.pdf view
binary file changed (absent → 6844 bytes)
+ examples/Complex/site/assets/style.scss view
@@ -0,0 +1,21 @@+$green: #00ff00;++body {+ font-family: sans-serif;+ font-size: 16px;+}++.structure {+ margin-left: auto;+ margin-right: auto;+ width: 600px;+}++h1 {+ color: $green;+}++.deep {+ border-left: 2px solid black;+ padding: 1em;+}
@@ -0,0 +1,19 @@+<!--PREAMBLE+date: 2018-01-30+postTitle: "Code related stuff"+tags:+ - awesome+-->++This shows code-related stuff! A cool `variable`.++```python+import unittest++class TestSomething(unittest.TestCase):+ def test1(self):+ self.assert_(True)++if __name__ == '__main__':+ unittest.main()+```
+ examples/Complex/site/blog/2018-01-31-crazy-markdown.markdown view
@@ -0,0 +1,13 @@+<!--PREAMBLE+date: 2018-01-31+postTitle: "Crazy Markdown"+-->++Life is like a *cloud*. Clouds are like, just some **cold water in cloud+form**. So life is like cold water in <em>cloud</em> form.++```+I am <em>more</em> like me. These em tags should be escaped.+```++The website's title is **${title}** and this post's title is **${postTitle}**.
+ examples/Complex/site/blog/2018-02-01-variables.markdown view
@@ -0,0 +1,25 @@+<!--PREAMBLE+date: 2018-02-01+postTitle: "Variables"+tags:+ - awesome+innerVariable: "My inner variable"+duplicateVariableSandwich: "Duplicate variable from 2018-02-01-variables.markdown"+duplicateVariable: "Duplicate variable from 2018-02-01-variables.markdown"+-->++Variable test.++Post title: ${postTitle}++Post date: ${date}++The secret value: ${secret}++Layout secret: ${layoutSecret}++Post layout secret: ${postLayoutSecret}++Duplicate variable sandwich: ${duplicateVariableSandwich}++Duplicate variable: ${duplicateVariable}
+ examples/Complex/site/deep/deep1.md view
@@ -0,0 +1,7 @@+<div class="deep">+## Deep 1++Here's the **body**:++${body}+</div>
+ examples/Complex/site/deep/deep2.html view
@@ -0,0 +1,7 @@+<div class="deep">+<h2>Deep 2</h2>++<p>Hey deep 2.</p>++${body}+</div>
+ examples/Complex/site/deep/deep3.markdown view
@@ -0,0 +1,7 @@+## Deep 3++Loops over every Deep 4 content:++${for(deep4s)}+${this.content}+${end}
+ examples/Complex/site/deep/deep4a.markdown view
@@ -0,0 +1,8 @@+<div class="deep">+## Deep 4A++> A quote from deep 4A.+> Is here.++A [link](http://google.com) here.+</div>
+ examples/Complex/site/deep/deep4b.md view
@@ -0,0 +1,5 @@+<div class="deep">+## Deep 4B++This is Deep 4b.+</div>
+ examples/Complex/site/deep/deep5.html view
@@ -0,0 +1,5 @@+<div class="deep">+<h2>Deep 5</h2>++<p>This is the deep 5 thing.</p>+</div>
+ examples/Complex/site/index.html view
@@ -0,0 +1,9 @@+<h1>Complex Regression Test</h1>++<p>The complex test.</p>++${partial("post-list.html")}++<p><a href="/blog/rss.xml">RSS Feed</a></p>++<p><a href="/deep">Deep</a></p>
+ examples/Complex/site/layout.html view
@@ -0,0 +1,19 @@+<!--PREAMBLE+layoutSecret: "Preamble variable from layout.html"+duplicateVariableSandwich: "Duplicate variable from layout.html"+-->+<!DOCTYPE html>+<html>+ <head>+ <meta charset="utf-8" />+ <title>${title}</title>+ <link rel="stylesheet" type="text/css" href="/assets/style.css"/>+ </head>+<body>+ ${if(innerVariable)}+ <h1>Layout knows about the variable innerVariable: ${innerVariable}</h1>+ ${end}+ ${body}+</body>+</html>+
+ examples/Complex/site/post-bullet.html view
@@ -0,0 +1,3 @@+<li>+ <a href="${this.url}">${postTitle} - ${date}</a>+</li>
+ examples/Complex/site/post-layout.html view
@@ -0,0 +1,19 @@+<!--PREAMBLE+postLayoutSecret: "This is the post layout variable in post-layout.html"+duplicateVariable: "Duplicate variable from post-layout.html"+-->+<div>+ <h2>${postTitle}</h2>+ <div>${date}</div>+ <div>+ ${if(tags)}+ Tags: ${for(tags)} <a href="${this.url}">${tag}</a> ${end}+ ${end}+ </div>+ <hr />+ <div>Duplicate variable sandwich: ${duplicateVariableSandwich}</div>+ <div>Duplicate variable: ${duplicateVariable}</div>+ <div>+ ${body}+ </div>+</div>
+ examples/Complex/site/post-list.html view
@@ -0,0 +1,5 @@+<ul>+ ${for(posts)}+ ${partial("post-bullet.html")}+ ${end}+</ul>
+ examples/Complex/site/rss.xml view
@@ -0,0 +1,18 @@+<?xml version="1.0" encoding="UTF-8"?>+<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">+<channel>+ <title>Example RSS Feed</title>+ <link>http://example.com/rss.xml</link>+ <description>An example RSS feed.</description>++ ${for(posts)}+ <item>+ <link>https://example.com${this.url}</link>+ <guid>https://example.com${this.url}</guid>+ <pubDate>${date}</pubDate>+ <title>${postTitle}</title>+ <description>${this.content}</description>+ </item>+ ${end}+</channel>+</rss>
+ examples/Complex/site/tag-list.html view
@@ -0,0 +1,3 @@+<h1>Posts tagged with <code>${tag}</code></h1>++${partial("post-list.html")}
+ examples/Docs/site/default.scss view
@@ -0,0 +1,145 @@+$bodyColor: #151311; /* Dark grey with a little of red saturation */+$anchorColor: #ff3366; /* red persimmon */+$darkGray: #333;+$lightGray: #eee;++html {+ font-family: 'Fira Sans', sans-serif;+}++body {+ color: $bodyColor;+ font-size: 18px;+ line-height: 1.4em;+ padding: 0;+ margin: 0;+}++a {+ color: $anchorColor;+ text-decoration: underline;+}++a:hover {+ text-decoration: underline;+}++a.sourceLine {+ color: $bodyColor;+ text-decoration: none;+}++img {+ max-width: 100%;+}++#structure {+ position: relative;+ height: auto;+ margin-left: auto;+ margin-right: auto;+ margin-top: 5em;+ padding: 0 1em;+ max-width: 840px;+}++#header {+ font-weight: 300;+ float: left;+ width: 7em;+ text-align: right;+ margin-top: -1em;+ padding-right: 1em;+ border-right: 4px solid $darkGray;++ min-height: 400px;+}++h1 {+ font-size: 2em;+ line-height: 1em;+}++h2 {+ font-size: 1.5em;+}++h3 {+ font-size: 1.17em;+}++#header h1 a {+ color: #151311;+ text-decoration: none;+}++#header h1 {+ font-weight: 300;+}++#header hr {+ border: 2px solid $darkGray;+ margin-top: 23px 0;+}++#content {+ margin-left: 8em;+ padding-left: 1.5em;+}++pre, code {+ font-family: 'Fira Mono', monospace;+}++pre {+ font-size: 14px;+ line-height: 1.22em;+}++pre {+ background-color: $lightGray;+ padding: 1em;+ overflow: scroll;+}++ul.nav {+ list-style: none;+ margin: 0;+ padding: 0;+}++ul.nav li {+ margin-bottom: 1em;+}++.figure .caption {+ font-size: 14px;+ color: #666;+}++.note {+ border: 3px solid #ccc;+ padding: 0 1em;+ background: $lightGray;+ font-size: 0.8em;+}++@media (max-width: 700px) {+ #structure {+ padding: 0 0.5em;+ margin-top: 2em;+ }++ #header {+ float: none;+ min-height: 0;+ border: none;+ width: auto;+ text-align: left;+ }++ #content {+ margin-left: 0;+ padding-left: 0;+ }+}
+ examples/Docs/site/guides/environment.markdown view
@@ -0,0 +1,69 @@+# The Environment++When you load and render pages in Pencil, an environment of variables is calculated. This is what allows you to reference variables defined from other pages.++But Pencil’s variable closure works differently than most programming languages. Consider this example:++```+print value+if (true) {+ let value = "Hello!"+}+```++In most languages, this wouldn’t run because (1) you can’t use something before it’s declared and (2) `value`’s scope exists only inside the `if`-block. But because Pencil is purpose-built for building web pages, we bashfully reject these sane limitations.++**Pencil’s variable scoping** allows any page in the structure to reference any variable from any other page in the structure.++```Haskell+layout <- load "layout.html"+about <- load "about.html"++render $ layout <|| about+```++Both `layout.html` and `about.html` can declare new variables in the preamble, and they can access each other’s variables. They can also access, of course, the “global” variables defined in the `Config`’s environment.++The reason for this is simple: it’s often the case that the outer pages (e.g. the layout) will need to use a variable defined in the inner pages. When you render a blog post, the blog post’s title is defined in the inner page, but the layout may need to know it as part of its header tag: `<h1>$${postTitle}</h1>`.++The reverse is also true. Often times, an inner page will need access to an outer page’s variables. For example, a comment widget that requires the blog post ID.++**Duplicate Variable Rule**. If two pages in the structure define the same variable name, then the value is based off the context:++- If the variable is defined in the page, then the page gets that variable’s value.+- Otherwise, the deepest page’s value wins.++## Manipulating the Environment++Though page preambles and variable scoping provide a common pattern for most pages, sometimes you’ll need to manipulate the variable values yourself, in Haskell code. Pencil provides several helper functions to help you gain access to the raw variable context.++As seen in the first tutorial, you can define “global” variables in the config.++```haskell+config :: Config+config =+ (updateEnv (insertText "title" "My Awesome Website")+ defaultConfig+```++These variables follow the same rule as page-defined variables. Think of them as a part of a “Page 0” that begins every structure.++You can use `updateEnv` with methods like `insert`, `insertText`, `insertPages`, `adjust`.++You can also use `getPageEnv` and `setPageEnv`, along with the functions above, to manipulate each page’s environment to your liking. These are the functions that `Pencil.Blog` uses to build up the default blogging functionality.++## Running inside a modified environment++If you modify an environment, you’ll want to run code _inside_ that environment. Pencil provides `withEnv`, which is one way to do it:++```haskell+postsEnv <- (insertPages "posts" posts env >>= insertPages "recommendedPosts" recommendedPosts)++withEnv postsEnv (render (layoutPage <|| indexPage))+```++You can also use `Control.Monad.Reader`’s `local` (which is re-exported by the Pencil module, so you don’t need to import any new modules). The example below modifies the display value function to `toTextRss`, so that the RSS content is HTML-escaped.++```haskell+local (setDisplayValue toTextRss) (render rssFeedStruct)+```
+ examples/Docs/site/guides/images/structure-collection.png view
binary file changed (absent → 14719 bytes)
+ examples/Docs/site/guides/images/structure.png view
binary file changed (absent → 11851 bytes)
+ examples/Docs/site/guides/pages-and-structures.markdown view
@@ -0,0 +1,144 @@+# Pages and Structures++Pages and Structures are fundamental data types in Pencil. Understand them and you shall succeed.++A **Page** contains:++- The contents of the file that you loaded.+- The outbound file path.+- Settings on _how_ the page should be rendered. For example, `escapeXml :: Page -> Page` is a function that will tell the page to escape XML/HTML tags when rendered.++That seems easy enough. But if all we had was the `Page` type, Pencil wouldn’t be all that useful. It would be just a glorified Markdown renderer. To support page re-use, we need some way to *combine* or *stitch* different pages together. This is where the `Structure` type comes in.++A **Structure** defines a nesting structure of `Page`s. Think of them like [Russian nesting dolls](https://en.wikipedia.org/wiki/Matryoshka_doll).++Consider the canonical Pencil example:++```haskell+layout <- load "layout.html"+index <- load "index.markdown"+render $ layout <|| index+```++Here, we combine two pages, `layout` and `index`, into a single structure via `layout <|| index`. As described in the tutorials, underneath the hood a `Structure` is a non-empty linked list.++Here’s a diagram of a longer example:++```+render $ layout <|| a <| b <| c+```++++Each page's content is accessible to the page above through `body`.++So when `render` is called on this structure, Pencil does the following:++- Gathers all the variables from all the pages in the structure.+- Renders the contents of `index` with this combined environment.+ - This means that `index.markdown` can reference variables defined in `layout.html`, in `index.markdown`, or in the global environment specified in the `Config`.+- Inserts the rendered HTML content into the `body` variable.+- Now, it renders contents of `layout`. Note that because we now have `body` available, `layout.html` can use `${body}` in its contents, which is replaced with the contents of `index`.+- Writes a new file in the output directory, `index.html`.++So how did Pencil decide on the output file path?++**The Default File Path Rule**. By default Pencil uses the file path of last non-collection `Page` in the `Structure`.++In this case, that would be `index`’s file path. When we call `load "index.markdown"`, `load` looks at the original extension (`.markdown`) and automatically converts it to `.html`.++If we wanted a different output file name, we could use some of the provided methods like [`rename`](https://hackage.haskell.org/package/pencil/docs/Pencil.html#v:rename) and [`move`](https://hackage.haskell.org/package/pencil/docs/Pencil.html#v:move):++```+render $ layout <|| rename "another-name.html" index+```++If you don’t want to use the last page’s file path, you can set any preceding page as the file path by using `useFilePath`. For example, say the last item in your structure is a comments widget:++```haskell+layout <- load "layout.html"+about <- load "about.html"+comment <- load "comment-widget.html"++render $ layout <|| about <| comment+```++By default, this would render your page into the file named `commen-widget.html`. To use `about`’s file path:++```+render $ layout <|| useFilePath about <| comment+```++And now the output file path is `about.html`.++## Collections++Sometimes we'll want to nest not just a single page, but a collection of pages. For example, we may want to build a page listing all of the blog posts we've loaded. In this page, we'd want to loop through each post and render the post's title, date and URL.++Pencil accomplishes this through collections, which is also covered in [Tutorial 3: Blogging](/pencil/03-blogging/).++Consider this example:++```haskell+layout <- load "layout.html"+index <- load "index.html"++post1 <- load "post1.markdown"+post2 <- load "post2.markdown"+let posts = [post1, post2]++render $ layout <|| index <<| coll "posts" posts+```++We load two posts, and want to somehow inject them into our index page so that we can iterate through each post and print out the post's title. To do this, we create a collection through `coll "posts" posts`. The first argument, `"posts"` is the variable name that will hold your pages. We then push this collection into the structure using `(<<|)`.++You can now access this in `index.html` like this:++```html+<ul>+$${for(posts)}+ <li>+ <a href=$${this.url}>${postTitle} - $${date}</a>+ </li>+$${end}+</ul>+```++- All variables defined by the post page (e.g. if the preamble contains `postTitle` and `date`) is accessible inside the for-loop.+- Each page's URL is accessible through the special variable `this.url`.+- Each page's content is accessible through the special variable `this.content`.++**The Collection Rule** states that a collection (1) must be the last element in the structure and (2) not the first.++So you can't have a structure with just a collection, for example. This rule is there to keep things simple, and it makes sense too. For most use-cases, the collection is the "most important thing" for the page we're trying to build and does not need inner-pages.++Here is a diagram describing the collection structure above:++++But what if you need more than one collection? Say you want to list your *top* blog posts, and the rest of them below. You can do this “manually”:++```haskell+indexEnv <- (insertPages "posts" posts env >>= insertPages "recommendedPosts" recommendedPosts)+indexPage <- load "index.html"+withEnv indexEnv (render (layoutPage <|| indexPage))+```++## Commonly Used Functions++Check out the [Hackage docs](http://hackage.haskell.org/package/pencil/docs/Pencil.html) for detailed information and examples.++### Page++- `load` and `loadDir`+- `loadAndRender`+- `rename`+- `move`++### Structure++- `struct`+- `coll`+- `(<||)`+- `(<|)`+- `(<<||)`
+ examples/Docs/site/guides/static-assets.markdown view
@@ -0,0 +1,30 @@+# Dealing with Static Assets++Some files don't need any special manipulation. We just want to either copy-and-paste the files from our source directory to the output directory, or a straight-forward conversion (e.g. SCSS to CSS) without needing pages and structures and environment variables.++Pencil understands this, and supplies a couple of functions to make this easy. The [Hackage documentation](http://hackage.haskell.org/package/pencil/docs/Pencil.html#t:Resource) has lots of examples. You can also look at the [Complex example](https://github.com/elben/pencil/tree/master/examples/Complex), which tests all of these scenarios.++If you have a folder of mixed assets like SCSS files and images, you can use [`loadAndRender`](http://hackage.haskell.org/package/pencil/docs/Pencil.html#v:loadAndRender):++```haskell+loadAndRender "assets/"+loadAndRender "profile-pic.jpg"+```++Convertible files will be converted, but binary files are copy-and-pasted through.++If you have files that you want to render *without* any conversion, like a Markdown file that you want to *keep as* Markdown, use [`passthrough`](http://hackage.haskell.org/package/pencil/docs/Pencil.html#v:loadAndRender):++```haskell+passthrough "assets/" >>= render+passthroguh "example.markdown" >>= render+```++You can also combine these functions with file-path-changing functions like `move` and `rename`:++```haskell+move "assets/example.markdown" <$> passthrough "assets/ex.markdown" >>= render+```++We use the functor `<$>` function (i.e. `fmap`) because `passthrough` has the type `FilePath -> PencilApp Resource`.+
+ examples/Docs/site/guides/templates.markdown view
@@ -0,0 +1,93 @@+# Templates++Pencil comes with a simple templating engine. Templates allow us to build web pages dynamically using Haskell code. Every blog post, for example, can share a common HTML template.++Pencil templates are regular HTML and Markdown (and other formats that Pencil supports) files that contain a **preamble** or **directives**.++## Preamble++Preambles are environment variable declarations inside your source files. A file may only have one preamble, and they must be declared at the top of the file. A preamble is YAML wrapped in an HTML comment. An example Markdown file with a preamble:++```+<!--PREAMBLE+postTitle: "Why I Love Python"+date: 2010-01-30+tags:+ - python+-->++I learned Java first. So when I found out about Python,+it was *love at first sight*.+```++In the above example, Pencil will intelligently parse the `date` value as a [`VDateTime`](https://hackage.haskell.org/package/pencil/docs/Pencil-Internal-Env.html#v:VDateTime). See [`toDateTime`](https://hackage.haskell.org/package/pencil/docs/Pencil-Internal-Env.html#v:toDateTime), which describes the date formats that Pencil will try to coerce into a `VDateTime`.++## Directives++Directives allow you to render variables. They are surrounded by `$${...}`.++### Variables++The simplest directive is the variable directive.++```+Hello $${name}!+```++The above template will render the value of the variable `name`, which is expected to be in the environment at `render`. If the variable is not found, the final render will literally include `${name}`.++### If block++The `if` directive allows us to render content based off the existence of a variable in the current environment.++```+$${if(name)}+ Hello $${name}!+$${end}+```++In this case, we now make sure that `name` is available before rendering.++### For loop++The `for` directive allows for looping over arrays. This is useful for things like rendering a list of blog post titles, linking each line to the actual blog post.++```+<ul>+$${for(posts)}+ <li><a href="$${this.url}">$${postTitle}</a> - $${date}</li>+$${end}+</ul>+```++`posts` must either be a collection node in the Structure, or a [`VEnvList`](https://hackage.haskell.org/package/pencil/docs/Pencil-Internal-Env.html#v:VEnvList) variable. The example will render each post's title, publish date, and will link it to `this.url`. Note that inside the `for` block, the scope changes to each post's environment. So `${postTitle}` will render each post's title.++`this.url` is a special variable that is automatically inserted for you inside a loaded `Page`. It points to the page's destination file path.++### Partials++The `partial` directive injects another template file into the current file. The directives inside the partial are rendered in the same environmental context as the `partial` directive.++Think of partials as just copy-and-pasting snippet from one file to another. Unlike Structures, partials cannot define environment variables.++In the example below, the first `partial` is rendered with the current+environment. The `partial` inside the `for` loop receives the same+environment as any other snippet inside the loop, and thus has access to the environment inside each post.++```+${partial("partials/nav-bar.html")}++${for(posts)}+ ${partial("partials/post-item.html")}+${end}+```++### Escaping++If you want a literal “$${name}” to be rendered, you need to *escape* the dollar sign by doubling it. So in the example below, Pencil will not parse it as a directive, but will render “$${name}” instead:++```+$$${name}+```++[This very source file](https://github.com/elben/pencil/blob/master/examples/Docs/site/guides/templates.markdown) uses escapes.
+ examples/Docs/site/index.markdown view
@@ -0,0 +1,29 @@+Pencil is a static site generator written in Haskell.++With Pencil, you write Haskell code to generate your website from source files+(HTML, CSS, Markdown). Pencil comes pre-loaded with blogging, tagging,+templating, and Markdown and Sass/Scss. And it's flexible enough to extend for+your own needs.++Jump right in with the first tutorial, [Getting+Started](/pencil/tutorials/01-getting-started/).++This website itself is generated via Pencil. You can view the source code+[here](https://github.com/elben/elben.github.io).++### Why use Pencil when there's already Hakyll?++I used [Hakyll](https://jaspervdj.be/hakyll/) for a while but found it a bit too+complicated for my needs. As a Haskell beginner I found it difficult extend+beyond what was provided because I couldn't understand what was going on+underneath the hood.++Pencil is a simpler, beginner-friendly alternative to Hakyll. It's especially+designed to be easy to understand and modify.++Pencil and the tutorials present here were designed with the beginner Haskeller+in mind. It is expected, however, that you have a good grasp of basic Haskell+syntax, functions, algebraic data types (the `data` keyword), records, maps and+filters, and data structures like `Data.Map`. A preliminary knowledge of how to+use monads is also expected, though you don't need to know exactly how they're+implemented.
+ examples/Docs/site/layout.html view
@@ -0,0 +1,72 @@+<!DOCTYPE html>+<html>++<head>+ <meta charset="utf-8">+ <title>${title}</title>+ <link rel="stylesheet" href="/pencil/default.css" />+ <link href="https://fonts.googleapis.com/css?family=Fira+Sans:300,400,400i,700" rel="stylesheet">+ <link href="https://fonts.googleapis.com/css?family=Fira+Mono:400,700" rel="stylesheet">+</head>++<body>+ <div id="structure">+ <div id="header">+ <h1><a href="/pencil">Pencil</a></h1>++ <div class="nav-section">+ <h2>Tutorials</h2>++ <ul class="nav">+ <li>+ <a href="/pencil/tutorials/01-getting-started/">+ Tutorial 1: Getting Started+ </a>+ </li>+ <li>+ <a href="/pencil/tutorials/02-deploying-to-github-pages-using-circle/">+ Tutorial 2: Deploying to GitHub Pages using Circle+ </a>+ </li>+ <li>+ <a href="/pencil/tutorials/03-blogging/">+ Tutorial 3: Blogging+ </a>+ </li>+ </ul>+ </div>++ <div class="nav-section">+ <h2>Guides</h2>+ <ul class="nav">+ <li>+ <a href="/pencil/guides/pages-and-structures/">+ Pages and Structures+ </a>+ </li>+ <li>+ <a href="/pencil/guides/templates/">+ Templates+ </a>+ </li>+ <li>+ <a href="/pencil/guides/environment/">+ The Environment+ </a>+ </li>+ <li>+ <a href="/pencil/guides/static-assets/">+ Dealing with Static Assets+ </a>+ </li>+ </ul>+ </div>+ </div>++ <div id="content">+ ${body}+ </div>+ </div>+</body>++</html>
+ examples/Docs/site/tutorials/01-getting-started.markdown view
@@ -0,0 +1,285 @@+# Tutorial 1: Getting Started++This tutorial walks you through our first Pencil website. At the end of this+tutorial, you'll understand some of the core Pencil concepts and have the+beginnings of a website built out.++You may find it useful to also have [Pencil's Haddock+page](https://hackage.haskell.org/package/pencil/docs/Pencil.html)+open as a reference.++We'll be using [stack](http://haskellstack.org) in this tutorial, so make sure+you have it installed. Let's create our project:++```sh+stack new my-website simple --resolver=lts-13.21+cd my-website+```++Open `my-website.cabal` and look for the `executable my-website-exe` section. Add `pencil` into the `build-depends` section. It should look something like this:++```+executable my-website+ hs-source-dirs: src+ main-is: Main.hs+ default-language: Haskell2010+ build-depends: base >= 4.7 && < 5+ , pencil+```++We'll also need to add `pencil` as as an `extra-deps` in `stack.yml`, since+`pencil` is not in Stackage yet:++```yaml+extra-deps:+ - pencil-1.0.1+```++Now we're going to add some source files. First, let's make a new directory called+`site/`, that will contain all of our website's HTML, Markdown and CSS files.++```+mkdir site+```++Our `my-website` folder should have a directory structure that looks something+like this:++```+my-website.cabal+src/+ Main.hs+site/+```++Create a new file, `site/layout.html`. This will be our website's main+template. Copy-and-paste this into `layout.html`:++```html+<!DOCTYPE html>+<html>+ <head>+ <meta charset="utf-8" />+ <title>$${title}</title>+ <link rel="stylesheet" type="text/css" href="stylesheet.css"/>+ </head>+<body>+ <div class="structure">+ $${body}+ </div>+</body>+</html>+```++Notice that `layout.html` contains the strings `$${title}` and `$${body}`. These+are variables, and they allow us to dynamically inject content into this shared+layout.++Let's also create a stylesheet. Create a new file in `site/` called+`stylesheet.scss`, with this content:++```+$bgColor: #ffffff;++body {+ background-color: $bgColor;+ font-family: sans-serif;+ font-size: 18px;+}++.structure {+ margin-left: auto;+ margin-right: auto;+ width: 600px;+}+```++Notice that we're using the `.scss` extension, and we have that weird+`$bgColor` thing. This is because we're using+[Sass/Scss](http://sass-lang.com) for our styling. I like Scss because it's a+super set of CSS, so you can write plain-old CSS but "add on" the Scss parts+(like variables) when you need it.++The final source file we'll add is `index.markdown`. This will contain our index+page's content in Markdown. You'll see how easy it is convert Markdown to+HTML, and inject it into our HTML-based layout.++`index.markdown` contains:++```+# My Awesome Website++Welcome to my *awesome* [website](http://example.com)!+```++## Writing some Haskell++OK, let's write some Haskell! Fill `src/Main.hs` with this:++```haskell+{-# LANGUAGE OverloadedStrings #-}++module Main where++import Pencil++website :: PencilApp ()+website = do+ index <- load "index.markdown"+ render index++main :: IO ()+main = run website defaultConfig+```++Let's build our project and try it out.++```+stack build+stack exec my-website+```++This should create an `out/` directory with an `index.html` file, which contains+our Markdown content rendered as HTML. It's basic stuff, but we're getting+somewhere.++## Rendering Pages++Let's look at what's happening inside the `website` function:++```haskell+website :: PencilApp ()+website = do+ index <- load "index.markdown"+ render index+```++The first thing you see is `index <- load "index.markdown"`. This is the+primary way we load source files in Pencil. `load` will open the given file+and convert it if necessary. It's smart enough to know, based off the file+extension, *how* to convert the files. So Markdown becomes HTML, and SCSS+becomes CSS. This is done under the `IO` monad because it's not a pure+function (it's reading a file). This is why we save the result to `index`+using `<-` inside a `do` block.++`index` is a `Page`. A `Page` holds a page's content (e.g. HTML tags) and+variables (e.g. `$${title}` and `$${body}` as seen in `layout.html`). It's+an important data type in Pencil.++And finally we _render_ the page into an actual HTML file by calling `render index`.+Underneath the hood, `render` replaces variables in the HTML with their values.++## Structuring our Pages++Of course most websites are too complex for a single Markdown file. We want+templates and CSS styling.++Change the `website` function to this:++```haskell+website :: PencilApp ()+website = do+ layout <- load "layout.html"+ index <- load "index.markdown"+ render (layout <|| index)++ loadAndRender "stylesheet.scss"+```++The call to `loadAndRender` loads and compiles our Scss file into `stylesheet.css` in+our output directory. Look at the source code of+[`loadAndRender`](https://hackage.haskell.org/package/pencil/docs/Pencil.html#v:loadAndRender).+It's just a call to `load` with a `render` at the end.++`layout <- load "layout.html"` is familiar—we load a layout file into a `Page`.+But is `(layout <|| index)` about?++It's common to share some common template across many pages. Specifically, we want the+contents of a page to be injected into another page. In this case, we want the contents of `index.markdown` inside the `$${body}`+position of `layout.html`.++To do this, Pencil provides the concept of a `Structure`. A `Structure` is a+list of `Page`s, defining a nesting order. Think [Russian nesting+dolls](https://en.wikipedia.org/wiki/Matryoshka_doll). The first element+defines the outer-most container, and subsequent elements are _inside_ the+previous element.++<div class="note">+Underneath the hood, a `Structure` is a `NonEmpty Page`, which is a list that cannot be empty.+You can read more about `Structure`s+[here](https://hackage.haskell.org/package/pencil/docs/Pencil.html#g:3).+</div>++When you have two Pages, you can combine them into a Structure using `(<||)`+(pronounced "smash"). So `(layout <|| index)` tells Pencil to insert the+contents of `index` into the `$${body}` variable of `layout`.++There is also another method, `(<|)` (pronounced "push") that pushes a+`Page` into an exiting `Structure`.++For example, if we had a global layout and an _inner_ layout, we could do this:++```haskell+layout <- load "layout.html"+inner <- load "inner.html"+index <- load "index.markdown"++render (layout <|| inner <| index)+```++The `$${body}` variable in `inner.html` will be replaced with the contents of+`index.markdown`. And that _combined_ content is what replaces the `$${body}`+variable in `layout.html`.++Back in our original example, we need to add the `title` variable into our+`Config` for the layout's `$${title}` variable. So let's create our own+called `config`, which is a modified version of `defaultConfig`:++```haskell+config :: Config+config =+ updateEnv (insertText "title" "My Awesome Website")+ defaultConfig++main :: IO ()+main = run website config+```++Check out the+[documentation](https://hackage.haskell.org/package/pencil/docs/Pencil.html) for+more information on `updateEnv` and `insertText`.++<div class="note">+`PencilApp` is Pencil's monad transformer. Don't worry if you aren't familiar+with monad transformers. In simple terms, `PencilApp` is a function that takes+a `Config` and does a bunch of stuff under the `IO` monad (e.g. reading source files, converting Markdown to HTML, and writing HTML files).++This is why we have to "run" our `website` function inside `main`; we have to+give the `PencilApp` function a `Config`. For now, we just pass in the default+provided by Pencil, `defaultConfig`.+</div>++# Generating our website++To generate and serve our website, run the following commands:++```+stack build+stack exec my-website+cd out && python -m SimpleHTTPServer 8000+```++Go to [http://localhost:8000](http://localhost:8000). Note that we're using+Python's HTTP server to serve our HTML files so that our relative URLs work+correctly.++And that's it! In this tutorial, you learned several important concepts:++- `load` is the primary way we load source files into `Page`s.+- A `Page` knows about our text content and template variables.+- You can smash `Page`s together into a `Structure` using `(<||)`, and reference+ them using the `${body}` template variable.+- You can set global variables in the `Config`.++Next, we'll setup continuous integration with CircleCI and GitHub Pages for+automatic deployments. Continue onward to [Tutorial 2: Deploying to GitHub Pages using Circle](/pencil/tutorials/02-deploying-to-github-pages-using-circle/)
+ examples/Docs/site/tutorials/02-deploying-to-github-pages-using-circle.markdown view
@@ -0,0 +1,111 @@+# Tutorial 2: Deploying to GitHub Pages using Circle++In this tutorial, we'll deploy our website that we started in Tutorial 1 to+[GitHub Pages](https://pages.github.com) using [CircleCI](https://circleci.com).+By the end of this tutorial, you'll have:++- Your source code in the `source` branch.+- CircleCI building your website's generated files from `source` branch.+- CircleCI automatically commiting your generated files into `master`.+- GitHub hosting the files in `master`.++## Configuring GitHub++In your GitHub repository's settings page (Settings -> Options -> GitHub Pages),+set the "Source" branch to `master` for GitHub Pages. This tells GitHub that+we'll want to deploy whatever is in `master` as a static website to+http://myusername.github.io/my-website.++++Now that we're using the `master` branch for our generated files, we need to+move our code into a different branch (e.g. `source`):++```+git checkout -b source+git push origin+```++Our build and deploy scripts will automatically generate commits to `master`, so+you should no longer use that. You'll want to set `source` as the default+branch in the GitHub repository settings (Settings -> Branches -> Default branch).++++## Makefile++Let's generate a `Makefile` using the script provided by Pencil:++```sh+bash <(curl -fsSL https://raw.githubusercontent.com/elben/pencil/master/bin/gen-stack-makefile)+```++Check out the generated `Makefile` to make sure that your GitHub username, repo,+and project name looks correct.++With this `Makefile`, you can now just type `make` to build all your+dependencies and generate your website. We'll also use this in the CircleCI+build.++## CircleCI Configuration++We'll also setup a [CircleCI](https://circleci.com) to automatically build and+deploy our website whenever we push our source.++In your project's directory, generate the required CircleCI configuration files+using this script:++```sh+bash <(curl -fsSL https://raw.githubusercontent.com/elben/pencil/master/bin/gen-stack-circle-config)+```++The CircleCI build plan generated in `.circleci/config.yml` folder will:++- Build the `source` branch and other branches that is _not_ `master`. This+ generates your website's static content.+- Deploy the `master` branch. It "deploys" your website by taking the+ static content generated and commiting those files as a git commit in the+ `master` branch. So your `source` branch contains Haskell code, and your+ `master` branch contains the generated static content.+- Once a commit is generated in `master`, this triggers GitHub Pages to deploy+ the static content on their webservers.+- There is also a `.circleci/noop.yml` config that we inject into the `master`+ branch so that CircleCI ignores that branch.++You can read more about the CircleCI configuration [here](https://circleci.com/docs/2.0/configuration-reference/).++## Putting it all together++With that, lets commit and push the files to the remote `source` branch:++```+git add .circleci Makefile+git commit -m "Add Makefile and .circleci files"+git push+```++Now we need to set up our project in CircleCI:++1. Go to the [CircleCI dashboard](https://circleci.com/dashboard).+2. Click on "Add Projects".+3. Add your website repo by clicking "Set Up Project".+4. In the project setup page, just click on "Start building", since we've+ already created our `.circleci/config.yml` file.++At this point, Circle should start building your website. Wait for the build to+finish, and Circle should automatically push your generated website to the+`master` branch.++If CircleCI fails to push to GitHub, you may need to set up your SSH keys to+have write access to the GitHub repo. Follow the instructions in your project's+"Checkout SSH keys" settings page to set up either a user key or a deploy key+with write access (circleci.com/gh/your-username/your-project/edit#checkout).++Once the build is done (it may take a while at first as it's downloading and+caching all the Haskell dependencies), GitHub will deploy your website to GitHub+will then deploy your website to+[myusername.github.io/my-website](http://myusername.github.io/my-website)++And that's it! You've successfully set up continuous integration. Any new pushes+to `source` will now kick-off a build and deploy.+
+ examples/Docs/site/tutorials/03-blogging.markdown view
@@ -0,0 +1,303 @@+# Tutorial 3: Blogging++This tutorial walks through how we can set up a blog on our website by following the [Blog example](https://github.com/elben/pencil/tree/master/examples/Blog) found in the Pencil `examples/` repo.++Let's consider all the features we'd want for our blog:++- Each blog post should get its own URL. Something like `mywebsite.com/blog/my-first-post/`.+- Be able to add tags to a post.+- An archive page that lists all the blogs post. Perhaps at `mywebsite.com/blog`.+- An archive page per tag, listing all posts tagged with that tag.+- An RSS feed.++That's a pretty thorough feature list. But because Pencil aims to make life as easy for you as possible, it's loaded with a bunch of these things. They're not "raw" functionality built into Pencil, but more like user-land features that were so useful that it made it into the core library. And it's all included when you `import Pencil`.++Pencil also ascribes to the philosophy of convention over configuration. And though you could configure these various tools to your liking or even write your own version (none of the functions are too complicated or very long), will follow Pencil's convention for blogging.++So let's get started!++## Rendering blog posts++Unlike the first tutorial, we're not going to walk through each file you need to create and so forth. Since we're following the [Blog example](https://github.com/elben/pencil/tree/master/examples/Blog), I trust that you'll be able to follow along in your own code base.++First, peruse the `site/` folder ([here](https://github.com/elben/pencil/tree/master/examples/Blog/site)) and get comfortable with a couple of things in there.++- The `blog/` directory contains all of our blog posts, in Markdown format.+- [`layout.html`](https://github.com/elben/pencil/blob/master/examples/Blog/site/layout.html) is the layout we'll use for every page we render.+- [`post-layout.html`](https://github.com/elben/pencil/blob/master/examples/Blog/site/post-layout.html) is the layout we'll use for each actual blog post. Notice the for-loop in there for each tag.++If you open `2018-01-30-code-related-stuff.markdown`, you'll see a preamble section at the beginning.++```html+<!--PREAMBLE+date: 2018-01-30+postTitle: "Code related stuff"+tags:+ - awesome+-->+```++Note that `date` and `postTitle` are required. We also see that this one was tagged with "awesome". We'll get to tags further down.++Now, for our first stab:++```Haskell+{-# LANGUAGE OverloadedStrings #-}++module Main where++import Pencil+import qualified Data.HashMap.Strict as H+import qualified Data.Text as T++websiteTitle :: T.Text+websiteTitle = "My Blog"++config :: Config+config =+ (updateEnv (insertText "title" websiteTitle) .+ setSourceDir "examples/Blog/site/" .+ setOutputDir "examples/Blog/out/"+ ) defaultConfig++website :: PencilApp ()+website = do+ loadAndRender "assets/style.scss"+ + layout <- load "layout.html"+ postLayout <- load "post-layout.html"++ posts <- loadPosts "blog/"+ + render $ fmap ((layout <|| postLayout <|)) posts+ +main :: IO ()+main = run website config+```++With these first few lines of code, you should be able to see something working. As usual:++```+stack exec my-website+cd out && python -m SimpleHTTPServer 8000+```++And you should see our blog posts rendered! For example: [http://localhost:8000/blog/code-related-stuff/](http://localhost:8000/blog/code-related-stuff/). Of course it's not listing any tags yet, and we don't have a blog index or anything. But it's a start.++Now, you should be familiar with the `load` function by now. But here we see `loadPosts`. This is a helper function provided by Pencil to do the following:++- Load all pages found in the `blog/` folder.+- Rename the file path from `blog/2018-01-31-ramblings.markdown` to `blog/ramblings/`.+- Sort by the date specified in each page's `date` variable found in the preamble.+- And return the list of `Page`s.++Don't be shy about reading the source code. You'll find that I sent more characters explaining it above than the actual source of `loadPosts`.++To render each post, we had this:++```haskell+render $ fmap ((layout <|| postLayout <|)) posts+```++This converts a list of `Page`s into a list of `Structure`s, pushing each page into the layout structure. We then render the list of `Structure`.++<div class="note">+There's an instance of the `Render` typeclass for list of `Page`s. That's why you can call `render` on a list.+</div>++If you loaded one of the rendered pages in your browser, you'll notice that the `<title>` tag still just says "My Blog". But shouldn't it contain the title of the blog post also?++Well, this ain't the first time someone ran into this problem. Use `injectTitle` to fix it:++```+render $+ fmap ((layout <|| postLayout <|) . injectTitle websiteTitle)+ posts+```++`injectTitle` takes the `postTitle` variable in each post's preamble and puts it in the front of the given website title. Rebuild the website, and the "Code related stuff" post now has the title "Code related stuff - My Blog".++<div class="note">+The dot operator `(.)` is function composition. `(a . b . c) d` translates to `a (b (c d))`.+</div>++## Listing all blog posts++We'll of course want to build a page that lists out all of our blog posts. To do that, let's add a couple of lines to our `website` function:++```+index <- load "index.html"+render (layout <|| index <<| coll "posts" posts)+```++Ooo, some new stuff! Let's talk about structures. As you know from the previous tutorials (and reference pages, and the [Pencil docs](http://hackage.haskell.org/package/pencil/docs/Pencil.html)) A structure is a nesting of pages. But sometimes we'll want to nest not just a single page, but a collection of pages. In our case, we need our index page to have access to the list of post pages so that we can, in our template, loop through each post and display its `postTitle` along with its `date` and URL.++Well, that's possible, as long as the collection element is (1) always the last element in the structure and (2) not the first. So you can't have a structure with just a collection, for example.++This rule is there to keep things simple, and it makes sense too. For most use-cases, the collection is the "most important thing" for the page we're trying to build.++Let's talk about the contents of `index.html` too.++```html+<h1>My Blog</h1>++<p>This is my blog. Check out my blog posts:</p>++${partial("post-list.html")}++<p><a href="/rss.xml">RSS Feed</a></p>+```++First, ignore the RSS stuff—we'll get to it later. But look out that `${partial()}` template directive. A partial is just a copy-paste of the contents in the partial. Nothing fancy is going on. So we'll need to check out `post-list.html`:++```html+<ul>+ ${for(posts)}+ ${partial("post-bullet.html")}+ ${end}+</ul>+```++Interesting! Here we loop over the `posts` variable. This comes from that collection structure that we built, when we did `coll "posts" posts`. For each post, we render the `post-bullet.html` partial:++```html+<li>+ <a href="${this.url}">${postTitle} - ${date}</a>+</li>+```++What you'll want to understand here is the fact that when we are inside the for-loop, the _environment context_ has changed. We have access to the post's variables like `postTitle` and `date`. And note that `this.url` is _automatically_ set by Pencil during render.++So if we build our website now, we'll see a sweet index page listing all of our blog posts!++**Exercise**: Create a new blog post and re-build your website. Reference variables in your blog post, like `${postTitle}` and `${title}` (which is the website's main title).++## Tags, Tags, Tags++So we are now rendering each blog post, and we have a listing of blog posts. Time to deal with tags.++A bit of warning: there's some hand-wavy stuff in this section. Not because it's magical, but because it can be confusing to explain without actually looking at the implementation.++Add these lines to the `website` function:++```haskell+tagPages <- buildTagPages "tag-list.html" posts+render $ fmap (layout <||) (H.elems tagPages)+```++`buildTagPages` takes `tag-list.html` and a list of post pages. It returns (hand-waving about to happen) a hash map of tags (which is just a `Text`) to a `Page`. That `Page` is a version of the page loaded from `tag-list.html`, but with the `tag` variable set to the tag in the key, and a `posts` array containing all posts that was tagged with the current tag. Oh, and the page's URL is set to `blog/tags/${tag}/`.++Yeah, `buildTagPages` is complicated. If you want to control some of the configuration, use `buildTagPagesWith`.++So as an example, the value for the key `"awesome"` is a `Page` where the `posts` variable contain `Page`s containing the posts from `2018-01-30-code-related-stuff.markdown` and `2018-02-01-more-ramblings.markdown`, since they were both tagged with `awesome`. The URL for the page is `blog/tags/awesome/`.++We actually can now page for each tag, listing out all the posts tagged with the tag. This is what `render $ fmap (layout <||) (H.elems tagPages)` does. It takes each `Page`, which is a version of `tag-list.html`, and renders it.++Re-build your website, and check out [localhost:8000/blog/tags/awesome/](http://localhost:8000/blog/tags/awesome/).++OK that's pretty sweet. But we also want to list out the tags on each of the blog post page that we rendered. And not only that, but we want to link each tag to the tag listing page. This is common-enough and confusing-enough to warrant a built-in method. We can use `injectTags` during the blog post render, like this:++```+render $+ fmap ((layout <|| postLayout <|)+ . injectTags tagPages+ . injectTitle websiteTitle)+ posts+```++Basically we're giving each post the entire mapping of tags to posts. Open `post-layout.html` and notice this:++```+${if(tags)}+ Tags: ${for(tags)} <a href="${this.url}">${tag}</a> ${end}+${end}+```++`injectTags` filters out the `tagPages` you passed in only to the tags found in the current page. Thus we can loop through `tags` and link to the tag listing page, just like that.++There ya go. Tags. It's complicated underneath the hood, but at least the method are easy to use, right? As always, if you're interested in learning more, the source code is always available. If you want a more interesting tagging system (sub-tags?), the existing implementation would serve as a good starting point.++## RSS++The final thing we'll tackle is RSS. Let's look at `rss.xml`:++```xml+<?xml version="1.0" encoding="UTF-8"?>+<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">+<channel>+ <title>Example RSS Feed</title>+ <link>http://example.com/rss.xml</link>+ <description>An example RSS feed.</description>++ ${for(posts)}+ <item>+ <link>https://example.com${this.url}</link>+ <guid>https://example.com${this.url}</guid>+ <pubDate>${date}</pubDate>+ <title>${postTitle}</title>+ <description>${this.content}</description>+ </item>+ ${end}+</channel>+</rss>+```++This is our template. We loop over ever post and render the post's URL, title, date and content. `this.content` is something we've never seen before. Any page's content is always available through the special `this.content` variable. So to render each page's content, all we need to do is type `${this.content}`.++We use `this.content` instead of `body` because `body` refers to the *inner* page's content. But inside the for-loop, the context is the current page being looped over. And our post page does not have an inner page to render, so `body` is not available.++Modifying `website`, we can add:++```Haskell+rssLayout <- load "rss.xml"++let rssFeedStruct = struct rssLayout <<|+ coll "posts" (fmap escapeXml (take 10 posts))++local (setDisplayValue toTextRss)+ (render rssFeedStruct)+```++I think we understand `load "rss.xml"` by now, but what is that monstrosity in the next line? Examining it from right-to-left:++- Take only the ten newest posts.+- Mark each `Page` as `escapeXml`. This is because we need the HTML tags of our posts to be escaped when we render it in the `<description>` tag. The `escapeXml` function just marks a boolean inside of the `Page` to note it to escape tags whenever the page is rendered.+- We've seen `coll "posts"` before. It puts our ten pages into the structure as a collection.+- `struct page` initializes the page in a structure. Then we push the collection element into the structure using `<<|`.++So now we have a structure containing all of the RSS posts we want to render. Can't we call `render rssFeedStruct`? Well, almost.++The RSS specification expects the `<pubDate>` value to be in the RFC 822 date format (e.g. `Wed, 31 Jan 2018 00:00:00 +0000`). But Pencil's default renderer will render a `VDateTime` in `YYYY-MM-DD` format. The transformation of `Value`s to their final string outputs is defined in the config by [`configDisplayValue`](http://hackage.haskell.org/package/pencil/docs/Pencil-Internal-Pencil.html#v:configDisplayValue). As you can see in the Hackage documentation, the default value is [`toText`](http://hackage.haskell.org/package/pencil/docs/Pencil-Internal-Env.html#v:toText). We need to replace `toText` with a different one, a Pencil-supplied one called [`toTextRss`](http://hackage.haskell.org/package/pencil/docs/src/Pencil.Env.Internal.html#toTextRss). The only difference between the two is how `VDateTime` values are rendered.++We could update our `config` value to:++```haskell+config :: Config+config =+ (updateEnv (insertText "title" websiteTitle) .+ setSourceDir "examples/Blog/site/" .+ setOutputDir "examples/Blog/out/" .+ setDisplayValue toTextRss+ ) defaultConfig+```++But that would change how dates are rendered *everywhere*. What we need to do is *temporarily* set `configDisplayValue` too `toTextRss`.++This is where [`local`](https://hackage.haskell.org/package/transformers/docs/Control-Monad-Trans-Reader.html#v:local) comes in. `local` allows us to temporarily run some command in a *modified* `Config` environment.++```+local (setDisplayValue toTextRss) (render rssFeedStruct)+```++The above means that `render rssFeedStructure` runs with `configDisplayValue` set to `toTextRss`.++Let's re-compile our website one more time and check out [localhost:8000/rss.xml](http://localhost:8000/rss.xml). We have an RSS feed indeed.++## Conclusion++In this tutorial you learned:++- How to use Pencil's built-in blogging functionality to load blog posts, render them, build an archive page, and deal with tags.+- How partials work.+- How to push collection of pages into a structure.+- How to run commands in a temporarily-modified `Config` environment.
+ examples/Docs/site/tutorials/images/tutorial-1-github-base-branch.png view
binary file changed (absent → 124562 bytes)
+ examples/Docs/site/tutorials/images/tutorial-1-github-settings.png view
binary file changed (absent → 171423 bytes)
pencil.cabal view
@@ -1,5 +1,5 @@ name: pencil-version: 1.0.0+version: 1.0.1 synopsis: Static site generator description: Pencil is a static site generator. Use Pencil to build a personal website, a@@ -23,7 +23,35 @@ , examples/Simple/site/*.scss , examples/Simple/site/*.markdown , examples/Blog/site/*.html+ , examples/Blog/site/assets/*.scss+ , examples/Blog/site/*.xml , examples/Blog/site/blog/*.markdown+ , examples/Complex/site/*.html+ , examples/Complex/site/*.xml+ , examples/Complex/site/assets/*.markdown+ , examples/Complex/site/assets/*.jpg+ , examples/Complex/site/assets/*.scss+ , examples/Complex/site/assets/conversions/*.markdown+ , examples/Complex/site/assets/conversions/*.jpg+ , examples/Complex/site/assets/conversions/*.scss+ , examples/Complex/site/assets/fun/*.markdown+ , examples/Complex/site/assets/fun/*.jpg+ , examples/Complex/site/assets/fun/*.pdf+ , examples/Complex/site/assets/passthroughs/*.markdown+ , examples/Complex/site/assets/passthroughs/*.jpg+ , examples/Complex/site/assets/passthroughs/*.pdf+ , examples/Complex/site/assets/passthroughs/*.scss+ , examples/Complex/site/blog/*.markdown+ , examples/Complex/site/deep/*.markdown+ , examples/Complex/site/deep/*.md+ , examples/Complex/site/deep/*.html+ , examples/Docs/site/*.markdown+ , examples/Docs/site/*.scss+ , examples/Docs/site/*.html+ , examples/Docs/site/guides/*.markdown+ , examples/Docs/site/guides/images/*.png+ , examples/Docs/site/tutorials/*.markdown+ , examples/Docs/site/tutorials/images/*.png -- Once cabal 2.4 is enforced, we could just list them out as: -- , examples/**/*.html