packages feed

emanote-1.0.2.0: src/Emanote/Route/SiteRoute/Type.hs

{-# LANGUAGE DeriveAnyClass #-}

module Emanote.Route.SiteRoute.Type (
  SiteRoute (..),
  VirtualRoute (..),
  ResourceRoute (..),
  decodeVirtualRoute,
  encodeVirtualRoute,
  encodeTagIndexR,
) where

import Data.Aeson (ToJSON)
import Emanote.Pandoc.Markdown.Syntax.HashTag qualified as HT
import Emanote.Route.Ext qualified as Ext
import Emanote.Route.ModelRoute (LMLRoute, StaticFileRoute, lmlRouteCase)
import Emanote.Route.R qualified as R
import Network.URI.Slug qualified as Slug
import Relude hiding (show)
import Text.Show (show)

-- | A route to a virtual resource (not in `Model`)
data VirtualRoute
  = VirtualRoute_Index
  | VirtualRoute_TagIndex [HT.TagNode]
  | VirtualRoute_Export
  | VirtualRoute_StorkIndex
  | VirtualRoute_TaskIndex
  deriving stock (Eq, Ord, Show, Generic)
  deriving anyclass (ToJSON)

{- | A route to a resource in `Model`

 This is *mostly isomorphic* to `ModelRoute`, except for containing the
 absolute path to the static file.
-}
data ResourceRoute
  = ResourceRoute_StaticFile StaticFileRoute FilePath
  | ResourceRoute_LML LMLRoute
  deriving stock (Eq, Show, Ord, Generic)
  deriving anyclass (ToJSON)

data SiteRoute
  = SiteRoute_VirtualRoute VirtualRoute
  | SiteRoute_ResourceRoute ResourceRoute
  | SiteRoute_MissingR FilePath
  | SiteRoute_AmbiguousR FilePath (NonEmpty LMLRoute)
  deriving stock (Eq, Ord, Generic)

instance Show SiteRoute where
  show = \case
    SiteRoute_MissingR urlPath ->
      "404: " <> urlPath
    SiteRoute_AmbiguousR urlPath _notes ->
      "Amb: " <> urlPath
    SiteRoute_ResourceRoute rr ->
      case rr of
        ResourceRoute_StaticFile r _fp ->
          show r
        ResourceRoute_LML r ->
          show $ lmlRouteCase r
    SiteRoute_VirtualRoute x ->
      show x

decodeVirtualRoute :: FilePath -> Maybe VirtualRoute
decodeVirtualRoute fp =
  (VirtualRoute_Index <$ decodeIndexR fp)
    <|> (VirtualRoute_TagIndex <$> decodeTagIndexR fp)
    <|> (VirtualRoute_Export <$ decodeExportR fp)
    <|> (VirtualRoute_StorkIndex <$ decodeStorkIndexR fp)
    <|> (VirtualRoute_TaskIndex <$ decodeTaskIndexR fp)

decodeIndexR :: FilePath -> Maybe ()
decodeIndexR fp = do
  "-" :| ["all"] <- pure $ R.unRoute $ R.decodeHtmlRoute fp
  pass

decodeExportR :: FilePath -> Maybe ()
decodeExportR fp = do
  "-" :| ["export.json"] <- R.unRoute <$> R.decodeAnyRoute fp
  pass

decodeStorkIndexR :: FilePath -> Maybe ()
decodeStorkIndexR fp = do
  "-" :| ["stork.st"] <- R.unRoute <$> R.decodeAnyRoute fp
  pass

decodeTagIndexR :: FilePath -> Maybe [HT.TagNode]
decodeTagIndexR fp = do
  "-" :| "tags" : tagPath <- pure $ R.unRoute $ R.decodeHtmlRoute fp
  pure $ fmap (HT.TagNode . Slug.unSlug) tagPath

decodeTaskIndexR :: FilePath -> Maybe ()
decodeTaskIndexR fp = do
  "-" :| ["tasks"] <- pure $ R.unRoute $ R.decodeHtmlRoute fp
  pass

-- NOTE: The sentinel route slugs in this function should match with those of
-- the decoders above.
encodeVirtualRoute :: VirtualRoute -> FilePath
encodeVirtualRoute = \case
  VirtualRoute_TagIndex tagNodes ->
    R.encodeRoute $ encodeTagIndexR tagNodes
  VirtualRoute_Index ->
    R.encodeRoute $ R.R @() @('Ext.Html) $ "-" :| ["all"]
  VirtualRoute_Export ->
    R.encodeRoute $ R.R @Ext.SourceExt @('Ext.AnyExt) $ "-" :| ["export.json"]
  VirtualRoute_StorkIndex ->
    R.encodeRoute $ R.R @Ext.SourceExt @('Ext.AnyExt) $ "-" :| ["stork.st"]
  VirtualRoute_TaskIndex ->
    R.encodeRoute $ R.R @() @('Ext.Html) $ "-" :| ["tasks"]

encodeTagIndexR :: [HT.TagNode] -> R.R 'Ext.Html
encodeTagIndexR tagNodes =
  R.R $ "-" :| "tags" : fmap (fromString . toString . HT.unTagNode) tagNodes