2 {-# LANGUAGE ConstraintKinds #-}
4 {-# LANGUAGE DeriveFunctor #-}
6 {-# LANGUAGE UndecidableInstances #-}
8 {-# LANGUAGE AllowAmbiguousTypes #-}
10 {-# LANGUAGE RankNTypes #-}
12 {-# LANGUAGE InstanceSigs #-}
14 module Literate.Web.Semantics.Compiler where
16 import Control.Applicative (Applicative (..))
17 import Control.Monad (Monad (..), forM, forM_, join, sequence, (>=>))
18 import Control.Monad.Classes qualified as MC
19 import Control.Monad.Trans.Class qualified as MT
20 import Control.Monad.Trans.Reader qualified as MT
22 import Data.ByteString.Lazy qualified as BSL
23 import Data.Either (Either (..))
24 import Data.Eq (Eq (..))
25 import Data.Foldable (toList)
26 import Data.Function (const, id, ($), (.))
27 import Data.Functor (Functor (..), (<$>))
28 import Data.Kind (Constraint, Type)
29 import Data.List qualified as List
30 import Data.Maybe (Maybe (..))
31 import Data.Ord (Ord (..))
32 import Data.Proxy (Proxy (..))
33 import Data.Semigroup (Semigroup (..))
34 import Data.String (fromString)
35 import Data.Text (Text)
36 import Data.Text qualified as Text
37 import Data.Tuple (curry, fst, snd)
38 import GHC.Generics (Generic)
39 import GHC.Stack (HasCallStack)
40 import Literate.Web.Syntaxes
41 import Literate.Web.Types.MIME
42 import Literate.Web.Types.URL
43 import Symantic qualified as Sym
44 import System.Directory qualified as Sys
45 import System.FilePath qualified as Sys
46 import System.FilePattern.Directory qualified as Sys
47 import System.IO qualified as Sys
48 import Text.Show (Show (..))
49 import Type.Reflection ((:~:) (..))
50 import Prelude (undefined)
53 newtype Compiler m a = Compiler {unCompiler :: {-FIXME: is m required?-}m [Output a]}
57 MC.MonadExec Sys.IO m =>
60 CompilerToF a (m BSL.ByteString) ->
62 compile CompilerEnv{..} router content = do
63 outputs <- unCompiler router
64 MC.exec @Sys.IO $ Sys.removePathForcibly compilerEnvDest
65 forM_ outputs $ \Output{..} -> do
67 ( List.intercalate "." $
68 encodePath outputPath :
69 ( if List.null outputExts
71 else Text.unpack . encodePathSegment <$> outputExts
74 -- hPutStrLn stderr $ "mkdir: " <> show (Sys.takeDirectory destPath)
76 Sys.createDirectoryIfMissing True $
77 compilerEnvDest Sys.</> Sys.takeDirectory destPath
78 -- hPutStrLn stderr $ "write: " <> show destPath
79 bsl <- outputData content
81 BSL.writeFile (compilerEnvDest Sys.</> destPath) $
85 compi :: Compiler m (m BSL.ByteString) -> Compiler m (m BSL.ByteString)
89 -- MC.MonadExec Sys.IO m =>
91 -- Compiler m (m BSL.ByteString) ->
93 -- compile2 CompilerEnv{..} router = do
94 -- outputs <- unCompiler router
95 -- MC.exec @Sys.IO $ Sys.removePathForcibly compilerEnvDest
96 -- -- FIXME: use pipes
97 -- forM_ outputs $ \Output{..} -> do
99 -- ( List.intercalate "." $
100 -- encodePath outputPath :
101 -- ( if List.null outputExts
103 -- else Text.unpack . encodePathSegment <$> outputExts
106 -- -- hPutStrLn stderr $ "mkdir: " <> show (Sys.takeDirectory destPath)
108 -- Sys.createDirectoryIfMissing True $
109 -- compilerEnvDest Sys.</> Sys.takeDirectory destPath
110 -- -- hPutStrLn stderr $ "write: " <> show destPath
111 -- outputBSL <- outputData content
113 -- BSL.writeFile (compilerEnvDest Sys.</> destPath) $
116 manifest :: forall m a. Monad m => Compiler m a -> m [Sys.FilePath]
118 outputs <- unCompiler router
123 ( List.intercalate "." $
124 encodePath (outputPath out) :
125 ( if List.null (outputExts out)
127 else Text.unpack . encodePathSegment <$> outputExts out
132 -- instance Applicative m => Applicative (Compiler m) where
133 -- pure = Compiler . pure . pure . pure
134 -- liftA2 f (Compiler a) (Compiler b) = Compiler (liftA2 (liftA2 (liftA2 f)) a b)
135 -- instance Monad m => Monad (Compiler m) where
137 -- Compiler mloa >>= a2mlob =
141 -- forM loa $ \oa -> do
142 -- lob <- unCompiler $ a2mlob $ outputData oa
146 -- { outputPath = outputPath oa <> outputPath ob
147 -- , outputExts = outputExts oa <> outputExts ob
149 instance Applicative m => Sym.ProductFunctor (Compiler m) where
150 Compiler a <.> Compiler b = Compiler $ liftA2 (liftA2 (<.>)) a b
151 Compiler a <. Compiler b = Compiler $ liftA2 (liftA2 (<.)) a b
152 Compiler a .> Compiler b = Compiler $ liftA2 (liftA2 (.>)) a b
153 --instance Applicative m => Sym.AlternativeFunctor (Compiler m) where
154 -- Compiler a <|> Compiler b = Compiler (liftA2 (<>) a b)
155 instance Applicative m => Sym.SumFunctor (Compiler m) where
156 Compiler a <+> Compiler b = Compiler $ liftA2 (\as bs -> (a2e <$> as) <> (b2e <$> bs)) a b
158 a2e :: Output a -> Output (Either a b)
159 a2e o = o{ outputData = outputData o . fst }
160 b2e :: Output b -> Output (Either a b)
161 b2e o = o{ outputData = outputData o . snd }
163 instance (Applicative m, CompilerUnToF a) => Optionable a (Compiler m) where
164 optional (Compiler ma) = Compiler $ (\as -> (a2n <$> as) <> (a2j <$> as)) <$> ma
166 a2n :: Output a -> Output (Maybe a)
167 a2n o = o{ outputData = ($ Nothing) }
168 a2j :: Output a -> Output (Maybe a)
169 a2j o = o{ outputData = \k -> outputData o $ compilerUnToF @(CompilerIsToF a) $ k . Just }
170 -- optional (Compiler ma) = Compiler $ (\as -> (a2n <$> as) <> (a2j <$> as)) <$> ma
171 -- --pure Nothing Sym.<|> (Just <$> ma)
173 -- a2n :: Output a -> Output (Maybe a)
174 -- a2n o = o{ outputData = fst }
175 -- a2j :: Output a -> Output (Maybe a)
176 -- a2j o = o{ outputData = outputData o . snd }
178 -- ** Class 'CompilerUnToF'
179 type CompilerUnToF a = CompilerUnToFIf (CompilerIsToF a) a
180 class CompilerUnToFIf (t :: Bool) a where
181 compilerUnToF :: (a -> next) -> CompilerToFIf t a next
182 instance CompilerUnToFIf 'True () where
183 compilerUnToF = ($ ())
184 instance (CompilerUnToF a, CompilerUnToF b) => CompilerUnToFIf 'True (a, b) where
185 compilerUnToF ab2n = compilerUnToF @(CompilerIsToF a) $ \a -> compilerUnToF @(CompilerIsToF b) $ \b -> ab2n (a,b)
186 instance (CompilerUnToF a, CompilerUnToF b) => CompilerUnToFIf 'True (Either a b) where
187 compilerUnToF e2n = ( compilerUnToF @(CompilerIsToF a) $ e2n . Left
188 , compilerUnToF @(CompilerIsToF b) $ e2n . Right
190 instance CompilerUnToFIf 'False a where
197 , CompilerIsToF a ~ 'False
198 , e ~ Sym.EoT (Sym.ADT a)
201 ) => Dataable__ a (Compiler m) where
202 data__ :: sem (Sym.EoT (Sym.ADT a)) -> sem a
203 data__ (Compiler e) = Compiler ((data__ <$>) <$> e)
209 , CompilerIsToF a ~ 'False
210 --, CompilerIsToF eot ~ 'False
211 , eot ~ Sym.EoT (Sym.ADT a)
213 ) => Dataable__ a Output where
214 data__ :: sem (Sym.EoT (Sym.ADT a)) -> sem a
215 data__ o = o { outputData = \k -> (outputData o) $ compilerUnToF @(CompilerIsToF eot) $ k . Sym.adtOfeot }
216 instance Applicative m => PathSegmentable (Compiler m) where
217 pathSegment s = Compiler $ pure
229 , end ~ m BSL.ByteString
230 , MimeTypes ts (MimeEncodable a)
232 Responsable a ts n end (Compiler m)
234 response = Compiler $ pure $
235 ( \(_mediaType, MimeType (Proxy :: Proxy t)) ->
238 , outputExts = [decodePathSegment (fileExtension @t)]
239 , outputData = \(Refl, Response ma) -> do
241 pure $ mimeEncode @_ @t a
244 <$> toList (mimeTypesMap @ts @(MimeEncodable a))
246 -- ** Type 'CompilerEnv'
247 data CompilerEnv = CompilerEnv
248 { compilerEnvDest :: Sys.FilePath
249 , -- , compilerEnvSource :: Sys.FilePath
250 compilerEnvIndex :: Sys.FilePath
251 --, compilerEnvModel :: model
252 -- , compilerEnvPath :: [PathSegment]
257 -- TODO: use Seq instead of []
258 data Output a = Output
259 { outputPath :: [PathSegment]
260 , outputExts :: [PathSegment]
261 , outputData :: forall next. CompilerToF a next -> next
262 --, outputBSL :: BSL.ByteString
263 -- , outputType :: MimeType (MimeEncodable a)
266 -- instance Sym.SumFunctor Output where
268 -- { outputPath = outputPath a <> outputPath b
269 -- , outputExts = outputExts a <> outputExts b
270 -- , outputData = \(a2n, b2n) -> outputData a a2n
272 instance Sym.ProductFunctor Output where
274 { outputPath = outputPath a <> outputPath b
275 , outputExts = outputExts a <> outputExts b
276 , outputData = outputData b . outputData a
279 { outputPath = outputPath a <> outputPath b
280 , outputExts = outputExts a <> outputExts b
281 , outputData = outputData b . outputData a
284 { outputPath = outputPath a <> outputPath b
285 , outputExts = outputExts a <> outputExts b
286 , outputData = outputData b . outputData a
288 -- deriving (Functor, Show)
289 -- instance Applicative Output where
295 -- --, outputBSL = ""
296 -- -- , outputType = mediaType @PlainText
300 -- { outputPath = outputPath oa2b <> outputPath oa
301 -- , outputExts = outputExts oa2b <> outputExts oa
302 -- , outputData = outputData oa2b (outputData oa)
303 -- --, outputBSL = outputBSL oa2b <> outputBSL oa
307 -- * Type family 'CompilerToF'
308 type CompilerToF a next = CompilerToFIf (CompilerIsToF a) a next
309 type family CompilerToFIf t a next :: Type where
311 CompilerToFIf 'True (a, b) next = CompilerToF a (CompilerToF b next)
312 -- For '<+>', request both branches.
313 CompilerToFIf 'True (Either l r) next = (CompilerToF l next, CompilerToF r next)
314 --CompilerToFIf 'True (Maybe a) next = (CompilerToF () next, CompilerToF a next)
315 -- Useless to ask '()' as argument.
316 CompilerToFIf 'True () next = next
317 -- Enable a different return value for each function.
318 CompilerToFIf 'True (Sym.Endpoint end a) next = (next :~: end, a)
319 -- Everything else becomes a new argument.
320 CompilerToFIf 'False a next = a -> next
323 -- | This 'Bool' is added to 'ToFIf' to avoid overlapping instances.
324 type family CompilerIsToF a :: Bool where
325 CompilerIsToF () = 'True
326 CompilerIsToF (a, b) = 'True
327 CompilerIsToF (Either l r) = 'True
328 --CompilerIsToF (Maybe a) = 'True
329 CompilerIsToF (Sym.Endpoint end a) = 'True
330 CompilerIsToF a = 'False
333 -- -- pathSegments _ss = Compiler $
334 -- -- MT.ReaderT $ \s ->
335 -- -- -- TODO: assert Set.member s ss
337 -- -- MT.modify' $ \st ->
338 -- -- st{compilerStatePath = compilerStatePath st </> Text.unpack (encodePathSegment s)}
343 -- 'Text "The instance (Capturable a Compiler)"
344 -- ':$$: 'Text "is disabled when compiling to a static Web site."
345 -- ':$$: 'Text "You can use (whenInterpreter @Compiler siteNotUsingCapturable siteUsingCapturable)"
346 -- ':$$: 'Text "to replace calls to any method of Capturable."
347 -- ) => Capturable a Compiler where
348 -- capturePathSegment = undefined
351 -- 'Text "The instance (Capturable a (Reader model Compiler))"
352 -- ':$$: 'Text "is disabled when compiling to a static Web site."
353 -- ':$$: 'Text "You can use (whenInterpreter @(Reader model Compiler) siteNotUsingCapturable siteUsingCapturable)"
354 -- ':$$: 'Text "to replace calls to any method of Capturable."
355 -- ) => Capturable a (Reader model Compiler) where
356 -- capturePathSegment = undefined
359 -- -- choosePathSegments _s = Compiler $ MT.ReaderT $ \s -> return $ Endo (List.reverse s <>)
361 -- instance Capturable Compiler where
362 -- type CapturableConstraint Compiler =
363 -- capturePathSegment _n = Compiler $ MT.ReaderT $ \s ->
364 -- lift $ MT.modify' $ \st ->
365 -- st{compilerStatePath = compilerStatePath st </> Text.unpack (encodePathSegment s)}
368 -- instance Copyable Compiler where
369 -- copy path = Compiler $
371 -- MT.ReaderT $ \env -> do
373 -- doesPathExist (compilerEnvSource env </> path) >>= \case
375 -- Sys.hPutStrLn Sys.stderr $
378 -- ( (compilerEnvSource env </> path)
379 -- , (compilerEnvDest env </> path)
381 -- copyDirRecursively
383 -- (compilerEnvSource env </> path)
384 -- (compilerEnvDest env)
386 -- Sys.hPutStrLn Sys.stderr $ "error: path does not exist: " <> (compilerEnvSource env </> path)
387 -- instance Encodable fmt a => Contentable fmt a Compiler where
388 -- content = Compiler $
389 -- MT.ReaderT $ \a -> MT.ReaderT $ \env -> do
391 -- let destPath = compilerEnvDest env </> compilerStatePath st
393 -- -- Sys.hPutStrLn Sys.stderr $ "mkdir: " <> show (Sys.takeDirectory destPath)
394 -- createDirectoryIfMissing True (Sys.takeDirectory destPath)
395 -- -- Sys.hPutStrLn Sys.stderr $ "write: " <> show destPath
396 -- BSL.writeFile destPath $ encode @fmt a
398 -- --instance Endable Compiler where
399 -- -- end = Compiler $ return $ Endo id
401 -- --pathSegments _cs = Compiler $ MT.ReaderT $ \s -> return $ Endo (s :)
402 -- -- instance Fileable Compiler where
403 -- -- type FileableConstraint Compiler = Typeable
404 -- -- static = Compiler $ MT.ReaderT $ \_a ->
405 -- -- return $ Endo (\x -> x)
408 -- -- * The 'Compiler' interpreter
410 -- -- | Create files according to the given model of type 'a'.
411 -- newtype Compiler a = Compiler
412 -- { unCompiler :: [Comp a]
414 -- deriving (Show, Functor)
416 -- instance Applicative Compiler where
417 -- pure = Compiler . pure . pure
418 -- Compiler f <*> Compiler x = Compiler $ (<*>) <$> f <*> x
420 -- -- instance Monad Compiler where
422 -- -- Compiler x >>= f = Compiler (x >>=)
424 -- compile :: Show a => Renderable a => Compiler a -> CompilerEnv -> Sys.IO ()
425 -- compile compiler conf@CompilerEnv{..} = do
426 -- createDirectoryIfMissing True compilerEnvDest
427 -- let router = unCompiler compiler
428 -- when (null router) $
429 -- error "no router, nothing to compile"
430 -- -- Sys.hPutStrLn Sys.stderr $ "conf: " <> show conf
431 -- -- forM_ router $ \comp -> do
432 -- -- Sys.hPutStrLn Sys.stderr $ "route: " <> show comp
433 -- forM_ router $ \Comp{..} -> do
434 -- -- Sys.hPutStrLn Sys.stderr $ "route: " <> show comp
435 -- let routePath = pathOfPathSegments compPathSegments
436 -- in case render compData of
437 -- Left staticPath ->
438 -- doesPathExist (compilerEnvSource </> staticPath) >>= \case
440 -- -- TODO: In current branch, we don't expect this to be a directory.
441 -- -- Although the user may pass it, but review before merge.
442 -- Sys.hPutStrLn Sys.stderr $
445 -- ( (compilerEnvSource </> staticPath)
446 -- , (compilerEnvDest </> staticPath)
448 -- copyDirRecursively
450 -- (compilerEnvSource </> staticPath)
453 -- Sys.hPutStrLn Sys.stderr $ "error: path does not exist: " <> (compilerEnvSource </> staticPath)
454 -- -- log LevelWarn $ toText $ "? " <> staticPath <> " (missing)"
456 -- let destPath = compilerEnvDest </> routePath
458 -- maybe (routePath </> compilerEnvIndex)
459 -- (routePath Sys.FilePath.<.>)
462 -- -- Sys.hPutStrLn Sys.stderr $ "mkdir: " <> show (Sys.takeDirectory destPath)
463 -- createDirectoryIfMissing True (Sys.takeDirectory destPath)
464 -- -- Sys.hPutStrLn Sys.stderr $ "write: " <> show destPath
465 -- BSL.writeFile destPath bs
467 -- -- ** Class 'Renderable'
468 -- class Renderable a where
469 -- render :: a -> Either Sys.FilePath BSL.ByteString
470 -- instance Renderable () where
472 -- --Left $ pathOfPathSegments compPathSegments
476 -- data Comp a = Comp
477 -- { compPathSegments :: [PathSegment] -- TODO: Endo? Seq?
479 -- -- , compType :: MimeType (MimeEncodable a)
481 -- deriving instance Eq a => Eq (Comp a)
482 -- deriving instance Ord a => Ord (Comp a)
483 -- deriving instance Show a => Show (Comp a)
484 -- deriving instance Functor Comp
485 -- instance Applicative Comp where
488 -- { compPathSegments = []
490 -- -- , compType = mediaType @PlainText
494 -- { compPathSegments = compPathSegments f <> compPathSegments x
495 -- , compData = compData f (compData x)
496 -- -- , compType = compType f <> compType x
499 -- instance IsoFunctor Compiler where
500 -- (<%>) Iso{..} = (a2b <$>)
501 -- instance ProductFunctor Compiler where
502 -- (<.>) = liftA2 (,)
505 -- instance SumFunctor Compiler where
509 -- ((Left <$>) <$> unCompiler x)
510 -- ((Right <$>) <$> unCompiler y)
511 -- instance Optionable Compiler where
514 -- Comp { compPathSegments = []
515 -- , compData = Nothing
516 -- -- , compType = Nothing
518 -- ((Just <$>) <$> unCompiler x)
519 -- instance PathSegmentable Compiler where
520 -- pathSegment s = Compiler
523 -- { compPathSegments = [s]
525 -- -- , compType = PlainText
530 -- [ Comp{ compPathSegments = [s]
532 -- -- , compType = Nothing
536 -- instance ContentTypeable PlainText () Compiler where
540 -- { compPathSegments = []
542 -- --, compType = mediaType @PlainText
546 -- -- instance Repeatable Compiler where
547 -- -- many0 (Compiler x) =
549 -- -- ((\Comp{} -> Comp [] []) <$> x)
550 -- -- <> ((\(Comp s a) -> Comp s [a]) <$> x)
551 -- -- <> ((\(Comp s a) -> Comp (s <> s) [a, a]) <$> x)
552 -- -- many1 (Compiler x) =
554 -- -- ((\(Comp s a) -> Comp s [a]) <$> x)
555 -- -- <> ((\(Comp s a) -> Comp (s <> s) [a, a]) <$> x)
556 -- -- instance Endable Compiler where
557 -- -- end = Compiler [Comp [] ()]
558 -- -- instance Capturable Compiler where
559 -- -- capturePathSegment n = Compiler $ [Comp [n] n]
560 -- -- instance Constantable c Compiler where
561 -- -- constant = pure
564 copyDirRecursively ::
570 -- | Source file path relative to CWD
572 -- | Absolute path to source file to copy.
574 -- | Directory *under* which the source file/dir will be copied
577 copyDirRecursively srcRel srcAbs destParent = do
578 Sys.doesFileExist srcAbs >>= \case
580 Sys.hPutStrLn Sys.stderr $ "copyDirRecursively: " <> show ("srcAbs is a file", srcAbs)
581 copyFileCreatingParents srcAbs (destParent Sys.</> srcRel)
583 Sys.doesDirectoryExist srcAbs >>= \case
585 Sys.hPutStrLn Sys.stderr $ "copyDirRecursively: " <> show ("srcAbs is neither a file nor directory", srcAbs)
587 -- throw $ StaticAssetMissing srcAbs
589 Sys.hPutStrLn Sys.stderr $ "copyDirRecursively: " <> show ("srcAbs is a directory", srcAbs)
590 Sys.createDirectoryIfMissing True (destParent Sys.</> srcRel)
591 fs <- Sys.getDirectoryFiles srcAbs ["**"]
593 let a = srcAbs Sys.</> fp
594 b = destParent Sys.</> srcRel Sys.</> fp
595 copyFileCreatingParents a b
597 copyFileCreatingParents a b = do
598 Sys.createDirectoryIfMissing True (Sys.takeDirectory b)