From bbae6ddb97c0825f6b0b689f4d9eeac67515d1c1 Mon Sep 17 00:00:00 2001 From: pacien Date: Sat, 21 Dec 2019 19:28:58 +0100 Subject: compiler: init stack project --- compiler/src/Lib.hs | 6 ++++++ 1 file changed, 6 insertions(+) create mode 100644 compiler/src/Lib.hs (limited to 'compiler/src') diff --git a/compiler/src/Lib.hs b/compiler/src/Lib.hs new file mode 100644 index 0000000..d36ff27 --- /dev/null +++ b/compiler/src/Lib.hs @@ -0,0 +1,6 @@ +module Lib + ( someFunc + ) where + +someFunc :: IO () +someFunc = putStrLn "someFunc" -- cgit v1.2.3 From 8de4411269ae85789c1cc7d81a9ecf0facbe78ff Mon Sep 17 00:00:00 2001 From: pacien Date: Mon, 23 Dec 2019 05:09:25 +0100 Subject: compiler: add base structures and encoding --- compiler/src/Lib.hs | 178 ++++++++++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 174 insertions(+), 4 deletions(-) (limited to 'compiler/src') diff --git a/compiler/src/Lib.hs b/compiler/src/Lib.hs index d36ff27..c52e095 100644 --- a/compiler/src/Lib.hs +++ b/compiler/src/Lib.hs @@ -1,6 +1,176 @@ +{-# LANGUAGE DuplicateRecordFields, DeriveGeneric #-} + + +-- ldgallery - A static generator which turns a collection of tagged +-- pictures into a searchable web gallery. +-- +-- Copyright (C) 2019 Pacien TRAN-GIRARD +-- 2019 Guillaume FOUET +-- +-- This program is free software: you can redistribute it and/or modify +-- it under the terms of the GNU Affero General Public License as +-- published by the Free Software Foundation, either version 3 of the +-- License, or (at your option) any later version. +-- +-- This program is distributed in the hope that it will be useful, +-- but WITHOUT ANY WARRANTY; without even the implied warranty of +-- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +-- GNU Affero General Public License for more details. +-- +-- You should have received a copy of the GNU Affero General Public License +-- along with this program. If not, see . + + module Lib - ( someFunc - ) where + ( testRun + ) where + + +import GHC.Generics + +import Control.Monad.IO.Class (MonadIO, liftIO) +import Control.Exception (Exception, throwIO) + +import Data.Function +import Data.Maybe (fromMaybe) +import Data.List (map) +import Data.Char (toLower) +import Data.Text (Text, empty, pack) +import Data.Yaml (ParseException, decodeFileEither) +import Data.Aeson + +import System.FilePath +import System.Directory.Tree +import System.Directory + + +encodingOptions :: Options +encodingOptions = defaultOptions + { fieldLabelModifier = map toLower + , constructorTagModifier = map toLower + , sumEncoding = defaultTaggedObject + { tagFieldName = "type" + , contentsFieldName = "contents" + } + } + + +-- input structure + +data SidecarItemMetadata = SidecarItemMetadata + { title :: Maybe Text + , date :: Maybe Text + , description :: Maybe Text + , tags :: Maybe [Text] + } deriving Generic + +instance FromJSON SidecarItemMetadata where + parseJSON = genericParseJSON encodingOptions + + +-- output structures + +type ResourcePath = Text +type Tag = Text +type FileSizeKB = Int + + +data Resolution = Resolution + { width :: Int + , height :: Int + } deriving Generic + +instance ToJSON Resolution where + toJSON = genericToJSON encodingOptions + toEncoding = genericToEncoding encodingOptions + + +data ItemProperties = + Directory { items :: [Item] } + | Image { resolution :: Resolution, filesize :: FileSizeKB } +-- | Video { filesize :: FileSizeKB } + | Unknown + deriving Generic + +instance ToJSON ItemProperties where + toJSON = genericToJSON encodingOptions + toEncoding = genericToEncoding encodingOptions + + +data Item = Item + { title :: Text + , date :: Text -- TODO: checked ISO8601 date + , description :: Text + , tags :: [Tag] + , path :: ResourcePath + , thumbnail :: Maybe ResourcePath + , properties :: ItemProperties + } deriving Generic + +instance ToJSON Item where + toJSON = genericToJSON encodingOptions + toEncoding = genericToEncoding encodingOptions + + +-- mapping + +data LoadException = LoadException String ParseException deriving Show +instance Exception LoadException + +decodeYamlFile :: (MonadIO m, FromJSON a) => FilePath -> m a +decodeYamlFile fpath = + liftIO $ Data.Yaml.decodeFileEither fpath + >>= either (throwIO . LoadException fpath) return + + +metadataDirTree :: DirTree FilePath -> IO (DirTree SidecarItemMetadata) +metadataDirTree (Failed _ ferr) = ioError ferr +metadataDirTree f@(File _ fpath) = + decodeYamlFile fpath + >>= \metadata -> return f { file = metadata } +metadataDirTree d@(Dir _ dcontents) = + filter canContainMetadata dcontents + & mapM metadataDirTree + >>= \contents -> return d { contents = contents } + where + canContainMetadata (Dir _ _) = True + canContainMetadata (File fname _) = isExtensionOf ".yaml" fname + + +toItemTree :: (MonadIO m) => [FileName] -> DirTree SidecarItemMetadata -> m Item +toItemTree pathTo d@(Dir dname dcontents) = + mapM (toItemTree path) dcontents + >>= \items -> return Item + { title = pack dname + , date = empty -- TODO: would it make sense to take the date of child elements? + , description = empty + , tags = [] -- TODO: aggregate tags from childs + , path = pack $ joinPath $ "items":path -- FIXME: use URL path instead of system path sep + , thumbnail = Nothing + , properties = Directory { items = items }} + where + path = pathTo ++ [dname] +toItemTree pathTo f@(File fname metadata) = + return Item + { title = optMeta title (pack fname) + , date = optMeta date empty -- TODO: check and normalise dates + , description = optMeta description empty + , tags = optMeta tags [] + , path = pack $ joinPath $ "items":(pathTo ++ [fname]) -- FIXME: use URL path instead of system path sep + , thumbnail = Just $ pack $ joinPath $ "thumbnails":(pathTo ++ [fname]) -- FIXME: use URL path instead of system path sep + , properties = Unknown } -- TODO + where + optMeta get fallback = fromMaybe fallback $ get (metadata::SidecarItemMetadata) + + +process :: FilePath -> FilePath -> IO () +process inputDir outputDir = + readDirectoryWith return inputDir + >>= metadataDirTree . dirTree + >>= toItemTree [] + >>= return . show . toEncoding + >>= liftIO . putStrLn + -someFunc :: IO () -someFunc = putStrLn "someFunc" +testRun :: IO () +testRun = process "../example" "../out" -- cgit v1.2.3 From 81cfb110248a8f98cd084533f00a98a507d9518b Mon Sep 17 00:00:00 2001 From: pacien Date: Mon, 23 Dec 2019 07:39:27 +0100 Subject: compiler: fix item tree tag aggregation and path concat --- compiler/src/Lib.hs | 65 ++++++++++++++++++++++++++++++++--------------------- 1 file changed, 40 insertions(+), 25 deletions(-) (limited to 'compiler/src') diff --git a/compiler/src/Lib.hs b/compiler/src/Lib.hs index c52e095..6cecfc5 100644 --- a/compiler/src/Lib.hs +++ b/compiler/src/Lib.hs @@ -34,12 +34,14 @@ import Control.Exception (Exception, throwIO) import Data.Function import Data.Maybe (fromMaybe) import Data.List (map) +import Data.Set (fromList, toList) import Data.Char (toLower) import Data.Text (Text, empty, pack) import Data.Yaml (ParseException, decodeFileEither) import Data.Aeson -import System.FilePath +import System.FilePath (isExtensionOf) +import qualified System.FilePath.Posix (joinPath) import System.Directory.Tree import System.Directory @@ -137,37 +139,50 @@ metadataDirTree d@(Dir _ dcontents) = canContainMetadata (File fname _) = isExtensionOf ".yaml" fname -toItemTree :: (MonadIO m) => [FileName] -> DirTree SidecarItemMetadata -> m Item -toItemTree pathTo d@(Dir dname dcontents) = - mapM (toItemTree path) dcontents - >>= \items -> return Item - { title = pack dname - , date = empty -- TODO: would it make sense to take the date of child elements? - , description = empty - , tags = [] -- TODO: aggregate tags from childs - , path = pack $ joinPath $ "items":path -- FIXME: use URL path instead of system path sep - , thumbnail = Nothing - , properties = Directory { items = items }} - where - path = pathTo ++ [dname] -toItemTree pathTo f@(File fname metadata) = - return Item - { title = optMeta title (pack fname) - , date = optMeta date empty -- TODO: check and normalise dates - , description = optMeta description empty - , tags = optMeta tags [] - , path = pack $ joinPath $ "items":(pathTo ++ [fname]) -- FIXME: use URL path instead of system path sep - , thumbnail = Just $ pack $ joinPath $ "thumbnails":(pathTo ++ [fname]) -- FIXME: use URL path instead of system path sep - , properties = Unknown } -- TODO +unique :: Ord a => [a] -> [a] +unique = Data.Set.toList . Data.Set.fromList + + +joinURLPath :: [FileName] -> Text +joinURLPath = pack . System.FilePath.Posix.joinPath + + +toItemTree :: (MonadIO m) => FilePath -> FilePath -> DirTree SidecarItemMetadata -> m Item +toItemTree itemsDir thumbnailsDir = nodeToItem [] where - optMeta get fallback = fromMaybe fallback $ get (metadata::SidecarItemMetadata) + nodeToItem pathTo d@(Dir dname dcontents) = + mapM (nodeToItem path) dcontents + >>= \items -> return Item + { title = pack dname + , date = empty + , description = empty + , tags = aggregateTags items + , path = joinURLPath $ itemsDir:path + , thumbnail = Nothing + , properties = Directory { items = items } } + where + path = pathTo ++ [dname] + aggregateTags = unique . concatMap (\item -> tags (item::Item)) + + nodeToItem pathTo f@(File fname metadata) = + return Item + { title = optMeta title (pack fname) + , date = optMeta date empty -- TODO: check and normalise dates + , description = optMeta description empty + , tags = optMeta tags [] + , path = joinURLPath $ itemsDir:path + , thumbnail = Just $ joinURLPath $ thumbnailsDir:path + , properties = Unknown } -- TODO + where + path = pathTo ++ [fname] + optMeta get fallback = fromMaybe fallback $ get (metadata::SidecarItemMetadata) process :: FilePath -> FilePath -> IO () process inputDir outputDir = readDirectoryWith return inputDir >>= metadataDirTree . dirTree - >>= toItemTree [] + >>= toItemTree "items" "thumbnails" >>= return . show . toEncoding >>= liftIO . putStrLn -- cgit v1.2.3 From 139e2b76d23b13d2b3bb70fb1d5c1ea9dc255513 Mon Sep 17 00:00:00 2001 From: pacien Date: Mon, 23 Dec 2019 11:19:33 +0100 Subject: compiler: export aggregated json index --- compiler/src/Lib.hs | 68 ++++++++++++++++++++++++++++++++++++----------------- 1 file changed, 47 insertions(+), 21 deletions(-) (limited to 'compiler/src') diff --git a/compiler/src/Lib.hs b/compiler/src/Lib.hs index 6cecfc5..e21751c 100644 --- a/compiler/src/Lib.hs +++ b/compiler/src/Lib.hs @@ -40,7 +40,7 @@ import Data.Text (Text, empty, pack) import Data.Yaml (ParseException, decodeFileEither) import Data.Aeson -import System.FilePath (isExtensionOf) +import System.FilePath ((), dropFileName, dropExtension, isExtensionOf) import qualified System.FilePath.Posix (joinPath) import System.Directory.Tree import System.Directory @@ -147,44 +147,70 @@ joinURLPath :: [FileName] -> Text joinURLPath = pack . System.FilePath.Posix.joinPath -toItemTree :: (MonadIO m) => FilePath -> FilePath -> DirTree SidecarItemMetadata -> m Item +toItemTree :: (MonadIO m) => FilePath -> FilePath -> DirTree SidecarItemMetadata -> m (Item, DirTree SidecarItemMetadata) toItemTree itemsDir thumbnailsDir = nodeToItem [] where nodeToItem pathTo d@(Dir dname dcontents) = mapM (nodeToItem path) dcontents - >>= \items -> return Item - { title = pack dname - , date = empty - , description = empty - , tags = aggregateTags items - , path = joinURLPath $ itemsDir:path - , thumbnail = Nothing - , properties = Directory { items = items } } + >>= return . unzip + >>= \(items, _) -> return + ( Item + { title = pack dname + , date = empty + , description = empty + , tags = aggregateTags items + , path = joinURLPath $ itemsDir:path + , thumbnail = Nothing + , properties = Directory { items = items } } + , d) where path = pathTo ++ [dname] aggregateTags = unique . concatMap (\item -> tags (item::Item)) nodeToItem pathTo f@(File fname metadata) = - return Item - { title = optMeta title (pack fname) - , date = optMeta date empty -- TODO: check and normalise dates - , description = optMeta description empty - , tags = optMeta tags [] - , path = joinURLPath $ itemsDir:path - , thumbnail = Just $ joinURLPath $ thumbnailsDir:path - , properties = Unknown } -- TODO + return + ( Item + { title = optMeta title $ pack $ dropExtension fname + , date = optMeta date empty -- TODO: check and normalise dates + , description = optMeta description empty + , tags = optMeta tags [] + , path = joinURLPath $ itemsDir:path + , thumbnail = Just $ joinURLPath $ thumbnailsDir:path + , properties = Unknown } -- TODO + , f) where path = pathTo ++ [fname] optMeta get fallback = fromMaybe fallback $ get (metadata::SidecarItemMetadata) +unrooted :: AnchoredDirTree a -> DirTree a +unrooted t = (dirTree t) { name = "" } + + +writeJSON :: ToJSON a => FilePath -> a -> IO () +writeJSON path obj = + createDirectoryIfMissing True (dropFileName path) + >> Data.Aeson.encodeFile path obj + + +infixl 1 >>>>>> +(>>>>>>) :: Monad m => m a -> (a -> m b) -> m a +a >>>>>> f = a >>= f >>= return a + + process :: FilePath -> FilePath -> IO () process inputDir outputDir = readDirectoryWith return inputDir - >>= metadataDirTree . dirTree - >>= toItemTree "items" "thumbnails" - >>= return . show . toEncoding + >>= return . unrooted + >>= metadataDirTree + >>= toItemTree itemsDir thumbnailsDir + >>>>>> writeJSON (outputDir indexFile) . fst + >>= return . show . toEncoding . fst >>= liftIO . putStrLn + where + itemsDir = "items" + thumbnailsDir = "thumbnails" + indexFile = "index.json" testRun :: IO () -- cgit v1.2.3 From 819ec9bfb9674375f696741816184fef06af68ed Mon Sep 17 00:00:00 2001 From: pacien Date: Tue, 24 Dec 2019 07:34:14 +0100 Subject: compiler: assemble trees --- compiler/src/Lib.hs | 154 +++++++++++++++++++++++++++++++++------------------- 1 file changed, 97 insertions(+), 57 deletions(-) (limited to 'compiler/src') diff --git a/compiler/src/Lib.hs b/compiler/src/Lib.hs index e21751c..70a2cca 100644 --- a/compiler/src/Lib.hs +++ b/compiler/src/Lib.hs @@ -32,7 +32,7 @@ import Control.Monad.IO.Class (MonadIO, liftIO) import Control.Exception (Exception, throwIO) import Data.Function -import Data.Maybe (fromMaybe) +import Data.Maybe (fromMaybe, listToMaybe) import Data.List (map) import Data.Set (fromList, toList) import Data.Char (toLower) @@ -40,7 +40,7 @@ import Data.Text (Text, empty, pack) import Data.Yaml (ParseException, decodeFileEither) import Data.Aeson -import System.FilePath ((), dropFileName, dropExtension, isExtensionOf) +import System.FilePath ((), joinPath, dropFileName, dropExtension, isExtensionOf) import qualified System.FilePath.Posix (joinPath) import System.Directory.Tree import System.Directory @@ -64,7 +64,7 @@ data SidecarItemMetadata = SidecarItemMetadata , date :: Maybe Text , description :: Maybe Text , tags :: Maybe [Text] - } deriving Generic + } deriving (Generic, Show) instance FromJSON SidecarItemMetadata where parseJSON = genericParseJSON encodingOptions @@ -80,7 +80,7 @@ type FileSizeKB = Int data Resolution = Resolution { width :: Int , height :: Int - } deriving Generic + } deriving (Generic, Show) instance ToJSON Resolution where toJSON = genericToJSON encodingOptions @@ -92,7 +92,7 @@ data ItemProperties = | Image { resolution :: Resolution, filesize :: FileSizeKB } -- | Video { filesize :: FileSizeKB } | Unknown - deriving Generic + deriving (Generic, Show) instance ToJSON ItemProperties where toJSON = genericToJSON encodingOptions @@ -107,7 +107,7 @@ data Item = Item , path :: ResourcePath , thumbnail :: Maybe ResourcePath , properties :: ItemProperties - } deriving Generic + } deriving (Generic, Show) instance ToJSON Item where toJSON = genericToJSON encodingOptions @@ -125,87 +125,127 @@ decodeYamlFile fpath = >>= either (throwIO . LoadException fpath) return -metadataDirTree :: DirTree FilePath -> IO (DirTree SidecarItemMetadata) -metadataDirTree (Failed _ ferr) = ioError ferr -metadataDirTree f@(File _ fpath) = - decodeYamlFile fpath - >>= \metadata -> return f { file = metadata } -metadataDirTree d@(Dir _ dcontents) = - filter canContainMetadata dcontents - & mapM metadataDirTree - >>= \contents -> return d { contents = contents } +toMetaTree :: DirTree FilePath -> IO (DirTree SidecarItemMetadata) +toMetaTree tree = return (filterDir canContainMetadata tree) >>= metaNode where - canContainMetadata (Dir _ _) = True - canContainMetadata (File fname _) = isExtensionOf ".yaml" fname + -- TODO: exclude hidden files (name starting with '.')? + canContainMetadata :: DirTree a -> Bool + canContainMetadata (File fname _) = isExtensionOf ".yaml" fname + canContainMetadata (Dir _ _) = True + + metaNode :: DirTree FilePath -> IO (DirTree SidecarItemMetadata) + metaNode (Failed _ ferr) = ioError ferr + metaNode file@(File _ fpath) = decodeYamlFile fpath + >>= \metadata -> return file { file = metadata } + metaNode dir@(Dir _ dcontents) = mapM metaNode dcontents + >>= \contents -> return dir { contents = contents } unique :: Ord a => [a] -> [a] unique = Data.Set.toList . Data.Set.fromList - joinURLPath :: [FileName] -> Text joinURLPath = pack . System.FilePath.Posix.joinPath -toItemTree :: (MonadIO m) => FilePath -> FilePath -> DirTree SidecarItemMetadata -> m (Item, DirTree SidecarItemMetadata) -toItemTree itemsDir thumbnailsDir = nodeToItem [] +toItemTree :: FilePath -> FilePath -> DirTree SidecarItemMetadata -> IO Item +toItemTree itemsDir thumbnailsDir = itemNode [] where - nodeToItem pathTo d@(Dir dname dcontents) = - mapM (nodeToItem path) dcontents - >>= return . unzip - >>= \(items, _) -> return - ( Item - { title = pack dname - , date = empty - , description = empty - , tags = aggregateTags items - , path = joinURLPath $ itemsDir:path - , thumbnail = Nothing - , properties = Directory { items = items } } - , d) + itemNode :: [FileName] -> DirTree SidecarItemMetadata -> IO Item + itemNode pathTo (Dir dname dcontents) = + mapM (itemNode path) dcontents + >>= \items -> return Item + { title = pack dname + , date = empty + , description = empty + , tags = aggregateChildTags items + , path = joinURLPath $ itemsDir:path + , thumbnail = Nothing + , properties = Directory items } where path = pathTo ++ [dname] - aggregateTags = unique . concatMap (\item -> tags (item::Item)) - - nodeToItem pathTo f@(File fname metadata) = - return - ( Item - { title = optMeta title $ pack $ dropExtension fname - , date = optMeta date empty -- TODO: check and normalise dates - , description = optMeta description empty - , tags = optMeta tags [] - , path = joinURLPath $ itemsDir:path - , thumbnail = Just $ joinURLPath $ thumbnailsDir:path - , properties = Unknown } -- TODO - , f) + aggregateChildTags = unique . concatMap (\item -> tags (item::Item)) + + itemNode pathTo (File fname metadata) = + return Item + { title = optMeta title $ pack name + , date = optMeta date empty -- TODO: check and normalise dates + , description = optMeta description empty + , tags = optMeta tags [] + , path = joinURLPath $ itemsDir:path + , thumbnail = Just $ joinURLPath $ thumbnailsDir:path + , properties = Unknown } -- TODO where - path = pathTo ++ [fname] + name = dropExtension fname + path = pathTo ++ [name] optMeta get fallback = fromMaybe fallback $ get (metadata::SidecarItemMetadata) +data ObjectTree = ObjectTree + { pathTo :: [ObjectTree] + , meta :: (DirTree SidecarItemMetadata) + , item :: Item } deriving Show + +rootObjectTree :: DirTree SidecarItemMetadata -> Item -> ObjectTree +rootObjectTree = ObjectTree [] + +toObjectTree :: (DirTree SidecarItemMetadata -> IO Item) -> DirTree SidecarItemMetadata -> IO ObjectTree +toObjectTree itemGen meta = itemGen meta >>= return . (rootObjectTree meta) + +flatten :: ObjectTree -> [ObjectTree] +flatten object@(ObjectTree _ (File _ _) _) = [object] +flatten object@(ObjectTree pathTo (Dir _ dcontents) item) = + zip dcontents (items $ properties item) + & map (uncurry $ ObjectTree $ pathTo ++ [object]) + & concatMap flatten + & (:) object + +objFileName :: ObjectTree -> FileName +objFileName (ObjectTree _ (Dir name _) _) = name +objFileName (ObjectTree _ (File name _) _) = dropExtension name -- without ".yaml" + +objFilePath :: ObjectTree -> FilePath +objFilePath obj@(ObjectTree pathTo _ _) = + (map (name . meta) pathTo) ++ [objFileName obj] + & System.FilePath.joinPath + + +data FileTransform = FileTransform + { src :: FilePath + , dst :: FilePath } deriving Show + + +isUpToDate :: FilePath -> FilePath -> IO Bool +isUpToDate ref target = + do + refTime <- getModificationTime ref + targetTime <- getModificationTime target + return (target >= ref) + + unrooted :: AnchoredDirTree a -> DirTree a unrooted t = (dirTree t) { name = "" } - writeJSON :: ToJSON a => FilePath -> a -> IO () writeJSON path obj = createDirectoryIfMissing True (dropFileName path) >> Data.Aeson.encodeFile path obj - -infixl 1 >>>>>> -(>>>>>>) :: Monad m => m a -> (a -> m b) -> m a -a >>>>>> f = a >>= f >>= return a - +passthrough :: Monad m => (a -> m b) -> a -> m a +passthrough f a = return a >>= f >>= \_ -> return a process :: FilePath -> FilePath -> IO () process inputDir outputDir = readDirectoryWith return inputDir >>= return . unrooted - >>= metadataDirTree - >>= toItemTree itemsDir thumbnailsDir - >>>>>> writeJSON (outputDir indexFile) . fst - >>= return . show . toEncoding . fst + >>= toMetaTree + >>= toObjectTree (toItemTree itemsDir thumbnailsDir) + >>= passthrough (writeJSON (outputDir indexFile) . item) + >>= return . flatten +-- >>= mapM (return . pathTo) + >>= return . (map objFilePath) + >>= return . show +-- >>= return . show . toEncoding . item >>= liftIO . putStrLn where itemsDir = "items" -- cgit v1.2.3 From 0b2f6fb420d213b4ee718b9ac79cc3f9fa7678d5 Mon Sep 17 00:00:00 2001 From: pacien Date: Wed, 25 Dec 2019 21:04:31 +0100 Subject: compiler: refactor transform stages --- compiler/src/Files.hs | 104 ++++++++++++++++++++ compiler/src/Gallery.hs | 123 +++++++++++++++++++++++ compiler/src/Input.hs | 95 ++++++++++++++++++ compiler/src/Lib.hs | 251 ++++++----------------------------------------- compiler/src/Resource.hs | 58 +++++++++++ compiler/src/Utils.hs | 49 +++++++++ 6 files changed, 461 insertions(+), 219 deletions(-) create mode 100644 compiler/src/Files.hs create mode 100644 compiler/src/Gallery.hs create mode 100644 compiler/src/Input.hs create mode 100644 compiler/src/Resource.hs create mode 100644 compiler/src/Utils.hs (limited to 'compiler/src') diff --git a/compiler/src/Files.hs b/compiler/src/Files.hs new file mode 100644 index 0000000..7948842 --- /dev/null +++ b/compiler/src/Files.hs @@ -0,0 +1,104 @@ +{-# LANGUAGE DuplicateRecordFields, DeriveGeneric #-} + +-- ldgallery - A static generator which turns a collection of tagged +-- pictures into a searchable web gallery. +-- +-- Copyright (C) 2019 Pacien TRAN-GIRARD +-- +-- This program is free software: you can redistribute it and/or modify +-- it under the terms of the GNU Affero General Public License as +-- published by the Free Software Foundation, either version 3 of the +-- License, or (at your option) any later version. +-- +-- This program is distributed in the hope that it will be useful, +-- but WITHOUT ANY WARRANTY; without even the implied warranty of +-- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +-- GNU Affero General Public License for more details. +-- +-- You should have received a copy of the GNU Affero General Public License +-- along with this program. If not, see . + + +module Files + ( FileName, LocalPath, WebPath, Path + , (), (), localPath, webPath + , FSNode(..), AnchoredFSNode(..) + , nodePath, nodeName, isHidden, flatten, filterDir, readDirectory + ) where + + +import Control.Monad (filterM, mapM) +import Data.Bool (bool) +import Data.List (isPrefixOf, length, deleteBy) +import Data.Function ((&)) +import System.Directory (doesDirectoryExist, listDirectory) +import qualified System.FilePath +import qualified System.FilePath.Posix +import Utils + + +type FileName = String +type LocalPath = String +type WebPath = String + + -- | Reversed path component list +type Path = [FileName] + +() :: Path -> Path -> Path +l r = r ++ l + +( FileName -> Path +path ) :: FileName -> Path -> Path +file /> path = path ++ [file] + +localPath :: Path -> LocalPath +localPath = System.FilePath.joinPath . reverse + +webPath :: Path -> WebPath +webPath = System.FilePath.Posix.joinPath . reverse + + +data FSNode = File Path | Dir Path [FSNode] deriving Show +data AnchoredFSNode = AnchoredFSNode + { anchor :: LocalPath + , root :: FSNode } deriving Show + +nodePath :: FSNode -> Path +nodePath (File path) = path +nodePath (Dir path _) = path + +nodeName :: FSNode -> FileName +nodeName = head . nodePath + +isHidden :: FSNode -> Bool +isHidden node = "." `isPrefixOf` filename && length filename > 1 + where filename = nodeName node + +flatten :: FSNode -> [FSNode] +flatten file@(File _) = [file] +flatten dir@(Dir _ childs) = dir:(concatMap flatten childs) + +-- | Filters a dir tree. The root is always returned. +filterDir :: (FSNode -> Bool) -> FSNode -> FSNode +filterDir _ file@(File _) = file +filterDir cond (Dir path childs) = + filter cond childs & map (filterDir cond) & Dir path + +readDirectory :: LocalPath -> IO AnchoredFSNode +readDirectory root = mkNode [""] >>= return . AnchoredFSNode root + where + mkNode :: Path -> IO FSNode + mkNode path = + (doesDirectoryExist $ localPath (root /> path)) + >>= bool (mkFileNode path) (mkDirNode path) + + mkFileNode :: Path -> IO FSNode + mkFileNode path = return $ File path + + mkDirNode :: Path -> IO FSNode + mkDirNode path = + (listDirectory $ localPath (root /> path)) + >>= mapM (mkNode . ((>= return . Dir path diff --git a/compiler/src/Gallery.hs b/compiler/src/Gallery.hs new file mode 100644 index 0000000..3be62ad --- /dev/null +++ b/compiler/src/Gallery.hs @@ -0,0 +1,123 @@ +{-# LANGUAGE DuplicateRecordFields, DeriveGeneric, DeriveAnyClass #-} + +-- ldgallery - A static generator which turns a collection of tagged +-- pictures into a searchable web gallery. +-- +-- Copyright (C) 2019 Pacien TRAN-GIRARD +-- +-- This program is free software: you can redistribute it and/or modify +-- it under the terms of the GNU Affero General Public License as +-- published by the Free Software Foundation, either version 3 of the +-- License, or (at your option) any later version. +-- +-- This program is distributed in the hope that it will be useful, +-- but WITHOUT ANY WARRANTY; without even the implied warranty of +-- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +-- GNU Affero General Public License for more details. +-- +-- You should have received a copy of the GNU Affero General Public License +-- along with this program. If not, see . + + +module Gallery + ( GalleryItem(..), buildGalleryTree + ) where + + +import GHC.Generics (Generic) +import Data.Char (toLower) +import Data.Function ((&)) +import Data.Maybe (fromMaybe) + +import Data.Aeson (ToJSON, genericToJSON, genericToEncoding) +import qualified Data.Aeson as JSON + +importĀ Utils +import Files +import Input +import Resource + + +encodingOptions :: JSON.Options +encodingOptions = JSON.defaultOptions + { JSON.fieldLabelModifier = map toLower + , JSON.constructorTagModifier = map toLower + , JSON.sumEncoding = JSON.defaultTaggedObject + { JSON.tagFieldName = "type" + , JSON.contentsFieldName = "contents" + } + } + + +type ResourcePath = String +type Tag = String +type FileSizeKB = Int + + +data Resolution = Resolution + { width :: Int + , height :: Int + } deriving (Generic, Show) + +instance ToJSON Resolution where + toJSON = genericToJSON encodingOptions + toEncoding = genericToEncoding encodingOptions + + +data GalleryItemProps = + Directory { items :: [GalleryItem] } +-- | Image { resolution :: Resolution, filesize :: FileSizeKB } +-- | Video { filesize :: FileSizeKB } + | Unknown + deriving (Generic, Show) + +instance ToJSON GalleryItemProps where + toJSON = genericToJSON encodingOptions + toEncoding = genericToEncoding encodingOptions + + +-- TODO: fuse GalleryItem and GalleryItemProps +data GalleryItem = GalleryItem + { title :: String + , date :: String -- TODO: checked ISO8601 date + , description :: String + , tags :: [Tag] + , path :: ResourcePath + , thumbnail :: Maybe ResourcePath + , properties :: GalleryItemProps + } deriving (Generic, Show) + +instance ToJSON GalleryItem where + toJSON = genericToJSON encodingOptions + toEncoding = genericToEncoding encodingOptions + + +buildGalleryTree :: ResourceTree -> GalleryItem +buildGalleryTree (ItemResource sidecar path@(filename:_) thumbnailPath) = + GalleryItem + { title = optMeta title filename + , date = optMeta date "" -- TODO: check and normalise dates + , description = optMeta description "" + , tags = optMeta tags [] + , path = webPath path + , thumbnail = Just $ webPath thumbnailPath + , properties = Unknown } -- TODO + where + optMeta :: (Sidecar -> Maybe a) -> a -> a + optMeta get fallback = fromMaybe fallback $ get sidecar + +buildGalleryTree (DirResource dirItems path@(dirname:_) thumbnailPath) = + map buildGalleryTree dirItems + & \items -> GalleryItem + { title = dirname + -- TODO: consider using the most recent item's date? what if empty? + , date = "" + -- TODO: consider allowing metadata sidecars for directories too + , description = "" + , tags = aggregateChildTags items + , path = webPath path + , thumbnail = fmap webPath thumbnailPath + , properties = Directory items } + where + aggregateChildTags :: [GalleryItem] -> [Tag] + aggregateChildTags = unique . concatMap (\item -> tags (item::GalleryItem)) diff --git a/compiler/src/Input.hs b/compiler/src/Input.hs new file mode 100644 index 0000000..78622bf --- /dev/null +++ b/compiler/src/Input.hs @@ -0,0 +1,95 @@ +{-# LANGUAGE DuplicateRecordFields, DeriveGeneric, DeriveAnyClass #-} + +-- ldgallery - A static generator which turns a collection of tagged +-- pictures into a searchable web gallery. +-- +-- Copyright (C) 2019 Pacien TRAN-GIRARD +-- +-- This program is free software: you can redistribute it and/or modify +-- it under the terms of the GNU Affero General Public License as +-- published by the Free Software Foundation, either version 3 of the +-- License, or (at your option) any later version. +-- +-- This program is distributed in the hope that it will be useful, +-- but WITHOUT ANY WARRANTY; without even the implied warranty of +-- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +-- GNU Affero General Public License for more details. +-- +-- You should have received a copy of the GNU Affero General Public License +-- along with this program. If not, see . + + +module Input + ( Sidecar, title, date, description, tags + , InputTree(..), readInputTree + ) where + + +import GHC.Generics (Generic) +import Control.Exception (Exception, throwIO) +import Control.Monad.IO.Class (MonadIO, liftIO) +import Data.Function ((&)) +import Data.Maybe (mapMaybe, catMaybes) +import Data.List (find) +import Data.Yaml (ParseException, decodeFileEither) +import Data.Aeson (FromJSON) +import System.FilePath (isExtensionOf, dropExtension) + +import Files +import Utils + + +data LoadException = LoadException String ParseException deriving Show +instance Exception LoadException + +decodeYamlFile :: (MonadIO m, FromJSON a) => Path -> m a +decodeYamlFile path = + liftIO $ Data.Yaml.decodeFileEither fpath + >>= either (throwIO . LoadException fpath) return + where + fpath = localPath path + + +-- | Tree representing the input from the input directory. +data InputTree = + InputFile + { path :: Path + , sidecar :: Sidecar } + | InputDir + { path :: Path + , thumbnailPath :: Maybe Path + , items :: [InputTree] } + deriving Show + +data Sidecar = Sidecar + { title :: Maybe String + , date :: Maybe String + , description :: Maybe String + , tags :: Maybe [String] + } deriving (Generic, FromJSON, Show) + + +readInputTree :: AnchoredFSNode -> IO InputTree +readInputTree (AnchoredFSNode anchor root@Dir{}) = + filterDir (neg isHidden) root & mkDirNode + where + mkInputNode :: FSNode -> IO (Maybe InputTree) + mkInputNode (File path@(filename:pathto)) | ".yaml" `isExtensionOf` filename = + decodeYamlFile (anchor /> path) + >>= return . InputFile ((dropExtension filename):pathto) + >>= return . Just + mkInputNode File{} = return Nothing + mkInputNode dir@Dir{} = mkDirNode dir >>= return . Just + + mkDirNode :: FSNode -> IO InputTree + mkDirNode (Dir path items) = + mapM mkInputNode items + >>= return . catMaybes + >>= return . InputDir path (findThumbnail items) + where + findThumbnail :: [FSNode] -> Maybe Path + findThumbnail = (fmap nodePath) . (find matchThumbnail) + + matchThumbnail :: FSNode -> Bool + matchThumbnail Dir{} = False + matchThumbnail (File (filename:_)) = (dropExtension filename) == "thumbnail" diff --git a/compiler/src/Lib.hs b/compiler/src/Lib.hs index 70a2cca..bab7e9c 100644 --- a/compiler/src/Lib.hs +++ b/compiler/src/Lib.hs @@ -1,11 +1,7 @@ -{-# LANGUAGE DuplicateRecordFields, DeriveGeneric #-} - - -- ldgallery - A static generator which turns a collection of tagged -- pictures into a searchable web gallery. -- -- Copyright (C) 2019 Pacien TRAN-GIRARD --- 2019 Guillaume FOUET -- -- This program is free software: you can redistribute it and/or modify -- it under the terms of the GNU Affero General Public License as @@ -26,232 +22,49 @@ module Lib ) where -import GHC.Generics - -import Control.Monad.IO.Class (MonadIO, liftIO) -import Control.Exception (Exception, throwIO) - -import Data.Function -import Data.Maybe (fromMaybe, listToMaybe) -import Data.List (map) -import Data.Set (fromList, toList) -import Data.Char (toLower) -import Data.Text (Text, empty, pack) -import Data.Yaml (ParseException, decodeFileEither) -import Data.Aeson - -import System.FilePath ((), joinPath, dropFileName, dropExtension, isExtensionOf) -import qualified System.FilePath.Posix (joinPath) -import System.Directory.Tree -import System.Directory - - -encodingOptions :: Options -encodingOptions = defaultOptions - { fieldLabelModifier = map toLower - , constructorTagModifier = map toLower - , sumEncoding = defaultTaggedObject - { tagFieldName = "type" - , contentsFieldName = "contents" - } - } - - --- input structure - -data SidecarItemMetadata = SidecarItemMetadata - { title :: Maybe Text - , date :: Maybe Text - , description :: Maybe Text - , tags :: Maybe [Text] - } deriving (Generic, Show) - -instance FromJSON SidecarItemMetadata where - parseJSON = genericParseJSON encodingOptions - - --- output structures - -type ResourcePath = Text -type Tag = Text -type FileSizeKB = Int - - -data Resolution = Resolution - { width :: Int - , height :: Int - } deriving (Generic, Show) - -instance ToJSON Resolution where - toJSON = genericToJSON encodingOptions - toEncoding = genericToEncoding encodingOptions - - -data ItemProperties = - Directory { items :: [Item] } - | Image { resolution :: Resolution, filesize :: FileSizeKB } --- | Video { filesize :: FileSizeKB } - | Unknown - deriving (Generic, Show) - -instance ToJSON ItemProperties where - toJSON = genericToJSON encodingOptions - toEncoding = genericToEncoding encodingOptions - - -data Item = Item - { title :: Text - , date :: Text -- TODO: checked ISO8601 date - , description :: Text - , tags :: [Tag] - , path :: ResourcePath - , thumbnail :: Maybe ResourcePath - , properties :: ItemProperties - } deriving (Generic, Show) - -instance ToJSON Item where - toJSON = genericToJSON encodingOptions - toEncoding = genericToEncoding encodingOptions - +import GHC.Generics (Generic) +import Data.Function ((&)) +import System.Directory (createDirectoryIfMissing) +import System.FilePath (dropFileName, ()) +import Data.Aeson (ToJSON, encodeFile) --- mapping +import Files (FileName, readDirectory) +import Input (readInputTree) +import Resource (buildResourceTree) +import Gallery (buildGalleryTree) -data LoadException = LoadException String ParseException deriving Show -instance Exception LoadException -decodeYamlFile :: (MonadIO m, FromJSON a) => FilePath -> m a -decodeYamlFile fpath = - liftIO $ Data.Yaml.decodeFileEither fpath - >>= either (throwIO . LoadException fpath) return - - -toMetaTree :: DirTree FilePath -> IO (DirTree SidecarItemMetadata) -toMetaTree tree = return (filterDir canContainMetadata tree) >>= metaNode - where - -- TODO: exclude hidden files (name starting with '.')? - canContainMetadata :: DirTree a -> Bool - canContainMetadata (File fname _) = isExtensionOf ".yaml" fname - canContainMetadata (Dir _ _) = True - - metaNode :: DirTree FilePath -> IO (DirTree SidecarItemMetadata) - metaNode (Failed _ ferr) = ioError ferr - metaNode file@(File _ fpath) = decodeYamlFile fpath - >>= \metadata -> return file { file = metadata } - metaNode dir@(Dir _ dcontents) = mapM metaNode dcontents - >>= \contents -> return dir { contents = contents } - - -unique :: Ord a => [a] -> [a] -unique = Data.Set.toList . Data.Set.fromList - -joinURLPath :: [FileName] -> Text -joinURLPath = pack . System.FilePath.Posix.joinPath - - -toItemTree :: FilePath -> FilePath -> DirTree SidecarItemMetadata -> IO Item -toItemTree itemsDir thumbnailsDir = itemNode [] - where - itemNode :: [FileName] -> DirTree SidecarItemMetadata -> IO Item - itemNode pathTo (Dir dname dcontents) = - mapM (itemNode path) dcontents - >>= \items -> return Item - { title = pack dname - , date = empty - , description = empty - , tags = aggregateChildTags items - , path = joinURLPath $ itemsDir:path - , thumbnail = Nothing - , properties = Directory items } - where - path = pathTo ++ [dname] - aggregateChildTags = unique . concatMap (\item -> tags (item::Item)) - - itemNode pathTo (File fname metadata) = - return Item - { title = optMeta title $ pack name - , date = optMeta date empty -- TODO: check and normalise dates - , description = optMeta description empty - , tags = optMeta tags [] - , path = joinURLPath $ itemsDir:path - , thumbnail = Just $ joinURLPath $ thumbnailsDir:path - , properties = Unknown } -- TODO - where - name = dropExtension fname - path = pathTo ++ [name] - optMeta get fallback = fromMaybe fallback $ get (metadata::SidecarItemMetadata) - - -data ObjectTree = ObjectTree - { pathTo :: [ObjectTree] - , meta :: (DirTree SidecarItemMetadata) - , item :: Item } deriving Show - -rootObjectTree :: DirTree SidecarItemMetadata -> Item -> ObjectTree -rootObjectTree = ObjectTree [] - -toObjectTree :: (DirTree SidecarItemMetadata -> IO Item) -> DirTree SidecarItemMetadata -> IO ObjectTree -toObjectTree itemGen meta = itemGen meta >>= return . (rootObjectTree meta) - -flatten :: ObjectTree -> [ObjectTree] -flatten object@(ObjectTree _ (File _ _) _) = [object] -flatten object@(ObjectTree pathTo (Dir _ dcontents) item) = - zip dcontents (items $ properties item) - & map (uncurry $ ObjectTree $ pathTo ++ [object]) - & concatMap flatten - & (:) object - -objFileName :: ObjectTree -> FileName -objFileName (ObjectTree _ (Dir name _) _) = name -objFileName (ObjectTree _ (File name _) _) = dropExtension name -- without ".yaml" - -objFilePath :: ObjectTree -> FilePath -objFilePath obj@(ObjectTree pathTo _ _) = - (map (name . meta) pathTo) ++ [objFileName obj] - & System.FilePath.joinPath - - -data FileTransform = FileTransform - { src :: FilePath - , dst :: FilePath } deriving Show +writeJSON :: ToJSON a => FileName -> a -> IO () +writeJSON path obj = + createDirectoryIfMissing True (dropFileName path) + >> encodeFile path obj -isUpToDate :: FilePath -> FilePath -> IO Bool -isUpToDate ref target = +process :: FilePath -> FilePath -> IO () +process inputDirPath outputDirPath = do - refTime <- getModificationTime ref - targetTime <- getModificationTime target - return (target >= ref) + inputDir <- readDirectory inputDirPath + putStrLn "\nINPUT DIR" + putStrLn (show inputDir) + outputDir <- readDirectory outputDirPath + putStrLn "\nOUTPUT DIR" + putStrLn (show outputDir) -unrooted :: AnchoredDirTree a -> DirTree a -unrooted t = (dirTree t) { name = "" } + inputTree <- readInputTree inputDir + putStrLn "\nINPUT TREE" + putStrLn (show inputTree) -writeJSON :: ToJSON a => FilePath -> a -> IO () -writeJSON path obj = - createDirectoryIfMissing True (dropFileName path) - >> Data.Aeson.encodeFile path obj + let resourceTree = buildResourceTree inputTree + putStrLn "\nRESOURCE TREE" + putStrLn (show resourceTree) -passthrough :: Monad m => (a -> m b) -> a -> m a -passthrough f a = return a >>= f >>= \_ -> return a + -- TODO: make buildResourceTree build a resource compilation strategy + -- TODO: clean up output dir by comparing its content with the resource tree + -- TODO: execute (in parallel) the resource compilation strategy list -process :: FilePath -> FilePath -> IO () -process inputDir outputDir = - readDirectoryWith return inputDir - >>= return . unrooted - >>= toMetaTree - >>= toObjectTree (toItemTree itemsDir thumbnailsDir) - >>= passthrough (writeJSON (outputDir indexFile) . item) - >>= return . flatten --- >>= mapM (return . pathTo) - >>= return . (map objFilePath) - >>= return . show --- >>= return . show . toEncoding . item - >>= liftIO . putStrLn - where - itemsDir = "items" - thumbnailsDir = "thumbnails" - indexFile = "index.json" + buildGalleryTree resourceTree & writeJSON (outputDirPath "index.json") testRun :: IO () -testRun = process "../example" "../out" +testRun = process "../../example" "../../out" diff --git a/compiler/src/Resource.hs b/compiler/src/Resource.hs new file mode 100644 index 0000000..04e315a --- /dev/null +++ b/compiler/src/Resource.hs @@ -0,0 +1,58 @@ +{-# LANGUAGE DuplicateRecordFields, DeriveGeneric, DeriveAnyClass #-} + +-- ldgallery - A static generator which turns a collection of tagged +-- pictures into a searchable web gallery. +-- +-- Copyright (C) 2019 Pacien TRAN-GIRARD +-- +-- This program is free software: you can redistribute it and/or modify +-- it under the terms of the GNU Affero General Public License as +-- published by the Free Software Foundation, either version 3 of the +-- License, or (at your option) any later version. +-- +-- This program is distributed in the hope that it will be useful, +-- but WITHOUT ANY WARRANTY; without even the implied warranty of +-- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +-- GNU Affero General Public License for more details. +-- +-- You should have received a copy of the GNU Affero General Public License +-- along with this program. If not, see . + + +module Resource + ( ResourceTree(..) + , buildResourceTree + ) where + + +import Data.Function ((&)) +import Files +import Input + + +-- | Tree representing the compiled gallery resources. +data ResourceTree = + ItemResource + { sidecar :: Sidecar + , path :: Path + , itemThumbnailPath :: Path } + | DirResource + { items :: [ResourceTree] + , path :: Path + , dirThumbnailPath :: Maybe Path } + deriving Show + + + -- TODO: actually generate compilation strategies +buildResourceTree :: InputTree -> ResourceTree +buildResourceTree = resNode + where + resNode (InputFile path sidecar) = + ItemResource sidecar (itemsDir /> path) (thumbnailsDir /> path) + + resNode (InputDir path thumbnailPath items) = + map resNode items + & \dirItems -> DirResource dirItems (itemsDir /> path) Nothing + + itemsDir = "items" + thumbnailsDir = "thumbnails" diff --git a/compiler/src/Utils.hs b/compiler/src/Utils.hs new file mode 100644 index 0000000..794382c --- /dev/null +++ b/compiler/src/Utils.hs @@ -0,0 +1,49 @@ +-- ldgallery - A static generator which turns a collection of tagged +-- pictures into a searchable web gallery. +-- +-- Copyright (C) 2019 Pacien TRAN-GIRARD +-- +-- This program is free software: you can redistribute it and/or modify +-- it under the terms of the GNU Affero General Public License as +-- published by the Free Software Foundation, either version 3 of the +-- License, or (at your option) any later version. +-- +-- This program is distributed in the hope that it will be useful, +-- but WITHOUT ANY WARRANTY; without even the implied warranty of +-- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +-- GNU Affero General Public License for more details. +-- +-- You should have received a copy of the GNU Affero General Public License +-- along with this program. If not, see . + + +module Utils + ( conj, neg + , unique + , passthrough + ) where + + +import qualified Data.List +import qualified Data.Set + + +-- predicates + +conj :: (a -> Bool) -> (a -> Bool) -> a -> Bool +conj p q x = (p x) && (q x) + +neg :: (a -> Bool) -> a -> Bool +neg p x = not (p x) + + +-- lists + +unique :: Ord a => [a] -> [a] +unique = Data.Set.toList . Data.Set.fromList + + +-- monads + +passthrough :: Monad m => (a -> m b) -> a -> m a +passthrough f a = return a >>= f >>= \_ -> return a -- cgit v1.2.3 From 5b35285daa62fb9c10280fb43e340ba7b0746f5a Mon Sep 17 00:00:00 2001 From: pacien Date: Wed, 25 Dec 2019 22:48:34 +0100 Subject: compiler: add gallery config file handling --- compiler/src/Files.hs | 2 +- compiler/src/Input.hs | 13 ++++++------- compiler/src/Lib.hs | 41 +++++++++++++++++++++++++++++++++++------ 3 files changed, 42 insertions(+), 14 deletions(-) (limited to 'compiler/src') diff --git a/compiler/src/Files.hs b/compiler/src/Files.hs index 7948842..30e4b94 100644 --- a/compiler/src/Files.hs +++ b/compiler/src/Files.hs @@ -32,9 +32,9 @@ import Data.Bool (bool) import Data.List (isPrefixOf, length, deleteBy) import Data.Function ((&)) import System.Directory (doesDirectoryExist, listDirectory) + import qualified System.FilePath import qualified System.FilePath.Posix -import Utils type FileName = String diff --git a/compiler/src/Input.hs b/compiler/src/Input.hs index 78622bf..fa36d59 100644 --- a/compiler/src/Input.hs +++ b/compiler/src/Input.hs @@ -20,7 +20,8 @@ module Input - ( Sidecar, title, date, description, tags + ( decodeYamlFile + , Sidecar, title, date, description, tags , InputTree(..), readInputTree ) where @@ -42,12 +43,10 @@ import Utils data LoadException = LoadException String ParseException deriving Show instance Exception LoadException -decodeYamlFile :: (MonadIO m, FromJSON a) => Path -> m a +decodeYamlFile :: (MonadIO m, FromJSON a) => FileName -> m a decodeYamlFile path = - liftIO $ Data.Yaml.decodeFileEither fpath - >>= either (throwIO . LoadException fpath) return - where - fpath = localPath path + liftIO $ Data.Yaml.decodeFileEither path + >>= either (throwIO . LoadException path) return -- | Tree representing the input from the input directory. @@ -75,7 +74,7 @@ readInputTree (AnchoredFSNode anchor root@Dir{}) = where mkInputNode :: FSNode -> IO (Maybe InputTree) mkInputNode (File path@(filename:pathto)) | ".yaml" `isExtensionOf` filename = - decodeYamlFile (anchor /> path) + decodeYamlFile (localPath $ anchor /> path) >>= return . InputFile ((dropExtension filename):pathto) >>= return . Just mkInputNode File{} = return Nothing diff --git a/compiler/src/Lib.hs b/compiler/src/Lib.hs index bab7e9c..abdbeb7 100644 --- a/compiler/src/Lib.hs +++ b/compiler/src/Lib.hs @@ -1,3 +1,5 @@ +{-# LANGUAGE DuplicateRecordFields, DeriveGeneric, DeriveAnyClass #-} + -- ldgallery - A static generator which turns a collection of tagged -- pictures into a searchable web gallery. -- @@ -26,23 +28,32 @@ import GHC.Generics (Generic) import Data.Function ((&)) import System.Directory (createDirectoryIfMissing) import System.FilePath (dropFileName, ()) -import Data.Aeson (ToJSON, encodeFile) +import Data.Aeson (Object, ToJSON, FromJSON, encodeFile) import Files (FileName, readDirectory) -import Input (readInputTree) +import Input (decodeYamlFile, readInputTree) import Resource (buildResourceTree) import Gallery (buildGalleryTree) -writeJSON :: ToJSON a => FileName -> a -> IO () -writeJSON path obj = - createDirectoryIfMissing True (dropFileName path) - >> encodeFile path obj +data CompilerConfig = CompilerConfig + { dummy :: Maybe String -- TODO + } deriving (Generic, FromJSON, Show) + +data GalleryConfig = GalleryConfig + { compiler :: CompilerConfig + , viewer :: Data.Aeson.Object + } deriving (Generic, FromJSON, Show) + +readConfig :: FileName -> IO GalleryConfig +readConfig = decodeYamlFile process :: FilePath -> FilePath -> IO () process inputDirPath outputDirPath = do + config <- readConfig (inputDirPath "gallery.yaml") + inputDir <- readDirectory inputDirPath putStrLn "\nINPUT DIR" putStrLn (show inputDir) @@ -60,10 +71,28 @@ process inputDirPath outputDirPath = putStrLn (show resourceTree) -- TODO: make buildResourceTree build a resource compilation strategy + -- (need to know the settings) + -- flatten the tree of resources and their strategies + -- filter resources that are already up to date + -- (or recompile everything if the config file has changed!) + -- execute in parallel + -- TODO: clean up output dir by comparing its content with the resource tree + -- aggregate both trees as list + -- compute the difference + -- sort by deepest and erase files and dirs + -- TODO: execute (in parallel) the resource compilation strategy list + -- need to find a good library for that buildGalleryTree resourceTree & writeJSON (outputDirPath "index.json") + writeJSON (outputDirPath "viewer.json") (viewer config) + + where + writeJSON :: ToJSON a => FileName -> a -> IO () + writeJSON path obj = + createDirectoryIfMissing True (dropFileName path) + >> encodeFile path obj testRun :: IO () -- cgit v1.2.3 From 45163fbc93b2bf2f7cb1fc3242ce5d3f51076601 Mon Sep 17 00:00:00 2001 From: pacien Date: Wed, 25 Dec 2019 22:56:16 +0100 Subject: cosmetic --- compiler/src/Lib.hs | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) (limited to 'compiler/src') diff --git a/compiler/src/Lib.hs b/compiler/src/Lib.hs index abdbeb7..2068b4a 100644 --- a/compiler/src/Lib.hs +++ b/compiler/src/Lib.hs @@ -28,7 +28,9 @@ import GHC.Generics (Generic) import Data.Function ((&)) import System.Directory (createDirectoryIfMissing) import System.FilePath (dropFileName, ()) -import Data.Aeson (Object, ToJSON, FromJSON, encodeFile) + +import Data.Aeson (ToJSON, FromJSON) +import qualified Data.Aeson as JSON import Files (FileName, readDirectory) import Input (decodeYamlFile, readInputTree) @@ -42,7 +44,7 @@ data CompilerConfig = CompilerConfig data GalleryConfig = GalleryConfig { compiler :: CompilerConfig - , viewer :: Data.Aeson.Object + , viewer :: JSON.Object } deriving (Generic, FromJSON, Show) readConfig :: FileName -> IO GalleryConfig @@ -92,7 +94,7 @@ process inputDirPath outputDirPath = writeJSON :: ToJSON a => FileName -> a -> IO () writeJSON path obj = createDirectoryIfMissing True (dropFileName path) - >> encodeFile path obj + >> JSON.encodeFile path obj testRun :: IO () -- cgit v1.2.3 From 2a6467272e18af4864745b9d0267f9fa3ed382dd Mon Sep 17 00:00:00 2001 From: pacien Date: Thu, 26 Dec 2019 01:13:42 +0100 Subject: compiler: implement output dir cleanup --- compiler/src/Files.hs | 11 ++++++----- compiler/src/Lib.hs | 32 +++++++++++++++++++------------- compiler/src/Resource.hs | 35 ++++++++++++++++++++++++++++++----- 3 files changed, 55 insertions(+), 23 deletions(-) (limited to 'compiler/src') diff --git a/compiler/src/Files.hs b/compiler/src/Files.hs index 30e4b94..77a8c5b 100644 --- a/compiler/src/Files.hs +++ b/compiler/src/Files.hs @@ -23,7 +23,7 @@ module Files ( FileName, LocalPath, WebPath, Path , (), (), localPath, webPath , FSNode(..), AnchoredFSNode(..) - , nodePath, nodeName, isHidden, flatten, filterDir, readDirectory + , nodePath, nodeName, isHidden, flattenDir, filterDir, readDirectory ) where @@ -76,9 +76,10 @@ isHidden :: FSNode -> Bool isHidden node = "." `isPrefixOf` filename && length filename > 1 where filename = nodeName node -flatten :: FSNode -> [FSNode] -flatten file@(File _) = [file] -flatten dir@(Dir _ childs) = dir:(concatMap flatten childs) +-- | DFS with intermediate dirs first. +flattenDir :: FSNode -> [FSNode] +flattenDir file@(File _) = [file] +flattenDir dir@(Dir _ childs) = dir:(concatMap flattenDir childs) -- | Filters a dir tree. The root is always returned. filterDir :: (FSNode -> Bool) -> FSNode -> FSNode @@ -87,7 +88,7 @@ filterDir cond (Dir path childs) = filter cond childs & map (filterDir cond) & Dir path readDirectory :: LocalPath -> IO AnchoredFSNode -readDirectory root = mkNode [""] >>= return . AnchoredFSNode root +readDirectory root = mkNode [] >>= return . AnchoredFSNode root where mkNode :: Path -> IO FSNode mkNode path = diff --git a/compiler/src/Lib.hs b/compiler/src/Lib.hs index 2068b4a..643e5f6 100644 --- a/compiler/src/Lib.hs +++ b/compiler/src/Lib.hs @@ -26,15 +26,17 @@ module Lib import GHC.Generics (Generic) import Data.Function ((&)) -import System.Directory (createDirectoryIfMissing) +import Data.Ord (comparing) +import Data.List (sortBy, length) +import System.Directory (createDirectoryIfMissing, removePathForcibly) import System.FilePath (dropFileName, ()) import Data.Aeson (ToJSON, FromJSON) import qualified Data.Aeson as JSON -import Files (FileName, readDirectory) +import Files (FileName, readDirectory, localPath, flattenDir, root, (/>)) import Input (decodeYamlFile, readInputTree) -import Resource (buildResourceTree) +import Resource (ResourceTree, buildResourceTree, outputDiff) import Gallery (buildGalleryTree) @@ -60,10 +62,6 @@ process inputDirPath outputDirPath = putStrLn "\nINPUT DIR" putStrLn (show inputDir) - outputDir <- readDirectory outputDirPath - putStrLn "\nOUTPUT DIR" - putStrLn (show outputDir) - inputTree <- readInputTree inputDir putStrLn "\nINPUT TREE" putStrLn (show inputTree) @@ -79,18 +77,26 @@ process inputDirPath outputDirPath = -- (or recompile everything if the config file has changed!) -- execute in parallel - -- TODO: clean up output dir by comparing its content with the resource tree - -- aggregate both trees as list - -- compute the difference - -- sort by deepest and erase files and dirs + cleanup resourceTree outputDirPath -- TODO: execute (in parallel) the resource compilation strategy list -- need to find a good library for that - buildGalleryTree resourceTree & writeJSON (outputDirPath "index.json") - writeJSON (outputDirPath "viewer.json") (viewer config) + buildGalleryTree resourceTree + & writeJSON (outputDirPath "index.json") + + viewer config + & writeJSON (outputDirPath "viewer.json") where + cleanup :: ResourceTree -> FileName -> IO () + cleanup resourceTree outputDir = + readDirectory outputDir + >>= return . outputDiff resourceTree . root + >>= return . sortBy (flip $ comparing length) -- nested files before dirs + >>= return . map (localPath . (/>) outputDir) + >>= mapM_ removePathForcibly + writeJSON :: ToJSON a => FileName -> a -> IO () writeJSON path obj = createDirectoryIfMissing True (dropFileName path) diff --git a/compiler/src/Resource.hs b/compiler/src/Resource.hs index 04e315a..60b783e 100644 --- a/compiler/src/Resource.hs +++ b/compiler/src/Resource.hs @@ -22,10 +22,13 @@ module Resource ( ResourceTree(..) , buildResourceTree + , flattenResourceTree + , outputDiff ) where import Data.Function ((&)) +import Data.List ((\\)) import Files import Input @@ -34,25 +37,47 @@ import Input data ResourceTree = ItemResource { sidecar :: Sidecar - , path :: Path + , resPath :: Path , itemThumbnailPath :: Path } | DirResource { items :: [ResourceTree] - , path :: Path + , resPath :: Path , dirThumbnailPath :: Maybe Path } deriving Show - -- TODO: actually generate compilation strategies +-- TODO: actually generate compilation strategies buildResourceTree :: InputTree -> ResourceTree buildResourceTree = resNode where resNode (InputFile path sidecar) = - ItemResource sidecar (itemsDir /> path) (thumbnailsDir /> path) + ItemResource + { sidecar = sidecar + , resPath = itemsDir /> path + , itemThumbnailPath = thumbnailsDir /> path } resNode (InputDir path thumbnailPath items) = map resNode items - & \dirItems -> DirResource dirItems (itemsDir /> path) Nothing + & \dirItems -> DirResource + { items = dirItems + , resPath = itemsDir /> path + , dirThumbnailPath = fmap ((/>) thumbnailsDir) thumbnailPath } itemsDir = "items" thumbnailsDir = "thumbnails" + + +flattenResourceTree :: ResourceTree -> [ResourceTree] +flattenResourceTree item@ItemResource{} = [item] +flattenResourceTree dir@(DirResource items _ _) = + dir:(concatMap flattenResourceTree items) + + +outputDiff :: ResourceTree -> FSNode -> [Path] +outputDiff resources ref = (fsPaths ref) \\ (resPaths resources) + where + resPaths :: ResourceTree -> [Path] + resPaths = map resPath . flattenResourceTree + + fsPaths :: FSNode -> [Path] + fsPaths = map nodePath . tail . flattenDir -- cgit v1.2.3 From aead07929e6ed13375b86539b1679a88993c9cf5 Mon Sep 17 00:00:00 2001 From: pacien Date: Thu, 26 Dec 2019 08:03:31 +0100 Subject: compiler: extract config and remove utils --- compiler/src/Config.hs | 49 +++++++++++++++++++++++++++++++++++++++++++++++++ compiler/src/Gallery.hs | 6 +++++- compiler/src/Input.hs | 3 +-- compiler/src/Lib.hs | 35 +++++++++++------------------------ compiler/src/Utils.hs | 49 ------------------------------------------------- 5 files changed, 66 insertions(+), 76 deletions(-) create mode 100644 compiler/src/Config.hs delete mode 100644 compiler/src/Utils.hs (limited to 'compiler/src') diff --git a/compiler/src/Config.hs b/compiler/src/Config.hs new file mode 100644 index 0000000..6f04818 --- /dev/null +++ b/compiler/src/Config.hs @@ -0,0 +1,49 @@ +{-# LANGUAGE DuplicateRecordFields, DeriveGeneric, DeriveAnyClass #-} + +-- ldgallery - A static generator which turns a collection of tagged +-- pictures into a searchable web gallery. +-- +-- Copyright (C) 2019 Pacien TRAN-GIRARD +-- +-- This program is free software: you can redistribute it and/or modify +-- it under the terms of the GNU Affero General Public License as +-- published by the Free Software Foundation, either version 3 of the +-- License, or (at your option) any later version. +-- +-- This program is distributed in the hope that it will be useful, +-- but WITHOUT ANY WARRANTY; without even the implied warranty of +-- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +-- GNU Affero General Public License for more details. +-- +-- You should have received a copy of the GNU Affero General Public License +-- along with this program. If not, see . + + +module Config + ( GalleryConfig(..) + , CompilerConfig(..) + , readConfig + ) where + +import GHC.Generics (Generic) +import Data.Aeson (ToJSON, FromJSON) +import qualified Data.Aeson as JSON + +import Files (FileName) +import Input (decodeYamlFile) + + +data CompilerConfig = CompilerConfig + { dummy :: Maybe String -- TODO + } deriving (Generic, FromJSON, Show) + +data GalleryConfig = GalleryConfig + { compiler :: CompilerConfig + , viewer :: JSON.Object + } deriving (Generic, FromJSON, Show) + +-- TODO: add compiler config keys and their default values + + +readConfig :: FileName -> IO GalleryConfig +readConfig = decodeYamlFile diff --git a/compiler/src/Gallery.hs b/compiler/src/Gallery.hs index 3be62ad..ce52523 100644 --- a/compiler/src/Gallery.hs +++ b/compiler/src/Gallery.hs @@ -32,7 +32,8 @@ import Data.Maybe (fromMaybe) import Data.Aeson (ToJSON, genericToJSON, genericToEncoding) import qualified Data.Aeson as JSON -importĀ Utils +import qualified Data.Set as Set + import Files import Input import Resource @@ -121,3 +122,6 @@ buildGalleryTree (DirResource dirItems path@(dirname:_) thumbnailPath) = where aggregateChildTags :: [GalleryItem] -> [Tag] aggregateChildTags = unique . concatMap (\item -> tags (item::GalleryItem)) + + unique :: Ord a => [a] -> [a] + unique = Set.toList . Set.fromList diff --git a/compiler/src/Input.hs b/compiler/src/Input.hs index fa36d59..681f169 100644 --- a/compiler/src/Input.hs +++ b/compiler/src/Input.hs @@ -37,7 +37,6 @@ import Data.Aeson (FromJSON) import System.FilePath (isExtensionOf, dropExtension) import Files -import Utils data LoadException = LoadException String ParseException deriving Show @@ -70,7 +69,7 @@ data Sidecar = Sidecar readInputTree :: AnchoredFSNode -> IO InputTree readInputTree (AnchoredFSNode anchor root@Dir{}) = - filterDir (neg isHidden) root & mkDirNode + filterDir (not . isHidden) root & mkDirNode where mkInputNode :: FSNode -> IO (Maybe InputTree) mkInputNode (File path@(filename:pathto)) | ".yaml" `isExtensionOf` filename = diff --git a/compiler/src/Lib.hs b/compiler/src/Lib.hs index 643e5f6..b2bbe15 100644 --- a/compiler/src/Lib.hs +++ b/compiler/src/Lib.hs @@ -24,47 +24,28 @@ module Lib ) where -import GHC.Generics (Generic) import Data.Function ((&)) import Data.Ord (comparing) import Data.List (sortBy, length) import System.Directory (createDirectoryIfMissing, removePathForcibly) import System.FilePath (dropFileName, ()) -import Data.Aeson (ToJSON, FromJSON) +import Data.Aeson (ToJSON) import qualified Data.Aeson as JSON +import Config import Files (FileName, readDirectory, localPath, flattenDir, root, (/>)) import Input (decodeYamlFile, readInputTree) import Resource (ResourceTree, buildResourceTree, outputDiff) import Gallery (buildGalleryTree) -data CompilerConfig = CompilerConfig - { dummy :: Maybe String -- TODO - } deriving (Generic, FromJSON, Show) - -data GalleryConfig = GalleryConfig - { compiler :: CompilerConfig - , viewer :: JSON.Object - } deriving (Generic, FromJSON, Show) - -readConfig :: FileName -> IO GalleryConfig -readConfig = decodeYamlFile - - process :: FilePath -> FilePath -> IO () process inputDirPath outputDirPath = do config <- readConfig (inputDirPath "gallery.yaml") - inputDir <- readDirectory inputDirPath - putStrLn "\nINPUT DIR" - putStrLn (show inputDir) - inputTree <- readInputTree inputDir - putStrLn "\nINPUT TREE" - putStrLn (show inputTree) let resourceTree = buildResourceTree inputTree putStrLn "\nRESOURCE TREE" @@ -77,11 +58,11 @@