packages feed

ema-0.8.0.0: src/Ema/Route/Lib/Extra/SlugRoute.hs

module Ema.Route.Lib.Extra.SlugRoute (
  SlugRoute,
  mkSlugRoute,
) where

import Data.Map.Strict qualified as Map
import Data.Text qualified as T
import Ema.Route.Class (IsRoute (..))
import Ema.Route.Prism (htmlSuffixPrism, toPrism_)
import Network.URI.Slug (Slug)
import Network.URI.Slug qualified as Slug
import Optics.Core (prism', (%))
import System.FilePath (splitExtension, splitPath)

{- | Route to a file that is associated with a value of type `a`.

 A route to foo/bar/qux.md, for instance, is encoded as /foo/bar/qux. ie., the
 extension is dropped.
-}
newtype SlugRoute (a :: Type) = SlugRoute {unSlugRoute :: NonEmpty Slug}
  deriving stock (Eq, Ord, Show, Generic)

instance IsRoute (SlugRoute a) where
  type RouteModel (SlugRoute a) = Map (SlugRoute a) a
  routePrism m =
    let encode (SlugRoute slugs) =
          toString $ T.intercalate "/" $ Slug.unSlug <$> toList slugs
        decode fp = do
          guard $ not $ null fp
          slugs <- nonEmpty $ fromString . toString <$> T.splitOn "/" (toText fp)
          let r = SlugRoute slugs
          guard $ Map.member r m
          pure r
     in toPrism_ $ htmlSuffixPrism % prism' encode decode
  routeUniverse = Map.keys

mkSlugRoute :: forall a. FilePath -> Maybe (String, SlugRoute a)
mkSlugRoute (splitExtension -> (relFp, ext')) = do
  let slugs = fromString . toString . T.dropWhileEnd (== '/') . toText <$> splitPath relFp
  (ext',) <$> viaNonEmpty SlugRoute slugs