mirror of
https://github.com/srid/ema.git
synced 2024-12-02 09:15:10 +03:00
Simplify FileRoute further by using FilePath instead of [Slug]
This commit is contained in:
parent
edd49514bf
commit
7f6f15946c
@ -115,7 +115,6 @@ library
|
|||||||
Ema.Generate
|
Ema.Generate
|
||||||
Ema.Route
|
Ema.Route
|
||||||
Ema.Route.Slug
|
Ema.Route.Slug
|
||||||
Ema.Route.UrlStrategy
|
|
||||||
Ema.Server
|
Ema.Server
|
||||||
|
|
||||||
if flag(with-examples)
|
if flag(with-examples)
|
||||||
|
@ -26,13 +26,13 @@ data Route
|
|||||||
data Model = Model Text
|
data Model = Model Text
|
||||||
|
|
||||||
instance FileRoute Route where
|
instance FileRoute Route where
|
||||||
encodeRoute =
|
encodeFileRoute =
|
||||||
Ema.htmlSlugs . \case
|
\case
|
||||||
Index -> mempty
|
Index -> "index.html"
|
||||||
About -> one "about"
|
About -> "about.html"
|
||||||
decodeRoute = \case
|
decodeFileRoute = \case
|
||||||
[] -> Just Index
|
"index.html" -> Just Index
|
||||||
["about"] -> Just About
|
"about.html" -> Just About
|
||||||
_ -> Nothing
|
_ -> Nothing
|
||||||
|
|
||||||
main :: IO ()
|
main :: IO ()
|
||||||
|
@ -27,13 +27,12 @@ data Route
|
|||||||
deriving (Show, Enum, Bounded)
|
deriving (Show, Enum, Bounded)
|
||||||
|
|
||||||
instance FileRoute Route where
|
instance FileRoute Route where
|
||||||
encodeRoute =
|
encodeFileRoute = \case
|
||||||
Ema.htmlSlugs . \case
|
Index -> "index.html"
|
||||||
Index -> mempty
|
OnlyTime -> "time.html"
|
||||||
OnlyTime -> one "time"
|
decodeFileRoute = \case
|
||||||
decodeRoute = \case
|
"index.html" -> Just Index
|
||||||
[] -> Just Index
|
"time.html" -> Just OnlyTime
|
||||||
["time"] -> Just OnlyTime
|
|
||||||
_ -> Nothing
|
_ -> Nothing
|
||||||
|
|
||||||
main :: IO ()
|
main :: IO ()
|
||||||
|
@ -6,7 +6,7 @@ module Ema.Generate where
|
|||||||
|
|
||||||
import Control.Exception (throw)
|
import Control.Exception (throw)
|
||||||
import Control.Monad.Logger
|
import Control.Monad.Logger
|
||||||
import Ema.Route (FileRoute, routeFile)
|
import Ema.Route (FileRoute (encodeFileRoute))
|
||||||
import System.Directory (copyFile, createDirectoryIfMissing, doesDirectoryExist, doesFileExist, doesPathExist)
|
import System.Directory (copyFile, createDirectoryIfMissing, doesDirectoryExist, doesFileExist, doesPathExist)
|
||||||
import System.FilePath (takeDirectory, (</>))
|
import System.FilePath (takeDirectory, (</>))
|
||||||
import System.FilePattern.Directory (getDirectoryFiles)
|
import System.FilePattern.Directory (getDirectoryFiles)
|
||||||
@ -33,7 +33,7 @@ generate dest model staticAssets routes render = do
|
|||||||
error $ "Destination does not exist: " <> toText dest
|
error $ "Destination does not exist: " <> toText dest
|
||||||
log LevelInfo $ "Writing " <> show (length routes) <> " routes"
|
log LevelInfo $ "Writing " <> show (length routes) <> " routes"
|
||||||
forM_ routes $ \r -> do
|
forM_ routes $ \r -> do
|
||||||
let fp = dest </> routeFile r
|
let fp = dest </> encodeFileRoute r
|
||||||
log LevelInfo $ toText $ "W " <> fp
|
log LevelInfo $ toText $ "W " <> fp
|
||||||
let !s = render model r
|
let !s = render model r
|
||||||
liftIO $ do
|
liftIO $ do
|
||||||
|
@ -35,7 +35,7 @@ import System.FSNotify
|
|||||||
withManager,
|
withManager,
|
||||||
)
|
)
|
||||||
import System.FilePath (isRelative, makeRelative)
|
import System.FilePath (isRelative, makeRelative)
|
||||||
import System.FilePattern (FilePattern, matchMany, (?==))
|
import System.FilePattern (FilePattern, (?==))
|
||||||
import System.FilePattern.Directory (getDirectoryFilesIgnore)
|
import System.FilePattern.Directory (getDirectoryFilesIgnore)
|
||||||
import UnliftIO (MonadUnliftIO, toIO, withRunInIO)
|
import UnliftIO (MonadUnliftIO, toIO, withRunInIO)
|
||||||
|
|
||||||
|
@ -3,52 +3,66 @@
|
|||||||
|
|
||||||
module Ema.Route
|
module Ema.Route
|
||||||
( FileRoute (..),
|
( FileRoute (..),
|
||||||
htmlSlugs,
|
|
||||||
routeUrl,
|
routeUrl,
|
||||||
routeFile,
|
decodeUrlRoute,
|
||||||
Slug (unSlug),
|
Slug (unSlug),
|
||||||
decodeSlug,
|
decodeSlug,
|
||||||
encodeSlug,
|
encodeSlug,
|
||||||
UrlStrategy (..),
|
|
||||||
)
|
)
|
||||||
where
|
where
|
||||||
|
|
||||||
import Data.Default (def)
|
import qualified Data.List.NonEmpty as NE
|
||||||
import Ema.Route.Slug (Slug (unSlug), decodeSlug, encodeSlug)
|
import qualified Data.Text as T
|
||||||
import Ema.Route.UrlStrategy
|
import Ema.Route.Slug (Slug (unSlug), decodeSlug, encodeSlug, unicodeNormalize)
|
||||||
( UrlStrategy (..),
|
import qualified Network.URI.Encode as UE
|
||||||
slugFileWithStrategy,
|
import System.FilePath ((</>))
|
||||||
slugRelUrlWithStrategy,
|
|
||||||
)
|
|
||||||
|
|
||||||
-- | Route to a generated file.
|
-- | Route to a generated file.
|
||||||
class FileRoute route where
|
class FileRoute route where
|
||||||
-- | Slug path as well as the extension of the file corresponding to this
|
-- | Slug path as well as the extension of the file corresponding to this
|
||||||
-- route.
|
-- route.
|
||||||
encodeRoute :: route -> ([Slug], String)
|
encodeFileRoute :: route -> FilePath
|
||||||
|
|
||||||
-- | Decode a slug path into a route. The final component of the slug path
|
-- | Decode a slug path into a route. The final component of the slug path
|
||||||
-- will contain the extension if there is any.
|
-- will contain the extension if there is any.
|
||||||
decodeRoute :: [Slug] -> Maybe route
|
decodeFileRoute :: FilePath -> Maybe route
|
||||||
|
|
||||||
htmlSlugs :: [Slug] -> ([Slug], String)
|
|
||||||
htmlSlugs = (,".html")
|
|
||||||
|
|
||||||
-- | The unit model is useful when using Ema in pure fashion (see @Ema.runEmaPure@) with a single route (index.html) only.
|
-- | The unit model is useful when using Ema in pure fashion (see @Ema.runEmaPure@) with a single route (index.html) only.
|
||||||
instance FileRoute () where
|
instance FileRoute () where
|
||||||
encodeRoute () = ([], ".html")
|
encodeFileRoute () = "index.html"
|
||||||
decodeRoute = \case
|
decodeFileRoute = \case
|
||||||
[] -> Just ()
|
"index.html" -> Just ()
|
||||||
_ -> Nothing
|
_ -> Nothing
|
||||||
|
|
||||||
|
-- | Decode a URL path into a route
|
||||||
|
decodeUrlRoute :: FileRoute route => Text -> Maybe route
|
||||||
|
decodeUrlRoute (toString -> s) =
|
||||||
|
decodeFileRoute s
|
||||||
|
<|> decodeFileRoute (s <> ".html")
|
||||||
|
<|> decodeFileRoute (s </> "index.html")
|
||||||
|
|
||||||
-- | Return the relative URL of the given route
|
-- | Return the relative URL of the given route
|
||||||
--
|
--
|
||||||
-- As the returned URL is relative, you will have to either make it absolute (by
|
-- As the returned URL is relative, you will have to either make it absolute (by
|
||||||
-- prepending with `/`) or set the `<base>` URL in your HTML head element.
|
-- prepending with `/`) or set the `<base>` URL in your HTML head element.
|
||||||
routeUrl :: forall r. FileRoute r => r -> Text
|
routeUrl :: forall r. FileRoute r => r -> Text
|
||||||
routeUrl r =
|
routeUrl =
|
||||||
slugRelUrlWithStrategy def (encodeRoute r)
|
relUrlFromPath . encodeFileRoute
|
||||||
|
where
|
||||||
routeFile :: forall r. FileRoute r => r -> FilePath
|
relUrlFromPath :: FilePath -> Text
|
||||||
routeFile r =
|
relUrlFromPath fp =
|
||||||
slugFileWithStrategy def (encodeRoute r)
|
case T.stripSuffix ".html" (toText fp) of
|
||||||
|
Just htmlFp ->
|
||||||
|
case nonEmpty (UE.encodeText . unicodeNormalize <$> T.splitOn "/" htmlFp) of
|
||||||
|
Nothing ->
|
||||||
|
""
|
||||||
|
Just (removeLastIf "index" -> partsSansIndex) ->
|
||||||
|
T.intercalate "/" partsSansIndex
|
||||||
|
Nothing ->
|
||||||
|
UE.encodeText . unicodeNormalize . toText $ fp
|
||||||
|
where
|
||||||
|
removeLastIf :: Eq a => a -> NonEmpty a -> [a]
|
||||||
|
removeLastIf x xs =
|
||||||
|
if NE.last xs == x
|
||||||
|
then NE.init xs
|
||||||
|
else toList xs
|
@ -31,7 +31,8 @@ instance IsString Slug where
|
|||||||
if "/" `T.isInfixOf` s
|
if "/" `T.isInfixOf` s
|
||||||
then error ("Slug cannot contain a slash: " <> s)
|
then error ("Slug cannot contain a slash: " <> s)
|
||||||
else Slug (unicodeNormalize s)
|
else Slug (unicodeNormalize s)
|
||||||
where
|
|
||||||
-- Normalize varying non-ascii strings (in filepaths / slugs) to one
|
-- Normalize varying non-ascii strings (in filepaths / slugs) to one
|
||||||
-- representation, so that they can be reliably linked to.
|
-- representation, so that they can be reliably linked to.
|
||||||
unicodeNormalize = UT.normalize UT.NFC . toText
|
unicodeNormalize :: Text -> Text
|
||||||
|
unicodeNormalize = UT.normalize UT.NFC . toText
|
||||||
|
@ -1,49 +0,0 @@
|
|||||||
{-# LANGUAGE InstanceSigs #-}
|
|
||||||
{-# LANGUAGE QuasiQuotes #-}
|
|
||||||
|
|
||||||
module Ema.Route.UrlStrategy where
|
|
||||||
|
|
||||||
import Data.Default (Default, def)
|
|
||||||
import qualified Data.List.NonEmpty as NE
|
|
||||||
import qualified Data.Text as T
|
|
||||||
import Ema.Route.Slug (Slug (unSlug), decodeSlug, encodeSlug)
|
|
||||||
import System.FilePath (joinPath)
|
|
||||||
|
|
||||||
data UrlStrategy
|
|
||||||
= -- | URLs always end with a slash, and correspond to index.html in that folder
|
|
||||||
UrlStrategy_FolderOnly
|
|
||||||
| -- | Pretty URLs without ugly .html ext or slash-suffix
|
|
||||||
UrlStrategy_HtmlOnlySansExt
|
|
||||||
deriving (Eq, Show, Ord)
|
|
||||||
|
|
||||||
instance Default UrlStrategy where
|
|
||||||
def = UrlStrategy_HtmlOnlySansExt
|
|
||||||
|
|
||||||
slugRelUrlWithStrategy :: UrlStrategy -> ([Slug], String) -> Text
|
|
||||||
slugRelUrlWithStrategy strat (slugs, ".html") =
|
|
||||||
case strat of
|
|
||||||
UrlStrategy_FolderOnly ->
|
|
||||||
T.intercalate "/" (encodeSlug <$> slugs)
|
|
||||||
UrlStrategy_HtmlOnlySansExt ->
|
|
||||||
case nonEmpty slugs of
|
|
||||||
Nothing ->
|
|
||||||
""
|
|
||||||
Just (removeLastIf (decodeSlug "index") -> slugsWithoutIndex) ->
|
|
||||||
T.intercalate "/" (encodeSlug <$> slugsWithoutIndex)
|
|
||||||
where
|
|
||||||
removeLastIf :: Eq a => a -> NonEmpty a -> [a]
|
|
||||||
removeLastIf x xs =
|
|
||||||
if NE.last xs == x
|
|
||||||
then NE.init xs
|
|
||||||
else toList xs
|
|
||||||
slugRelUrlWithStrategy _ (slugs, ext) =
|
|
||||||
T.intercalate "/" (encodeSlug <$> slugs) <> toText ext
|
|
||||||
|
|
||||||
slugFileWithStrategy :: UrlStrategy -> ([Slug], String) -> FilePath
|
|
||||||
slugFileWithStrategy strat (slugs, ext) =
|
|
||||||
case strat of
|
|
||||||
UrlStrategy_FolderOnly ->
|
|
||||||
joinPath $ fmap (toString . unSlug) slugs <> ["index" <> ext]
|
|
||||||
UrlStrategy_HtmlOnlySansExt ->
|
|
||||||
let (term :| (reverse -> parts)) = fromMaybe ("index" :| []) $ nonEmpty (reverse $ fmap (toString . unSlug) slugs)
|
|
||||||
in joinPath $ parts <> [term <> ext]
|
|
@ -9,8 +9,7 @@ import Control.Monad.Logger
|
|||||||
import Data.LVar (LVar)
|
import Data.LVar (LVar)
|
||||||
import qualified Data.LVar as LVar
|
import qualified Data.LVar as LVar
|
||||||
import qualified Data.Text as T
|
import qualified Data.Text as T
|
||||||
import Ema.Route (FileRoute (..))
|
import Ema.Route (FileRoute (..), decodeUrlRoute)
|
||||||
import qualified Ema.Route.Slug as Slug
|
|
||||||
import GHC.IO.Unsafe (unsafePerformIO)
|
import GHC.IO.Unsafe (unsafePerformIO)
|
||||||
import NeatInterpolation (text)
|
import NeatInterpolation (text)
|
||||||
import qualified Network.HTTP.Types as H
|
import qualified Network.HTTP.Types as H
|
||||||
@ -128,7 +127,7 @@ runServerWithWebSocketHotReload port model render = do
|
|||||||
<> show @Text err
|
<> show @Text err
|
||||||
<> "</pre><p>Once you fix your code this page will automatically update.</body>"
|
<> "</pre><p>Once you fix your code this page will automatically update.</body>"
|
||||||
routeFromPathInfo =
|
routeFromPathInfo =
|
||||||
decodeRoute @route . fmap Slug.decodeSlug
|
decodeUrlRoute @route . T.intercalate "/"
|
||||||
-- TODO: It would be good have this also get us the stack trace.
|
-- TODO: It would be good have this also get us the stack trace.
|
||||||
unsafeCatch :: Exception e => a -> (e -> a) -> a
|
unsafeCatch :: Exception e => a -> (e -> a) -> a
|
||||||
unsafeCatch x f = unsafePerformIO $ catch (seq x $ pure x) (pure . f)
|
unsafeCatch x f = unsafePerformIO $ catch (seq x $ pure x) (pure . f)
|
||||||
|
Loading…
Reference in New Issue
Block a user