# I'm Different

Posted on May 22, 2016 by Tommy M. McGuire

I’m different!

– Crow T. Robot

Well, lookie here! I’ve done it. I’ve converted this blog from Blogger to a self-hosted Hakyll setup. And it only took several hundred automated Lego ferrets a couple of days.

The process wasn’t actually too difficult. Blogger supplies a .xml backup, which can be parsed by Jekyll‘s jekyll-import command to produce .html files which can be simply plopped into a Hakyll posts directory. Almost. jekyll-import leaves the tags component of the posts’ metadata blocks in a YAML-style, one-tag-per-line with a hyphen prefix; Hakyll wants them to be comma-separated on one line. Further, setting replace-internal-link to true is needed to transform internal links in the posts into a URL that can be man-handled into something relative.

Here’s an ed script to do the heavy lifting.

/tags:/+1,/^m/-1 s/$/,/ /tags:/+1,/^m/-1 s/-// /tags:/,/^m/-1 j s/,$//
1,$s/{{ site.baseurl }}//g 1,$ s/{% post_url $$[^ ]*$$ %}/\/posts\/\1.html/g
w
q

The first four lines handle the tag situation, first adding a comma to the end of every tag line, then stripping off the hyphen, and then joining the lines into one, and finally removing the terminal, extraneous comma. The next two lines handle URLS, first by removing the site.baseurl variable and then transforming the post_url thing into a default Hakyll /posts/... local URL.

Note: I’ve since altered how Hakyll generates URLs for posts, so the internal links are all broken. I just realized I need to train a ferret to go through and fix them up again. Geeze. Is my work never done? Do you all realize how much training snackies cost these days?

Copying comments out of the backup required a combination of hacking on jekyll-import and manual work; existing comments should be preserved but won’t be pretty. Sorry.

Anyway, for anyone interested, here’s my current site.hs:

main :: IO ()
main = hakyllWith hakyllConfig $do match "images/*"$ do
route   idRoute
compile copyFileCompiler

match "css/*" $do route idRoute compile compressCssCompiler These two rules copy the images and css directories into their appropriate locations.  tags <- buildTags "posts/*" (fromCapture "tags/*.html") tagsRules tags$ \tag pattern -> do
let title = "Posts tagged \"" ++ tag ++ "\""
route idRoute
compile $do posts <- chronological =<< loadAll pattern let ctx = constField "title" title mappend listField "posts" postCtx (return posts) mappend defaultContext makeItem "" >>= loadAndApplyTemplate "templates/tag.html" ctx >>= loadAndApplyTemplate "templates/default.html" ctx >>= relativizeUrls This bit is pretty much cobbled together from examples in the documentation, to generate tags pages (click on the haskell link above to see one).  match "pages/*"$ do
route $gsubRoute "pages/" (const "p/") composeRoutes setExtension "html" let tagsCtx = postCtxWithTags$ sorted tags
compile $pandocCompiler >>= loadAndApplyTemplate "templates/post.html" tagsCtx >>= loadAndApplyTemplate "templates/default.html" tagsCtx >>= relativizeUrls match "posts/*"$ do
route $customRoute oldStylePath composeRoutes setExtension "html" let tagsCtx = postCtxWithTags$ sorted tags
compile $pandocCompiler >>= saveSnapshot "content" >>= loadAndApplyTemplate "templates/post.html" tagsCtx >>= loadAndApplyTemplate "templates/default.html" tagsCtx >>= relativizeUrls Next are rules for pages and posts directories; pages are the Web Authentication and Parsing with Derivatives pages in the header. What you are currently reading is a post.  create ["atom.xml"]$ do
route idRoute
compile $do let feedCtx = postCtx mappend bodyField "description" posts <- fmap (take 10) . recentFirst =<< loadAllSnapshots "posts/*" "content" renderAtom feedConfiguration feedCtx posts create ["rss.xml"]$ do
route idRoute
compile $do let feedCtx = postCtx mappend bodyField "description" posts <- fmap (take 10) . recentFirst =<< loadAllSnapshots "posts/*" "content" renderRss feedConfiguration feedCtx posts And these two rules create the RSS and Atom feeds, as plain XML files.  create ["archive.html"]$ do
route idRoute
compile $do posts <- recentFirst =<< loadAll "posts/*" let archiveCtx = listField "posts" postCtx (return posts) mappend constField "title" "Archives" mappend defaultContext makeItem "" >>= loadAndApplyTemplate "templates/archive.html" archiveCtx >>= loadAndApplyTemplate "templates/default.html" archiveCtx >>= relativizeUrls match "index.html"$ do
route idRoute
compile $do posts <- fmap (take 10) . recentFirst =<< loadAll "posts/*" let indexCtx = listField "posts" postCtx (return posts) mappend constField "title" "Home" mappend tagCloudField "tagCloud" 50 150 tags mappend defaultContext getResourceBody >>= applyAsTemplate indexCtx >>= loadAndApplyTemplate "templates/default.html" indexCtx >>= relativizeUrls match "templates/*"$ compile templateBodyCompiler

Then it creates the archive and index pages.

The only part that isn’t taken more or less directly from some tutorial or example code is the custom route for pages, which is used to convert what Hakyll would come up with normally into what Blogger used.

-- Blogger paths for posts were /year/month/titlish; this function converts a
-- filename starting with year-month-date-titlish and turns it into a filepath
-- matching the Blogger format.
oldStylePath :: Identifier -> FilePath
oldStylePath ident = year </> month </> titlish
where basename = takeBaseName $toFilePath ident parts = splitAll "-" basename [year,month] = take 2 parts titlish = intercalate "-"$ drop 3 parts

The formatting is done with Bootstrap, the fonts Merriweather and Merriweather Sans are from Google Fonts, and I added a Disqus comment thingy. I have also added MathJax for the occasional math formatting (so don’t complain if you momentarily see some TeX which is replaced by pretty mathy stuff).

And then a miracle occurs, and Maniagnosis exsists. Can I get an Amen?

Now, as to why I did it…I’m afraid I don’t understand the question.

Site proudly generated by Hakyll.