2021-07-23 21:35:02 -04:00
|
|
|
|
import qualified Data.ByteString.Lazy as LazyBS
|
|
|
|
|
import Data.Function ((&))
|
|
|
|
|
import Data.List (sortBy)
|
|
|
|
|
import Data.Ord (comparing)
|
|
|
|
|
import Data.Text (Text)
|
|
|
|
|
import qualified Data.Text as Text
|
2021-07-25 08:48:08 -04:00
|
|
|
|
import Misc
|
2021-07-23 21:35:02 -04:00
|
|
|
|
import qualified YAML
|
2024-09-15 11:46:06 -04:00
|
|
|
|
import YAML ((.:), (.:?), (.!=), (##=))
|
2021-07-23 21:35:02 -04:00
|
|
|
|
import qualified System.Console.GetOpt as GetOpt
|
|
|
|
|
import qualified System.FilePath.Find as Find
|
2021-07-25 08:48:08 -04:00
|
|
|
|
import qualified System.FilePath as Path
|
2021-07-23 21:35:02 -04:00
|
|
|
|
|
2024-09-16 13:45:50 -04:00
|
|
|
|
-- | generates a yaml list for a list of posts; either for a given tag, or all
|
|
|
|
|
-- posts. the posts are in chronological order, newest first.
|
|
|
|
|
--
|
|
|
|
|
-- example (exact yaml layout not guaranteed; whatever @HsYAML@ feels
|
|
|
|
|
-- like doing):
|
|
|
|
|
--
|
|
|
|
|
-- @
|
|
|
|
|
-- ---
|
|
|
|
|
-- title: "posts tagged ‘ATS’"
|
|
|
|
|
-- posts:
|
|
|
|
|
-- - file: fib.html
|
|
|
|
|
-- title: fibonacci in maude and ats
|
|
|
|
|
-- date: mon 24 october 2022
|
|
|
|
|
-- date-rss: Mon, 24 Oct 2022 00:00:01 UT
|
|
|
|
|
-- tags: [computer, maude, ATS]
|
|
|
|
|
-- summary: fibonacci numbers in the languages maude and ATS.
|
|
|
|
|
-- - file: ats.html
|
|
|
|
|
-- title: a little ats program
|
|
|
|
|
-- date: fri 16 september 2022
|
|
|
|
|
-- date-rss: Fri, 16 Sep 2022 00:00:01 UT
|
|
|
|
|
-- tags: [computer, ATS, cool languages]
|
|
|
|
|
-- summary: >
|
|
|
|
|
-- a little program in a little-known, little-documented language
|
|
|
|
|
-- called ATS.
|
|
|
|
|
-- ...
|
|
|
|
|
-- @
|
2021-07-23 21:35:02 -04:00
|
|
|
|
main :: IO ()
|
|
|
|
|
main = do
|
2024-09-16 13:45:50 -04:00
|
|
|
|
Opts {..} <- getOptions
|
2021-07-23 21:35:02 -04:00
|
|
|
|
files <- Find.findL True (pure True) (Find.extension Find.==? ".md") dir
|
2021-07-25 08:48:08 -04:00
|
|
|
|
infos <- filter (checkTag tag) <$> traverse (getInfo dir) files
|
2021-07-23 21:35:02 -04:00
|
|
|
|
let content = makeContent title infos
|
|
|
|
|
case out of
|
|
|
|
|
Nothing -> LazyBS.putStr content
|
|
|
|
|
Just fn -> LazyBS.writeFile fn content
|
|
|
|
|
|
|
|
|
|
makeContent :: Text -> [PostInfo] -> LazyBS.ByteString
|
|
|
|
|
makeContent title is' = "---\n" <> YAML.encode1 val <> "...\n" where
|
2024-09-16 11:02:09 -04:00
|
|
|
|
is = sortBy (flip $ comparing date) is'
|
2021-07-23 21:35:02 -04:00
|
|
|
|
val = YAML.obj [("title" ##= title), ("posts" ##= is)]
|
|
|
|
|
|
|
|
|
|
|
2024-09-16 13:45:50 -04:00
|
|
|
|
-- | whether a post has the given tag
|
2021-07-23 21:35:02 -04:00
|
|
|
|
checkTag :: Maybe Text -> PostInfo -> Bool
|
|
|
|
|
checkTag Nothing _ = True
|
2024-09-16 11:02:09 -04:00
|
|
|
|
checkTag (Just t) i = t `elem` tags i
|
2021-07-23 21:35:02 -04:00
|
|
|
|
|
|
|
|
|
|
2024-09-16 13:45:50 -04:00
|
|
|
|
-- | command line options
|
2021-07-23 21:35:02 -04:00
|
|
|
|
data Options =
|
|
|
|
|
Opts {
|
2024-09-16 13:45:50 -04:00
|
|
|
|
-- | first argument: directory containing the .md files
|
|
|
|
|
dir :: !FilePath,
|
|
|
|
|
-- | second argument: title for the output page
|
|
|
|
|
title :: !Text,
|
|
|
|
|
-- | @-t, --tag@: filter by tag
|
|
|
|
|
tag :: !(Maybe Text),
|
|
|
|
|
-- | @-o, --out@: write output to file (otherwise stdout)
|
|
|
|
|
out :: !(Maybe FilePath)
|
2021-07-23 21:35:02 -04:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
getOptions :: IO Options
|
|
|
|
|
getOptions = getOptionsWith hdr defOpts optDescrs where
|
|
|
|
|
hdr prog = "usage: " <> prog <> " [OPTION...] DIR TITLE\n\
|
|
|
|
|
\ --- get info about posts in DIR and use given title"
|
|
|
|
|
|
2024-09-16 13:45:50 -04:00
|
|
|
|
optDescrs :: [GetOpt.OptDescr (Options -> Options)]
|
|
|
|
|
optDescrs =
|
|
|
|
|
[GetOpt.Option "t" ["tag"]
|
|
|
|
|
(GetOpt.ReqArg (\t o -> o {tag = Just $ Text.pack t}) "TAG")
|
|
|
|
|
"list only posts with the given tag",
|
|
|
|
|
GetOpt.Option "o" ["out"]
|
|
|
|
|
(GetOpt.ReqArg (\f o -> o {out = Just f}) "FILE")
|
|
|
|
|
"write output to FILE"]
|
2021-07-23 21:35:02 -04:00
|
|
|
|
|
2024-09-16 13:45:50 -04:00
|
|
|
|
defOpts :: [String] -> Maybe Options
|
|
|
|
|
defOpts [dir, Text.pack -> title] =
|
|
|
|
|
Just $ Opts {dir, title, tag = Nothing, out = Nothing}
|
|
|
|
|
defOpts _ = Nothing
|
2021-07-23 21:35:02 -04:00
|
|
|
|
|
|
|
|
|
|
2024-09-16 11:02:09 -04:00
|
|
|
|
-- | the front matter info we care about
|
|
|
|
|
data PostInfo =
|
|
|
|
|
Info {
|
2024-09-16 13:45:50 -04:00
|
|
|
|
-- | the (input) file name, relative to 'dir' in options
|
2024-09-16 11:02:09 -04:00
|
|
|
|
file :: FilePath,
|
2024-09-16 13:45:50 -04:00
|
|
|
|
title :: Text, -- ^ post @title@
|
|
|
|
|
date :: IsoDate, -- ^ post @date@
|
|
|
|
|
tags :: [Text], -- ^ post @tags@
|
|
|
|
|
summary :: Maybe Text -- ^ post @summary@ (optional)
|
2024-09-16 11:02:09 -04:00
|
|
|
|
}
|
|
|
|
|
|
2024-09-16 13:45:50 -04:00
|
|
|
|
-- | @getInfo dir file@: read the front matter of @dir/file@
|
|
|
|
|
--
|
|
|
|
|
-- the 'file' field is just the filename, with @dir@ removed
|
2021-07-25 08:48:08 -04:00
|
|
|
|
getInfo :: FilePath -> FilePath -> IO PostInfo
|
|
|
|
|
getInfo dir file = do
|
2021-07-23 21:35:02 -04:00
|
|
|
|
yaml <- YAML.readHeader file
|
2021-07-25 08:48:08 -04:00
|
|
|
|
let dirs = Path.splitPath dir
|
|
|
|
|
let file' = Path.joinPath $ drop (length dirs) $ Path.splitPath file
|
2021-07-23 21:35:02 -04:00
|
|
|
|
unwrap file $ YAML.parseEither $
|
2024-09-15 11:46:06 -04:00
|
|
|
|
yaml & YAML.withMap "title, date, tags, summary?" \m ->
|
2021-07-25 08:48:08 -04:00
|
|
|
|
Info <$> pure file'
|
2024-09-15 11:46:06 -04:00
|
|
|
|
<*> m .: "title"
|
|
|
|
|
<*> m .: "date"
|
|
|
|
|
<*> m .: "tags" .!= []
|
|
|
|
|
<*> m .:? "summary"
|
2021-07-23 21:35:02 -04:00
|
|
|
|
|
|
|
|
|
instance YAML.ToYAML PostInfo where
|
2024-09-16 11:02:09 -04:00
|
|
|
|
toYAML (Info {..}) = YAML.obj
|
|
|
|
|
["date" ##= showDate date,
|
|
|
|
|
"date-rss" ##= rssDate date,
|
|
|
|
|
"title" ##= title,
|
|
|
|
|
"tags" ##= tags,
|
|
|
|
|
"file" ##= htmlFile file,
|
|
|
|
|
"summary" ##= summary]
|
|
|
|
|
|
2024-09-16 13:45:50 -04:00
|
|
|
|
-- | replace @.md@ (or whatever) extension with @.html@
|
2024-09-16 11:02:09 -04:00
|
|
|
|
htmlFile :: FilePath -> Text
|
|
|
|
|
htmlFile f = Text.pack $ Path.replaceExtension f "html"
|