reploy/Utils.hs

157 lines
4.9 KiB
Haskell

{-
- Copyright (C) 2023 University of Luxembourg
-
- Licensed under the Apache License, Version 2.0 (the "License"); you may not
- use this file except in compliance with the License. You may obtain a copy
- of the License from the LICENSE file in this repository, or at:
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
- WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
- License for the specific language governing permissions and limitations
- under the License.
-}
{-# LANGUAGE OverloadedStrings #-}
module Utils where
import Control.Monad (filterM, forM)
import Control.Monad.IO.Class
import Data.List.Extra (stripSuffix)
import Data.Maybe (isJust)
import qualified Data.Text as T
import Lens.Micro.Mtl
import System.Directory
( createDirectoryIfMissing
, doesDirectoryExist
, getDirectoryContents
)
import qualified System.FilePath
import System.FilePath (splitDirectories, takeDirectory)
import Text.Pandoc.Definition
import qualified Text.Pandoc.Walk
import Types
-- | A shortcut for `liftIO`.
io :: MonadIO m => IO a -> m a
io = liftIO
-- | A helper for throwing an error if something is `Nothing`
just :: String -> Maybe a -> a
just _ (Just val) = val
just err Nothing = error ("Missing: " ++ err)
-- | Test for whether something listy has a suffix
hasSuffix :: Eq a => [a] -> [a] -> Bool
hasSuffix s = isJust . stripSuffix s
-- | The same second as from arrows et al.
second :: (a -> b) -> (c, a) -> (c, b)
second f (a, b) = (a, f b)
-- | A pandoc walker for modifying the URLs.
walkURLs ::
(FilePath -> Site FilePath)
-> Text.Pandoc.Definition.Pandoc
-> Site Text.Pandoc.Definition.Pandoc
walkURLs f = Text.Pandoc.Walk.walkM go
where
go (Link a i (u, t)) = do
u' <- T.pack <$> f (T.unpack u)
pure $ Link a i (u', t)
go (Image a i (u, t)) = do
u' <- T.pack <$> f (T.unpack u)
pure $ Image a i (u', t)
go x = pure x
-- | A pandoc walker for adding the local links to the headings (links are
-- appended and get a given class)
addHeadingLinks ::
T.Text -> Text.Pandoc.Definition.Pandoc -> Text.Pandoc.Definition.Pandoc
addHeadingLinks cls = Text.Pandoc.Walk.walk go
where
go h@(Header lvl attr@(id, _, _) inlines) =
Header
lvl
attr
(inlines
++ [ Link
("", [cls], [])
[Str "#"]
("#" <> id, "Link to this section")
])
go x = x
-- | @"https://example.com" `hasUriScheme` "https"@
hasUriScheme :: String -> String -> Bool
hasUriScheme x = all id . zipWith (==) x . (++ ":")
-- | @unAbsolute "/root/path" == "root/path"@
unAbsolute :: String -> String
unAbsolute = dropWhile (== '/')
-- | Lift a function that processes `Block`s to a function that -- processes a
-- `Pandoc`.
withPandocBlocks :: ([Block] -> [Block]) -> Pandoc -> Pandoc
withPandocBlocks f (Pandoc meta blocks) = Pandoc meta (f blocks)
infixr 5 </>
a </> b@('/':_) =
error
$ "internal error: unchecked concatenation of absolute path: "
++ a
++ " </> "
++ b
a </> b = (System.FilePath.</>) a b
-- | Get all contents of a directory. (Interned from Hakyll.)
getRecursiveContents ::
(FilePath -> IO Bool) -- ^ Ignore this file/directory
-> FilePath -- ^ Directory to search
-> IO [FilePath] -- ^ List of files found
getRecursiveContents ignore top = go ""
where
isProper x
| x `elem` [".", ".."] = return False
| otherwise = not <$> ignore x
go dir = do
dirExists <- doesDirectoryExist (top </> dir)
if not dirExists
then return []
else do
names <- filterM isProper =<< getDirectoryContents (top </> dir)
paths <-
forM names $ \name -> do
let rel = dir </> name
isDirectory <- doesDirectoryExist (top </> rel)
if isDirectory
then go rel
else return [rel]
return $ concat paths
-- | A nice tool interned from Relude.
foldMapM :: (Monoid b, Monad m, Foldable f) => (a -> m b) -> f a -> m b
foldMapM f xs = foldr step return xs mempty
where
step x r z = f x >>= \y -> r $! z `mappend` y
-- | Source paths from a source-y directory. The paths that have to be ignored
-- by config `notSourceDirs` are omitted.
sourcePaths :: Monoid a => FilePath -> (FilePath -> Site a) -> Site a
sourcePaths fp process = do
notSource <- use notSourceDirs
let ignoreDir ds
| null ds = False
| last ds `elem` notSource = True
| otherwise = False
io (getRecursiveContents (pure . ignoreDir . splitDirectories) fp)
>>= foldMapM process
-- | Given a path to a file, try to make the path writable by making all
-- directories on the path. (Interned from Hakyll.)
makeDirectories :: FilePath -> IO ()
makeDirectories = createDirectoryIfMissing True . takeDirectory