-- | Site-wide configuration: identity, where content lives, what groups exist, shared templates
module MathiasSM.Config (
    -- * Hakyll's 'Configuration'
    siteConfiguration,

    -- * Deployment config
    SiteConfig (..),
    Env (..),
    baseUrl,

    -- * Files
    requiredContent,
    mediaDir,
    projectsGroup,
    postSnapshot,

    -- * 'Pattern's for files
    writingsPattern,
    pagesPattern,
    postsPattern,
    projectsPattern,
    socialsPattern,
    dataPattern,
    dataSchemaPattern,
    templatePattern,
    contentCssPattern,

    -- * Directories
    dataDir,

    -- * Identifiers

    -- ** Templates
    siteTemplate,
    minimalTemplate,
    pageTemplate,
    postTemplate,
    resolveTemplate,

    -- ** Data files
    socialsFile,
)
where

import Data.String (fromString)
import Hakyll (Configuration (ignoreFile), Identifier, Pattern, Snapshot, defaultConfiguration, fromFilePath, (.||.))
import MathiasSM.Yaml.Website (HighlightStyle)
import System.FilePath.Posix (takeFileName, (<.>), (</>))

-- | The build environment: 'Dev' also publishes the preview pages under @/example/@
data Env = Dev | Prod
    deriving (Env -> Env -> Bool
(Env -> Env -> Bool) -> (Env -> Env -> Bool) -> Eq Env
forall a. (a -> a -> Bool) -> (a -> a -> Bool) -> Eq a
$c== :: Env -> Env -> Bool
== :: Env -> Env -> Bool
$c/= :: Env -> Env -> Bool
/= :: Env -> Env -> Bool
Eq, Int -> Env -> ShowS
[Env] -> ShowS
Env -> FilePath
(Int -> Env -> ShowS)
-> (Env -> FilePath) -> ([Env] -> ShowS) -> Show Env
forall a.
(Int -> a -> ShowS) -> (a -> FilePath) -> ([a] -> ShowS) -> Show a
$cshowsPrec :: Int -> Env -> ShowS
showsPrec :: Int -> Env -> ShowS
$cshow :: Env -> FilePath
show :: Env -> FilePath
$cshowList :: [Env] -> ShowS
showList :: [Env] -> ShowS
Show)

{- | Build-time site configuration, gathered once from the environment by "MathiasSM.Cli".
Every field maps to an environment variable; see @.env.example@
-}
data SiteConfig = SiteConfig
    { SiteConfig -> FilePath
siteName :: String
    -- ^ Site name, exposed to templates as @site-name@
    , SiteConfig -> FilePath
siteDescription :: String
    -- ^ Site tagline, exposed as @site-description@
    , SiteConfig -> FilePath
siteAuthor :: String
    -- ^ Site author, exposed as @site-author@
    , SiteConfig -> Int
siteCopyrightYear :: Int
    -- ^ Copyright year, exposed as @site-copyrightYear@
    , SiteConfig -> FilePath
siteDomain :: String
    -- ^ Domain without scheme, exposed as @site-domain@
    , SiteConfig -> FilePath
twitterHandle :: String
    -- ^ Twitter (X) handle (without \@), used for the social cards of posts
    , SiteConfig -> FilePath
mathjaxVersion :: String
    {- ^ MathJax CDN version (from @mathjax:@ in @website.yaml@), exposed as @mathjax-version@

    I might some day decide not to use the CDN and rather vend the bundle.
    -}
    , SiteConfig -> HighlightStyle
highlightStyle :: HighlightStyle
    -- ^ Pandoc syntax-highlighting style (from @highlight.style@ in @website.yaml@), driving the generated CSS
    , SiteConfig -> FilePath
contentDir :: FilePath
    -- ^ Where the actual content lives
    , SiteConfig -> FilePath
assetsDir :: FilePath
    -- ^ Where the assests live (@/assets/@ by default)
    , SiteConfig -> FilePath
templatesDir :: FilePath
    -- ^ Where the templates live (@/templates/@ by default)
    , SiteConfig -> FilePath
contentTemplatesDir :: FilePath
    -- ^ Content-provided templates (@content/templates/@), overriding the vendored set file by file
    , SiteConfig -> FilePath
contentCssDir :: FilePath
    -- ^ Content-provided styles (@content/css/@), loaded after the vendored styles
    , SiteConfig -> [FilePath]
contentTemplateNames :: [FilePath]
    -- ^ The template names (relative to 'contentTemplatesDir') the content provides
    , SiteConfig -> Env
siteEnv :: Env
    -- ^ Whether this is a dev build (which publishes @/example/@ preview pages)
    }

{- | Hakyll's default configuration, except that `.well-known` folders are not ignored
(Hakyll skips every dot-folder, but `assets/static/.well-known/` must be published)
TODO: Review if condition is correct, or even needed
-}
siteConfiguration :: Configuration
siteConfiguration :: Configuration
siteConfiguration = Configuration
defaultConfiguration{ignoreFile = ignore}
  where
    ignore :: FilePath -> Bool
