As of this week, every post on this blog is prefaced with an Estimated Reading Time (ERT). It's about giving readers an honest estimate of how much time they will spend reading the post.
ERT for ethical blogging
Implementing ERT in Hakyll
To add an ERT to every post, we'll implement a new Hakyll Compiler
. I was already using a pandocCompiler
and there is a pandocCompilerWithTransform
that is going to do most of the work:
import Hakyll
import Text.Pandoc.Definition
import Text.Pandoc.Options
myCompiler :: Compiler (Item String)
myCompiler= pandocCompilerWithTransform
defaultHakyllReaderOptions
defaultHakyllWriterOptions myTransform
The function pandocCompilerWithTransform
expects a Pandoc
transformation myTransform :: Pandoc -> Pandoc
. and applies it to the result of the pandoc compilation. We'll implement this Pandoc
transformation such that it adds the ERT at the top of the blogpost.
myTransform :: Pandoc -> Pandoc
@(Pandoc meta blocks) = (Pandoc meta (ert:blocks))
myTransform pwhere ert = Para [ SmallCaps [Str "[ERT: ", Str $ timeEstimateString p ++ "]"] ]
Concretely, the ERT will be the estimated amount of time required for an average reader to read the post.
timeEstimateString :: Pandoc -> String
= toClockString . timeEstimateSeconds
timeEstimateString
toClockString :: Int -> String
toClockString i| i >= 60 * 60 = show hours ++ "h" ++ show minutes ++ "m" ++ show seconds ++ "s"
| i >= 60 = show minutes ++ "m" ++ show seconds ++ "s"
| otherwise = show seconds ++ "s"
where
= i `quot` (60 * 60)
hours = (i `rem` (60 * 60)) `quot` 60
minutes = i `rem` 60 seconds
According to Google, the average reading speed is around $300$ words per minute, or $5$ words per second.
timeEstimateSeconds :: Pandoc -> Int
= (`quot` 5) . nrWords timeEstimateSeconds
Also according to Google, there are about $5$ letters in a English word on average.
nrWords :: Pandoc -> Int
= (`quot` 5) . nrLetters nrWords
Finally there's the matter of actually counting the number of letters in the post. The code is is lentghy and boring, but here is anyway, in case you would like to reuse it.
nrLetters :: Pandoc -> Int
Pandoc _ bs) = sum $ map cb bs
nrLetters (where
= sum . map cb
cbs = sum . map cbs
cbss = sum . map cbss
cbsss
cb :: Block -> Int
Plain is) = cis is
cb (Para is) = cis is
cb (CodeBlock _ s) = length s
cb (RawBlock _ s) = length s
cb (BlockQuote bs) = cbs bs
cb (OrderedList _ bss) = cbss bss
cb (BulletList bss) = cbss bss
cb (DefinitionList ls) = sum $ map (\(is, bss) -> cis is + cbss bss) ls
cb (Header _ _ is) = cis is
cb (HorizontalRule = 0
cb Table is _ _ tc tcs) = cis is + cbss tc + cbsss tcs
cb (Div _ bs) = cbs bs
cb (Null = 0
cb
= sum . map ci
cis = sum . map cis
ciss
ci :: Inline -> Int
Str s) = length s
ci (Emph is) = cis is
ci (Strong is) = cis is
ci (Strikeout is) = cis is
ci (Superscript is) = cis is
ci (Subscript is) = cis is
ci (SmallCaps is) = cis is
ci (Quoted _ is) = cis is
ci (Cite _ is) = cis is
ci (Code _ s) = length s
ci (Space = 1
ci SoftBreak = 1
ci LineBreak = 1
ci Math _ s) = length s
ci (RawInline _ s) = length s
ci (Link _ is (_, s)) = cis is + length s
ci (Image _ is (_, s)) = cis is + length s
ci (Note bs) = cbs bs ci (