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 instance Functor m => Sym.Voidable (Compiler m) where
152 void _a (Compiler ma) =
154 (\os -> (\o -> o{outputData = id}) <$> os) <$> ma
156 -- optional (Compiler ma) = Compiler $ (\as -> forNothing : (forJust <$> as)) <$> ma
157 -- --pure Nothing Sym.<|> (Just <$> ma)
159 -- forNothing :: Output a -> Output (Maybe a)
160 -- forNothing o = o{ outputData = fst }
161 -- forJust :: Output a -> Output (Maybe a)
162 -- forJust o = o{ outputData = outputData o . snd }
168 , Sym.IsToF a ~ 'False
169 , eot ~ Sym.EoT (Sym.ADT a)
173 Dataable a (Compiler m)
175 dataType :: sem (Sym.EoT (Sym.ADT a)) -> sem a
176 dataType (Compiler mos) = Compiler ((dataType <$>) <$> mos)
181 , Sym.IsToF a ~ 'False
182 , eot ~ Sym.EoT (Sym.ADT a)
187 dataType :: sem (Sym.EoT (Sym.ADT a)) -> sem a
188 dataType o = o{outputData = \k -> (outputData o) $ Sym.tofOffun $ k . Sym.adtOfeot}
189 instance Applicative m => PathSegmentable (Compiler m) where
196 { outputPathSegs = [s]
197 , outputPathExts = []
205 , MimeTypes ts (MimeEncodable a)
207 Responsable a ts n (Compiler m)
212 ( \(_mediaType, MimeType (Proxy :: Proxy t)) ->
216 { outputPathSegs = []
217 , outputPathExts = [decodePathSegment (fileExtension @t)]
219 , outputData = \(CompilerEndpoint Refl (Response ma)) ->
220 mimeEncode @_ @t <$> ma
223 <$> toList (mimeTypesMap @ts @(MimeEncodable a))
227 data Output a = Output
228 { outputPath :: OutputPath
229 , outputData :: forall next. (a --> next) -> next
232 outputBuilder :: Output a -> (a --> m BSB.Builder) -> m BSB.Builder
233 outputBuilder = outputData
235 outputPathRelative :: Output a -> OutputPath
236 outputPathRelative out
237 | List.null (outputPathExts outPath) = outPath{outputPathExts = ["txt"]}
238 | otherwise = outPath
240 outPath = outputPath out
242 -- *** Type 'OutputPath'
243 data OutputPath = OutputPath
244 { outputPathSegs :: [PathSegment]
245 , outputPathExts :: [PathSegment]
247 deriving (Eq, Ord, Show)
249 outputPathFile :: OutputPath -> Sys.FilePath
250 outputPathFile outPath =
251 List.intercalate "." $
252 encodePath (outputPathSegs outPath)
253 : (Text.unpack . encodePathSegment <$> outputPathExts outPath)
255 instance Semigroup OutputPath where
258 { outputPathSegs = outputPathSegs x <> outputPathSegs y
259 , outputPathExts = outputPathExts x <> outputPathExts y
261 instance Monoid OutputPath where
262 mempty = OutputPath{outputPathSegs = [], outputPathExts = []}
264 instance Sym.ProductFunctor Output where
267 { outputPath = outputPath a <> outputPath b
268 , outputData = outputData b . outputData a
272 { outputPath = outputPath a <> outputPath b
273 , outputData = outputData b . outputData a
277 { outputPath = outputPath a <> outputPath b
278 , outputData = outputData b . outputData a
281 -- compile :: Show a => Renderable a => Compiler a -> CompilerEnv -> Sys.IO ()
282 -- compile compiler conf@CompilerEnv{..} = do
283 -- createDirectoryIfMissing True compilerEnvDest
284 -- let router = unCompiler compiler
285 -- when (null router) $
286 -- error "no router, nothing to compile"
287 -- -- Sys.hPutStrLn Sys.stderr $ "conf: " <> show conf
288 -- -- forM_ router $ \comp -> do
289 -- -- Sys.hPutStrLn Sys.stderr $ "route: " <> show comp
290 -- forM_ router $ \Comp{..} -> do
291 -- -- Sys.hPutStrLn Sys.stderr $ "route: " <> show comp
292 -- let routePath = pathOfPathSegments compPathSegments
293 -- in case render compData of
294 -- Left staticPath ->
295 -- doesPathExist (compilerEnvSource </> staticPath) >>= \case
297 -- -- TODO: In current branch, we don't expect this to be a directory.
298 -- -- Although the user may pass it, but review before merge.
299 -- Sys.hPutStrLn Sys.stderr $
302 -- ( (compilerEnvSource </> staticPath)
303 -- , (compilerEnvDest </> staticPath)
305 -- copyDirRecursively
307 -- (compilerEnvSource </> staticPath)
310 -- Sys.hPutStrLn Sys.stderr $ "error: path does not exist: " <> (compilerEnvSource </> staticPath)
311 -- -- log LevelWarn $ toText $ "? " <> staticPath <> " (missing)"
313 -- let outputFullPath = compilerEnvDest </> routePath
315 -- maybe (routePath </> compilerEnvIndex)
316 -- (routePath Sys.FilePath.<.>)
319 -- -- Sys.hPutStrLn Sys.stderr $ "mkdir: " <> show (Sys.takeDirectory outputFullPath)
320 -- createDirectoryIfMissing True (Sys.takeDirectory outputFullPath)
321 -- -- Sys.hPutStrLn Sys.stderr $ "write: " <> show outputFullPath
322 -- BSL.writeFile outputFullPath bs
324 copyDirRecursively ::
326 -- | Source file path relative to CWD
328 -- | Absolute path to source file to copy.
330 -- | Directory *under* which the source file/dir will be copied
333 copyDirRecursively srcRel srcAbs destParent = do
334 Sys.doesFileExist srcAbs >>= \case
336 Sys.hPutStrLn Sys.stderr $ "copyDirRecursively: " <> show ("srcAbs is a file" :: Text, srcAbs)
337 copyFileCreatingParents srcAbs (destParent Sys.</> srcRel)
339 Sys.doesDirectoryExist srcAbs >>= \case
341 Sys.hPutStrLn Sys.stderr $ "copyDirRecursively: " <> show ("srcAbs is neither a file nor directory" :: Text, srcAbs)
343 -- throw $ StaticAssetMissing srcAbs
345 Sys.hPutStrLn Sys.stderr $ "copyDirRecursively: " <> show ("srcAbs is a directory" :: Text, srcAbs)
346 Sys.createDirectoryIfMissing True (destParent Sys.</> srcRel)
347 fs <- Sys.getDirectoryFiles srcAbs ["**"]
350 a = srcAbs Sys.</> fp
351 b = destParent Sys.</> srcRel Sys.</> fp
352 copyFileCreatingParents a b
354 copyFileCreatingParents a b = do
355 Sys.createDirectoryIfMissing True (Sys.takeDirectory b)