ignore FilePath
path = ShowS
takeFileName FilePath
path FilePath -> FilePath -> Bool
forall a. Eq a => a -> a -> Bool
/= FilePath
".well-known" Bool -> Bool -> Bool
&& Configuration -> FilePath -> Bool
ignoreFile Configuration
defaultConfiguration FilePath
path

-- | Public origin of the site, without trailing slash
baseUrl :: SiteConfig -> String
baseUrl :: SiteConfig -> FilePath
baseUrl SiteConfig
cfg = FilePath
"https://" FilePath -> ShowS
forall a. [a] -> [a] -> [a]
++ SiteConfig -> FilePath
siteDomain SiteConfig
cfg

-- | Where the site's media lives: `branding/`, `icons/`, `photos/` (published at `images/`)
mediaDir :: SiteConfig -> FilePath
mediaDir :: SiteConfig -> FilePath
mediaDir SiteConfig
cfg = SiteConfig -> FilePath
contentDir SiteConfig
cfg FilePath -> ShowS
</> FilePath
"media"

{- | Files the site cannot build without, relative to the repo root
(@website.yaml@ is loaded separately by "MathiasSM.Yaml.Website")
-}
requiredContent :: SiteConfig -> [FilePath]
requiredContent :: SiteConfig -> [FilePath]
requiredContent SiteConfig
cfg = [SiteConfig -> FilePath
contentDir SiteConfig
cfg FilePath -> ShowS
</> FilePath
"socials.yaml"]

-- | The group whose items are the projects (its index page lists `projects/`)
projectsGroup :: String
projectsGroup :: FilePath
projectsGroup = FilePath
"showcase"

-- | Every file under 'contentDir'@/writings/@
writingsPattern :: SiteConfig -> Pattern
writingsPattern :: SiteConfig -> Pattern
writingsPattern SiteConfig
cfg = FilePath -> Pattern
forall a. IsString a => FilePath -> a
fromString (FilePath -> Pattern) -> FilePath -> Pattern
forall a b. (a -> b) -> a -> b
$ SiteConfig -> FilePath
contentDir SiteConfig
cfg FilePath -> ShowS
</> FilePath
"writings" FilePath -> ShowS
</> FilePath
"**"

-- | A standalone page or group index page (a flat file under 'contentDir'@/writings/@) named @name@
pagesPattern :: SiteConfig -> String -> Pattern
pagesPattern :: SiteConfig -> FilePath -> Pattern
pagesPattern SiteConfig
cfg FilePath
name = FilePath -> Pattern
forall a. IsString a => FilePath -> a
fromString (FilePath -> Pattern) -> FilePath -> Pattern
forall a b. (a -> b) -> a -> b
$ SiteConfig -> FilePath
contentDir SiteConfig
cfg FilePath -> ShowS
</> FilePath
"writings" FilePath -> ShowS
</> FilePath
name FilePath -> ShowS
<.> FilePath
"*"

-- | Items of a post group
postsPattern :: SiteConfig -> String -> Pattern
postsPattern :: SiteConfig -> FilePath -> Pattern
postsPattern SiteConfig
cfg FilePath
group = FilePath -> Pattern
forall a. IsString a => FilePath -> a
fromString (FilePath -> Pattern) -> FilePath -> Pattern
forall a b. (a -> b) -> a -> b
$ SiteConfig -> FilePath
contentDir SiteConfig
cfg FilePath -> ShowS
</> FilePath
"writings" FilePath -> ShowS
</> FilePath
group FilePath -> ShowS
</> FilePath
"**"

-- | All showcase projects
projectsPattern :: SiteConfig -> Pattern
projectsPattern :: SiteConfig -> Pattern
projectsPattern SiteConfig
cfg = FilePath -> Pattern
forall a. IsString a => FilePath -> a
fromString (FilePath -> Pattern) -> FilePath -> Pattern
forall a b. (a -> b) -> a -> b
$ SiteConfig -> FilePath
contentDir SiteConfig
cfg FilePath -> ShowS
</> FilePath
"projects" FilePath -> ShowS
</> FilePath
"*.yaml"

-- | The single socials file (a list of accounts)
socialsPattern :: SiteConfig -> Pattern
socialsPattern :: SiteConfig -> Pattern
socialsPattern SiteConfig
cfg = FilePath -> Pattern
forall a. IsString a => FilePath -> a
fromString (FilePath -> Pattern) -> FilePath -> Pattern
forall a b. (a -> b) -> a -> b
$ SiteConfig -> FilePath
contentDir SiteConfig
cfg FilePath -> ShowS
</> FilePath
"socials.yaml"

-- | Where generic data files (any YAML) live
dataDir :: SiteConfig -> FilePath
dataDir :: SiteConfig -> FilePath
dataDir SiteConfig
cfg = SiteConfig -> FilePath
contentDir SiteConfig
cfg FilePath -> ShowS
</> FilePath
"data"

