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 (..), fromMaybe)
23 import Data.Monoid (Last (..), 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 (MediaType, m BSB.Builder))
92 siteMap router content = do
93 outputs <- unCompiler router
96 [ ( outputPathRelative out
98 ( outputType out & getLast & fromMaybe (mediaTypeFor (Proxy @HTML))
99 , outputData out content
105 -- ** Type 'CompilerEnv'
106 data CompilerEnv = CompilerEnv
107 { compilerEnvDest :: Sys.FilePath
108 , -- , compilerEnvSource :: Sys.FilePath
109 compilerEnvIndex :: Sys.FilePath
110 -- , compilerEnvModel :: model
111 -- , compilerEnvPath :: [PathSegment]
115 -- instance Applicative m => Applicative (Compiler m) where
116 -- pure = Compiler . pure . pure . pure
117 -- liftA2 f (Compiler a) (Compiler b) = Compiler (liftA2 (liftA2 (liftA2 f)) a b)
118 -- instance Monad m => Monad (Compiler m) where
120 -- Compiler mloa >>= a2mlob =
124 -- forM loa $ \oa -> do
125 -- lob <- unCompiler $ a2mlob $ outputData oa
129 -- { outputPath = outputPath oa <> outputPath ob
130 -- , outputExts = outputExts oa <> outputExts ob
132 instance Applicative m => Sym.ProductFunctor (Compiler m) where
133 Compiler a <.> Compiler b = Compiler $ liftA2 (liftA2 (<.>)) a b
134 Compiler a <. Compiler b = Compiler $ liftA2 (liftA2 (<.)) a b
135 Compiler a .> Compiler b = Compiler $ liftA2 (liftA2 (.>)) a b
137 -- instance Applicative m => Sym.AlternativeFunctor (Compiler m) where
138 -- Compiler a <|> Compiler b = Compiler (liftA2 (<>) a b)
140 instance Applicative m => Sym.SumFunctor (Compiler m) where
141 Compiler a <+> Compiler b = Compiler $ liftA2 (\as bs -> (a2e <$> as) <> (b2e <$> bs)) a b
143 a2e :: Output a -> Output (Either a b)
144 a2e o = o{outputData = outputData o . fst}
145 b2e :: Output b -> Output (Either a b)
146 b2e o = o{outputData = outputData o . snd}
148 instance (Applicative m, Sym.ToFable a) => Optionable a (Compiler m) where
149 optional (Compiler ma) = Compiler $ (\as -> forNothing : (forJust <$> as)) <$> ma
151 forNothing :: Output (Maybe a)
154 { outputPath = mempty
155 , outputType = mempty
156 , outputData = ($ Nothing)
158 forJust :: Output a -> Output (Maybe a)
159 forJust o = o{outputData = \k -> outputData o $ Sym.tofOffun $ k . Just}
161 instance Functor m => Sym.Voidable (Compiler m) where
162 void _a (Compiler ma) =
164 (\os -> (\o -> o{outputData = id}) <$> os) <$> ma
166 -- optional (Compiler ma) = Compiler $ (\as -> forNothing : (forJust <$> as)) <$> ma
167 -- --pure Nothing Sym.<|> (Just <$> ma)
169 -- forNothing :: Output a -> Output (Maybe a)
170 -- forNothing o = o{ outputData = fst }
171 -- forJust :: Output a -> Output (Maybe a)
172 -- forJust o = o{ outputData = outputData o . snd }
178 , Sym.IsToF a ~ 'False
179 , eot ~ Sym.EoT (Sym.ADT a)
183 Dataable a (Compiler m)
185 dataType :: sem (Sym.EoT (Sym.ADT a)) -> sem a
186 dataType (Compiler mos) = Compiler ((dataType <$>) <$> mos)
191 , Sym.IsToF a ~ 'False
192 , eot ~ Sym.EoT (Sym.ADT a)
197 dataType :: sem (Sym.EoT (Sym.ADT a)) -> sem a
198 dataType o = o{outputData = \k -> (outputData o) $ Sym.tofOffun $ k . Sym.adtOfeot}
199 instance Applicative m => PathSegmentable (Compiler m) where
206 { outputPathSegs = [s]
207 , outputPathExts = []
209 , outputType = mempty
216 , MimeTypes ts (MimeEncodable a)
218 Responsable a ts n (Compiler m)
223 ( \(mediaType_, MimeType (Proxy :: Proxy t)) ->
227 { outputPathSegs = []
228 , outputPathExts = [decodePathSegment (fileExtension @t)]
230 , outputType = Last $ Just mediaType_
231 , outputData = \(CompilerEndpoint Refl (Response ma)) ->
232 mimeEncode @_ @t <$> ma
235 <$> toList (mimeTypesMap @ts @(MimeEncodable a))
239 data Output a = Output
240 { outputPath :: OutputPath
241 , outputType :: Last MediaType
242 , outputData :: forall next. (a --> next) -> next
245 outputBuilder :: Output a -> (a --> m BSB.Builder) -> m BSB.Builder
246 outputBuilder = outputData
248 outputPathRelative :: Output a -> OutputPath
249 outputPathRelative out
250 | List.null (outputPathExts outPath) = outPath{outputPathExts = ["txt"]}
251 | otherwise = outPath
253 outPath = outputPath out
255 -- *** Type 'OutputPath'
256 data OutputPath = OutputPath
257 { outputPathSegs :: [PathSegment]
258 , outputPathExts :: [PathSegment]
260 deriving (Eq, Ord, Show)
262 outputPathFile :: OutputPath -> Sys.FilePath
263 outputPathFile outPath =
264 List.intercalate "." $
265 encodePath (outputPathSegs outPath)
266 : (Text.unpack . encodePathSegment <$> outputPathExts outPath)
268 instance Semigroup OutputPath where
271 { outputPathSegs = outputPathSegs x <> outputPathSegs y
272 , outputPathExts = outputPathExts x <> outputPathExts y
274 instance Monoid OutputPath where
275 mempty = OutputPath{outputPathSegs = [], outputPathExts = []}
277 instance Sym.ProductFunctor Output where
280 { outputPath = outputPath a <> outputPath b
281 , outputType = outputType a <> outputType b
282 , outputData = outputData b . outputData a
286 { outputPath = outputPath a <> outputPath b
287 , outputType = outputType a <> outputType b
288 , outputData = outputData b . outputData a
292 { outputPath = outputPath a <> outputPath b
293 , outputType = outputType a <> outputType b
294 , outputData = outputData b . outputData a
297 -- compile :: Show a => Renderable a => Compiler a -> CompilerEnv -> Sys.IO ()
298 -- compile compiler conf@CompilerEnv{..} = do
299 -- createDirectoryIfMissing True compilerEnvDest
300 -- let router = unCompiler compiler
301 -- when (null router) $
302 -- error "no router, nothing to compile"
303 -- -- Sys.hPutStrLn Sys.stderr $ "conf: " <> show conf
304 -- -- forM_ router $ \comp -> do
305 -- -- Sys.hPutStrLn Sys.stderr $ "route: " <> show comp
306 -- forM_ router $ \Comp{..} -> do
307 -- -- Sys.hPutStrLn Sys.stderr $ "route: " <> show comp
308 -- let routePath = pathOfPathSegments compPathSegments
309 -- in case render compData of
310 -- Left staticPath ->
311 -- doesPathExist (compilerEnvSource </> staticPath) >>= \case
313 -- -- TODO: In current branch, we don't expect this to be a directory.
314 -- -- Although the user may pass it, but review before merge.
315 -- Sys.hPutStrLn Sys.stderr $
318 -- ( (compilerEnvSource </> staticPath)
319 -- , (compilerEnvDest </> staticPath)
321 -- copyDirRecursively
323 -- (compilerEnvSource </> staticPath)
326 -- Sys.hPutStrLn Sys.stderr $ "error: path does not exist: " <> (compilerEnvSource </> staticPath)
327 -- -- log LevelWarn $ toText $ "? " <> staticPath <> " (missing)"
329 -- let outputFullPath = compilerEnvDest </> routePath
331 -- maybe (routePath </> compilerEnvIndex)
332 -- (routePath Sys.FilePath.<.>)
335 -- -- Sys.hPutStrLn Sys.stderr $ "mkdir: " <> show (Sys.takeDirectory outputFullPath)
336 -- createDirectoryIfMissing True (Sys.takeDirectory outputFullPath)
337 -- -- Sys.hPutStrLn Sys.stderr $ "write: " <> show outputFullPath
338 -- BSL.writeFile outputFullPath bs
340 copyDirRecursively ::
342 -- | Source file path relative to CWD
344 -- | Absolute path to source file to copy.
346 -- | Directory *under* which the source file/dir will be copied
349 copyDirRecursively srcRel srcAbs destParent = do
350 Sys.doesFileExist srcAbs >>= \case
352 Sys.hPutStrLn Sys.stderr $ "copyDirRecursively: " <> show ("srcAbs is a file" :: Text, srcAbs)
353 copyFileCreatingParents srcAbs (destParent Sys.</> srcRel)
355 Sys.doesDirectoryExist srcAbs >>= \case
357 Sys.hPutStrLn Sys.stderr $ "copyDirRecursively: " <> show ("srcAbs is neither a file nor directory" :: Text, srcAbs)
359 -- throw $ StaticAssetMissing srcAbs
361 Sys.hPutStrLn Sys.stderr $ "copyDirRecursively: " <> show ("srcAbs is a directory" :: Text, srcAbs)
362 Sys.createDirectoryIfMissing True (destParent Sys.</> srcRel)
363 fs <- Sys.getDirectoryFiles srcAbs ["**"]
366 a = srcAbs Sys.</> fp
367 b = destParent Sys.</> srcRel Sys.</> fp
368 copyFileCreatingParents a b
370 copyFileCreatingParents a b = do
371 Sys.createDirectoryIfMissing True (Sys.takeDirectory b)