packages feed

hakyll-convert-0.3.0.5: src/Hakyll/Convert/OutputFormat.hs

{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE ViewPatterns #-}

module Hakyll.Convert.OutputFormat (validOutputFormat, formatPath) where

import Data.Default
import qualified Data.Map.Strict as M
import qualified Data.Text as T
import Data.Time.Format (defaultTimeLocale, formatTime)
import Hakyll.Convert.Common
import System.FilePath

validOutputFormat :: T.Text -> Bool
validOutputFormat format
  | T.null format = False
  | otherwise =
      case formatPath format def of
        Just _ -> True
        Nothing -> False

formatPath :: T.Text -> DistilledPost -> Maybe T.Text
formatPath format post = T.concat <$> helper [] format
  where
    helper acc input =
      case T.uncons input of
        Just ('%', rest) ->
          case T.uncons rest of
            Just (ch, rest2) ->
              if ch `M.member` acceptableFormats
                then
                  let formatter = acceptableFormats M.! ch
                   in helper ((formatter post) : acc) rest2
                else Nothing
            Nothing -> Nothing
        Just (ch, rest) -> helper ((T.singleton ch) : acc) rest
        Nothing -> Just $ reverse acc

-- When adding a new format, don't forget to update the help message in
-- tools/hakyll-convert.hs
acceptableFormats :: M.Map Char (DistilledPost -> T.Text)
acceptableFormats =
  M.fromList
    [ -- this lets users put literal percent sign in the format)
      ('%', const $ T.singleton '%'),
      ('o', fmtOriginalPath), -- original filepath, like 2016/01/02/blog-post.html
      ('s', fmtSlug), -- original slug, i.e. "blog-post" from the example above
      ('y', fmtYear2), -- publication year, 2 digits
      ('Y', fmtYear4), -- publication year, 4 digits
      ('m', fmtMonth), -- publication month
      ('d', fmtDay), -- publication day
      ('H', fmtHour), -- publication hour
      ('M', fmtMinute), -- publication minute
      ('S', fmtSecond) -- publication second
    ]

fmtOriginalPath :: DistilledPost -> T.Text
fmtOriginalPath post =
  T.pack
    . dropTrailingSlash
    . dropExtensions
    $ chopUri
    $ T.unpack (dpUri post)
  where
    dropTrailingSlash = reverse . dropWhile (== '/') . reverse
    dropDomain path =
      -- carelessly assumes we can treat URIs like filepaths
      joinPath $
        drop 1 $ -- drop the domain
          splitPath path
    chopUri (dropPrefix "http://" -> ("", rest)) = dropDomain rest
    chopUri (dropPrefix "https://" -> ("", rest)) = dropDomain rest
    chopUri u =
      error $
        "We've wrongly assumed that blog post URIs start with http:// or https://, but we got: " ++ u

    dropPrefix :: (Eq a) => [a] -> [a] -> ([a], [a])
    dropPrefix (x : xs) (y : ys) | x == y = dropPrefix xs ys
    dropPrefix left right = (left, right)

fmtSlug :: DistilledPost -> T.Text
fmtSlug post =
  T.reverse
    . (T.takeWhile (/= '/'))
    . T.reverse
    $ fmtOriginalPath post

fmtDate :: String -> DistilledPost -> T.Text
fmtDate format = T.pack . (formatTime defaultTimeLocale format) . dpDate

fmtYear2 :: DistilledPost -> T.Text
fmtYear2 = fmtDate "%y"

fmtYear4 :: DistilledPost -> T.Text
fmtYear4 = fmtDate "%Y"

fmtMonth :: DistilledPost -> T.Text
fmtMonth = fmtDate "%m"

fmtDay :: DistilledPost -> T.Text
fmtDay = fmtDate "%d"

fmtHour :: DistilledPost -> T.Text
fmtHour = fmtDate "%H"

fmtMinute :: DistilledPost -> T.Text
fmtMinute = fmtDate "%M"

fmtSecond :: DistilledPost -> T.Text
fmtSecond = fmtDate "%S"