-- | Every generic data file, at any depth under 'dataDir'
dataPattern :: SiteConfig -> Pattern
dataPattern :: SiteConfig -> Pattern
dataPattern SiteConfig
cfg = FilePath -> Pattern
forall a. IsString a => FilePath -> a
fromString (FilePath -> Pattern) -> FilePath -> Pattern
forall a b. (a -> b) -> a -> b
$ SiteConfig -> FilePath
dataDir SiteConfig
cfg FilePath -> ShowS
</> FilePath
"**.yaml"

-- | The optional JSON Schema for each data file, co-located as @<name>.schema.json@
dataSchemaPattern :: SiteConfig -> Pattern
dataSchemaPattern :: SiteConfig -> Pattern
dataSchemaPattern SiteConfig
cfg = FilePath -> Pattern
forall a. IsString a => FilePath -> a
fromString (FilePath -> Pattern) -> FilePath -> Pattern
forall a b. (a -> b) -> a -> b
$ SiteConfig -> FilePath
dataDir SiteConfig
cfg FilePath -> ShowS
</> FilePath
"**.schema.json"

-- | Every template the site can use: the vendored defaults plus any content-provided overrides
templatePattern :: SiteConfig -> Pattern
templatePattern :: SiteConfig -> Pattern
templatePattern SiteConfig
cfg = FilePath -> Pattern
forall a. IsString a => FilePath -> a
fromString (SiteConfig -> FilePath
templatesDir SiteConfig
cfg FilePath -> ShowS
</> FilePath
"*") Pattern -> Pattern -> Pattern
.||. FilePath -> Pattern
forall a. IsString a => FilePath -> a
fromString (SiteConfig -> FilePath
contentTemplatesDir SiteConfig
cfg FilePath -> ShowS
</> FilePath
"*")

-- | Content-provided styles, loaded after the vendored ones
contentCssPattern :: SiteConfig -> Pattern
contentCssPattern :: SiteConfig -> Pattern
contentCssPattern SiteConfig
cfg = FilePath -> Pattern
forall a. IsString a => FilePath -> a
fromString (FilePath -> Pattern) -> FilePath -> Pattern
forall a b. (a -> b) -> a -> b
$ SiteConfig -> FilePath
contentCssDir SiteConfig
cfg FilePath -> ShowS
</> FilePath
"*.css"

-- | The snapshot holding each published post of a single group, as rendered HTML (before templates)
postSnapshot :: String -> Snapshot
postSnapshot :: ShowS
postSnapshot FilePath
group = FilePath
"published-" FilePath -> ShowS
forall a. [a] -> [a] -> [a]
++ FilePath
group

-- | The data files (YAML) read by contexts
socialsFile :: SiteConfig -> Identifier
socialsFile :: SiteConfig -> Identifier
socialsFile SiteConfig
cfg = FilePath -> Identifier
fromFilePath (FilePath -> Identifier) -> FilePath -> Identifier
forall a b. (a -> b) -> a -> b
$ SiteConfig -> FilePath
contentDir SiteConfig
cfg FilePath -> ShowS
</> FilePath
"socials.yaml"

-- | Templates every kind of page goes through
siteTemplate, minimalTemplate, pageTemplate, postTemplate :: SiteConfig -> Identifier
siteTemplate :: SiteConfig -> Identifier
siteTemplate SiteConfig
cfg = SiteConfig -> FilePath -> Identifier
resolveTemplate SiteConfig
cfg FilePath
"site.html"
minimalTemplate :: SiteConfig -> Identifier
minimalTemplate SiteConfig
cfg = SiteConfig -> FilePath -> Identifier
resolveTemplate SiteConfig
cfg FilePath
"minimal.html"
pageTemplate :: SiteConfig -> Identifier
pageTemplate SiteConfig
cfg = SiteConfig -> FilePath -> Identifier
resolveTemplate SiteConfig
cfg FilePath
"as-page.html"
postTemplate :: SiteConfig -> Identifier
postTemplate SiteConfig
cfg = SiteConfig -> FilePath -> Identifier
resolveTemplate SiteConfig
cfg FilePath
"as-post.html"

{- | Resolves a template's bare name (@\"site.html\"@) to the identifier to load:
the content-provided override when one exists, else the vendored default.
-}
resolveTemplate :: SiteConfig -> FilePath -> Identifier
resolveTemplate :: SiteConfig -> FilePath -> Identifier
resolveTemplate SiteConfig
cfg FilePath
name
    | FilePath
name FilePath -> [FilePath] -> Bool
forall a. Eq a => a -> [a] -> Bool
forall (t :: * -> *) a. (Foldable t, Eq a) => a -> t a -> Bool
`elem` SiteConfig -> [FilePath]
contentTemplateNames SiteConfig
cfg = FilePath -> Identifier
fromFilePath (SiteConfig -> FilePath
contentTemplatesDir SiteConfig
cfg FilePath -> ShowS
</> FilePath
name)
    | Bool
otherwise = FilePath -> Identifier
fromFilePath (SiteConfig -> FilePath
templatesDir SiteConfig
cfg FilePath -> ShowS
</> FilePath
name)