Commit 343b0d5aff98ca8f204751ae82b4b64277a8fb24 Parent: 0292014748caae952bbc8dd6225680d83c0a5135 Author: mcol <mcol@posteo.net> Date: 2021-11-25 14:29:19 +0000 Committer: mcol <mcol@posteo.net> Committed: 2021-11-25 14:29:19 +0000 Redesign templating logic - replaces most uses of filepath with path and path-io. - changes expected template dir structure - lets mutliple index scope templates to be used Closes #9 Closes #7
templates/tree.html Deleted
@@ -1,29 +0,0 @@ -{% extends "template.html.include" %} - -{%- block title -%} -{{ name }} - target -{%- endblock -%} - -{% block body %} -{% include "repo-table-header.html.include" %} - -<div id="content"> - <table id="files"> - <thead> - <tr> - <td><b>Mode</b></td> - <td><b>Name</b></td> - </tr> - </thead> - - <tbody> - {% for file in tree %} - <tr> - <td>{{ file.mode_symbolic }}</td> - <td><a href="{{ host }}/{{ name }}/file/{{ file.href }}">{{ file.path }}</a></td> - </tr> - {% endfor %} - </tbody> - </table> -</div> -{% endblock %}
templates/repo/tree.html Added
@@ -0,0 +1,29 @@ +{% extends "../template.html.include" %} + +{%- block title -%} +{{ name }} - target +{%- endblock -%} + +{% block body %} +{% include "repo-table-header.html.include" %} + +<div id="content"> + <table id="files"> + <thead> + <tr> + <td><b>Mode</b></td> + <td><b>Name</b></td> + </tr> + </thead> + + <tbody> + {% for file in tree %} + <tr> + <td>{{ file.mode_symbolic }}</td> + <td><a href="{{ host }}/{{ name }}/file/{{ file.href }}">{{ file.path }}</a></td> + </tr> + {% endfor %} + </tbody> + </table> +</div> +{% endblock %}
templates/repo/repo-table-header.html.include Added
@@ -0,0 +1,24 @@ +{#- This block is rendered at the top of every page. -#} + +<table id="head"> + <tr> + <td width="32px" rowspan="2"><a href="{{ host }}"><img src="{{ host }}/avatar.png" id="avatar"/></a></td> + <td rowspan="2"><a href="{{ host }}">{{ host }}</a> / <a href="{{ host }}">code</a> / <a href="{{ host }}/{{ name }}">{{ name }}</a></td> + <td class="link"><a href="{{ host }}/posts.html">posts</a></td> + </tr> + <tr> + <td class="link"><a href="{{ host }}/about.html">about</a></td> + </tr> +</table> +<hr> + +<table> + <tr><td><span class="desc">{{ description }}</span></td></tr> + <tr class="url"><td>git clone <a href="{{ host }}/{{ name }}">{{ host }}/{{ name }}</a></td></tr> + <tr><td> + <a href="{{ host }}/{{ name }}/tree.html">Files</a> | <a href="{{ host }}/{{ name }}/refs.html">Refs</a> + {% if readme %} | <a href="{{ host }}/{{ name }}/file/{{ readme.href }}">Readme</a>{% endif %} + {% if license %} | <a href="{{ host }}/{{ name }}/file/{{ license.href }}">License</a>{% endif %} + </td></tr> +</table> +<hr>
templates/repo/refs.html Added
@@ -0,0 +1,37 @@ +{% extends "../template.html.include" %} + +{%- block title -%} +{{ name }} - Refs +{%- endblock -%} + +{% block body %} +{% include "repo-table-header.html.include" %} + +<div id="content"> + <h2>Branches</h2> + <table id="branches"> + <thead> + <tr><td><b>Name</b></td><td colspan="2"><b>Last commit</b></td><td><b>Author</b></td></tr> + </thead> + <tbody> + {% for branch in branches %} + <tr><td>{{ branch.name }}</td><td>{{ branch.authored[:16] }}</td><td>{{ branch.title }}</td><td>{{ branch.author }}</td></tr> + {% endfor %} + </tbody> + </table> + + <br> + + <h2>Tags</h2> + <table id="tags"> + <thead> + <tr><td><b>Name</b></td><td colspan="2"><b>Last commit</b></td><td><b>Author</b></td></tr> + </thead> + <tbody> + {% for tag in tags %} + <tr><td>{{ tag.name }}</td><td>{{ tag.authored[:16] }}</td><td>{{ tag.title }}</td><td>{{ tag.author }}</td></tr> + {% endfor %} + </tbody> + </table> +</div> +{% endblock %}
templates/repo/index.html Added
@@ -0,0 +1,27 @@ +{% extends "../template.html.include" %} + +{%- block title -%} +{{ description }} +{%- endblock -%} + +{% block body %} +{% include "repo-table-header.html.include" %} + +<div id="content"> + <table id="log"> + <thead> + <tr> + <td><b>Date</b></td> + <td><b>Commit message</b></td> + <td><b>Author</b></td> + </tr> + </thead> + + <tbody> + {% for commit in commits[:50] %} + <tr><td>{{ commit.authored[:16] }}</td><td><a href="commit/{{ commit.id }}.html">{{ commit.title }}</a></td><td>{{ commit.author }}</td></tr> + {% endfor %} + </tbody> + </table> +</div> +{% endblock %}
templates/repo/file.html Added
@@ -0,0 +1,39 @@ +{% extends "../template.html.include" %} + +{%- block title -%} +{{ name }} - {{ file }} +{%- endblock -%} + +{% block body %} +{% include "repo-table-header.html.include" %} + +<div id="content"> + <p>{{ file.mode_symbolic }} {{ file }}</p><hr> + +{% if file.is_directory %} + <table id="files"> + <thead> + <tr> + <td><b>Mode</b></td> + <td><b>Name</b></td> + </tr> + </thead> + + <tbody> + {% for child in file.contents %} + <tr> + <td>{{ child.mode_symbolic }}</td> + <td><a href="{{ host }}/{{ name }}/file/{{ child.href }}">{{ child.path }}</a></td> + </tr> + {% endfor %} + </tbody> + </table> +{% else %} + <pre id="blob"> +{% for line in split(file.contents, '\n') %} +<a href="#l{{ loop.index }}" class="line" id="l{{ loop.index }}">{{ printf("%7v", loop.index) }}</a> {{ line }} +{% endfor %} +{% endif %} + </pre> +</div> +{% endblock %}
templates/repo/commit.html Added
@@ -0,0 +1,21 @@ +{% extends "../template.html.include" %} + +{%- block title -%} +{{ name }} - {{ commit.title }} +{%- endblock -%} + +{% block body %} +{% include "repo-table-header.html.include" %} + +<div id="content"> + <pre><b>Commit</b> <a href="{{ host }}/{{ name }}/commit/{{ commit.id }}.html">{{ commit.id }}</a> +<b>Parent:</b> <a href="{{ host }}/{{ name }}/commit/{{ commit.parent }}.html">{{ commit.parent }}</a> +<b>Author:</b> {{ commit.author }} <<a href="mailto:{{ commit.author_email }}">{{ commit.author_email }}</a>> +<b>Date:</b> {{ commit.authored }} +<b>Committer:</b> {{ commit.committer }} <<a href="mailto:{{ commit.committer_email }}">{{ commit.committer_email }}</a>> +<b>Committed:</b> {{ commit.committed }} + +{{ commit.message }} +</pre> +</div> +{% endblock %}
templates/repo-table-header.html.include Deleted
@@ -1,24 +0,0 @@ -{#- This block is rendered at the top of every page. -#} - -<table id="head"> - <tr> - <td width="32px" rowspan="2"><a href="{{ host }}"><img src="{{ host }}/avatar.png" id="avatar"/></a></td> - <td rowspan="2"><a href="{{ host }}">{{ host }}</a> / <a href="{{ host }}">code</a> / <a href="{{ host }}/{{ name }}">{{ name }}</a></td> - <td class="link"><a href="{{ host }}/posts.html">posts</a></td> - </tr> - <tr> - <td class="link"><a href="{{ host }}/about.html">about</a></td> - </tr> -</table> -<hr> - -<table> - <tr><td><span class="desc">{{ description }}</span></td></tr> - <tr class="url"><td>git clone <a href="{{ host }}/{{ name }}">{{ host }}/{{ name }}</a></td></tr> - <tr><td> - <a href="{{ host }}/{{ name }}/tree.html">Files</a> | <a href="{{ host }}/{{ name }}/refs.html">Refs</a> - {% if readme %} | <a href="{{ host }}/{{ name }}/file/{{ readme.href }}">Readme</a>{% endif %} - {% if license %} | <a href="{{ host }}/{{ name }}/file/{{ license.href }}">License</a>{% endif %} - </td></tr> -</table> -<hr>
templates/refs.html Deleted
@@ -1,37 +0,0 @@ -{% extends "template.html.include" %} - -{%- block title -%} -{{ name }} - Refs -{%- endblock -%} - -{% block body %} -{% include "repo-table-header.html.include" %} - -<div id="content"> - <h2>Branches</h2> - <table id="branches"> - <thead> - <tr><td><b>Name</b></td><td colspan="2"><b>Last commit</b></td><td><b>Author</b></td></tr> - </thead> - <tbody> - {% for branch in branches %} - <tr><td>{{ branch.name }}</td><td>{{ branch.authored[:16] }}</td><td>{{ branch.title }}</td><td>{{ branch.author }}</td></tr> - {% endfor %} - </tbody> - </table> - - <br> - - <h2>Tags</h2> - <table id="tags"> - <thead> - <tr><td><b>Name</b></td><td colspan="2"><b>Last commit</b></td><td><b>Author</b></td></tr> - </thead> - <tbody> - {% for tag in tags %} - <tr><td>{{ tag.name }}</td><td>{{ tag.authored[:16] }}</td><td>{{ tag.title }}</td><td>{{ tag.author }}</td></tr> - {% endfor %} - </tbody> - </table> -</div> -{% endblock %}
templates/index.html Modified
@@ -1,25 +1,26 @@ {% extends "template.html.include" %} {%- block title -%} -{{ description }} +gitserve - Index {%- endblock -%} {% block body %} -{% include "repo-table-header.html.include" %} +<table id="head"> + <tr><td width="32px" rowspan="2"><a href="{{ host }}"><img src="{{ host }}/avatar.png" id="avatar"/></a></td> + <td rowspan="2"><a href="{{ host }}">{{ host }}</a> / <a href="{{ host }}">code</a></td><td class="link"><a href="{{ host }}/posts.html">posts</a></td></tr><tr><td class="link"><a href="{{ host }}/about.html">about</a></td></tr> +</table> + +<hr> <div id="content"> - <table id="log"> + <table id="index"> <thead> - <tr> - <td><b>Date</b></td> - <td><b>Commit message</b></td> - <td><b>Author</b></td> - </tr> + <tr><td><b>Name</b></td><td><b>Description</b></td><td><b>Last commit</b></td></tr> </thead> <tbody> - {% for commit in commits[:50] %} - <tr><td>{{ commit.authored[:16] }}</td><td><a href="commit/{{ commit.id }}.html">{{ commit.title }}</a></td><td>{{ commit.author }}</td></tr> + {% for repo in repositories %} + <tr><td><a href="{{ repo.name }}">{{ repo.name }}</a></td><td>{{ repo.description }}</td><td>{{ repo.updated[:16] }}</td></tr> {% endfor %} </tbody> </table>
templates/index-main.html Deleted
@@ -1,28 +0,0 @@ -{% extends "template.html.include" %} - -{%- block title -%} -gitserve - Index -{%- endblock -%} - -{% block body %} -<table id="head"> - <tr><td width="32px" rowspan="2"><a href="{{ host }}"><img src="{{ host }}/avatar.png" id="avatar"/></a></td> - <td rowspan="2"><a href="{{ host }}">{{ host }}</a> / <a href="{{ host }}">code</a></td><td class="link"><a href="{{ host }}/posts.html">posts</a></td></tr><tr><td class="link"><a href="{{ host }}/about.html">about</a></td></tr> -</table> - -<hr> - -<div id="content"> - <table id="index"> - <thead> - <tr><td><b>Name</b></td><td><b>Description</b></td><td><b>Last commit</b></td></tr> - </thead> - - <tbody> - {% for repo in repositories %} - <tr><td><a href="{{ repo.name }}">{{ repo.name }}</a></td><td>{{ repo.description }}</td><td>{{ repo.updated[:16] }}</td></tr> - {% endfor %} - </tbody> - </table> -</div> -{% endblock %}
templates/file.html Deleted
@@ -1,39 +0,0 @@ -{% extends "template.html.include" %} - -{%- block title -%} -{{ name }} - {{ file }} -{%- endblock -%} - -{% block body %} -{% include "repo-table-header.html.include" %} - -<div id="content"> - <p>{{ file.mode_symbolic }} {{ file }}</p><hr> - -{% if file.is_directory %} - <table id="files"> - <thead> - <tr> - <td><b>Mode</b></td> - <td><b>Name</b></td> - </tr> - </thead> - - <tbody> - {% for child in file.contents %} - <tr> - <td>{{ child.mode_symbolic }}</td> - <td><a href="{{ host }}/{{ name }}/file/{{ child.href }}">{{ child.path }}</a></td> - </tr> - {% endfor %} - </tbody> - </table> -{% else %} - <pre id="blob"> -{% for line in split(file.contents, '\n') %} -<a href="#l{{ loop.index }}" class="line" id="l{{ loop.index }}">{{ printf("%7v", loop.index) }}</a> {{ line }} -{% endfor %} -{% endif %} - </pre> -</div> -{% endblock %}
templates/commit.html Deleted
@@ -1,21 +0,0 @@ -{% extends "template.html.include" %} - -{%- block title -%} -{{ name }} - {{ commit.title }} -{%- endblock -%} - -{% block body %} -{% include "repo-table-header.html.include" %} - -<div id="content"> - <pre><b>Commit</b> <a href="{{ host }}/{{ name }}/commit/{{ commit.id }}.html">{{ commit.id }}</a> -<b>Parent:</b> <a href="{{ host }}/{{ name }}/commit/{{ commit.parent }}.html">{{ commit.parent }}</a> -<b>Author:</b> {{ commit.author }} <<a href="mailto:{{ commit.author_email }}">{{ commit.author_email }}</a>> -<b>Date:</b> {{ commit.authored }} -<b>Committer:</b> {{ commit.committer }} <<a href="mailto:{{ commit.committer_email }}">{{ commit.committer_email }}</a>> -<b>Committed:</b> {{ commit.committed }} - -{{ commit.message }} -</pre> -</div> -{% endblock %}
src/Types.hs Modified
@@ -21,7 +21,7 @@ import Data.Text.Encoding.Error (lenientDecode) import Git import Git.Libgit2 (LgRepo) -import System.FilePath (takeFileName) +import Path (Abs, Dir, Path, dirname, toFilePath) import Text.Ginger.GVal (GVal, ToGVal, asBoolean, asHtml, asList, asLookup, asText, toGVal) import Text.Ginger.Html (Html, html) import Text.Ginger.Parse (SourcePos) @@ -37,23 +37,27 @@ as well as in a list of all repositories in the index template. -} data Repo = Repo - { repositoryPath :: FilePath + { repositoryPath :: Path Abs Dir , repositoryDescription :: Text , repositoryHead :: Maybe (Commit LgRepo) } +instance ToGVal m (Path b t) where + toGVal :: Path b t -> GVal m + toGVal = toGVal . toFilePath + instance ToGVal m Repo where toGVal :: Repo -> GVal m toGVal repo = def - { asHtml = html . pack . show . takeFileName . repositoryPath $ repo - , asText = pack . show . takeFileName . repositoryPath $ repo + { asHtml = html . pack . show . dirname . repositoryPath $ repo + , asText = pack . show . dirname . repositoryPath $ repo , asLookup = Just . repoAsLookup $ repo } repoAsLookup :: Repo -> Text -> Maybe (GVal m) repoAsLookup repo = \case - "name" -> Just . toGVal . takeFileName . repositoryPath $ repo + "name" -> Just . toGVal . dirname . repositoryPath $ repo "description" -> Just . toGVal . repositoryDescription $ repo "head" -> Just . toGVal . repositoryHead $ repo "updated" -> toGVal . show . signatureWhen . commitCommitter <$> repositoryHead repo
src/Templates.hs Modified
@@ -1,36 +1,25 @@ {-# LANGUAGE LambdaCase #-} +{-# LANGUAGE OverloadedStrings #-} module Templates ( - -- The Env data type and its constructors. - Env, - envConfig, - envTemplates, - envIndexTemplate, - envCommitTemplate, - envFileTemplate, - envForce, - -- The Template data type and its constructors. - Template, - templatePath, - templateGinger, - -- The entrypoint used by main. - loadTemplates, - -- The core functionality for which templates are used. + Env (..), + Template (..), + loadEnv, generate, ) where -import Control.Monad (void, (<=<)) +import Control.Monad (join, void, (<=<)) import Control.Monad.IO.Class (liftIO) import Control.Monad.Trans.Reader (ReaderT) -import Data.Char (toLower) import qualified Data.HashMap.Strict as HashMap -import Data.List (isSuffixOf) +import Data.List (find) import Data.Maybe (catMaybes) import Data.Text (Text, unpack) import Git.Libgit2 (LgRepo) -import Path (Abs, Dir, File, Path, dirname, filename, parseAbsDir, toFilePath, (</>)) -import Path.IO (copyDirRecur, copyFile, ensureDir, listDir) -import System.Directory (makeAbsolute) +import Path (Abs, Dir, File, Path, Rel, dirname, filename, parseAbsDir, toFilePath, (</>)) +import Path.IO (copyDirRecur, copyFile, doesDirExist, ensureDir, listDir) +import System.Directory (canonicalizePath) +import qualified System.FilePath as FP import System.IO.Error (tryIOError) import qualified Text.Ginger.AST as G import Text.Ginger.GVal (GVal) @@ -38,7 +27,7 @@ import Text.Ginger.Parse (ParserError (..), SourcePos, parseGingerFile) import Text.Ginger.Run (easyContext, runGingerT) -import Config +import Config (Config (..)) import Types {- @@ -47,136 +36,116 @@ -} data Env = Env { envConfig :: Config - , envTemplates :: [Template] - , envIndexTemplate :: Maybe Template + , envIndexTemplates :: [Template] , envCommitTemplate :: Maybe Template , envFileTemplate :: Maybe Template + , envRepoTemplates :: [Template] + , envOutputDirectory :: Path Abs Dir + , envRepoPaths :: [Path Abs Dir] + , envHost :: Text , envForce :: Bool } data Template = Template - { templatePath :: FilePath + { templatePath :: Path Rel File , templateGinger :: G.Template SourcePos } {- This creates the runtime environment, collecting the config and loading template data -from file. +from the template directory. -} -loadTemplates :: Bool -> Config -> IO Env -loadTemplates force config = do +loadEnv :: Bool -> Config -> IO Env +loadEnv force config = do + -- First ensure that the output directory exists + output <- parseAbsDir <=< canonicalizePath . outputDirectory $ config + ensureDir output + + -- Parse repos for env + repoPaths' <- mapM (parseAbsDir <=< canonicalizePath) . repoPaths $ config + + -- Find template files, copying the static files as is + (dirs, files) <- ls . templateDirectory $ config + (_, filesRepo) <- ls $ templateDirectory config FP.</> "repo" + copyStaticDirs output dirs + copyStaticFiles output files + -- Load files from template directory - templateFiles <- getFiles config - templates <- mapM loadTemplate (fmap toFilePath templateFiles) - -- Scoped templates - indexT <- loadTemplate . indexTemplate $ config - commitT <- loadTemplate . commitTemplate $ config - fileT <- loadTemplate . fileTemplate $ config + indexT <- collectTemplates files + commitT <- fmap join . mapM loadTemplate . find ((==) "commit.html" . toFilePath . filename) $ filesRepo + fileT <- fmap join . mapM loadTemplate . find ((==) "file.html" . toFilePath . filename) $ filesRepo + repoT <- collectTemplates . filter (flip notElem ["commit.html", "file.html"] . toFilePath . filename) $ filesRepo + -- Global environment return Env { envConfig = config - , envTemplates = catMaybes templates - , envIndexTemplate = indexT + , envIndexTemplates = indexT , envCommitTemplate = commitT , envFileTemplate = fileT + , envRepoTemplates = repoT + , envOutputDirectory = output + , envRepoPaths = repoPaths' + , envHost = host config , envForce = force } + where + collectTemplates :: [Path Abs File] -> IO [Template] + collectTemplates = fmap catMaybes . mapM loadTemplate . filter ((==) ".html" . FP.takeExtension . toFilePath) + + ls :: FilePath -> IO ([Path Abs Dir], [Path Abs File]) + ls dir = do + canon <- parseAbsDir =<< canonicalizePath dir + exists <- doesDirExist canon + if exists + then listDir canon + else return ([], []) + + copyStaticDirs :: Path Abs Dir -> [Path Abs Dir] -> IO () + copyStaticDirs output = mapM_ (\p -> copyDirRecur p (output </> dirname p)) . filterStaticDirs + filterStaticDirs = filter ((/=) "repo" . toFilePath) + + copyStaticFiles :: Path Abs Dir -> [Path Abs File] -> IO () + copyStaticFiles output = mapM_ (\p -> copyFile p (output </> filename p)) . filterStaticFiles + filterStaticFiles = filter (flip notElem [".html", ".include"] . FP.takeExtension . toFilePath) {- This is the generator function that receives repository-specific variables and uses Ginger to render templates using them. -} generate :: - FilePath -> + Path Abs File -> HashMap.HashMap Text (GVal RunRepo) -> Template -> ReaderT LgRepo IO () generate output context template = do - liftIO $ writeFile output "" -- Clear contents of file if it exists - void . runGingerT (easyContext (writeTo output) context) . templateGinger $ template - ----------------------------------------------------------------------------------------- --- Private ----------------------------------------------------------------------------- + let output' = toFilePath output + liftIO $ writeFile output' "" -- Clear contents of file if it exists + void . runGingerT (easyContext (writeTo output') context) . templateGinger $ template + where + -- This function gets the output HTML data and is responsible for saving it to file. + writeTo :: FilePath -> Html -> ReaderT LgRepo IO () + writeTo path = liftIO . appendFile path . unpack . htmlSource {- This takes the session's `Config` and maybe returns a loaded template for the ``indexTemplate`` setting. -} -loadTemplate :: FilePath -> IO (Maybe Template) +loadTemplate :: Path Abs File -> IO (Maybe Template) loadTemplate path = - parseGingerFile includeResolver path >>= \case - Right parsed -> return . Just . Template path $ parsed + parseGingerFile includeResolver (toFilePath path) >>= \case + Right parsed -> return . Just . Template (filename path) $ parsed Left err -> do - informError err + informError (toFilePath path) err return Nothing where -- An attempt at pretty printing the error message. - informError :: ParserError -> IO () - informError (ParserError msg Nothing) = - putStr $ "Template error: " <> path <> "\n" <> indent msg - informError (ParserError msg (Just pos)) = - putStrLn $ "Template error: " <> path <> "\n" <> indent (show pos <> "\n" <> msg) + informError p (ParserError msg Nothing) = + putStr $ "Template error: " <> p <> "\n" <> indent msg + informError p (ParserError msg (Just pos)) = + putStrLn $ "Template error: " <> p <> "\n" <> indent (show pos <> "\n" <> msg) indent = unlines . map (mappend " ") . lines -{- -This resolves Ginger's template includes. --} -includeResolver :: FilePath -> IO (Maybe String) -includeResolver path = either (const Nothing) Just <$> tryIOError (readFile path) - -{- -getFiles will look inside the template directory and generate a list of paths to likely -valid template files and another for static files. - -It's not very pretty, but we also copy the static files and folders from here because -I'm too lazy to work with Path too much. --} -getFiles :: Config -> IO [Path Abs File] -getFiles config = do - (dirs, files) <- ls . templateDirectory $ config - let files' = filter (not . isSuffixOf "include" . toFilePath) files - let templates = filter (isTemplate config) files' - let statics = filter (`notElem` templates) files' - output <- parseAbsDir <=< makeAbsolute . outputDirectory $ config - ensureDir output - copyStaticDirs output dirs - copyStaticFiles output statics - return templates - where - -- This gets fully qualified paths of the directory's contents - ls :: FilePath -> IO ([Path Abs Dir], [Path Abs File]) - ls = listDir <=< parseAbsDir <=< makeAbsolute - -{- -This is used to filter files in the template directory so that we only try to load HTML -files. --} -isTemplate :: Config -> Path Abs File -> Bool -isTemplate config = isTemplate' . map toLower . toFilePath - where - isTemplate' :: FilePath -> Bool - isTemplate' p = isSuffixOf "html" p && not (isTargeted config p) - isTargeted :: Config -> FilePath -> Bool - isTargeted c p = p `elem` [indexTemplate c, commitTemplate c, fileTemplate c] - -{- -These copies static files/folders into the output folder unchanged. --} -copyStaticFiles :: Path Abs Dir -> [Path Abs File] -> IO () -copyStaticFiles output = mapM_ copy - where - copy :: Path Abs File -> IO () - copy path = copyFile path $ output </> filename path - -copyStaticDirs :: Path Abs Dir -> [Path Abs Dir] -> IO () -copyStaticDirs output = mapM_ copy - where - copy :: Path Abs Dir -> IO () - copy path = copyDirRecur path $ output </> dirname path - -{- -This function gets the output HTML data and is responsible for saving it to file. --} -writeTo :: FilePath -> Html -> ReaderT LgRepo IO () -writeTo path = liftIO . appendFile path . unpack . htmlSource + -- This resolves template 'includes'. + includeResolver :: FilePath -> IO (Maybe String) + includeResolver p = either (const Nothing) Just <$> tryIOError (readFile p)
src/Repositories.hs Modified
@@ -22,13 +22,13 @@ import Data.Text.Encoding.Error (lenientDecode) import Git import Git.Libgit2 (LgRepo, lgFactory) -import System.Directory (createDirectoryIfMissing, doesFileExist) -import System.FilePath (takeFileName, (</>)) +import Path (Abs, Dir, File, Path, Rel, dirname, parseRelDir, parseRelFile, toFilePath, (</>)) +import Path.IO (doesFileExist, ensureDir) +import qualified System.FilePath as FP import System.IO.Error (tryIOError) import Text.Ginger.GVal (GVal, ToGVal, toGVal) -import Config -import Templates +import Templates (Env (..), Template (..), generate) import Types {- @@ -46,16 +46,15 @@ -} loadRepos :: Env -> IO [Repo] loadRepos env = do + let paths = envRepoPaths env descs <- mapM getDescription paths - return $ ($ Nothing) <$> zipWith Repo paths descs - where - paths = repoPaths . envConfig $ env + return . fmap ($ Nothing) . zipWith Repo paths $ descs {- Pass the repository's folder, get its description. -} -getDescription :: FilePath -> IO Text -getDescription = fmap (fromRight "") . tryIOError . fmap pack . readFile . (</> "description") +getDescription :: Path Abs Dir -> IO Text +getDescription = fmap (fromRight "") . tryIOError . fmap pack . readFile . flip FP.combine "description" . toFilePath {- This receives a file path to a single repository and tries to process it. If the @@ -63,16 +62,16 @@ (after informing the user of course). -} processRepo :: Env -> Repo -> IO Repo -processRepo env repo = withRepository lgFactory (repositoryPath repo) $ processRepo' env repo +processRepo env repo = withRepository lgFactory (toFilePath . repositoryPath $ repo) $ processRepo' env repo processRepo' :: Env -> Repo -> ReaderT LgRepo IO Repo processRepo' env repo = do - let name = takeFileName . repositoryPath $ repo - let output = outputDirectory (envConfig env) </> name + let name = dirname . repositoryPath $ repo + let output = envOutputDirectory env </> name resolveReference "HEAD" >>= \case Nothing -> do - liftIO . putStrLn $ "gitserve: " <> name <> ": Failed to resolve HEAD." + liftIO . putStrLn $ "gitserve: " <> show name <> ": Failed to resolve HEAD." return repo Just commitID -> do let gitHead = Tagged commitID @@ -85,12 +84,14 @@ let scope = package env name (repositoryDescription repo) commits tree tags branches -- Create the destination folders - liftIO . createDirectoryIfMissing True $ output </> "commit" - liftIO . createDirectoryIfMissing True $ output </> "file" + commitDir <- liftIO . parseRelDir $ "commit" + fileDir <- liftIO . parseRelDir $ "file" + liftIO . ensureDir $ output </> commitDir + liftIO . ensureDir $ output </> fileDir -- Run the generator -- let force = envForce env - mapM_ (genRepo output scope) $ envTemplates env + mapM_ (genRepo output scope) $ envRepoTemplates env mapM_ (genTarget output scope force $ envCommitTemplate env) commits mapM_ (genTarget output scope True $ envFileTemplate env) tree -- TODO: detect file changes @@ -105,7 +106,7 @@ -} package :: Env -> - FilePath -> + Path Rel Dir -> Text -> [Commit LgRepo] -> [TreeFile] -> @@ -114,8 +115,8 @@ HashMap.HashMap Text (GVal RunRepo) package env name description commits tree tags branches = HashMap.fromList - [ ("host", toGVal . host . envConfig $ env) - , ("name", toGVal . pack $ name) + [ ("host", toGVal . envHost $ env) + , ("name", toGVal . pack . init . toFilePath $ name) , ("description", toGVal description) , ("commits", toGVal . reverse $ commits) -- Could be optimised , ("tree", toGVal tree) @@ -200,13 +201,13 @@ dropName Nothing _ = Nothing genRepo :: - FilePath -> + Path Abs Dir -> HashMap.HashMap Text (GVal RunRepo) -> Template -> ReaderT LgRepo IO () genRepo output scope template = generate output' scope template where - output' = (</>) output . takeFileName . templatePath $ template + output' = output </> templatePath template ---------------------------------------------------------------------------------------- -- Targets ----------------------------------------------------------------------------- @@ -221,6 +222,11 @@ class ToGVal RunRepo a => Target a where identify :: a -> FilePath category :: a -> FilePath + dest :: a -> IO (Path Rel File) + dest t = do + dir <- parseRelDir . category $ t + file <- parseRelFile . identify $ t + return $ dir </> file instance Target (Commit LgRepo) where identify = (++ ".html") . show . untag . commitOid @@ -232,7 +238,7 @@ genTarget :: Target a => - FilePath -> + Path Abs Dir -> HashMap.HashMap Text (GVal RunRepo) -> Bool -> Maybe Template -> @@ -240,9 +246,10 @@ ReaderT LgRepo IO () genTarget _ _ _ Nothing _ = return () genTarget output scope force (Just template) target = do - let output' = output </> category target </> identify target + outFile <- liftIO . dest $ target + let output' = output </> outFile exists <- liftIO . doesFileExist $ output' when (force || not exists) $ do - liftIO . putStrLn $ "Writing " <> output' + liftIO . putStrLn $ "Writing " <> toFilePath output' let scope' = scope <> HashMap.fromList [(pack . category $ target, toGVal target)] generate output' scope' template
src/Main.hs Modified
@@ -8,12 +8,11 @@ import Data.Version (showVersion) import qualified Options.Applicative as O -import System.Directory (createDirectoryIfMissing) -import Config (getConfig, outputDirectory) +import Config (getConfig) import Index (runIndex) import Repositories (run) -import Templates (loadTemplates) +import Templates (loadEnv) {- Command line options @@ -63,6 +62,5 @@ then putStrLn $ "Your gitserve version is: " <> showVersion version else do conf <- getConfig (config options) - createDirectoryIfMissing True $ outputDirectory conf - env <- loadTemplates (force options) conf + env <- loadEnv (force options) conf runIndex env =<< run env
src/Index.hs Modified
@@ -7,49 +7,46 @@ import Control.Monad (void) import qualified Data.HashMap.Strict as HashMap import Data.Text (Text, unpack) -import System.FilePath ((</>)) +import Path (toFilePath) +import System.FilePath (combine) import Text.Ginger.GVal (GVal, toGVal) import Text.Ginger.Html (Html, htmlSource) import Text.Ginger.Parse (SourcePos) import Text.Ginger.Run (Run, easyRenderM) -import Config -import Templates +import Templates (Env (..), Template (..)) import Types {- -This creates the main index file from the index template, using information from all +This creates the main index files from the index templates, using information from all configured respositories. -} runIndex :: Env -> [Repo] -> IO () -runIndex env repos = - case envIndexTemplate env of - Nothing -> - return () - Just template -> do - putStrLn $ "Writing " <> output - writeFile output "" -- Clear contents of file if it exists - void $ - easyRenderM - (appendFile output . unpack . htmlSource) - (packageIndex config repos) - (templateGinger template) - where - config = envConfig env - output = outputDirectory config </> "index.html" +runIndex env repos = mapM_ (runIndexFile env repos) . envIndexTemplates $ env ---------------------------------------------------------------------------------------- -- Private ----------------------------------------------------------------------------- +runIndexFile :: Env -> [Repo] -> Template -> IO () +runIndexFile env repos template = do + let output = combine (toFilePath . envOutputDirectory $ env) . toFilePath . templatePath $ template + putStrLn $ "Writing " <> output + writeFile output "" -- Clear contents of file if it exists + void $ + easyRenderM + (appendFile output . unpack . htmlSource) + (packageIndex env repos) + (templateGinger template) + {- -This packages the variables that are available inside the index template. +This packages the variables that are available inside the index scope. -} packageIndex :: - Config -> + Env -> [Repo] -> HashMap.HashMap Text (GVal (Run SourcePos IO Html)) -packageIndex config repos = +packageIndex env repos = HashMap.fromList - [ ("host", toGVal $ host config) + [ ("host", toGVal $ envHost env) , ("repositories", toGVal repos) ]
src/Config.hs Modified
@@ -3,15 +3,8 @@ {-# LANGUAGE DerivingStrategies #-} module Config ( - Config, - repoPaths, - templateDirectory, - indexTemplate, - commitTemplate, - fileTemplate, + Config (..), getConfig, - outputDirectory, - host, ) where import Control.Monad ((<=<)) @@ -22,9 +15,6 @@ data Config = Config { repoPaths :: [FilePath] , templateDirectory :: FilePath - , indexTemplate :: FilePath - , commitTemplate :: FilePath - , fileTemplate :: FilePath , outputDirectory :: FilePath , host :: Text }
config.dhall Modified
@@ -14,9 +14,6 @@ let config = { repoPaths = folders , templateDirectory = "./templates" - , indexTemplate = "./templates/index-main.html" - , commitTemplate = "./templates/commit.html" - , fileTemplate = "./templates/file.html" , outputDirectory = "./output" , host = "http://localhost:8000" }