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:
myCompiler :: Compiler (Item String)
pandocCompilerWithTransform expects a
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
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"
= 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
= sum . map cb
cbs = sum . map cbs
cbss = sum . map cbss
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
= sum . map ci
cis = sum . map cis
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 (