119 lines
4.1 KiB
Haskell
119 lines
4.1 KiB
Haskell
{-# LANGUAGE GHC2021 #-}
|
|
{-# LANGUAGE OverloadedStrings #-}
|
|
-- | Site-wide configuration loaded from @site.yaml@ at the project root.
|
|
--
|
|
-- The config is exposed as top-level pure values via @unsafePerformIO@ +
|
|
-- @NOINLINE@. This is safe because:
|
|
-- 1. The config is a build-time input that never changes during a build.
|
|
-- 2. The static site generator is single-shot — no concurrency.
|
|
-- 3. NOINLINE prevents GHC from duplicating the value.
|
|
--
|
|
-- 'Main.main' forces 'siteConfig' before Hakyll starts so a missing or
|
|
-- malformed @site.yaml@ fails loudly with a parse error rather than
|
|
-- crashing midway through a build.
|
|
module Config
|
|
( SiteConfig(..)
|
|
, NavLink(..)
|
|
, Portal(..)
|
|
, siteConfig
|
|
, siteHost
|
|
, defaultAuthor
|
|
) where
|
|
|
|
import Data.Aeson (FromJSON(..), withObject, (.:), (.:?), (.!=))
|
|
import Data.Yaml (decodeFileThrow)
|
|
import Data.Text (Text)
|
|
import qualified Data.Text as T
|
|
import System.IO.Unsafe (unsafePerformIO)
|
|
|
|
-- ---------------------------------------------------------------------------
|
|
-- Types
|
|
-- ---------------------------------------------------------------------------
|
|
|
|
data SiteConfig = SiteConfig
|
|
{ siteName :: Text
|
|
, siteUrl :: Text
|
|
, siteDescription :: Text
|
|
, siteLanguage :: Text
|
|
, authorName :: Text
|
|
, authorEmail :: Text
|
|
, feedTitle :: Text
|
|
, feedDescription :: Text
|
|
, license :: Text
|
|
, sourceUrl :: Text
|
|
, gpgFingerprint :: Text
|
|
, gpgPubkeyUrl :: Text
|
|
, navLinks :: [NavLink]
|
|
, portals :: [Portal]
|
|
} deriving (Show)
|
|
|
|
data NavLink = NavLink
|
|
{ navHref :: Text
|
|
, navLabel :: Text
|
|
} deriving (Show)
|
|
|
|
data Portal = Portal
|
|
{ portalSlug :: Text
|
|
, portalName :: Text
|
|
} deriving (Show)
|
|
|
|
-- ---------------------------------------------------------------------------
|
|
-- JSON instances
|
|
-- ---------------------------------------------------------------------------
|
|
|
|
instance FromJSON SiteConfig where
|
|
parseJSON = withObject "SiteConfig" $ \o -> SiteConfig
|
|
<$> o .: "site-name"
|
|
<*> o .: "site-url"
|
|
<*> o .: "site-description"
|
|
<*> o .:? "site-language" .!= "en"
|
|
<*> o .: "author-name"
|
|
<*> o .: "author-email"
|
|
<*> o .:? "feed-title" .!= ""
|
|
<*> o .:? "feed-description" .!= ""
|
|
<*> o .:? "license" .!= ""
|
|
<*> o .:? "source-url" .!= ""
|
|
<*> o .:? "gpg-fingerprint" .!= ""
|
|
<*> o .:? "gpg-pubkey-url" .!= "/gpg/pubkey.asc"
|
|
<*> o .:? "nav" .!= []
|
|
<*> o .:? "portals" .!= []
|
|
|
|
instance FromJSON NavLink where
|
|
parseJSON = withObject "NavLink" $ \o -> NavLink
|
|
<$> o .: "href"
|
|
<*> o .: "label"
|
|
|
|
instance FromJSON Portal where
|
|
parseJSON = withObject "Portal" $ \o -> Portal
|
|
<$> o .: "slug"
|
|
<*> o .: "name"
|
|
|
|
-- ---------------------------------------------------------------------------
|
|
-- Global config value
|
|
-- ---------------------------------------------------------------------------
|
|
|
|
-- | Loaded from @site.yaml@ at the project root on first access.
|
|
-- @NOINLINE@ prevents GHC from duplicating the I/O. If @site.yaml@ is
|
|
-- missing or invalid, evaluation throws a parse exception.
|
|
{-# NOINLINE siteConfig #-}
|
|
siteConfig :: SiteConfig
|
|
siteConfig = unsafePerformIO (decodeFileThrow "site.yaml")
|
|
|
|
-- | The site's hostname, derived from 'siteUrl'. Used by 'Filters.Links'
|
|
-- to distinguish self-links from external links.
|
|
siteHost :: Text
|
|
siteHost = extractHost (siteUrl siteConfig)
|
|
where
|
|
extractHost url
|
|
| Just rest <- T.stripPrefix "https://" url = hostOf rest
|
|
| Just rest <- T.stripPrefix "http://" url = hostOf rest
|
|
| otherwise = T.toLower url
|
|
hostOf rest =
|
|
let withPort = T.takeWhile (\c -> c /= '/' && c /= '?' && c /= '#') rest
|
|
in T.toLower (T.takeWhile (/= ':') withPort)
|
|
|
|
-- | Default author name as a 'String', for Hakyll metadata APIs that use
|
|
-- 'String' rather than 'Text'.
|
|
defaultAuthor :: String
|
|
defaultAuthor = T.unpack (authorName siteConfig)
|