{-# LANGUAGE NamedFieldPuns #-}
{-# LANGUAGE OverloadedStrings #-}
module Gemoire.Gemlog
(
Gemlog (..)
, generatePosts
, generateGemfeed
, generateAtom
, defPost
, defGemfeed
, defGemfeedEntry
, defAtom
, defAtomEntry
) where
import Data.Bool (bool)
import Data.HashMap.Strict (findWithDefault, fromList, singleton)
import Data.Text (Text)
import qualified Data.Text as T
import qualified Data.Text.IO as TIO
import System.Directory (createDirectoryIfMissing, doesDirectoryExist)
import System.Directory.Extra (listFilesRecursive)
import System.FilePath (takeExtension, takeFileName, (</>))
import Gemoire.Gemlog.Feed
( defAtom
, defAtomEntry
, defGemfeed
, defGemfeedEntry
, escapeFeed
, lastModified
, sortPosts
)
import Gemoire.Gemlog.Post (defPost, readPost)
import Gemoire.Template (Template, Values, format)
data Gemlog = Gemlog
{ Gemlog -> Text
title :: !Text
, Gemlog -> Text
author :: !Text
, Gemlog -> String
sourceDir :: !FilePath
, Gemlog -> Text
baseURL :: !Text
, Gemlog -> Template
postTemplate :: Template
, Gemlog -> (Template, Template)
gemfeedTemplates :: (Template, Template)
, Gemlog -> (Template, Template)
atomTemplates :: (Template, Template)
, Gemlog -> Values
overrides :: !Values
}
deriving (Int -> Gemlog -> ShowS
[Gemlog] -> ShowS
Gemlog -> String
(Int -> Gemlog -> ShowS)
-> (Gemlog -> String) -> ([Gemlog] -> ShowS) -> Show Gemlog
forall a.
(Int -> a -> ShowS) -> (a -> String) -> ([a] -> ShowS) -> Show a
$cshowsPrec :: Int -> Gemlog -> ShowS
showsPrec :: Int -> Gemlog -> ShowS
$cshow :: Gemlog -> String
show :: Gemlog -> String
$cshowList :: [Gemlog] -> ShowS
showList :: [Gemlog] -> ShowS
Show, Gemlog -> Gemlog -> Bool
(Gemlog -> Gemlog -> Bool)
-> (Gemlog -> Gemlog -> Bool) -> Eq Gemlog
forall a. (a -> a -> Bool) -> (a -> a -> Bool) -> Eq a
$c== :: Gemlog -> Gemlog -> Bool
== :: Gemlog -> Gemlog -> Bool
$c/= :: Gemlog -> Gemlog -> Bool
/= :: Gemlog -> Gemlog -> Bool
Eq)
generatePosts :: Gemlog -> FilePath -> IO ()
generatePosts :: Gemlog -> String -> IO ()
generatePosts glog :: Gemlog
glog@(Gemlog{Template
postTemplate :: Gemlog -> Template
postTemplate :: Template
postTemplate}) String
outputDir = do
[Values]
posts <- Gemlog -> IO [Values]
parsePosts Gemlog
glog
Bool -> String -> IO ()
createDirectoryIfMissing Bool
True String
outputDir
(Values -> IO ()) -> [Values] -> IO ()
forall (t :: * -> *) (m :: * -> *) a b.
(Foldable t, Monad m) =>
(a -> m b) -> t a -> m ()
mapM_ (\Values
p -> String -> String -> IO ()
writeFile (String
outputDir String -> ShowS
</> Values -> String
fileName Values
p) (String -> IO ()) -> String -> IO ()
forall a b. (a -> b) -> a -> b
$ Text -> String
T.unpack (Text -> String) -> (Text -> Text) -> Text -> String
forall b c a. (b -> c) -> (a -> b) -> a -> c
. Text -> Text
crlf (Text -> String) -> Text -> String
forall a b. (a -> b) -> a -> b
$ Template -> Values -> Text
format Template
postTemplate Values
p) [Values]
posts
generateGemfeed :: Gemlog -> FilePath -> IO ()
generateGemfeed :: Gemlog -> String -> IO ()
generateGemfeed = Bool -> Gemlog -> String -> IO ()
generateFeed Bool
True
generateAtom :: Gemlog -> FilePath -> IO ()
generateAtom :: Gemlog -> String -> IO ()
generateAtom = Bool -> Gemlog -> String -> IO ()
generateFeed Bool
False
generateFeed :: Bool -> Gemlog -> FilePath -> IO ()
generateFeed :: Bool -> Gemlog -> String -> IO ()
generateFeed Bool
gemfeed glog :: Gemlog
glog@(Gemlog{Text
title :: Gemlog -> Text
title :: Text
title, Text
author :: Gemlog -> Text
author :: Text
author, Text
baseURL :: Gemlog -> Text
baseURL :: Text
baseURL, Values
overrides :: Gemlog -> Values
overrides :: Values
overrides}) String
path = do
let (Template
feed, Template
entry) = (Gemlog -> (Template, Template))
-> (Gemlog -> (Template, Template))
-> Bool
-> Gemlog
-> (Template, Template)
forall a. a -> a -> Bool -> a
bool Gemlog -> (Template, Template)
atomTemplates Gemlog -> (Template, Template)
gemfeedTemplates Bool
gemfeed Gemlog
glog
ending :: Text
ending = Text -> Text -> Bool -> Text
forall a. a -> a -> Bool -> a
bool Text
"" Text
"\CR" Bool
gemfeed Text -> Text -> Text
forall a. Semigroup a => a -> a -> a
<> Text
"\LF"
fname :: String
fname = String -> String -> Bool -> String
forall a. a -> a -> Bool -> a
bool String
"atom.xml" String
"index.gmi" Bool
gemfeed
String
output <- String -> String -> Bool -> String
forall a. a -> a -> Bool -> a
bool String
path (String
path String -> ShowS
</> String
fname) (Bool -> String) -> IO Bool -> IO String
forall (f :: * -> *) a b. Functor f => (a -> b) -> f a -> f b
<$> String -> IO Bool
doesDirectoryExist String
path
[Values]
posts <- [Values] -> [Values]
sortPosts ([Values] -> [Values]) -> IO [Values] -> IO [Values]
forall (f :: * -> *) a b. Functor f => (a -> b) -> f a -> f b
<$> Gemlog -> IO [Values]
parsePosts Gemlog
glog
String -> Text -> IO ()
TIO.writeFile String
output
(Text -> IO ())
-> ([(Text, Text)] -> Text) -> [(Text, Text)] -> IO ()
forall b c a. (b -> c) -> (a -> b) -> a -> c
. (Text -> Text) -> (Text -> Text) -> Bool -> Text -> Text
forall a. a -> a -> Bool -> a
bool Text -> Text
forall a. a -> a
id Text -> Text
crlf Bool
gemfeed
(Text -> Text)
-> ([(Text, Text)] -> Text) -> [(Text, Text)] -> Text
forall b c a. (b -> c) -> (a -> b) -> a -> c
. Template -> Values -> Text
format Template
feed
(Values -> Text)
-> ([(Text, Text)] -> Values) -> [(Text, Text)] -> Text
forall b c a. (b -> c) -> (a -> b) -> a -> c
. Bool -> Values -> Values
escapeFeed Bool
gemfeed
(Values -> Values)
-> ([(Text, Text)] -> Values) -> [(Text, Text)] -> Values
forall b c a. (b -> c) -> (a -> b) -> a -> c
. (Values
overrides Values -> Values -> Values
forall a. Semigroup a => a -> a -> a
<>)
(Values -> Values)
-> ([(Text, Text)] -> Values) -> [(Text, Text)] -> Values
forall b c a. (b -> c) -> (a -> b) -> a -> c
. ([Values] -> Values
lastModified [Values]
posts Values -> Values -> Values
forall a. Semigroup a => a -> a -> a
<>)
(Values -> Values)
-> ([(Text, Text)] -> Values) -> [(Text, Text)] -> Values
forall b c a. (b -> c) -> (a -> b) -> a -> c
. [(Text, Text)] -> Values
forall k v. (Eq k, Hashable k) => [(k, v)] -> HashMap k v
fromList
([(Text, Text)] -> IO ()) -> [(Text, Text)] -> IO ()
forall a b. (a -> b) -> a -> b
$ [ (Text
"title", Text
title)
, (Text
"author", Text
author)
, (Text
"base", Text
baseURL)
, (Text
"url", (Char -> Bool) -> Text -> Text
T.dropWhileEnd (Char -> Char -> Bool
forall a. Eq a => a -> a -> Bool
== Char
'/') Text
baseURL Text -> Text -> Text
forall a. Semigroup a => a -> a -> a
<> Text
"/" Text -> Text -> Text
forall a. Semigroup a => a -> a -> a
<> String -> Text
T.pack (ShowS
takeFileName String
output))
,
( Text
"entries"
, Text -> [Text] -> Text
T.intercalate Text
ending ([Text] -> Text) -> [Text] -> Text
forall a b. (a -> b) -> a -> b
$
(Values -> Text) -> [Values] -> [Text]
forall a b. (a -> b) -> [a] -> [b]
map (Text -> Text
T.stripEnd (Text -> Text) -> (Values -> Text) -> Values -> Text
forall b c a. (b -> c) -> (a -> b) -> a -> c
. (Text -> Text) -> (Text -> Text) -> Bool -> Text -> Text
forall a. a -> a -> Bool -> a
bool Text -> Text
forall a. a -> a
id Text -> Text
crlf Bool
gemfeed (Text -> Text) -> (Values -> Text) -> Values -> Text
forall b c a. (b -> c) -> (a -> b) -> a -> c
. Template -> Values -> Text
format Template
entry (Values -> Text) -> (Values -> Values) -> Values -> Text
forall b c a. (b -> c) -> (a -> b) -> a -> c
. Bool -> Values -> Values
escapeFeed Bool
gemfeed) [Values]
posts
)
]
parsePosts :: Gemlog -> IO [Values]
parsePosts :: Gemlog -> IO [Values]
parsePosts (Gemlog{Text
title :: Gemlog -> Text
title :: Text
title, Text
author :: Gemlog -> Text
author :: Text
author, String
sourceDir :: Gemlog -> String
sourceDir :: String
sourceDir, Text
baseURL :: Gemlog -> Text
baseURL :: Text
baseURL, Values
overrides :: Gemlog -> Values
overrides :: Values
overrides}) =
(Values -> Values) -> [Values] -> [Values]
forall a b. (a -> b) -> [a] -> [b]
map (\Values
p -> Text -> Text -> Values
forall k v. Hashable k => k -> v -> HashMap k v
singleton Text
"url" ((Char -> Bool) -> Text -> Text
T.dropWhileEnd (Char -> Char -> Bool
forall a. Eq a => a -> a -> Bool
== Char
'/') Text
baseURL Text -> Text -> Text
forall a. Semigroup a => a -> a -> a
<> Text
"/" Text -> Text -> Text
forall a. Semigroup a => a -> a -> a
<> String -> Text
T.pack (Values -> String
fileName Values
p)) Values -> Values -> Values
forall a. Semigroup a => a -> a -> a
<> Values
p)
([Values] -> [Values]) -> IO [Values] -> IO [Values]
forall (f :: * -> *) a b. Functor f => (a -> b) -> f a -> f b
<$> ( (String -> IO Values) -> [String] -> IO [Values]
forall (t :: * -> *) (m :: * -> *) a b.
(Traversable t, Monad m) =>
(a -> m b) -> t a -> m (t b)
forall (m :: * -> *) a b. Monad m => (a -> m b) -> [a] -> m [b]
mapM (Values -> String -> IO Values
readPost ([(Text, Text)] -> Values
forall k v. (Eq k, Hashable k) => [(k, v)] -> HashMap k v
fromList [(Text
"gemlog", Text
title), (Text
"author", Text
author)] Values -> Values -> Values
forall a. Semigroup a => a -> a -> a
<> Values
overrides))
([String] -> IO [Values])
-> ([String] -> [String]) -> [String] -> IO [Values]
forall b c a. (b -> c) -> (a -> b) -> a -> c
. (String -> Bool) -> [String] -> [String]
forall a. (a -> Bool) -> [a] -> [a]
filter ((String -> String -> Bool
forall a. Eq a => a -> a -> Bool
== String
".gmi") (String -> Bool) -> ShowS -> String -> Bool
forall b c a. (b -> c) -> (a -> b) -> a -> c
. ShowS
takeExtension)
([String] -> IO [Values]) -> IO [String] -> IO [Values]
forall (m :: * -> *) a b. Monad m => (a -> m b) -> m a -> m b
=<< String -> IO [String]
listFilesRecursive String
sourceDir
)
fileName :: Values -> FilePath
fileName :: Values -> String
fileName Values
p = ShowS
takeFileName ShowS -> (Text -> String) -> Text -> String
forall b c a. (b -> c) -> (a -> b) -> a -> c
. Text -> String
T.unpack (Text -> String) -> Text -> String
forall a b. (a -> b) -> a -> b
$ Text -> Text -> Values -> Text
forall k v. (Eq k, Hashable k) => v -> k -> HashMap k v -> v
findWithDefault Text
"post.gmi" Text
"path" Values
p
crlf :: Text -> Text
crlf :: Text -> Text
crlf = [Text] -> Text
T.unlines ([Text] -> Text) -> (Text -> [Text]) -> Text -> Text
forall b c a. (b -> c) -> (a -> b) -> a -> c
. (Text -> Text) -> [Text] -> [Text]
forall a b. (a -> b) -> [a] -> [b]
map ((Text -> Text -> Text
forall a. Semigroup a => a -> a -> a
<> Text
"\CR") (Text -> Text) -> (Text -> Text) -> Text -> Text
forall b c a. (b -> c) -> (a -> b) -> a -> c
. (Char -> Bool) -> Text -> Text
T.dropWhileEnd (Char -> Char -> Bool
forall a. Eq a => a -> a -> Bool
== Char
'\CR')) ([Text] -> [Text]) -> (Text -> [Text]) -> Text -> [Text]
forall b c a. (b -> c) -> (a -> b) -> a -> c
. Text -> [Text]
T.lines