úÎiķ_ō­      !"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`abcdefghijklmnopqrstuvwxyz{|}~€‚ƒ„…†‡ˆ‰Š‹ŒŽ‘’“”•–—˜™š›œžŸ ĄĒĢĪĨͧĻĐŠŦŽNonet  Environment map of variables to s.2Represents the data types found in an environment.6This includes at least Data.Aeson Value type (https:/hackage.haskell.orgpackage aeson-1.2.3.07docs/Data-Aeson.html#t:Value), plus other useful ones. Converts an Aeson Value to a Pencil . _Render for human consumption. This is the default one. Pass into Config as part of the Reader? FAccepted format is ISO 8601 (YYYY-MM-DD), optionally with an appended  THH:MM:SS+. Example: 2010-01-30, 2010-01-30T09:08:00  Helper for ­= using ISO 8601. YYYY-MM-DDTHH:MM:SS and YYYY-MM-DD formats. [https://hackage.haskell.org/package/time-1.9/docs/Data-Time-Format.html#v:iso8601DateFormat IDefine an ordering for possibly-missing Value. Nothings are ordered last.Sort by newest first.Returns true if the given Value is a VArray! that contains the given string.  Safeg†Pencil's tokens for content. Pencil's Page AST.$Convert Tokens to PNode AST.,transform [TokText "hello", TokText "world"][PText "hello",PText "world"]!transform [TokIf "title", TokEnd][PIf "title" []]Ctransform [TokIf "title", TokText "hello", TokText "world", TokEnd]+[PIf "title" [PText "hello",PText "world"]] 6${if(title)} ${for(posts)} world ${end} ${end}Jtransform [TokIf "title", TokFor "posts", TokText "world", TokEnd, TokEnd],[PIf "title" [PFor "posts" [PText "world"]]] Šbegin now ${if(title)} hello world ${if(body)} ${body} ${someothervar} wahh ${end} final thing ${end} the lastlineôtransform [TokText "begin", TokText "now", TokIf "title", TokText "hello", TokText "world", TokIf "body", TokVar "body", TokVar "someothervar", TokText "wahh", TokEnd, TokText "final", TokText "thing", TokEnd, TokText "the", TokText "lastline"]ļ[PText "begin",PText "now",PIf "title" [PText "hello",PText "world",PIf "body" [PVar "body",PVar "someothervar",PText "wahh"],PText "final",PText "thing"],PText "the",PText "lastline"] ><!--PREAMBLE foo: bar do: - re - me --> Hello world ${foo}\transform [TokPreamble "foo: bar\ndo:\n - re\n -me", TokText "Hello world ", TokVar "foo"]J[PPreamble "foo: bar\ndo:\n - re\n -me",PText "Hello world ",PVar "foo"]%‰Converts Tokens, which is just the raw list of parsed tokens, into PNodes which are the tree-structure expressions (i.e. if/for nesting)ĸĖThis function works by using a stack to keep track of where we are for nested expressions such as if and for statements. When a token that starts a nesting is found (like a TokIf), a "meta" expression (PMetaIf) is pushed into the stack. When we finally see an end token (TokEnd), we pop all the expressions off the stack until the first meta tag (e.g PMetaIf) is reached. All the expressions popped off are now known to be nested inside that if statement.&jPop nodes until we hit a If/For statement. Return pair (constructor found, nodes popped, remaining stack)' Helper for &.(Render nodes as string.)Render node as string.*Render tokens.+ Render token., Parse text.-Parse everything.5parse parseEverything "" "Hello ${man} and ${woman}."PRight [TokText "Hello ",TokVar "man",TokText " and ",TokVar "woman",TokText "."]Jparse parseEverything "" "Hello ${man} and ${if(woman)} text here ${end}."lRight [TokText "Hello ",TokVar "man",TokText " and ",TokIf "woman",TokText " text here ",TokEnd,TokText "."]Fparse parseEverything "" "Hi ${for(people)} ${name}, ${end} everyone!"hRight [TokText "Hi ",TokFor "people",TokText " ",TokVar "name",TokText ", ",TokEnd,TokText " everyone!"]yparse parseEverything "" "${realvar} $.get(javascript) $$ $$$ $} $( $45.50 $$escape $${escape2} wonderful life! ${truth}"“Right [TokVar "realvar",TokText " $.get(javascript) $$ $$$ $} $( $45.50 $$escape ",TokText "${",TokText "escape2} wonderful life! ",TokVar "truth"]dparse parseEverything "" "<!--PREAMBLE \n foo: bar\ndo:\n - re\n -me\n -->waffle house ${lyfe}"bRight [TokPreamble " \n foo: bar\ndo:\n - re\n -me\n ",TokText "waffle house ",TokVar "lyfe"]qparse parseEverything "" "YO ${foo} <!--PREAMBLE \n ${foo}: bar\ndo:\n - re\n -me\n -->waffle house ${lyfe}"ŒRight [TokText "YO ",TokVar "foo",TokText " ",TokPreamble " \n ${foo}: bar\ndo:\n - re\n -me\n ",TokText "waffle house ",TokVar "lyfe"]kThis is a degenerate case that we will just allow (for now) to go sideways: >>> parse parseEverything "" " bthis ${var never closes /b ${realvar}" Right [TokText " bthis ",TokVar "var never closes /b ${realvar"].Parse variables./Parse preamble.0Parse the start of a PREAMBLE.1Parse partial commands.9parse parsePartial "" "${partial(\"my/file/name.html\")}"&Right (TokPartial "my/file/name.html")2Parse escape sequence "$${""parse parseEscape "" "$${example}"Right (TokText "${")3Parse boring, boring text.*parse parseContent "" "hello ${ffwe} you!"Right (TokText "hello ")2parse parseContent "" "hello $.get() $ $( $$ you!",Right (TokText "hello $.get() $ $( $$ you!")ÜBecause of our first parser to grab a character that is not a $, we can't grab strings that start with a $, even if it's text. It's a bug, just deal with it for now. >>> isLeft $ parse parseContent "" "$$$ what" True*isLeft $ parse parseContent "" "${name}!!"True4Parse for loop declaration.!parse parseFor "" "${for(posts)}"Right (TokFor "posts")Iparse parseFor "" "${for(variable name with spaces technically allowed)}">Right (TokFor "variable name with spaces technically allowed")%isLeft $ parse parseFor "" "${for()}"True'isLeft $ parse parseFor "" "${for foo}"True5Parse if directive.6!General parse template functions.7Parse end keyword.parse parseEnd "" "${end}" Right TokEnd%isLeft $ parse parseEnd "" "${enddd}"True8ZA hack to capture strings that "almost" are templates. I couldn't figure out another way.9many1Till p end will parse one or more p until @end.From Qhttps://hackage.haskell.org/package/pandoc-1.10.0.4/docs/Text-Pandoc-Parsing.html:)Find the preamble content from the given PNodes.;Returns True if the PNode is a  PPreamble.<Gets the content of the  PPreamble+#"! $%&'()*+,-./0123456789:;<+ !"#$%&'()*+,-./0123456789:;<  !"#None6;<=AļKAŠTo render something is to create the output web pages, rendering template directives into their final form using the current environment.BRenders a as web page(s).CA  Structure is a list of G1s, defining a nesting order. Think of them like  -https://en.wikipedia.org/wiki/Matryoshka_dollRussian nesting dollsT. The first element defines the outer-most container, and subsequent elements are inside the previous element.You commonly use  Structures to insert a Pagej containing content (e.g. a blog post) into a container (e.g. a layout shared across all your web pages).Build structures using š, ˜ and ™. Īlayout <- load toHtml "layout.html" index <- load toHtml "index.markdown" about <- load toHtml "about.markdown" render (layout <|| index) render (layout <|| about) &In the example above we load a layout Page@, which can be an HTML page defining the outer structures like  <html></html> . Assuming  layout.html has the template directive ${body} (note that body> is a special variable generated during structure-building), layout <|| index tells B$ that you want the rendered body of index to be injected into the ${body} directive inside of layout. StructureAs also control the closure of variables. Variables defined in a Pages are accessible both by Page&s above and below. This allows inner PagePs to define variables like the blog post title, which may be used in the outer Page to set the <title> tag. In this way,  Structure allows efficient Page" reuse. See the private function x to learn more about how  Structures are evaluated. Note that this differs than the ${partial(...)}Þ directive, which has no such variable closures. The partial directive is much simpler think of them as copy-and-pasting snippets from one file to another. The partial has has the same environment as the parent context.DUse Resourceč to load and render files that don't need any manipulation other than conversion (e.g. Sass to CSS), or for static files that you want to copy as-is (e.g. binary files like images, or text files that require no other processing).Use ”, “ and „ to build a Resource from a file.In the example below,  robots.txt and everything in the images/# directory will be rendered as-is. Rpassthrough "robots.txt" >> render loadResources id True True "images/" >> render Fin and out file pathsGĒThe Page is an important data type in Pencil. It contains the parsed template of a file (e.g. of Markdown or HTML files). It may have template directives (e.g. ${body}z) that has not yet been rendered, and an environment loaded from the preamble section of the file. A Page also contains K , which is the output file path.K The rendered output path of this page. Defaults to the input file path. This file path is used to generate the self URL that is injected into the environment.L?Enum for file types that can be parsed and converted by Pencil.RPKnown Pencil errors that we know how to either recover from or quit gracefully.S%Failed to read a file as a text file.TDFile not found. We may or may not know the file we were looking for.U^Variable is not in the environment. Variable name, and file where the variable was reference.V The main Config* needed to build your website. Your app's Config is passed into the _ monad transformer.Use `J as a starting point, along with the config-modification helpers such as b._:The main monad transformer stack for a Pencil application.This unrolls to: 5PencilApp a = Config -> IO (Except PencilException a)The ExceptTŠ monad allows us to catch "checked" exceptions; errors that we know how to handle, in PencilException. Note that Unknown "unchecked" exceptions can still go through IO.` This default Config( gives you everything you need to start.Default values:  Config { X = "site/" , Y = "out/" , Z = HashMap.empty , [ =   , \+ = Text.Sass.Options.defaultSassOptions , ] = Text.Pandoc.def , ^S = Text.Pandoc.def { Text.Pandoc.writerHighlight = True } , 'configDisplayValue =   } a1The directory path of your web page source files.b8Sets the source directory of your web page source files.c.The directory path of your rendered web pages.d5Sets the output directory of your rendered web pages.eThe environment of the Config, which is what the  PencilApp] monad transformer uses. This is where variables are set for rendering template directives.f;Sets the current environment. You may also want to look at › if you want to B" things in a modified environment.g Update the  inside the V.hThe Ū for rendering Sass/Scss files.i Sets the Ū.jThe O for reading files that use Pandoc. Supported formats by Pencil are: Markdown.k Sets the C. For example, you may want to enable some Pandoc extensions like  : ,setPandocReaderOptions (Text.Pandoc.def {  9 = extensionsFromList [Ext_literate_haskell] }) config lThe  Q for rendering files that use Pandoc. Supported formats by Pencil are: Markdown.m Sets the  .nThe function that renders  to text.oSets the function that renders Y to text. Overwrite this with your own function if you would like to change how certain s are rendered (e.g. ). &myRender :: Value -> T.Text myRender ( dt) = Ŋ $     "%e %B %Y" dt myRender t =  ) t ... setDisplayValue myRender config $In the above example, we change the  VDateTime rendering to show 25 December 2017". Leave everything else unchanged.pRun the Pencil app.+Note that this can throw a fatal exception.qTGiven a file path, look at all file paths and find the one that seems most similar.rA ° of file extensions (e.g. markdown) to L.M:  html, htmN:  markdown, mdO: cssP:  sass, scsss Mapping of L/ to the final converted format. Only contains Ls that Pencil will convert.N: htmlP: csst Converts a L[ into its converted webpage extension, if Pencil would convert it (e.g. Markdown to HTML).toExtension Markdown Just "html"u"Takes a file path and returns the L, defaulting to Q$ if it's not a supported extension.v Returns the  from a G.w Sets the  in a G.x5Applies the environment variables on the given pages.The Cy is expected to be ordered by inner-most content first (such that the final, HTML structure layout is last in the list).‹The returned Page contains the Nodes of the fully rendered page, the fully-applied environment, and the URL of the last (inner-most) Page.ÜThe variable application works by applying the outer environments down into the inner environments, until it hits the lowest environment, in which the page is rendered. Once done, this rendered content is saved as the ${body}F variable for the parent structure, which is then applied, and so on.RAs an example, there is the common scenario where we have a default layout (e.g.  default.html=), with the full HTML structure, but no body. It has only a ${body}ĸ4 template variable inside. This is the parent layout. There is a child layout, the partial called "blog-post.html", which has HTML for rendering a blog post, like usage of ${postTitle} and ${postDate}. Inside this, there is another child layout, the blog post content itself, which defines the variables  postTitle and postDate-, and may renderer parent variables such as  websiteTitle. ĸĪ+--------------+ | | <--- default.html | | Defines websiteTitle | +---------+ | | | |<+----- blog-post.html | | +-----+ | | Renders ${postTitle}, ${postDate} | | | | | | | | | | | | | | | |<+-+----- blog-article-content.markdown | | | | | | Renders ${websiteTitle} | | +-----+ | | Defines postTitle, postDate | +---------+ | +--------------+ĸ In this case, we want to accumulate the environment variables, starting from default.html, to blog-post.html, and the markdown file's variables. Combine all of that, then render the blog post content. This content is then injected into the parent's environment as a ${body}+ variable, for use in blog-post.html. Now that3 content is injected into the parent environment's ${body}B variable, which is then used to render the full-blown HTML page.yApply  Structure and convert to Page.[It's simpler to implement if NonEmpty is ordered outer-structure first (e.g. HTML layout).z]Helper to inject a file path into a VarNotInEnv exception. Rethrow the exception afterwards.{|Loads the given file as a text file. Throws an exception into the ExceptT monad transformer if the file is not a text file.| Converts the IOError to a known R.How to test errors: zimport Control.Exception import qualified Data.Text.IO as TIO (e -> print (ioe_description (e :: IOError)) >> return "") handle (TIO.readFile "foo") }mReturns true if the IOError is an invalid byte sequence error. This suggests that the file is a binary file.~3Returns true if the IOError is due to missing file./Loads and parses the given file path. Converts N files to HTML, compiles P2 files into CSS, and leaves everything else alone.€TEvaluate the nodes in the given environment. Note that it returns an IO because of ${partial(..)}' calls that requires us to load a file. Sort given Page%s by the specified ordering function.‚0Filter by a variable's value in the environment.ƒ‰Given a variable (whose value is assumed to be an array of VText) and list of pages, group the pages by the VText found in the variable.ĸ6For example, say each Page has a variable "tags" that is a list of tags. The first Page has a "tags" variable that is an VArray [VText "a"], and the second Page has a "tags" variable that is an VArray [VText "a", VText "b"]. The final output would be a map fromList [("a", [page1, page2]), ("b", [page2])].„!Loads file in given directory as Ds.…^Lists files in given directory. The file paths returned is prefixed with the given directory.†… helper.‡ Merges two Env)s together, biased towards the left-hand Env on duplicates.ˆInsert text into the given Env. ?env <- asks getEnv insertText "title" "My Awesome Website" env ‰Insert Pages into the given Env.  posts <-  "blog/" env <- asks e insertPages "posts" posts env Š+Modify a variable in the given environment.‹Insert Value into the given Env.Œ/Convert known Aeson types into known Env types.#Converts an Aeson Object to an Env.Ž$Copy file from source to output dir.(Replaces the file path's extension with .html. • toHtml "about.markdown" ŪConverts a file path into a directory name, dropping the extension. Pages with a directory as its FilePath is rendered as an index file in that directory. For example, the pages/about.html is transformed into  pages/about/, which B would render the G to the file path pages/about/index.html.‘(Replaces the file path's extension with .css. • toCss "style.sass" ’<Converts file path into the expected extensions. This means  .markdown become .html, .sass becomes .css, and so forth. See s for conversion table. |-- Load everything inside the "assets/" folder, renaming converted files as -- expected, and leaving everything else alone. „ toExpected True True "assets/" “Loads a file as a Dļ. Use this for binary files (e.g. images) and for files without template directives. Regular files are still converted to their web page formats (e.g. Markdown to HTML, SASS to CSS). Ú-- Loads and renders the image as-is. Underneath the hood -- this is just a file copy. loadResource id "images/profile.jpg" >> render -- Loads and renders to about.index loadResource toHtml "about.markdown" >> render ”ƒLoads file as a pass-through. There is no content conversion, and template directives are ignored. In essence this is a file copy. #passthrough "robots.txt" >> render •óLoads a file into a Page, rendering the file (as determined by the file extension) into the proper output format (e.g. Markdown rendered to HTML, SCSS to CSS). Parses the template directives and preamble variables into its environment. The Page's Ks is determined by the given function, which expects the original file path, and returns the designated file path.dThe Page's designated file path is calculated and stored in the Page's environment in the variable this.url#. This allows the template to use  ${this.url}& to refer to the designated file path.Example: I-- Loads index.markdown with the designated file path of index.html load D "index.markdown" -- Keep the file path as-is load id "about.html" –UFind preamble node, and load as an Env. If no preamble is found, return a blank Env.—Loads and renders file as CSS. A-- Load, convert and render as style.css. renderCss "style.sass" ˜Creates a new  Structure from two Pages. dlayout <- load toHtml "layout.html" index <- load toHtml "index.markdown" render (layout <|| index) ™Pushes Page into  Structure. Šlayout <- load toHtml "layout.html" blogLayout <- load toHtml "blog-layout.html" blogPost <- load toHtml "myblogpost.markdown" render (layout <|| blogLayout <| blogPost) š Converts a Page into a  Structure.›[Runs the computation with the given environment. This is useful when you want to render a G or C with a modified environment.  withEnv (ˆ "newvar" "newval" env) (B page) ą instance of FileType. Environment variable name.ƒOrdering function to compare Value against. If the variable is not in the Env, the Page will be placed at the bottom of the order.‚6If true, include pages without the specified variable.Environment variable name.ƒEnvironment variable name.„ Recursive if True.'Handle as pass-throughs (file copy) if True.… Recursive if True.ˆEnvironment variable name.Text to insert.Environment to modify.‰Environment variable name.Page s to insert.Environment to modify.ŠEnvironment variable name.‹Environment variable name.Value to insert.Environment to modify.[ABCDFEGHKJILONPMQRSUTVW^]\[ZYX_`abcdefghijklmnopqrstuvwxyz{|}~€‚ƒ„…†‡ˆ‰Š‹ŒŽ‘’“”•–—˜™š›a_VWXYZ[\]^œ`abcdefghijklmnopqRSTULMNOPQrstuGHIJKvwxyz{|}~€‚ƒ„…†‡ˆ‰Š‹ŒDEFŽ‘’“”•–—C˜™š›ABĄ ŸžABDEFGHIJKLMNOPQRSTUVWXYZ[\]^NoneEZ5ABCDGLRV_`abcdefghijklmnoptuvw‚ƒ„…‡ˆ‰Š‹‘’“”•—˜™š›5_pGvw•›—C˜™šD“„”…AB‘’‡‹ˆ‰Š‚ƒV`abcdefgnohijklmLutRNone_ž§CLoads the given directory as a series of blog posts, sorted by the date, PREAMBLE environment variable. Posts with  draft: true are filtered out. posts <- loadBlogPosts "blog/" Ļ$Rewrites file path for blog posts. $/blog/2011-01-01-the-post-title.html => /blog/the-post-title/ĐGiven that the current Page has a  postTitle5 in the environment, inject the post title into the title= environment variable, prefixed with the given title prefix."This is useful for generating the <title>${title}</title> tags in your container layout. &injectTitle "My Awesome Website" post The above example may insert a title variable with the value #"How to do X - My Awesome Website".Š Helper of Ŧ! defaulting to the variable name posts$, and the tag index page file path blog/tags/my-tag-name/.  tagPages <- buildTagPages pages ŦBuild the tag index pages.Given blog post Pages with tags# variables in its PREAMBLE, builds Page/s that contain in its environment the list of PageYs that were tagged with that particular tag. Returns a map of tag of the tag index page. otagPages <- buildTagPagesWith "tag-list.html" "posts" (tag _ -> "blogtags" ++ ! tag ++ "/") posts Ž,Inject the given tagging map into the given Page's environment, as the tags variable, whose value is a VEnvList.Đ Title prefix.Ŧ'Partial to load for the Tag index pages`Variable name inserted into Tag index pages for the list of Pages tagged with the specified tag.Function to generate the URL of the tag pages.§ĻĐŠŦާĻĐŦŠŽē !"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]^_`abcdefghijjklmnopqrstuvwxyz{|}~€‚ƒ„…†‡ˆ‰Š‹ŒŽ‘’“”•–—˜™š›œžŸ ĄĒĢĪĨͧĻĐŠŦŽ­ŪŊ°ąēģīĩķ·ļđšŧž―ūŋĀÁÂÃÄÅÆĮČÉĘËĖÍ#pencil-0.1.2-6mUpwbPdm5KBp8K5izcaswPencilPencil.Internal.EnvPencil.Internal.ParserPencil.Internal.Pencil Pencil.Blog Text.Pandoc ReaderOptionsText.Pandoc.ExtensionsExt_literate_haskellText.Pandoc.OptionsreaderExtensions WriterOptionsTF formatTimedefaultTimeLocale loadBlogPosts Data.Textunpack mtl-2.2.1-DscMMmDQUE6GBfOSl4qMUHControl.Monad.Reader.ClassasksEnvValueVNullVTextVBool VDateTimeVArrayVEnvListtoValuetoText toDateTimemaybeParseIso8601 maybeOrdering dateOrderingarrayContainsString $fEqValue $fShowValueTokenTokTextTokVarTokForTokIf TokPartial TokPreambleTokEndPNodePTextPVarPForPIfPPartial PPreamblePMetaIfPMetaForPMetaEnd transformastpopNodes popNodes_ renderNodes renderNode renderTokens renderToken parseTextparseEverythingparseVar parsePreambleparsePreambleStart parsePartial parseEscape parseContentparseForparseIf parseFunctionparseEnd parseFakeVar many1TillfindPreambleText isPreamble preambleText $fShowPNode $fEqPNode $fShowToken $fEqTokenRenderrender StructureResourceSingle PassthroughPage pageNodespageEnv pageFilePathFileTypeHtmlMarkdownCssSassOtherPencilException NotTextFile FileNotFound VarNotInEnvConfigconfigSourceDirconfigOutputDir configEnvconfigDisplayValueconfigSassOptionsconfigPandocReaderOptionsconfigPandocWriterOptions PencilApp defaultConfig getSourceDir setSourceDir getOutputDir setOutputDirgetEnvsetEnv updateEnvgetSassOptionssetSassOptionsgetPandocReaderOptionssetPandocReaderOptionsgetPandocWriterOptionssetPandocWriterOptionsgetDisplayValuesetDisplayValuerunmostSimilarFile fileTypeMap extensionMap toExtensionfileType getPageEnv setPageEnvapplyapply_setVarNotInEnv loadTextFiletoPencilExceptionisInvalidByteSequence isNoSuchFileparseAndConvertTextFiles evalNodes sortByVar filterByVargroupByElements loadResourceslistDirlistDir_merge insertText insertPages updateEnvVal insertEnvmaybeInsertIntoEnv aesonToEnvcopyFiletoHtmltoDirtoCss toExpected loadResource passthroughloadfindEnv renderCss<||<| structurewithEnv$fDefaultConfig$fHashableFileType $fRender[] $fRenderPage$fRenderNonEmpty$fRenderResource$fShowPencilException $fEqFileType$fGenericFileType$fEqPage $fShowPage blogPostUrl injectTitle buildTagPagesbuildTagPagesWith injectTagsEnv time-1.8.0.2Data.Time.Format.Parse parseTimeM"hsass-0.4.2-Id9YGn4NhjtJLWepEsnkzpText.Sass.Options SassOptions#text-1.2.2.2-EakMpasry3jA6OIwSZhq9Mpack3unordered-containers-0.2.8.0-3iSQJVS3Sio885UUC852ojData.HashMap.BaseHashMap'hashable-1.2.6.1-JDYnvpSucMf1h1i2CUUtVbData.Hashable.ClassHashable