🐙 Templated web page generator for your git repositories
git clone https://github.com/m-col/gitja
Files | Refs | Readme | License

Commit 1403669b22b1435f588f71d985db158dc8613f39
Parent: 528d545f3e355e91555265e61ac46de7f93906e7
Author: mcol <mcol@posteo.net>
Date: 2021-10-05 22:13:18 +0100
Committer: mcol <mcol@posteo.net>
Committed: 2021-10-05 22:13:18 +0100

Move data and type code to own module

This moves half of the code from Repositories.hs to Types.hs to make
things easier to navigate and read. All of Types.hs is exported (and
only used by Repositories.hs). The warning for no explicit export list
is disabled.

stack.yaml Modified

@@ -11,6 +11,6 @@
 # These options are from the Kowainik style guide.
 ghc-options:
   "$locals": -Wall -Wcompat -Widentities -Wincomplete-uni-patterns
-    -Wincomplete-record-updates -Wredundant-constraints -Wmissing-export-lists
-    -Wpartial-fields -Wmissing-deriving-strategies -Wunused-packages
-    -fno-warn-unused-do-bind -fno-warn-orphans
+    -Wincomplete-record-updates -Wredundant-constraints -Wpartial-fields
+    -Wmissing-deriving-strategies -Wunused-packages -fno-warn-unused-do-bind
+    -fno-warn-orphans

src/Types.hs Added

