1 -- For Dataable instances
2 {-# LANGUAGE InstanceSigs #-}
4 {-# LANGUAGE RankNTypes #-}
5 -- For Dataable instances
6 {-# LANGUAGE UndecidableInstances #-}
8 module Literate.Web.Semantics.Compiler where
10 import Control.Applicative (Applicative (..))
11 import Control.Monad (Monad (..), forM_)
12 import Control.Monad.Classes qualified as MC
14 import Data.ByteString.Builder qualified as BSB
15 import Data.Either (Either (..))
17 import Data.Foldable (toList)
18 import Data.Function (id, ($), (.))
19 import Data.Functor (Functor (..), (<$>))
20 import Data.List qualified as List
21 import Data.Map.Strict qualified as Map
22 import Data.Maybe (Maybe (..))
23 import Data.Monoid (Monoid (..))
25 import Data.Proxy (Proxy (..))
26 import Data.Semigroup (Semigroup (..))
27 import Data.Text (Text)
28 import Data.Text qualified as Text
29 import Data.Tuple (fst, snd)
30 import GHC.Generics (Generic)
31 import GHC.Stack (HasCallStack)
32 import Symantic qualified as Sym
33 import System.Directory qualified as Sys
34 import System.FilePath qualified as Sys
35 import System.FilePattern.Directory qualified as Sys
36 import System.IO qualified as Sys
37 import Text.Show (Show (..))
38 import Type.Reflection ((:~:) (..))
40 import Literate.Web.Syntaxes
41 import Literate.Web.Types.MIME
42 import Literate.Web.Types.URL
46 -- | Interpreter building a static Web site.
48 -- Embed a 'Monad' @m@ to give access to a model if need be.
49 newtype Compiler m a = Compiler {unCompiler :: m [Output a]}
51 data CompilerEndpoint m a next = CompilerEndpoint
52 { compilerEndpointProof :: next :~: m BSB.Builder
53 , compilerEndpointData :: a
55 type instance Sym.ToFEndpoint (Compiler m) a next = CompilerEndpoint m a next
57 compilerEndpoint :: a -> CompilerEndpoint m a (m BSB.Builder)
58 compilerEndpoint = CompilerEndpoint Refl
60 compiler :: Compiler m (m BSB.Builder) -> Compiler m (m BSB.Builder)
64 MC.MonadExec Sys.IO m =>
67 (a --> m BSB.Builder) ->
69 compile CompilerEnv{..} router content = do
70 outputs <- unCompiler router
71 MC.exec @Sys.IO $ Sys.removePathForcibly compilerEnvDest
72 forM_ outputs $ \out -> do
73 let outFullPath = outputPathFile (outputPathRelative out)
74 -- MC.exec $ Sys.hPutStrLn Sys.stderr $ "mkdir: " <> show (Sys.takeDirectory outFullPath)
76 Sys.createDirectoryIfMissing True $
77 compilerEnvDest Sys.</> Sys.takeDirectory outFullPath
78 -- MC.exec $ Sys.hPutStrLn Sys.stderr $ "write: " <> show outFullPath
79 bsb <- outputData out content
81 Sys.withBinaryFile (compilerEnvDest Sys.</> outFullPath) Sys.WriteMode $ \h -> do
84 siteManifest :: Monad m => Compiler m a -> m [Sys.FilePath]
85 siteManifest router = (outputPathFile . outputPathRelative <$>) <$> unCompiler router
90 (a --> m BSB.Builder) ->
91 m (Map.Map OutputPath (m BSB.Builder))
92 siteMap router content = do
93 outputs <- unCompiler router
96 [ (outputPathRelative out, outputData out content)
100 -- ** Type 'CompilerEnv'
101 data CompilerEnv = CompilerEnv
102 { compilerEnvDest :: Sys.FilePath
103 , -- , compilerEnvSource :: Sys.FilePath
104 compilerEnvIndex :: Sys.FilePath
105 -- , compilerEnvModel :: model
106 -- , compilerEnvPath :: [PathSegment]
110 -- instance Applicative m => Applicative (Compiler m) where
111 -- pure = Compiler . pure . pure . pure
112 -- liftA2 f (Compiler a) (Compiler b) = Compiler (liftA2 (liftA2 (liftA2 f)) a b)
113 -- instance Monad m => Monad (Compiler m) where
115 -- Compiler mloa >>= a2mlob =
119 -- forM loa $ \oa -> do
120 -- lob <- unCompiler $ a2mlob $ outputData oa
124 -- { outputPath = outputPath oa <> outputPath ob
125 -- , outputExts = outputExts oa <> outputExts ob
127 instance Applicative m => Sym.ProductFunctor (Compiler m) where
128 Compiler a <.> Compiler b = Compiler $ liftA2 (liftA2 (<.>)) a b
129 Compiler a <. Compiler b = Compiler $ liftA2 (liftA2 (<.)) a b
130 Compiler a .> Compiler b = Compiler $ liftA2 (liftA2 (.>)) a b
132 -- instance Applicative m => Sym.AlternativeFunctor (Compiler m) where
133 -- Compiler a <|> Compiler b = Compiler (liftA2 (<>) a b)
135 instance Applicative m => Sym.SumFunctor (Compiler m) where
136 Compiler a <+> Compiler b = Compiler $ liftA2 (\as bs -> (a2e <$> as) <> (b2e <$> bs)) a b
138 a2e :: Output a -> Output (Either a b)
139 a2e o = o{outputData = outputData o . fst}
140 b2e :: Output b -> Output (Either a b)
141 b2e o = o{outputData = outputData o . snd}
143 instance (Applicative m, Sym.ToFable a) => Optionable a (Compiler m) where
144 optional (Compiler ma) = Compiler $ (\as -> forNothing : (forJust <$> as)) <$> ma
146 forNothing :: Output (Maybe a)
147 forNothing = Output{outputPath = mempty, outputData = ($ Nothing)}
148 forJust :: Output a -> Output (Maybe a)
149 forJust o = o{outputData = \k -> outputData o $ Sym.tofOffun $ k . Just}
151 -- optional (Compiler ma) = Compiler $ (\as -> forNothing : (forJust <$> as)) <$> ma
152 -- --pure Nothing Sym.<|> (Just <$> ma)
154 -- forNothing :: Output a -> Output (Maybe a)
155 -- forNothing o = o{ outputData = fst }
156 -- forJust :: Output a -> Output (Maybe a)
157 -- forJust o = o{ outputData = outputData o . snd }
163 , Sym.IsToF a ~ 'False
164 , eot ~ Sym.EoT (Sym.ADT a)
168 Dataable a (Compiler m)
170 dataType :: sem (Sym.EoT (Sym.ADT a)) -> sem a
171 dataType (Compiler mos) = Compiler ((dataType <$>) <$> mos)
176 , Sym.IsToF a ~ 'False
177 , eot ~ Sym.EoT (Sym.ADT a)
182 dataType :: sem (Sym.EoT (Sym.ADT a)) -> sem a
183 dataType o = o{outputData = \k -> (outputData o) $ Sym.tofOffun $ k . Sym.adtOfeot}
184 instance Applicative m => PathSegmentable (Compiler m) where
191 { outputPathSegs = [s]
192 , outputPathExts = []
200 , MimeTypes ts (MimeEncodable a)
202 Responsable a ts n (Compiler m)
207 ( \(_mediaType, MimeType (Proxy :: Proxy t)) ->
211 { outputPathSegs = []
212 , outputPathExts = [decodePathSegment (fileExtension @t)]
214 , outputData = \(CompilerEndpoint Refl (Response ma)) ->
215 mimeEncode @_ @t <$> ma
218 <$> toList (mimeTypesMap @ts @(MimeEncodable a))
222 data Output a = Output
223 { outputPath :: OutputPath
224 , outputData :: forall next. (a --> next) -> next
227 outputBuilder :: Output a -> (a --> m BSB.Builder) -> m BSB.Builder
228 outputBuilder = outputData
230 outputPathRelative :: Output a -> OutputPath
231 outputPathRelative out
232 | List.null (outputPathExts outPath) = outPath{outputPathExts = ["txt"]}
233 | otherwise = outPath
235 outPath = outputPath out
237 -- *** Type 'OutputPath'
238 data OutputPath = OutputPath
239 { outputPathSegs :: [PathSegment]
240 , outputPathExts :: [PathSegment]
242 deriving (Eq, Ord, Show)
244 outputPathFile :: OutputPath -> Sys.FilePath
245 outputPathFile outPath =
246 List.intercalate "." $
247 encodePath (outputPathSegs outPath)
248 : (Text.unpack . encodePathSegment <$> outputPathExts outPath)
250 instance Semigroup OutputPath where
253 { outputPathSegs = outputPathSegs x <> outputPathSegs y
254 , outputPathExts = outputPathExts x <> outputPathExts y
256 instance Monoid OutputPath where
257 mempty = OutputPath{outputPathSegs = [], outputPathExts = []}
259 instance Sym.ProductFunctor Output where
262 { outputPath = outputPath a <> outputPath b
263 , outputData = outputData b . outputData a
267 { outputPath = outputPath a <> outputPath b
268 , outputData = outputData b . outputData a
272 { outputPath = outputPath a <> outputPath b
273 , outputData = outputData b . outputData a
276 -- compile :: Show a => Renderable a => Compiler a -> CompilerEnv -> Sys.IO ()
277 -- compile compiler conf@CompilerEnv{..} = do
278 -- createDirectoryIfMissing True compilerEnvDest
279 -- let router = unCompiler compiler
280 -- when (null router) $
281 -- error "no router, nothing to compile"
282 -- -- Sys.hPutStrLn Sys.stderr $ "conf: " <> show conf
283 -- -- forM_ router $ \comp -> do
284 -- -- Sys.hPutStrLn Sys.stderr $ "route: " <> show comp
285 -- forM_ router $ \Comp{..} -> do
286 -- -- Sys.hPutStrLn Sys.stderr $ "route: " <> show comp
287 -- let routePath = pathOfPathSegments compPathSegments
288 -- in case render compData of
289 -- Left staticPath ->
290 -- doesPathExist (compilerEnvSource </> staticPath) >>= \case
292 -- -- TODO: In current branch, we don't expect this to be a directory.
293 -- -- Although the user may pass it, but review before merge.
294 -- Sys.hPutStrLn Sys.stderr $
297 -- ( (compilerEnvSource </> staticPath)
298 -- , (compilerEnvDest </> staticPath)
300 -- copyDirRecursively
302 -- (compilerEnvSource </> staticPath)
305 -- Sys.hPutStrLn Sys.stderr $ "error: path does not exist: " <> (compilerEnvSource </> staticPath)
306 -- -- log LevelWarn $ toText $ "? " <> staticPath <> " (missing)"
308 -- let outputFullPath = compilerEnvDest </> routePath
310 -- maybe (routePath </> compilerEnvIndex)
311 -- (routePath Sys.FilePath.<.>)
314 -- -- Sys.hPutStrLn Sys.stderr $ "mkdir: " <> show (Sys.takeDirectory outputFullPath)
315 -- createDirectoryIfMissing True (Sys.takeDirectory outputFullPath)
316 -- -- Sys.hPutStrLn Sys.stderr $ "write: " <> show outputFullPath
317 -- BSL.writeFile outputFullPath bs
319 copyDirRecursively ::
321 -- | Source file path relative to CWD
323 -- | Absolute path to source file to copy.
325 -- | Directory *under* which the source file/dir will be copied
328 copyDirRecursively srcRel srcAbs destParent = do
329 Sys.doesFileExist srcAbs >>= \case
331 Sys.hPutStrLn Sys.stderr $ "copyDirRecursively: " <> show ("srcAbs is a file" :: Text, srcAbs)
332 copyFileCreatingParents srcAbs (destParent Sys.</> srcRel)
334 Sys.doesDirectoryExist srcAbs >>= \case
336 Sys.hPutStrLn Sys.stderr $ "copyDirRecursively: " <> show ("srcAbs is neither a file nor directory" :: Text, srcAbs)
338 -- throw $ StaticAssetMissing srcAbs
340 Sys.hPutStrLn Sys.stderr $ "copyDirRecursively: " <> show ("srcAbs is a directory" :: Text, srcAbs)
341 Sys.createDirectoryIfMissing True (destParent Sys.</> srcRel)
342 fs <- Sys.getDirectoryFiles srcAbs ["**"]
345 a = srcAbs Sys.</> fp
346 b = destParent Sys.</> srcRel Sys.</> fp
347 copyFileCreatingParents a b
349 copyFileCreatingParents a b = do
350 Sys.createDirectoryIfMissing True (Sys.takeDirectory b)