This repository has been archived on 2024-10-24. You can view files and clone it, but cannot push or open issues or pull requests.
fradrive-old/src/Settings/WellKnownFiles/TH.hs
2020-08-10 21:59:16 +02:00

194 lines
6.9 KiB
Haskell

module Settings.WellKnownFiles.TH
( mkWellKnown
) where
import ClassyPrelude.Yesod
import Utils
import Language.Haskell.TH
import Language.Haskell.TH.Syntax hiding (Lift(..))
import qualified Language.Haskell.TH.Syntax as TH (Lift(..))
import System.Directory.Tree
import qualified Data.ByteString as BS
import Utils.Lens.TH
import Control.Lens
import Data.Set.Lens
import qualified Data.Text as Text
import qualified Data.Text.Encoding as Text
import Data.Char as Char (isAlphaNum, toUpper)
import qualified Data.Map as Map
import qualified Data.Set as Set
import Data.List.NonEmpty (NonEmpty(..))
import qualified Data.HashMap.Strict as HashMap
import qualified Data.HashSet as HashSet
import System.FilePath (splitDirectories, makeRelative)
import Settings.Mime
import Text.Blaze.Html (preEscapedToHtml)
import Control.Monad.Fail
nWellKnownFileName :: Name
nWellKnownFileName = mkName "WellKnownFileName"
nwellKnownFileNames :: Name
nwellKnownFileNames = mkName "wellKnownFileNames"
ngetWellKnownR :: Name
ngetWellKnownR = mkName "getWellKnownR"
nwellKnownHtmlLinks :: Name
nwellKnownHtmlLinks = mkName "wellKnownHtmlLinks"
mkWellKnown :: Lang -- ^ Default language
-> FilePath -- ^ Base directory
-> FilePath -- ^ Link file (@html_code.html@)
-> DecsQ
mkWellKnown defLang wellKnownBase wellKnownLinks = do
inputFiles <- fmap dirTree . liftIO $ readDirectoryWith (\f -> (f, ) <$> BS.readFile f) wellKnownBase
mapM_ qAddDependentFile $ inputFiles ^.. folded . _1
-- languageFiles :: Map Lang [(FilePath, ByteString)]
languageFiles <- if
| Dir{contents} <- inputFiles
-> return . Map.fromList $ do
(language, lContents) <- contents ^.. folded . $(multifocusL 2) _name id
Dir{} <- pure lContents
let
lContents' :: [(FilePath, ByteString)]
lContents' = flip mapMaybe (flattenDir lContents) $ \pFile -> do
File{..} <- pure pFile
guard $ name /= wellKnownLinks
return $ file & _1 %~ makeRelative (wellKnownBase </> language)
return (Text.pack language, lContents')
| otherwise
-> fail "wellKnownBase is not a directory"
fLanguages <- if
| defLang `Set.member` Map.keysSet languageFiles
, let languages' = Set.delete defLang $ Map.keysSet languageFiles
-> return $ defLang :| Set.toList languages'
| otherwise
-> fail "default language is missing in wellKnownBase"
-- languageLinks :: Map Lang ByteString
languageLinks <- if
| Dir{contents} <- inputFiles
-> return . Map.fromList $ do
(language, lContents) <- contents ^.. folded . $(multifocusL 2) _name id
Dir{} <- pure lContents
let
lContents' :: [ByteString]
lContents' = flip mapMaybe (flattenDir lContents) $ \pFile -> do
File{..} <- pure pFile
guard $ name == wellKnownLinks
return $ file ^. _2
c <- lContents'
return (Text.pack language, c)
| otherwise
-> fail "wellKnownBase is not a directory"
lLanguages <- if
| defLang `Set.member` Map.keysSet languageLinks
, let languages' = Set.delete defLang $ Map.keysSet languageLinks
-> return $ defLang :| Set.toList languages'
| otherwise
-> fail "default language is missing in wellKnownBase"
fVar <- newName "f"
hVar <- newName "h"
lVar <- newName "l"
let fileNames = setOf (folded . folded . _1) languageFiles
fileContents = Map.fromListWith (<>) $ do
(lang, fs) <- Map.toList languageFiles
(fName, fContent) <- fs
return ((fContent, mimeLookup $ Text.pack fName), Set.singleton (lang, fName))
wellKnownFileName = dataD
(cxt [])
nWellKnownFileName
[]
Nothing
[ normalC (mkName $ fNameManip fName) []
| fName <- Set.toList fileNames
]
(pure $ derivClause Nothing [[t|Eq|], [t|Ord|], [t|Bounded|], [t|Enum|], [t|Read|], [t|Show|], [t|Generic|], [t|Typeable|]])
wellKnownFileNameMapSig = sigD
nwellKnownFileNames
[t|HashMap [Text] $(conT nWellKnownFileName)|]
wellKnownFileNameMap = funD
nwellKnownFileNames
[ clause [] (normalB $ [e|HashMap.fromList|] `appE` listE [ [e|($(TH.lift . map Text.pack $ splitDirectories fName), $(conE . mkName $ fNameManip fName))|] | fName <- Set.toList fileNames ]) []
]
wellKnownFileNamePathMultiPiece = instanceD
(cxt [])
(conT ''PathMultiPiece `appT` conT nWellKnownFileName)
[ funD 'toPathMultiPiece
[ clause [conP (mkName $ fNameManip fName) []] (normalB . TH.lift . map Text.pack $ splitDirectories fName) []
| fName <- Set.toList fileNames
]
, funD 'fromPathMultiPiece
[ clause [] (normalB [e|flip HashMap.lookup $(varE nwellKnownFileNames)|]) []
]
]
wellKnownFileNameHashable = instanceD
(cxt [])
(conT ''Hashable `appT` conT nWellKnownFileName)
[]
getWellKnownRSig = sigD
ngetWellKnownR
[t|forall m. MonadHandler m => $(conT nWellKnownFileName) -> m TypedContent|]
getWellKnownR = funD
ngetWellKnownR
[ clause [varP fVar] (normalB [e|$(varE hVar) =<< selectLanguage fLanguages|])
[ funD hVar $
[ clause [varP lVar] (guardedB
[ (,) <$> normalG [e|HashSet.member ($(varE lVar), $(varE fVar)) $ HashSet.fromList $(listE [ tupE [TH.lift l, conE . mkName $ fNameManip fName] | (l, fName) <- Set.toList xs ])|]
<*> [e|TypedContent mime (toContent fContent) <$ setEtag $(TH.lift $ hashToText (mime, fContent))|]
]) []
| ((fContent, mime), xs) <- Map.toList fileContents
] ++ pure (clause [wildP] (normalB [e|notFound|]) [])
]
]
wellKnownHtmlLinksSig = sigD
nwellKnownHtmlLinks
[t|forall m. MonadWidget m => m ()|]
wellKnownHtmlLinks = funD
nwellKnownHtmlLinks
[ clause [] (normalB [e|toWidgetHead . preEscapedToHtml . $(varE hVar) =<< selectLanguage lLanguages|])
[ sigD hVar [t|Text -> Text|]
, funD hVar $
[ clause [varP lVar] (guardedB
[ (,) <$> normalG [|$(varE lVar) == lang|]
<*> TH.lift (Text.filter (`notElem` ['\r', '\n']) $ Text.decodeUtf8 c)
]) []
| (lang, c) <- Map.toList languageLinks
] ++ pure (clause [wildP] (normalB [e|mempty|]) [])
]
]
sequence
[ wellKnownFileName, wellKnownFileNameMapSig, wellKnownFileNameMap, wellKnownFileNamePathMultiPiece, wellKnownFileNameHashable
, getWellKnownRSig, getWellKnownR
, wellKnownHtmlLinksSig, wellKnownHtmlLinks
]
where
fNameManip = Text.unpack . mconcat . over (traverse . _head) Char.toUpper . filter (not . null) . Text.split (not . isAlphaNum) . Text.pack