@@ -0,0 +1,115 @@
+{-# Language LambdaCase #-}
+{-# Language OverloadedStrings #-}
+{-# Language FlexibleInstances #-}  -- Needed for `instance ToGVal`
+{-# Language MultiParamTypeClasses #-}  -- Needed for `instance ToGVal`
+{-# Language InstanceSigs #-}  -- Needed for toGVal type signature
+
+module Types where
+
+import Data.Default (def)
+import Data.Tagged (untag)
+import Data.Text (pack, Text, strip)
+import Data.Text.Encoding (decodeUtf8With)
+import Data.Text.Encoding.Error (lenientDecode)
+import Git
+import Git.Libgit2 (LgRepo)
+import Text.Ginger.GVal (GVal, toGVal, ToGVal, asText, asHtml, asLookup, asList)
+import Text.Ginger.Html (html)
+import qualified Data.Text as T
+
+{-
+GVal implementation for `Git.Commit r`, allowing commits to be rendered in Ginger
+templates.
+-}
+instance ToGVal m (Commit LgRepo) where
+    toGVal :: Commit LgRepo -> GVal m
+    toGVal commit = def
+        { asHtml = html . pack . show . commitLog $ commit
+        , asText = pack . show . commitLog $ commit
+        , asLookup = Just . commitAsLookup $ commit
+        }
+
+commitAsLookup :: Commit LgRepo -> Text -> Maybe (GVal m)
+commitAsLookup commit = \case
+    "id" -> Just . toGVal . show . untag . commitOid $ commit
+    "title" -> Just . toGVal . strip . T.takeWhile (/= '\n') . commitLog $ commit
+    "body" -> Just . toGVal . strip . T.dropWhile (/= '\n') . commitLog $ commit
+    "message" -> Just . toGVal . strip . commitLog $ commit
+    "author" -> Just . toGVal . strip . signatureName . commitAuthor $ commit
+    "committer" -> Just . toGVal . strip . signatureName . commitCommitter $ commit
+    "author_email" -> Just . toGVal . strip . signatureEmail . commitAuthor $ commit
+    "committer_email" -> Just . toGVal . strip . signatureEmail . commitCommitter $ commit
+    "authored" -> Just . toGVal . show . signatureWhen . commitAuthor $ commit
+    "committed" -> Just . toGVal . show . signatureWhen . commitCommitter $ commit
+    "encoding" -> Just . toGVal . strip . commitEncoding $ commit
+    _ -> Nothing
+
+{-
+Next we have some data used to represent a repository's tree and the different kinds of
+objects contained therein.
+-}
+data TreeFile = TreeFile
+    { treeFilePath :: TreeFilePath
+    , treeFileContents :: TreeFileContents
+    }
+
+data TreeFileContents = FileContents (TreeEntryMode, Text) | FolderContents [TreeFilePath]
+
+data TreeEntryMode = ModeDirectory | ModePlain | ModeExecutable | ModeSymlink | ModeSubmodule
+
+{-
+Some helper functions to convert from Haskell's LibGit2 BlobKind to our TreeEntryMode,
+and then from that to Git's octal representation, as seen when calling `git ls-tree
+<tree-ish>`
+-}
+blobkindToMode :: BlobKind -> TreeEntryMode
+blobkindToMode PlainBlob = ModePlain
+blobkindToMode ExecutableBlob = ModeExecutable
+blobkindToMode SymlinkBlob = ModeSymlink
+
+modeToOctal :: TreeEntryMode -> String
+modeToOctal ModeDirectory = "40000"
+modeToOctal ModePlain = "00644"
+modeToOctal ModeExecutable = "00755"
+modeToOctal ModeSymlink = "20000"
+modeToOctal ModeSubmodule = "60000"
+
+{-
+GVal implementations for data definitions above, allowing commits to be rendered in
+Ginger templates.
+-}
+instance ToGVal m TreeFile where
+    toGVal :: TreeFile -> GVal m
+    toGVal treefile = def
+        { asHtml = html . pack . show . treeFilePath $ treefile
+        , asText = pack . show . treeFilePath $ treefile
+        , asLookup = Just . treeAsLookup $ treefile
+        }
+
+instance ToGVal m TreeFileContents where
+    toGVal :: TreeFileContents -> GVal m
+    toGVal (FileContents (_, text)) = toGVal text
+    toGVal (FolderContents filePaths) = def
+        { asHtml = html . pack . show $ filePaths
+        , asText = pack . show $ filePaths
+        , asList = Just . fmap toGVal $ filePaths
+        }
+
+treeAsLookup :: TreeFile -> Text -> Maybe (GVal m)
+treeAsLookup treefile = \case
+    "path" -> Just . toGVal . treeFilePath $ treefile
+    "href" -> Just . toGVal . treePathToHref . treeFilePath $ treefile
+    "contents" -> Just . toGVal . treeFileContents $ treefile
+    "is_directory" -> Just . toGVal . treeFileIsDirectory $ treefile
+    _ -> Nothing
+
+treeFileIsDirectory :: TreeFile -> Bool
+treeFileIsDirectory treefile = case treeFileContents treefile of
+    FileContents _ -> False
+    FolderContents _ -> True
+
+{-
+Get the name of a tree file path's HTML file.
+-}
+treePathToHref :: TreeFilePath -> Text
+treePathToHref = flip T.append ".html" . T.replace "/" "." .  decodeUtf8With lenientDecode

src/Repositories.hs Modified

@@ -1,8 +1,6 @@
 {-# Language LambdaCase #-}
 {-# Language OverloadedStrings #-}
-{-# Language FlexibleInstances #-}  -- Needed for `instance ToGVal`
-{-# Language MultiParamTypeClasses #-}  -- Needed for `instance ToGVal`
-{-# Language InstanceSigs #-}  -- Needed for toGVal type signature
+{-# Language FlexibleInstances #-}  -- Needed for the Target class type constraints
 {-# Language FlexibleContexts #-}  -- Needed for the Target class type constraints
 
 module Repositories (
@@ -14,10 +12,9 @@
 import Control.Monad ((<=<))
 import Control.Monad.IO.Class (liftIO)
 import Control.Monad.Trans.Reader (ReaderT)
-import Data.Default (def)
 import Data.Either (fromRight)
 import Data.Tagged
-import Data.Text (pack, unpack, Text, strip)
+import Data.Text (pack, unpack, Text)
 import Data.Text.Encoding (decodeUtf8With)
 import Data.Text.Encoding.Error (lenientDecode)
 import Data.Maybe (mapMaybe)
@@ -26,15 +23,15 @@
 import System.Directory (createDirectoryIfMissing)
 import System.FilePath ((</>), takeFileName)
 import System.IO.Error (tryIOError)
-import Text.Ginger.GVal (GVal, toGVal, ToGVal, asText, asHtml, asLookup, asList)
-import Text.Ginger.Html (Html, html)
+import Text.Ginger.GVal (GVal, toGVal, ToGVal)
+import Text.Ginger.Html (Html)
 import Text.Ginger.Run (Run)
 import Text.Ginger.Parse (SourcePos)
 import qualified Data.HashMap.Strict as HashMap
-import qualified Data.Text as T
 
 import Config
 import Templates
+import Types
 
 {-
 This is the entrypoint that receives the ``Config`` and uses it to map over our
@@ -47,7 +44,7 @@
     return ()
 
 {-
-Pass the repository's folder, get its description. This is export so that Index.hs can
+Pass the repository's folder, get its description. This is exported so that Index.hs can
 use it too.
 -}
 getDescription :: FilePath -> IO Text
@@ -122,28 +119,6 @@
 loadCommit (CommitObjOid oid) = Just $ lookupCommit oid
 loadCommit _ = Nothing
 
-{-
-Valid modes:
-040000 Directory
-100644 Regular non-executable file
-100755 Regular executable file
-120000 Symbolic link
-160000 Submodule
--}
-data TreeEntryMode = ModeDirectory | ModePlain | ModeExecutable | ModeSymlink | ModeSubmodule
-
-toMode :: BlobKind -> TreeEntryMode
-toMode PlainBlob = ModePlain
-toMode ExecutableBlob = ModeExecutable
-toMode SymlinkBlob = ModeSymlink
-
-data TreeFileContents = FileContents (TreeEntryMode, Text) | FolderContents [TreeFilePath]
-
-data TreeFile = TreeFile
-    { treeFilePath :: TreeFilePath
-    , treeFileContents :: TreeFileContents
-    }
-
 getTree :: CommitOid LgRepo -> ReaderT LgRepo IO [TreeFile]
 getTree commitID = do
     entries <- listTreeEntries =<< lookupTree . commitTree =<< lookupCommit commitID
@@ -152,78 +127,12 @@
 
 gvalTreeEntry :: TreeEntry LgRepo -> ReaderT LgRepo IO TreeFileContents
 gvalTreeEntry (BlobEntry oid kind) =
-    FileContents . (,) (toMode kind) . decodeUtf8With lenientDecode <$> catBlob oid
+    FileContents . (,) (blobkindToMode kind) . decodeUtf8With lenientDecode <$> catBlob oid
 gvalTreeEntry (TreeEntry oid) = FolderContents . fmap fst <$> (listTreeEntries =<< lookupTree oid)
 gvalTreeEntry (CommitEntry _) = return . FileContents $ (ModeSubmodule, "No contents")  -- TODO
 
-
-{-
-Here we define how commits can be accessed and represented in Ginger templates.
--}
-instance ToGVal m (Commit LgRepo) where
-    toGVal :: Commit LgRepo -> GVal m
-    toGVal commit = def
-        { asHtml = html . pack . show . commitLog $ commit
-        , asText = pack . show . commitLog $ commit
-        , asLookup = Just . commitAsLookup $ commit
-        }
-
-commitAsLookup :: Commit LgRepo -> Text -> Maybe (GVal m)
-commitAsLookup commit = \case
-    "id" -> Just . toGVal . show . untag . commitOid $ commit
-    "title" -> Just . toGVal . strip . T.takeWhile (/= '\n') . commitLog $ commit
-    "body" -> Just . toGVal . strip . T.dropWhile (/= '\n') . commitLog $ commit
-    "message" -> Just . toGVal . strip . commitLog $ commit
-    "author" -> Just . toGVal . strip . signatureName . commitAuthor $ commit
-    "committer" -> Just . toGVal . strip . signatureName . commitCommitter $ commit
-    "author_email" -> Just . toGVal . strip . signatureEmail . commitAuthor $ commit
-    "committer_email" -> Just . toGVal . strip . signatureEmail . commitCommitter $ commit
-    "authored" -> Just . toGVal . show . signatureWhen . commitAuthor $ commit
-    "committed" -> Just . toGVal . show . signatureWhen . commitCommitter $ commit
-    "encoding" -> Just . toGVal . strip . commitEncoding $ commit
-    _ -> Nothing
-
-
-{-
-Here we define how files in the tree can be accessed by Ginger templates.
--}
-instance ToGVal m TreeFile where
-    toGVal :: TreeFile -> GVal m
-    toGVal treefile = def
-        { asHtml = html . pack . show . treeFilePath $ treefile
-        , asText = pack . show . treeFilePath $ treefile
-        , asLookup = Just . treeAsLookup $ treefile
-        }
-
-instance ToGVal m TreeFileContents where
-    toGVal :: TreeFileContents -> GVal m
-    toGVal (FileContents (_, text)) = toGVal text
-    toGVal (FolderContents filePaths) = def
-        { asHtml = html . pack . show $ filePaths
-        , asText = pack . show $ filePaths
-        , asList = Just . fmap toGVal $ filePaths
-        }
-
-treeAsLookup :: TreeFile -> Text -> Maybe (GVal m)
-treeAsLookup treefile = \case
-    "path" -> Just . toGVal . treeFilePath $ treefile
-    "href" -> Just . toGVal . treePathToHref . treeFilePath $ treefile
-    "contents" -> Just . toGVal . treeFileContents $ treefile
-    "is_directory" -> Just . toGVal . treeFileIsDirectory $ treefile
-    _ -> Nothing
-
-treeFileIsDirectory :: TreeFile -> Bool
-treeFileIsDirectory treefile = case treeFileContents treefile of
-    FileContents _ -> False
-    FolderContents _ -> True
-
-{-
-Get the name of a tree file path's HTML file.
--}
-treePathToHref :: TreeFilePath -> Text
-treePathToHref = flip T.append ".html" . T.replace "/" "." .  decodeUtf8With lenientDecode
-
 ----------------------------------------------------------------------------------------
+-- Targets -----------------------------------------------------------------------------
 
 {-
 A Target refers to a template scope and repository object whose information is available

gitserve.cabal Modified

@@ -16,7 +16,7 @@
 executable gitserve
   hs-source-dirs:      src
   main-is:             Main.hs
-  other-modules:       Config, Index, Repositories, Templates
+  other-modules:       Config, Index, Repositories, Templates, Types
   default-language:    Haskell2010
   build-depends:       base >= 4.7 && < 5
                      , conduit