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.Arrow ((>>>))
12 import Control.Monad (Monad (..), forM_)
13 import Control.Monad.Classes qualified as MC
15 import Data.ByteString.Builder qualified as BSB
16 import Data.Either (Either (..))
18 import Data.Foldable (toList)
19 import Data.Function (id, ($), (&), (.))
20 import Data.Functor (Functor (..), (<$>), (<&>))
21 import Data.List qualified as List
22 import Data.Map.Strict qualified as Map
23 import Data.Maybe (Maybe (..), fromMaybe)
24 import Data.Monoid (Last (..), Monoid (..))
26 import Data.Proxy (Proxy (..))
27 import Data.Semigroup (Semigroup (..))
28 import Data.Text (Text)
29 import Data.Text qualified as Text
30 import Data.Tuple (fst, snd)
31 import GHC.Generics (Generic)
32 import GHC.Stack (HasCallStack)
33 import Symantic qualified as Sym
34 import System.Directory qualified as Sys
35 import System.FilePath qualified as Sys
36 import System.FilePattern.Directory qualified as Sys
37 import System.IO qualified as Sys
38 import Text.Show (Show (..))
39 import Type.Reflection ((:~:) (..))
41 import Literate.Web.Syntaxes
42 import Literate.Web.Types.MIME
43 import Literate.Web.Types.URI
47 -- | Interpreter building a static Web site.
49 -- Embed a 'Monad' @m@ to give access to a model if need be.
50 newtype Compiler m a = Compiler {unCompiler :: m [Output a]}
52 data CompilerEndpoint m a next = CompilerEndpoint
53 { compilerEndpointProof :: next :~: m BSB.Builder
54 , compilerEndpointData :: a
56 type instance Sym.ToFEndpoint (Compiler m) a next = CompilerEndpoint m a next
58 compilerEndpoint :: a -> CompilerEndpoint m a (m BSB.Builder)
59 compilerEndpoint = CompilerEndpoint Refl
61 compiler :: Compiler m (m BSB.Builder) -> Compiler m (m BSB.Builder)
65 MC.MonadExec Sys.IO m =>
68 (a --> m BSB.Builder) ->
70 compile CompilerEnv{..} router content = do
71 outputs <- unCompiler router
72 MC.exec @Sys.IO $ Sys.removePathForcibly compilerEnvDest
73 forM_ outputs $ \out -> do
74 let outFullPath = outputPathFile (outputPathRelative out)
75 -- MC.exec $ Sys.hPutStrLn Sys.stderr $ "mkdir: " <> show (Sys.takeDirectory outFullPath)
77 Sys.createDirectoryIfMissing True $
78 compilerEnvDest Sys.</> Sys.takeDirectory outFullPath
79 -- MC.exec $ Sys.hPutStrLn Sys.stderr $ "write: " <> show outFullPath
80 bsb <- outputData out content
82 Sys.withBinaryFile (compilerEnvDest Sys.</> outFullPath) Sys.WriteMode $ \h -> do
85 siteManifest :: Monad m => Compiler m a -> m [Sys.FilePath]
86 siteManifest router = (outputPathFile . outputPathRelative <$>) <$> unCompiler router
91 (a --> m BSB.Builder) ->
92 m (Map.Map OutputPath (MediaType, m BSB.Builder))
93 siteMap router content = do
94 outputs <- unCompiler router
97 [ ( outputPathRelative out
99 ( outputType out & getLast & fromMaybe (mediaTypeFor (Proxy @HTML))
100 , outputData out content
106 -- ** Type 'CompilerEnv'
107 data CompilerEnv = CompilerEnv
108 { compilerEnvDest :: Sys.FilePath
109 , -- , compilerEnvSource :: Sys.FilePath
110 compilerEnvIndex :: Sys.FilePath
111 -- , compilerEnvModel :: model
112 -- , compilerEnvPath :: [PathSegment]
116 -- instance Applicative m => Applicative (Compiler m) where
117 -- pure = Compiler . pure . pure . pure
118 -- liftA2 f (Compiler a) (Compiler b) = Compiler (liftA2 (liftA2 (liftA2 f)) a b)
119 -- instance Monad m => Monad (Compiler m) where
121 -- Compiler mloa >>= a2mlob =
125 -- forM loa $ \oa -> do
126 -- lob <- unCompiler $ a2mlob $ outputData oa
130 -- { outputPath = outputPath oa <> outputPath ob
131 -- , outputExts = outputExts oa <> outputExts ob
133 instance Applicative m => Sym.ProductFunctor (Compiler m) where
134 Compiler a <.> Compiler b = Compiler $ liftA2 (liftA2 (<.>)) a b
135 Compiler a <. Compiler b = Compiler $ liftA2 (liftA2 (<.)) a b
136 Compiler a .> Compiler b = Compiler $ liftA2 (liftA2 (.>)) a b
138 -- instance Applicative m => Sym.AlternativeFunctor (Compiler m) where
139 -- Compiler a <|> Compiler b = Compiler (liftA2 (<>) a b)
141 instance Applicative m => Sym.SumFunctor (Compiler m) where
142 Compiler a <+> Compiler b = Compiler $ liftA2 (\as bs -> (a2e <$> as) <> (b2e <$> bs)) a b
144 a2e :: Output a -> Output (Either a b)
145 a2e o = o{outputData = outputData o . fst}
146 b2e :: Output b -> Output (Either a b)
147 b2e o = o{outputData = outputData o . snd}
149 instance (Applicative m, Sym.ToFable a) => Optionable a (Compiler m) where
150 optional (Compiler ma) = Compiler $ (\as -> forNothing : (forJust <$> as)) <$> ma
152 forNothing :: Output (Maybe a)
155 { outputPath = mempty
156 , outputType = mempty
157 , outputData = ($ Nothing)
159 forJust :: Output a -> Output (Maybe a)
160 forJust o = o{outputData = \k -> outputData o $ Sym.tofOffun $ k . Just}
162 instance Functor m => Sym.Voidable (Compiler m) where
163 void _a (Compiler ma) =
165 (\os -> (\o -> o{outputData = id}) <$> os) <$> ma
167 -- optional (Compiler ma) = Compiler $ (\as -> forNothing : (forJust <$> as)) <$> ma
168 -- --pure Nothing Sym.<|> (Just <$> ma)
170 -- forNothing :: Output a -> Output (Maybe a)
171 -- forNothing o = o{ outputData = fst }
172 -- forJust :: Output a -> Output (Maybe a)
173 -- forJust o = o{ outputData = outputData o . snd }
179 , Sym.IsToF a ~ 'False
180 , eot ~ Sym.EoT (Sym.ADT a)
184 Dataable a (Compiler m)
186 dataType :: sem (Sym.EoT (Sym.ADT a)) -> sem a
187 dataType (Compiler mos) = Compiler ((dataType <$>) <$> mos)
192 , Sym.IsToF a ~ 'False
193 , eot ~ Sym.EoT (Sym.ADT a)
198 dataType :: sem (Sym.EoT (Sym.ADT a)) -> sem a
199 dataType o = o{outputData = \k -> (outputData o) $ Sym.tofOffun $ k . Sym.adtOfeot}
200 instance Applicative m => PathSegmentable (Compiler m) where
207 { outputPathSegs = [s]
208 , outputPathExts = []
210 , outputType = mempty
217 , MimeTypes ts (MimeEncodable a)
219 Responsable a ts n (Compiler m)
224 ( \(mediaType_, MimeType (Proxy :: Proxy t)) ->
228 { outputPathSegs = []
230 [ ext & textToPathSegment
231 | let ext = fileExtension @t
232 , not (Text.null ext)
235 , outputType = Last $ Just mediaType_
236 , outputData = \(CompilerEndpoint Refl (Response ma)) ->
237 mimeEncode @_ @t <$> ma
240 <$> toList (mimeTypesMap @ts @(MimeEncodable a))
244 data Output a = Output
245 { outputPath :: OutputPath
246 , outputType :: Last MediaType
247 , outputData :: forall next. (a --> next) -> next
250 outputBuilder :: Output a -> (a --> m BSB.Builder) -> m BSB.Builder
251 outputBuilder = outputData
253 outputPathRelative :: Output a -> OutputPath
254 outputPathRelative out
255 | List.null (outputPathExts outPath) = outPath{outputPathExts = ["txt"]}
256 | otherwise = outPath
258 outPath = outputPath out
260 -- *** Type 'OutputPath'
261 data OutputPath = OutputPath
262 { outputPathSegs :: [PathSegment]
263 , outputPathExts :: [PathSegment]
265 deriving (Eq, Ord, Show)
267 outputPathFile :: OutputPath -> Sys.FilePath
268 outputPathFile outPath =
269 List.intercalate "." $
270 (outputPathSegs outPath & pathToFilePath)
271 : (outputPathExts outPath <&> (unPathSegment >>> Text.unpack))
273 instance Semigroup OutputPath where
276 { outputPathSegs = outputPathSegs x <> outputPathSegs y
277 , outputPathExts = outputPathExts x <> outputPathExts y
279 instance Monoid OutputPath where
280 mempty = OutputPath{outputPathSegs = [], outputPathExts = []}
282 instance Sym.ProductFunctor Output where
285 { outputPath = outputPath a <> outputPath b
286 , outputType = outputType a <> outputType b
287 , outputData = outputData b . outputData a
291 { outputPath = outputPath a <> outputPath b
292 , outputType = outputType a <> outputType b
293 , outputData = outputData b . outputData a
297 { outputPath = outputPath a <> outputPath b
298 , outputType = outputType a <> outputType b
299 , outputData = outputData b . outputData a
302 -- compile :: Show a => Renderable a => Compiler a -> CompilerEnv -> Sys.IO ()
303 -- compile compiler conf@CompilerEnv{..} = do
304 -- createDirectoryIfMissing True compilerEnvDest
305 -- let router = unCompiler compiler
306 -- when (null router) $
307 -- error "no router, nothing to compile"
308 -- -- Sys.hPutStrLn Sys.stderr $ "conf: " <> show conf
309 -- -- forM_ router $ \comp -> do
310 -- -- Sys.hPutStrLn Sys.stderr $ "route: " <> show comp
311 -- forM_ router $ \Comp{..} -> do
312 -- -- Sys.hPutStrLn Sys.stderr $ "route: " <> show comp
313 -- let routePath = pathOfPathSegments compPathSegments
314 -- in case render compData of
315 -- Left staticPath ->
316 -- doesPathExist (compilerEnvSource </> staticPath) >>= \case
318 -- -- TODO: In current branch, we don't expect this to be a directory.
319 -- -- Although the user may pass it, but review before merge.
320 -- Sys.hPutStrLn Sys.stderr $
323 -- ( (compilerEnvSource </> staticPath)
324 -- , (compilerEnvDest </> staticPath)
326 -- copyDirRecursively
328 -- (compilerEnvSource </> staticPath)
331 -- Sys.hPutStrLn Sys.stderr $ "error: path does not exist: " <> (compilerEnvSource </> staticPath)
332 -- -- log LevelWarn $ toText $ "? " <> staticPath <> " (missing)"
334 -- let outputFullPath = compilerEnvDest </> routePath
336 -- maybe (routePath </> compilerEnvIndex)
337 -- (routePath Sys.FilePath.<.>)
340 -- -- Sys.hPutStrLn Sys.stderr $ "mkdir: " <> show (Sys.takeDirectory outputFullPath)
341 -- createDirectoryIfMissing True (Sys.takeDirectory outputFullPath)
342 -- -- Sys.hPutStrLn Sys.stderr $ "write: " <> show outputFullPath
343 -- BSL.writeFile outputFullPath bs
345 copyDirRecursively ::
347 -- | Source file path relative to CWD
349 -- | Absolute path to source file to copy.
351 -- | Directory *under* which the source file/dir will be copied
354 copyDirRecursively srcRel srcAbs destParent = do
355 Sys.doesFileExist srcAbs >>= \case
357 Sys.hPutStrLn Sys.stderr $ "copyDirRecursively: " <> show ("srcAbs is a file" :: Text, srcAbs)
358 copyFileCreatingParents srcAbs (destParent Sys.</> srcRel)
360 Sys.doesDirectoryExist srcAbs >>= \case
362 Sys.hPutStrLn Sys.stderr $ "copyDirRecursively: " <> show ("srcAbs is neither a file nor directory" :: Text, srcAbs)
364 -- throw $ StaticAssetMissing srcAbs
366 Sys.hPutStrLn Sys.stderr $ "copyDirRecursively: " <> show ("srcAbs is a directory" :: Text, srcAbs)
367 Sys.createDirectoryIfMissing True (destParent Sys.</> srcRel)
368 fs <- Sys.getDirectoryFiles srcAbs ["**"]
371 a = srcAbs Sys.</> fp
372 b = destParent Sys.</> srcRel Sys.</> fp
373 copyFileCreatingParents a b
375 copyFileCreatingParents a b = do
376 Sys.createDirectoryIfMissing True (Sys.takeDirectory b)