packages feed

hakyll 2.4.4 → 3.0.0.2

raw patch · 72 files changed

+3772/−2268 lines, 72 filesdep +bytestringdep +hinotifydep +hopenssldep −networkdep −regex-tdfadep ~hamletPVP ok

version bump matches the API change (PVP)

Dependencies added: bytestring, hinotify, hopenssl, regex-pcre, snap-core, snap-server, strict-concurrency, tagsoup, unix, utf8-string

Dependencies removed: network, regex-tdfa

Dependency ranges changed: hamlet

API changes (from Hackage documentation)

- Network.Hakyll.SimpleServer: instance Eq Request
- Network.Hakyll.SimpleServer: instance Eq Response
- Network.Hakyll.SimpleServer: instance Ord Request
- Network.Hakyll.SimpleServer: instance Ord Response
- Network.Hakyll.SimpleServer: instance Show Request
- Network.Hakyll.SimpleServer: instance Show Response
- Network.Hakyll.SimpleServer: simpleServer :: PortNumber -> FilePath -> IO () -> IO ()
- Text.Hakyll: defaultHakyllConfiguration :: String -> HakyllConfiguration
- Text.Hakyll: hakyll :: String -> Hakyll () -> IO ()
- Text.Hakyll: hakyllWithConfiguration :: HakyllConfiguration -> Hakyll () -> IO ()
- Text.Hakyll: runDefaultHakyll :: Hakyll a -> IO a
- Text.Hakyll.Configurations.Static: staticConfiguration :: Hakyll ()
- Text.Hakyll.Context: Context :: Map String String -> Context
- Text.Hakyll.Context: instance Binary Context
- Text.Hakyll.Context: instance Monoid Context
- Text.Hakyll.Context: instance Show Context
- Text.Hakyll.Context: newtype Context
- Text.Hakyll.Context: unContext :: Context -> Map String String
- Text.Hakyll.ContextManipulations: changeExtension :: String -> HakyllAction Context Context
- Text.Hakyll.ContextManipulations: changeUrl :: (String -> String) -> HakyllAction Context Context
- Text.Hakyll.ContextManipulations: changeValue :: String -> (String -> String) -> HakyllAction Context Context
- Text.Hakyll.ContextManipulations: copyValue :: String -> String -> HakyllAction Context Context
- Text.Hakyll.ContextManipulations: renderBody :: (String -> String) -> HakyllAction Context Context
- Text.Hakyll.ContextManipulations: renderDate :: String -> String -> String -> HakyllAction Context Context
- Text.Hakyll.ContextManipulations: renderDateWithLocale :: TimeLocale -> String -> String -> String -> HakyllAction Context Context
- Text.Hakyll.ContextManipulations: renderValue :: String -> String -> (String -> String) -> HakyllAction Context Context
- Text.Hakyll.ContextManipulations: takeBody :: HakyllAction Context String
- Text.Hakyll.CreateContext: addField :: String -> Either String (HakyllAction () String) -> HakyllAction Context Context
- Text.Hakyll.CreateContext: combine :: HakyllAction () Context -> HakyllAction () Context -> HakyllAction () Context
- Text.Hakyll.CreateContext: combineWithUrl :: FilePath -> HakyllAction () Context -> HakyllAction () Context -> HakyllAction () Context
- Text.Hakyll.CreateContext: createCustomPage :: FilePath -> [(String, Either String (HakyllAction () String))] -> HakyllAction () Context
- Text.Hakyll.CreateContext: createListing :: FilePath -> [FilePath] -> [HakyllAction () Context] -> [(String, Either String (HakyllAction () String))] -> HakyllAction () Context
- Text.Hakyll.CreateContext: createPage :: FilePath -> HakyllAction () Context
- Text.Hakyll.Feed: FeedConfiguration :: String -> String -> String -> String -> FeedConfiguration
- Text.Hakyll.Feed: data FeedConfiguration
- Text.Hakyll.Feed: feedAuthorName :: FeedConfiguration -> String
- Text.Hakyll.Feed: feedDescription :: FeedConfiguration -> String
- Text.Hakyll.Feed: feedTitle :: FeedConfiguration -> String
- Text.Hakyll.Feed: feedUrl :: FeedConfiguration -> String
- Text.Hakyll.Feed: renderAtom :: FeedConfiguration -> [HakyllAction () Context] -> Hakyll ()
- Text.Hakyll.Feed: renderRss :: FeedConfiguration -> [HakyllAction () Context] -> Hakyll ()
- Text.Hakyll.File: directory :: (FilePath -> Hakyll ()) -> FilePath -> Hakyll ()
- Text.Hakyll.File: getRecursiveContents :: FilePath -> Hakyll [FilePath]
- Text.Hakyll.File: havingExtension :: String -> [FilePath] -> [FilePath]
- Text.Hakyll.File: inDirectory :: FilePath -> FilePath -> Bool
- Text.Hakyll.File: inHakyllDirectory :: FilePath -> Hakyll Bool
- Text.Hakyll.File: isFileMoreRecent :: FilePath -> [FilePath] -> Hakyll Bool
- Text.Hakyll.File: isMoreRecent :: ClockTime -> [FilePath] -> Hakyll Bool
- Text.Hakyll.File: makeDirectories :: FilePath -> Hakyll ()
- Text.Hakyll.File: removeSpaces :: FilePath -> FilePath
- Text.Hakyll.File: sortByBaseName :: [FilePath] -> [FilePath]
- Text.Hakyll.File: toCache :: FilePath -> Hakyll FilePath
- Text.Hakyll.File: toDestination :: FilePath -> Hakyll FilePath
- Text.Hakyll.File: toRoot :: FilePath -> FilePath
- Text.Hakyll.File: toUrl :: FilePath -> Hakyll FilePath
- Text.Hakyll.HakyllAction: HakyllAction :: [FilePath] -> Either (Hakyll FilePath) (Hakyll FilePath -> Hakyll FilePath) -> (a -> Hakyll b) -> HakyllAction a b
- Text.Hakyll.HakyllAction: actionDependencies :: HakyllAction a b -> [FilePath]
- Text.Hakyll.HakyllAction: actionFunction :: HakyllAction a b -> a -> Hakyll b
- Text.Hakyll.HakyllAction: actionUrl :: HakyllAction a b -> Either (Hakyll FilePath) (Hakyll FilePath -> Hakyll FilePath)
- Text.Hakyll.HakyllAction: chain :: [HakyllAction a a] -> HakyllAction a a
- Text.Hakyll.HakyllAction: createFileHakyllAction :: FilePath -> Hakyll b -> HakyllAction () b
- Text.Hakyll.HakyllAction: createHakyllAction :: (a -> Hakyll b) -> HakyllAction a b
- Text.Hakyll.HakyllAction: createSimpleHakyllAction :: Hakyll b -> HakyllAction () b
- Text.Hakyll.HakyllAction: data HakyllAction a b
- Text.Hakyll.HakyllAction: instance Arrow HakyllAction
- Text.Hakyll.HakyllAction: instance Category HakyllAction
- Text.Hakyll.HakyllAction: runHakyllAction :: HakyllAction () a -> Hakyll a
- Text.Hakyll.HakyllAction: runHakyllActionIfNeeded :: HakyllAction () () -> Hakyll ()
- Text.Hakyll.HakyllMonad: BuildOnInterval :: PreviewMode
- Text.Hakyll.HakyllMonad: BuildOnRequest :: PreviewMode
- Text.Hakyll.HakyllMonad: HakyllConfiguration :: String -> Context -> FilePath -> FilePath -> Bool -> PreviewMode -> ParserState -> WriterOptions -> HamletSettings -> HakyllConfiguration
- Text.Hakyll.HakyllMonad: absoluteUrl :: HakyllConfiguration -> String
- Text.Hakyll.HakyllMonad: additionalContext :: HakyllConfiguration -> Context
- Text.Hakyll.HakyllMonad: askHakyll :: (HakyllConfiguration -> a) -> Hakyll a
- Text.Hakyll.HakyllMonad: cacheDirectory :: HakyllConfiguration -> FilePath
- Text.Hakyll.HakyllMonad: concurrentHakyll :: [Hakyll ()] -> Hakyll ()
- Text.Hakyll.HakyllMonad: data HakyllConfiguration
- Text.Hakyll.HakyllMonad: data PreviewMode
- Text.Hakyll.HakyllMonad: enableIndexUrl :: HakyllConfiguration -> Bool
- Text.Hakyll.HakyllMonad: forkHakyllWait :: Hakyll () -> Hakyll (MVar ())
- Text.Hakyll.HakyllMonad: getAdditionalContext :: HakyllConfiguration -> Context
- Text.Hakyll.HakyllMonad: hamletSettings :: HakyllConfiguration -> HamletSettings
- Text.Hakyll.HakyllMonad: instance Eq PreviewMode
- Text.Hakyll.HakyllMonad: instance Ord PreviewMode
- Text.Hakyll.HakyllMonad: instance Show PreviewMode
- Text.Hakyll.HakyllMonad: logHakyll :: String -> Hakyll ()
- Text.Hakyll.HakyllMonad: pandocParserState :: HakyllConfiguration -> ParserState
- Text.Hakyll.HakyllMonad: pandocWriterOptions :: HakyllConfiguration -> WriterOptions
- Text.Hakyll.HakyllMonad: previewMode :: HakyllConfiguration -> PreviewMode
- Text.Hakyll.HakyllMonad: siteDirectory :: HakyllConfiguration -> FilePath
- Text.Hakyll.HakyllMonad: type Hakyll = ReaderT HakyllConfiguration IO
- Text.Hakyll.Page: PageSection :: (String, String, Bool) -> PageSection
- Text.Hakyll.Page: data PageSection
- Text.Hakyll.Page: instance Show PageSection
- Text.Hakyll.Page: readPage :: FilePath -> Hakyll [PageSection]
- Text.Hakyll.Page: readPageAction :: FilePath -> HakyllAction () [PageSection]
- Text.Hakyll.Page: unPageSection :: PageSection -> (String, String, Bool)
- Text.Hakyll.Paginate: PaginateConfiguration :: String -> String -> String -> String -> PaginateConfiguration
- Text.Hakyll.Paginate: data PaginateConfiguration
- Text.Hakyll.Paginate: defaultPaginateConfiguration :: PaginateConfiguration
- Text.Hakyll.Paginate: firstLabel :: PaginateConfiguration -> String
- Text.Hakyll.Paginate: lastLabel :: PaginateConfiguration -> String
- Text.Hakyll.Paginate: nextLabel :: PaginateConfiguration -> String
- Text.Hakyll.Paginate: paginate :: PaginateConfiguration -> [HakyllAction () Context] -> [HakyllAction () Context]
- Text.Hakyll.Paginate: previousLabel :: PaginateConfiguration -> String
- Text.Hakyll.Pandoc: renderAction :: HakyllAction [PageSection] Context
- Text.Hakyll.Pandoc: renderActionWith :: HakyllAction ([PageSection], String -> String) Context
- Text.Hakyll.Regex: matchesRegex :: String -> String -> Bool
- Text.Hakyll.Regex: splitRegex :: String -> String -> [String]
- Text.Hakyll.Regex: substituteRegex :: String -> String -> String -> String
- Text.Hakyll.Render: css :: FilePath -> Hakyll ()
- Text.Hakyll.Render: render :: FilePath -> HakyllAction Context Context
- Text.Hakyll.Render: renderAndConcat :: [FilePath] -> [HakyllAction () Context] -> HakyllAction () String
- Text.Hakyll.Render: renderChain :: [FilePath] -> HakyllAction () Context -> Hakyll ()
- Text.Hakyll.Render: static :: FilePath -> Hakyll ()
- Text.Hakyll.Render: writePage :: HakyllAction Context ()
- Text.Hakyll.Tags: readCategoryMap :: String -> [FilePath] -> HakyllAction () TagMap
- Text.Hakyll.Tags: readTagMap :: String -> [FilePath] -> HakyllAction () TagMap
- Text.Hakyll.Tags: renderTagCloud :: (String -> String) -> Float -> Float -> HakyllAction TagMap String
- Text.Hakyll.Tags: renderTagLinks :: (String -> String) -> HakyllAction Context Context
- Text.Hakyll.Tags: type TagMap = Map String [HakyllAction () Context]
- Text.Hakyll.Tags: withTagMap :: HakyllAction () TagMap -> (String -> [HakyllAction () Context] -> Hakyll ()) -> Hakyll ()
- Text.Hakyll.Util: link :: String -> String -> String
- Text.Hakyll.Util: stripHtml :: String -> String
- Text.Hakyll.Util: trim :: String -> String
+ Hakyll.Core.CompiledItem: CompiledItem :: a -> CompiledItem
+ Hakyll.Core.CompiledItem: compiledItem :: (Binary a, Typeable a, Writable a) => a -> CompiledItem
+ Hakyll.Core.CompiledItem: data CompiledItem
+ Hakyll.Core.CompiledItem: instance Writable CompiledItem
+ Hakyll.Core.CompiledItem: unCompiledItem :: (Binary a, Typeable a, Writable a) => CompiledItem -> a
+ Hakyll.Core.Compiler: byExtension :: Compiler a b -> [(String, Compiler a b)] -> Compiler a b
+ Hakyll.Core.Compiler: cached :: (Binary a, Typeable a, Writable a) => String -> Compiler Resource a -> Compiler Resource a
+ Hakyll.Core.Compiler: data Compiler a b
+ Hakyll.Core.Compiler: fromDependency :: Identifier -> Compiler a a
+ Hakyll.Core.Compiler: getIdentifier :: Compiler a Identifier
+ Hakyll.Core.Compiler: getResourceString :: Compiler Resource String
+ Hakyll.Core.Compiler: getRoute :: Compiler a (Maybe FilePath)
+ Hakyll.Core.Compiler: getRouteFor :: Compiler Identifier (Maybe FilePath)
+ Hakyll.Core.Compiler: mapCompiler :: Compiler a b -> Compiler [a] [b]
+ Hakyll.Core.Compiler: require :: (Binary a, Typeable a, Writable a) => Identifier -> (b -> a -> c) -> Compiler b c
+ Hakyll.Core.Compiler: requireA :: (Binary a, Typeable a, Writable a) => Identifier -> Compiler (b, a) c -> Compiler b c
+ Hakyll.Core.Compiler: requireAll :: (Binary a, Typeable a, Writable a) => Pattern -> (b -> [a] -> c) -> Compiler b c
+ Hakyll.Core.Compiler: requireAllA :: (Binary a, Typeable a, Writable a) => Pattern -> Compiler (b, [a]) c -> Compiler b c
+ Hakyll.Core.Compiler: requireAll_ :: (Binary a, Typeable a, Writable a) => Pattern -> Compiler b [a]
+ Hakyll.Core.Compiler: require_ :: (Binary a, Typeable a, Writable a) => Identifier -> Compiler b a
+ Hakyll.Core.Compiler: runCompiler :: Compiler () CompileRule -> Identifier -> ResourceProvider -> Routes -> Store -> Bool -> Logger -> IO CompileRule
+ Hakyll.Core.Compiler: timedCompiler :: String -> Compiler a b -> Compiler a b
+ Hakyll.Core.Compiler: traceShowCompiler :: Show a => Compiler a a
+ Hakyll.Core.Compiler: unsafeCompiler :: (a -> IO b) -> Compiler a b
+ Hakyll.Core.Configuration: HakyllConfiguration :: FilePath -> FilePath -> (FilePath -> Bool) -> HakyllConfiguration
+ Hakyll.Core.Configuration: data HakyllConfiguration
+ Hakyll.Core.Configuration: defaultHakyllConfiguration :: HakyllConfiguration
+ Hakyll.Core.Configuration: destinationDirectory :: HakyllConfiguration -> FilePath
+ Hakyll.Core.Configuration: ignoreFile :: HakyllConfiguration -> FilePath -> Bool
+ Hakyll.Core.Configuration: storeDirectory :: HakyllConfiguration -> FilePath
+ Hakyll.Core.CopyFile: CopyFile :: FilePath -> CopyFile
+ Hakyll.Core.CopyFile: copyFileCompiler :: Compiler Resource CopyFile
+ Hakyll.Core.CopyFile: instance Binary CopyFile
+ Hakyll.Core.CopyFile: instance Eq CopyFile
+ Hakyll.Core.CopyFile: instance Ord CopyFile
+ Hakyll.Core.CopyFile: instance Show CopyFile
+ Hakyll.Core.CopyFile: instance Typeable CopyFile
+ Hakyll.Core.CopyFile: instance Writable CopyFile
+ Hakyll.Core.CopyFile: newtype CopyFile
+ Hakyll.Core.CopyFile: unCopyFile :: CopyFile -> FilePath
+ Hakyll.Core.DirectedGraph: data DirectedGraph a
+ Hakyll.Core.DirectedGraph: fromList :: Ord a => [(a, Set a)] -> DirectedGraph a
+ Hakyll.Core.DirectedGraph: member :: Ord a => a -> DirectedGraph a -> Bool
+ Hakyll.Core.DirectedGraph: neighbours :: Ord a => a -> DirectedGraph a -> Set a
+ Hakyll.Core.DirectedGraph: nodes :: Ord a => DirectedGraph a -> Set a
+ Hakyll.Core.DirectedGraph: reachableNodes :: Ord a => Set a -> DirectedGraph a -> Set a
+ Hakyll.Core.DirectedGraph: reverse :: Ord a => DirectedGraph a -> DirectedGraph a
+ Hakyll.Core.DirectedGraph: sanitize :: Ord a => DirectedGraph a -> DirectedGraph a
+ Hakyll.Core.DirectedGraph.DependencySolver: solveDependencies :: Ord a => DirectedGraph a -> [a]
+ Hakyll.Core.DirectedGraph.Dot: toDot :: Ord a => (a -> String) -> DirectedGraph a -> String
+ Hakyll.Core.DirectedGraph.Dot: writeDot :: Ord a => FilePath -> (a -> String) -> DirectedGraph a -> IO ()
+ Hakyll.Core.Identifier: Identifier :: [String] -> Identifier
+ Hakyll.Core.Identifier: instance Eq Identifier
+ Hakyll.Core.Identifier: instance IsString Identifier
+ Hakyll.Core.Identifier: instance Monoid Identifier
+ Hakyll.Core.Identifier: instance Ord Identifier
+ Hakyll.Core.Identifier: instance Show Identifier
+ Hakyll.Core.Identifier: newtype Identifier
+ Hakyll.Core.Identifier: parseIdentifier :: String -> Identifier
+ Hakyll.Core.Identifier: toFilePath :: Identifier -> FilePath
+ Hakyll.Core.Identifier: unIdentifier :: Identifier -> [String]
+ Hakyll.Core.Identifier.Pattern: data Pattern
+ Hakyll.Core.Identifier.Pattern: doesMatch :: Pattern -> Identifier -> Bool
+ Hakyll.Core.Identifier.Pattern: fromCapture :: Pattern -> Identifier -> Identifier
+ Hakyll.Core.Identifier.Pattern: fromCaptureString :: Pattern -> String -> Identifier
+ Hakyll.Core.Identifier.Pattern: fromCaptures :: Pattern -> [Identifier] -> Identifier
+ Hakyll.Core.Identifier.Pattern: instance Eq Pattern
+ Hakyll.Core.Identifier.Pattern: instance Eq PatternComponent
+ Hakyll.Core.Identifier.Pattern: instance IsString Pattern
+ Hakyll.Core.Identifier.Pattern: instance Show Pattern
+ Hakyll.Core.Identifier.Pattern: instance Show PatternComponent
+ Hakyll.Core.Identifier.Pattern: match :: Pattern -> Identifier -> Maybe [Identifier]
+ Hakyll.Core.Identifier.Pattern: matches :: Pattern -> [Identifier] -> [Identifier]
+ Hakyll.Core.Identifier.Pattern: parsePattern :: String -> Pattern
+ Hakyll.Core.Logger: data Logger
+ Hakyll.Core.Logger: flushLogger :: Logger -> IO ()
+ Hakyll.Core.Logger: makeLogger :: IO Logger
+ Hakyll.Core.Logger: report :: MonadIO m => Logger -> String -> m ()
+ Hakyll.Core.Logger: section :: MonadIO m => Logger -> String -> m ()
+ Hakyll.Core.Logger: timed :: MonadIO m => Logger -> String -> m a -> m a
+ Hakyll.Core.ResourceProvider: Resource :: Identifier -> Resource
+ Hakyll.Core.ResourceProvider: ResourceProvider :: [Resource] -> (Resource -> IO String) -> (Resource -> IO ByteString) -> ResourceProvider
+ Hakyll.Core.ResourceProvider: data ResourceProvider
+ Hakyll.Core.ResourceProvider: instance Eq Resource
+ Hakyll.Core.ResourceProvider: instance Ord Resource
+ Hakyll.Core.ResourceProvider: instance Show Resource
+ Hakyll.Core.ResourceProvider: newtype Resource
+ Hakyll.Core.ResourceProvider: resourceDigest :: ResourceProvider -> Resource -> IO [Word8]
+ Hakyll.Core.ResourceProvider: resourceExists :: ResourceProvider -> Identifier -> Bool
+ Hakyll.Core.ResourceProvider: resourceLazyByteString :: ResourceProvider -> Resource -> IO ByteString
+ Hakyll.Core.ResourceProvider: resourceList :: ResourceProvider -> [Resource]
+ Hakyll.Core.ResourceProvider: resourceModified :: ResourceProvider -> Resource -> Store -> IO Bool
+ Hakyll.Core.ResourceProvider: resourceString :: ResourceProvider -> Resource -> IO String
+ Hakyll.Core.ResourceProvider: unResource :: Resource -> Identifier
+ Hakyll.Core.ResourceProvider.FileResourceProvider: fileResourceProvider :: HakyllConfiguration -> IO ResourceProvider
+ Hakyll.Core.Routes: customRoute :: (Identifier -> FilePath) -> Routes
+ Hakyll.Core.Routes: data Routes
+ Hakyll.Core.Routes: idRoute :: Routes
+ Hakyll.Core.Routes: ifMatch :: Pattern -> Routes -> Routes
+ Hakyll.Core.Routes: instance Monoid Routes
+ Hakyll.Core.Routes: runRoutes :: Routes -> Identifier -> Maybe FilePath
+ Hakyll.Core.Routes: setExtension :: String -> Routes
+ Hakyll.Core.Rules: compile :: (Binary a, Typeable a, Writable a) => Pattern -> Compiler Resource a -> Rules
+ Hakyll.Core.Rules: create :: (Binary a, Typeable a, Writable a) => Identifier -> Compiler () a -> Rules
+ Hakyll.Core.Rules: data RulesM a
+ Hakyll.Core.Rules: metaCompile :: (Binary a, Typeable a, Writable a) => Compiler () [(Identifier, Compiler () a)] -> Rules
+ Hakyll.Core.Rules: metaCompileWith :: (Binary a, Typeable a, Writable a) => Identifier -> Compiler () [(Identifier, Compiler () a)] -> Rules
+ Hakyll.Core.Rules: route :: Pattern -> Routes -> Rules
+ Hakyll.Core.Rules: type Rules = RulesM ()
+ Hakyll.Core.Run: instance Applicative Runtime
+ Hakyll.Core.Run: instance Functor Runtime
+ Hakyll.Core.Run: instance Monad Runtime
+ Hakyll.Core.Run: run :: HakyllConfiguration -> Rules -> IO RuleSet
+ Hakyll.Core.Store: data Store
+ Hakyll.Core.Store: makeStore :: FilePath -> IO Store
+ Hakyll.Core.Store: storeGet :: (Binary a, Typeable a, Writable a) => Store -> String -> Identifier -> IO (Maybe a)
+ Hakyll.Core.Store: storeSet :: (Binary a, Typeable a, Writable a) => Store -> String -> Identifier -> a -> IO ()
+ Hakyll.Core.UnixFilter: unixFilter :: String -> [String] -> Compiler String String
+ Hakyll.Core.Util.Arrow: constA :: Arrow a => c -> a b c
+ Hakyll.Core.Util.Arrow: sequenceA :: Arrow a => [a b c] -> a b [c]
+ Hakyll.Core.Util.Arrow: unitA :: Arrow a => a b ()
+ Hakyll.Core.Util.File: getRecursiveContents :: Bool -> FilePath -> IO [FilePath]
+ Hakyll.Core.Util.File: isFileInternal :: HakyllConfiguration -> FilePath -> Bool
+ Hakyll.Core.Util.File: isFileObsolete :: FilePath -> [FilePath] -> IO Bool
+ Hakyll.Core.Util.File: makeDirectories :: FilePath -> IO ()
+ Hakyll.Core.Writable: class Writable a
+ Hakyll.Core.Writable: instance Writable [Char]
+ Hakyll.Core.Writable: instance Writable [Word8]
+ Hakyll.Core.Writable: write :: Writable a => FilePath -> a -> IO ()
+ Hakyll.Main: hakyll :: Rules -> IO ()
+ Hakyll.Main: hakyllWith :: HakyllConfiguration -> Rules -> IO ()
+ Hakyll.Web.CompressCss: compressCss :: String -> String
+ Hakyll.Web.CompressCss: compressCssCompiler :: Compiler Resource String
+ Hakyll.Web.Feed: FeedConfiguration :: String -> String -> String -> String -> FeedConfiguration
+ Hakyll.Web.Feed: data FeedConfiguration
+ Hakyll.Web.Feed: feedAuthorName :: FeedConfiguration -> String
+ Hakyll.Web.Feed: feedDescription :: FeedConfiguration -> String
+ Hakyll.Web.Feed: feedRoot :: FeedConfiguration -> String
+ Hakyll.Web.Feed: feedTitle :: FeedConfiguration -> String
+ Hakyll.Web.Feed: renderAtom :: FeedConfiguration -> Compiler [Page String] String
+ Hakyll.Web.Feed: renderRss :: FeedConfiguration -> Compiler [Page String] String
+ Hakyll.Web.FileType: Binary :: FileType
+ Hakyll.Web.FileType: Css :: FileType
+ Hakyll.Web.FileType: Html :: FileType
+ Hakyll.Web.FileType: LaTeX :: FileType
+ Hakyll.Web.FileType: LiterateHaskell :: FileType -> FileType
+ Hakyll.Web.FileType: Markdown :: FileType
+ Hakyll.Web.FileType: PlainText :: FileType
+ Hakyll.Web.FileType: Rst :: FileType
+ Hakyll.Web.FileType: data FileType
+ Hakyll.Web.FileType: fileType :: FilePath -> FileType
+ Hakyll.Web.FileType: getFileType :: Compiler a FileType
+ Hakyll.Web.FileType: instance Eq FileType
+ Hakyll.Web.FileType: instance Ord FileType
+ Hakyll.Web.FileType: instance Read FileType
+ Hakyll.Web.FileType: instance Show FileType
+ Hakyll.Web.Page: Page :: Map String String -> a -> Page a
+ Hakyll.Web.Page: addDefaultFields :: Compiler (Page a) (Page a)
+ Hakyll.Web.Page: data Page a
+ Hakyll.Web.Page: fromBody :: a -> Page a
+ Hakyll.Web.Page: fromMap :: Monoid a => Map String String -> Page a
+ Hakyll.Web.Page: pageBody :: Page a -> a
+ Hakyll.Web.Page: pageCompiler :: Compiler Resource (Page String)
+ Hakyll.Web.Page: pageMetadata :: Page a -> Map String String
+ Hakyll.Web.Page: readPageCompiler :: Compiler Resource (Page String)
+ Hakyll.Web.Page: sortByBaseName :: [Page a] -> [Page a]
+ Hakyll.Web.Page: toMap :: Page String -> Map String String
+ Hakyll.Web.Page.Metadata: changeField :: String -> (String -> String) -> Page a -> Page a
+ Hakyll.Web.Page.Metadata: copyField :: String -> String -> Page a -> Page a
+ Hakyll.Web.Page.Metadata: getField :: String -> Page a -> String
+ Hakyll.Web.Page.Metadata: getFieldMaybe :: String -> Page a -> Maybe String
+ Hakyll.Web.Page.Metadata: renderDateField :: String -> String -> String -> Page a -> Page a
+ Hakyll.Web.Page.Metadata: renderDateFieldWith :: TimeLocale -> String -> String -> String -> Page a -> Page a
+ Hakyll.Web.Page.Metadata: renderField :: String -> String -> (String -> String) -> Page a -> Page a
+ Hakyll.Web.Page.Metadata: setField :: String -> String -> Page a -> Page a
+ Hakyll.Web.Page.Metadata: setFieldA :: Arrow a => String -> a x String -> a (Page b, x) (Page b)
+ Hakyll.Web.Page.Read: readPage :: String -> Page String
+ Hakyll.Web.Pandoc: defaultHakyllParserState :: ParserState
+ Hakyll.Web.Pandoc: defaultHakyllWriterOptions :: WriterOptions
+ Hakyll.Web.Pandoc: pageReadPandoc :: Compiler (Page String) (Page Pandoc)
+ Hakyll.Web.Pandoc: pageReadPandocWith :: ParserState -> Compiler (Page String) (Page Pandoc)
+ Hakyll.Web.Pandoc: pageRenderPandoc :: Compiler (Page String) (Page String)
+ Hakyll.Web.Pandoc: pageRenderPandocWith :: ParserState -> WriterOptions -> Compiler (Page String) (Page String)
+ Hakyll.Web.Pandoc: readPandoc :: FileType -> String -> Pandoc
+ Hakyll.Web.Pandoc: readPandocWith :: ParserState -> FileType -> String -> Pandoc
+ Hakyll.Web.Pandoc: writePandoc :: Pandoc -> String
+ Hakyll.Web.Pandoc: writePandocWith :: WriterOptions -> Pandoc -> String
+ Hakyll.Web.Preview.Poll: previewPoll :: HakyllConfiguration -> Set Resource -> IO () -> IO ()
+ Hakyll.Web.Preview.Server: staticServer :: FilePath -> (FilePath -> IO ()) -> Int -> IO ()
+ Hakyll.Web.RelativizeUrls: relativizeUrls :: String -> String -> String
+ Hakyll.Web.RelativizeUrls: relativizeUrlsCompiler :: Compiler (Page String) (Page String)
+ Hakyll.Web.Tags: Tags :: Map String [Page a] -> Tags a
+ Hakyll.Web.Tags: data Tags a
+ Hakyll.Web.Tags: instance Binary a => Binary (Tags a)
+ Hakyll.Web.Tags: instance Show a => Show (Tags a)
+ Hakyll.Web.Tags: instance Typeable1 Tags
+ Hakyll.Web.Tags: instance Writable (Tags a)
+ Hakyll.Web.Tags: readCategory :: [Page a] -> Tags a
+ Hakyll.Web.Tags: readTags :: [Page a] -> Tags a
+ Hakyll.Web.Tags: readTagsWith :: (Page a -> [String]) -> [Page a] -> Tags a
+ Hakyll.Web.Tags: renderCategoryField :: String -> (String -> Identifier) -> Compiler (Page a) (Page a)
+ Hakyll.Web.Tags: renderTagCloud :: (String -> Identifier) -> Double -> Double -> Compiler (Tags a) String
+ Hakyll.Web.Tags: renderTagsField :: String -> (String -> Identifier) -> Compiler (Page a) (Page a)
+ Hakyll.Web.Tags: tagsMap :: Tags a -> Map String [Page a]
+ Hakyll.Web.Template: applySelf :: Page String -> Page String
+ Hakyll.Web.Template: applyTemplate :: Template -> Page String -> Page String
+ Hakyll.Web.Template: applyTemplateCompiler :: Identifier -> Compiler (Page String) (Page String)
+ Hakyll.Web.Template: data Template
+ Hakyll.Web.Template: templateCompiler :: Compiler Resource Template
+ Hakyll.Web.Template: templateCompilerWith :: HamletSettings -> Compiler Resource Template
+ Hakyll.Web.Template.Read: readHamletTemplate :: String -> Template
+ Hakyll.Web.Template.Read: readHamletTemplateWith :: HamletSettings -> String -> Template
+ Hakyll.Web.Template.Read: readTemplate :: String -> Template
+ Hakyll.Web.Util.String: replaceAll :: String -> (String -> String) -> String -> String
+ Hakyll.Web.Util.String: splitAll :: String -> String -> [String]
+ Hakyll.Web.Util.String: toSiteRoot :: String -> String
+ Hakyll.Web.Util.String: toUrl :: FilePath -> String
+ Hakyll.Web.Util.String: trim :: String -> String

Files

data/templates/atom-item.xml view
@@ -1,7 +1,7 @@ <entry>-    <title>$title</title>-    <link href="$absolute/$url" />-    <id>$absolute/$url</id>-    <updated>$timestamp</updated>-    <summary type="html"><![CDATA[$description]]></summary>+    <title>$title$</title>+    <link href="$root$$url$" />+    <id>$root$$url$</id>+    <updated>$timestamp$</updated>+    <summary type="html"><![CDATA[$description$]]></summary> </entry>
data/templates/atom.xml view
@@ -1,12 +1,12 @@ <?xml version="1.0" encoding="utf-8"?> <feed xmlns="http://www.w3.org/2005/Atom">-    <title>$title</title>-    <link href="$absolute/$url" rel="self" />-    <link href="$absolute" />-    <id>$absolute/$url</id>+    <title>$title$</title>+    <link href="$root$$url$" rel="self" />+    <link href="$root$" />+    <id>$root$$url$</id>     <author>-        <name>$authorName</name>+        <name>$authorName$</name>     </author>-    <updated>$timestamp</updated>-    $body+    <updated>$timestamp$</updated>+    $body$ </feed>
data/templates/rss-item.xml view
@@ -1,7 +1,7 @@ <item>-    <title>$title</title>-    <link>$absolute/$url</link>-    <description><![CDATA[$description]]></description>-    <pubDate>$timestamp</pubDate>-    <guid>$absolute/$url</guid>+    <title>$title$</title>+    <link>$root$$url$</link>+    <description><![CDATA[$description$]]></description>+    <pubDate>$timestamp$</pubDate>+    <guid>$root$$url$</guid> </item>
data/templates/rss.xml view
@@ -1,12 +1,12 @@ <?xml version="1.0" encoding="utf-8"?> <rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">     <channel>-        <title>$title</title>-        <link>$absolute</link>-        <description><![CDATA[$description]]></description>-        <atom:link href="$absolute/$url" rel="self"+        <title>$title$</title>+        <link>$root$</link>+        <description><![CDATA[$description$]]></description>+        <atom:link href="$root$$url$" rel="self"                    type="application/rss+xml" />-        <lastBuildDate>$timestamp</lastBuildDate>-        $body+        <lastBuildDate>$timestamp$</lastBuildDate>+        $body$     </channel>  </rss>
hakyll.cabal view
@@ -1,5 +1,5 @@ Name:               hakyll-Version:            2.4.4+Version:            3.0.0.2  Synopsis:           A simple static site generator library. Description:        A simple static site generator library, mainly aimed at@@ -17,6 +17,8 @@                     templates/atom-item.xml                     templates/rss.xml                     templates/rss-item.xml+extra-source-files: src-inotify/Hakyll/Web/Preview/Poll.hs+                    src-interval/Hakyll/Web/Preview/Poll.hs  build-type:         Simple @@ -24,45 +26,84 @@   type:             git   location:         git://github.com/jaspervdj/Hakyll.git +flag inotify+  description:      Use the inotify bindings for the preview server. Better, but+                    only works on Linux.+  default:          False+ library-  ghc-options:      -Wall-  hs-source-dirs:   src-  build-depends:    base >= 4 && < 5,-                    filepath == 1.*,-                    directory == 1.*,-                    containers == 0.*,-                    pandoc == 1.*,-                    regex-base >= 0.93,-                    regex-tdfa >= 1.1,-                    network == 2.*,-                    mtl >= 1,-                    old-locale == 1.*,-                    old-time == 1.*,-                    time >= 1.1,-                    binary >= 0.5,-                    hamlet >= 0.4.2,-                    blaze-html >= 0.4-  exposed-modules:  Network.Hakyll.SimpleServer-                    Text.Hakyll-                    Text.Hakyll.Context-                    Text.Hakyll.ContextManipulations-                    Text.Hakyll.CreateContext-                    Text.Hakyll.File-                    Text.Hakyll.HakyllMonad-                    Text.Hakyll.Regex-                    Text.Hakyll.Render-                    Text.Hakyll.HakyllAction-                    Text.Hakyll.Paginate-                    Text.Hakyll.Page-                    Text.Hakyll.Pandoc-                    Text.Hakyll.Util-                    Text.Hakyll.Tags-                    Text.Hakyll.Feed-                    Text.Hakyll.Configurations.Static-  other-modules:    Paths_hakyll-                    Text.Hakyll.Internal.Cache-                    Text.Hakyll.Internal.CompressCss-                    Text.Hakyll.Internal.FileType-                    Text.Hakyll.Internal.Template-                    Text.Hakyll.Internal.Template.Template-                    Text.Hakyll.Internal.Template.Hamlet+  ghc-options:        -Wall+  hs-source-dirs:     src++  if flag(inotify)+    hs-source-dirs:   src-inotify+    build-depends:    hinotify >= 0.3+  else+    hs-source-dirs:   src-interval++  build-depends:      base >= 4 && < 5,+                      filepath == 1.*,+                      directory == 1.*,+                      containers == 0.*,+                      pandoc == 1.*,+                      regex-base >= 0.93,+                      regex-pcre >= 0.93,+                      mtl >= 1,+                      old-locale == 1.*,+                      old-time == 1.*,+                      time >= 1.1,+                      binary >= 0.5,+                      hamlet >= 0.7,+                      blaze-html >= 0.4,+                      snap-server >= 0.4,+                      snap-core >= 0.4,+                      bytestring >= 0.9,+                      utf8-string >= 0.3,+                      tagsoup >= 0.12,+                      hopenssl >= 1.4,+                      unix >= 2.4,+                      strict-concurrency >= 0.2+  exposed-modules:    Hakyll+                      Hakyll.Main+                      Hakyll.Web.Util.String+                      Hakyll.Web.Preview.Server+                      Hakyll.Web.Preview.Poll+                      Hakyll.Web.CompressCss+                      Hakyll.Web.Template+                      Hakyll.Web.Feed+                      Hakyll.Web.Tags+                      Hakyll.Web.Pandoc+                      Hakyll.Web.FileType+                      Hakyll.Web.Page+                      Hakyll.Web.Template.Read+                      Hakyll.Web.RelativizeUrls+                      Hakyll.Web.Page.Read+                      Hakyll.Web.Page.Metadata+                      Hakyll.Core.ResourceProvider.FileResourceProvider+                      Hakyll.Core.Configuration+                      Hakyll.Core.Identifier.Pattern+                      Hakyll.Core.UnixFilter+                      Hakyll.Core.Util.Arrow+                      Hakyll.Core.Util.File+                      Hakyll.Core.ResourceProvider+                      Hakyll.Core.CompiledItem+                      Hakyll.Core.Compiler+                      Hakyll.Core.CopyFile+                      Hakyll.Core.Run+                      Hakyll.Core.Store+                      Hakyll.Core.Writable+                      Hakyll.Core.Identifier+                      Hakyll.Core.DirectedGraph.Dot+                      Hakyll.Core.DirectedGraph.DependencySolver+                      Hakyll.Core.DirectedGraph+                      Hakyll.Core.Rules+                      Hakyll.Core.Routes+                      Hakyll.Core.Logger+  other-modules:      Paths_hakyll+                      Hakyll.Web.Template.Read.Hakyll+                      Hakyll.Web.Template.Read.Hamlet+                      Hakyll.Web.Template.Internal+                      Hakyll.Web.Page.Internal+                      Hakyll.Core.Compiler.Internal+                      Hakyll.Core.DirectedGraph.Internal+                      Hakyll.Core.Rules.Internal
+ src-inotify/Hakyll/Web/Preview/Poll.hs view
@@ -0,0 +1,51 @@+-- | Filesystem polling with an inotify backend. Works only on linux.+--+module Hakyll.Web.Preview.Poll+    ( previewPoll+    ) where++import Control.Monad (forM_, when)+import Data.Set (Set)+import qualified Data.Set as S+import System.FilePath (takeDirectory, (</>))+import Data.List (isPrefixOf)++import System.INotify++import Hakyll.Core.Configuration+import Hakyll.Core.ResourceProvider+import Hakyll.Core.Identifier++-- | Calls the given callback when the directory tree changes+--+previewPoll :: HakyllConfiguration  -- ^ Configuration+            -> Set Resource         -- ^ Resources to watch+            -> IO ()                -- ^ Action called when something changes+            -> IO ()                -- ^ Can block forever+previewPoll _ resources callback = do+    -- Initialize inotify+    inotify <- initINotify++    let -- A set of file paths+        paths = S.map (toFilePath . unResource) resources++        -- A list of directories. Run it through a set so we have every+        -- directory only once.+        directories = S.toList $ S.map (notEmpty . takeDirectory) paths++        -- Problem: we can't add a watcher for "". So we make sure a directory+        -- name is not empty+        notEmpty "" = "."+        notEmpty x  = x++        -- Execute the callback when path is known+        ifResource path =+            let path' = if "./" `isPrefixOf` path then drop 2 path else path+            in when (path' `S.member` paths) callback++    -- Add a watcher for every directory+    forM_ directories $ \directory -> do+        _ <- addWatch inotify [Modify] directory $ \e -> case e of+            (Modified _ (Just p)) -> ifResource $ directory </> p+            _                     -> return ()+        return ()
+ src-interval/Hakyll/Web/Preview/Poll.hs view
@@ -0,0 +1,36 @@+-- | Interval-based implementation of preview polling, for the platforms which+-- are not supported by inotify.+--+module Hakyll.Web.Preview.Poll+    ( previewPoll+    ) where++import Control.Applicative ((<$>))+import Control.Concurrent (threadDelay)+import Control.Monad (when)+import System.Time (getClockTime)+import Data.Set (Set)+import qualified Data.Set as S+import System.Directory (getModificationTime)++import Hakyll.Core.Configuration+import Hakyll.Core.Identifier+import Hakyll.Core.ResourceProvider++-- | A preview thread that periodically recompiles the site.+--+previewPoll :: HakyllConfiguration  -- ^ Configuration+            -> Set Resource         -- ^ Resources to watch+            -> IO ()                -- ^ Action called when something changes+            -> IO ()                -- ^ Can block forever+previewPoll _ resources callback = do+    let files = map (toFilePath . unResource) $ S.toList resources+    time <- getClockTime+    loop files time+  where+    delay = 1000000+    loop files time = do+        threadDelay delay+        modified <- any (time <) <$> mapM getModificationTime files+        when modified callback+        loop files =<< getClockTime
+ src/Hakyll.hs view
@@ -0,0 +1,53 @@+-- | Top-level module exporting all modules that are interesting for the user+--+module Hakyll+    ( module Hakyll.Core.Compiler+    , module Hakyll.Core.CopyFile+    , module Hakyll.Core.Configuration+    , module Hakyll.Core.Identifier+    , module Hakyll.Core.Identifier.Pattern+    , module Hakyll.Core.ResourceProvider+    , module Hakyll.Core.Routes+    , module Hakyll.Core.Rules+    , module Hakyll.Core.UnixFilter+    , module Hakyll.Core.Util.Arrow+    , module Hakyll.Core.Util.File+    , module Hakyll.Core.Writable+    , module Hakyll.Main+    , module Hakyll.Web.CompressCss+    , module Hakyll.Web.Feed+    , module Hakyll.Web.FileType+    , module Hakyll.Web.Page+    , module Hakyll.Web.Page.Metadata+    , module Hakyll.Web.Page.Read+    , module Hakyll.Web.Pandoc+    , module Hakyll.Web.RelativizeUrls+    , module Hakyll.Web.Tags+    , module Hakyll.Web.Template+    , module Hakyll.Web.Util.String+    ) where++import Hakyll.Core.Compiler+import Hakyll.Core.CopyFile+import Hakyll.Core.Configuration+import Hakyll.Core.Identifier+import Hakyll.Core.Identifier.Pattern+import Hakyll.Core.ResourceProvider+import Hakyll.Core.Routes+import Hakyll.Core.Rules+import Hakyll.Core.UnixFilter+import Hakyll.Core.Util.Arrow+import Hakyll.Core.Util.File+import Hakyll.Core.Writable+import Hakyll.Main+import Hakyll.Web.CompressCss+import Hakyll.Web.Feed+import Hakyll.Web.FileType+import Hakyll.Web.Page+import Hakyll.Web.Page.Metadata+import Hakyll.Web.Page.Read+import Hakyll.Web.Pandoc+import Hakyll.Web.RelativizeUrls+import Hakyll.Web.Tags+import Hakyll.Web.Template+import Hakyll.Web.Util.String
+ src/Hakyll/Core/CompiledItem.hs view
@@ -0,0 +1,45 @@+-- | A module containing a box datatype representing a compiled item. This+-- item can be of any type, given that a few restrictions hold:+--+-- * we need a 'Typeable' instance to perform type-safe casts;+--+-- * we need a 'Binary' instance so we can serialize these items to the cache;+--+-- * we need a 'Writable' instance so the results can be saved.+--+{-# LANGUAGE ExistentialQuantification #-}+module Hakyll.Core.CompiledItem+    ( CompiledItem (..)+    , compiledItem+    , unCompiledItem+    ) where++import Data.Binary (Binary)+import Data.Typeable (Typeable, cast)+import Data.Maybe (fromMaybe)++import Hakyll.Core.Writable++-- | Box type for a compiled item+--+data CompiledItem =  forall a.  (Binary a, Typeable a, Writable a)+                  => CompiledItem a++instance Writable CompiledItem where+    write p (CompiledItem x) = write p x++-- | Box a value into a 'CompiledItem'+--+compiledItem :: (Binary a, Typeable a, Writable a)+             => a+             -> CompiledItem+compiledItem = CompiledItem++-- | Unbox a value from a 'CompiledItem'+--+unCompiledItem :: (Binary a, Typeable a, Writable a)+               => CompiledItem+               -> a+unCompiledItem (CompiledItem x) = fromMaybe error' $ cast x+  where+    error' = error "Hakyll.Core.CompiledItem.unCompiledItem: Unsupported type"
+ src/Hakyll/Core/Compiler.hs view
@@ -0,0 +1,333 @@+-- | A Compiler manages targets and dependencies between targets+--+-- The most distinguishing property of a 'Compiler' is that it is an Arrow. A+-- compiler of the type @Compiler a b@ is simply a compilation phase which takes+-- an @a@ as input, and produces a @b@ as output.+--+-- Compilers are chained using the '>>>' arrow operation. If we have a compiler+--+-- > getResourceString :: Compiler Resource String+--+-- which reads the resource, and a compiler+--+-- > readPage :: Compiler String (Page String)+--+-- we can chain these two compilers to get a+--+-- > (getResourceString >>> readPage) :: Compiler Resource (Page String)+--+-- Most compilers can be created by combining smaller compilers using '>>>'.+--+-- More advanced constructions are also possible using arrow, and sometimes+-- these are needed. For a good introduction to arrow, you can refer to+--+-- <http://en.wikibooks.org/wiki/Haskell/Understanding_arrows>+--+-- A construction worth writing a few paragraphs about here are the 'require'+-- functions. Different variants of this function are exported here, but they+-- all serve more or less the same goal.+--+-- When you use only '>>>' to chain your compilers, you get a linear pipeline --+-- it is not possible to add extra items from other compilers along the way.+-- This is where the 'require' functions come in.+--+-- This function allows you to reference other items, which are then added to+-- the pipeline. Let's look at this crappy ASCII illustration which represents+-- a pretty common scenario:+--+-- > read resource >>> pandoc render >>> layout >>> relativize URL's+-- >+-- > @templates/fancy.html@+--+-- We want to construct a pipeline of compilers to go from our resource to a+-- proper webpage. However, the @layout@ compiler takes more than just the+-- rendered page as input: it needs the @templates/fancy.html@ template as well.+--+-- This is an example of where we need the @require@ function. We can solve+-- this using a construction that looks like:+--+-- > ... >>> pandoc render >>> require >>> layout >>> ...+-- >                              |+-- > @templates/fancy.html@ ------/+--+-- This illustration can help us understand the type signature of 'require'.+--+-- > require :: (Binary a, Typeable a, Writable a)+-- >         => Identifier+-- >         -> (b -> a -> c)+-- >         -> Compiler b c+--+-- Let's look at it in detail:+--+-- > (Binary a, Typeable a, Writable a)+-- +-- These are constraints for the @a@ type. @a@ (the template) needs to have+-- certain properties for it to be required.+--+-- > Identifier+--+-- This is simply @templates/fancy.html@: the 'Identifier' of the item we want+-- to 'require', in other words, the name of the item we want to add to the+-- pipeline somehow.+--+-- > (b -> a -> c)+--+-- This is a function given by the user, specifying /how/ the two items shall be+-- merged. @b@ is the output of the previous compiler, and @a@ is the item we+-- just required -- the template. This means @c@ will be the final output of the+-- 'require' combinator.+--+-- > Compiler b c+--+-- Indeed, we have now constructed a compiler which takes a @b@ and produces a+-- @c@. This means that we have a linear pipeline again, thanks to the 'require'+-- function. So, the 'require' function actually helps to reduce to complexity+-- of Hakyll applications!+--+{-# LANGUAGE GeneralizedNewtypeDeriving #-}+module Hakyll.Core.Compiler+    ( Compiler+    , runCompiler+    , getIdentifier+    , getRoute+    , getRouteFor+    , getResourceString+    , fromDependency+    , require_+    , require+    , requireA+    , requireAll_+    , requireAll+    , requireAllA+    , cached+    , unsafeCompiler+    , traceShowCompiler+    , mapCompiler+    , timedCompiler+    , byExtension+    ) where++import Prelude hiding ((.), id)+import Control.Arrow ((>>>), (&&&), arr)+import Control.Applicative ((<$>))+import Control.Monad.Reader (ask)+import Control.Monad.Trans (liftIO)+import Control.Category (Category, (.), id)+import Data.Maybe (fromMaybe)+import System.FilePath (takeExtension)++import Data.Binary (Binary)+import Data.Typeable (Typeable)++import Hakyll.Core.Identifier+import Hakyll.Core.Identifier.Pattern+import Hakyll.Core.CompiledItem+import Hakyll.Core.Writable+import Hakyll.Core.ResourceProvider+import Hakyll.Core.Compiler.Internal+import Hakyll.Core.Store+import Hakyll.Core.Rules.Internal+import Hakyll.Core.Routes+import Hakyll.Core.Logger++-- | Run a compiler, yielding the resulting target and it's dependencies. This+-- version of 'runCompilerJob' also stores the result+--+runCompiler :: Compiler () CompileRule  -- ^ Compiler to run+            -> Identifier               -- ^ Target identifier+            -> ResourceProvider         -- ^ Resource provider+            -> Routes                   -- ^ Route+            -> Store                    -- ^ Store+            -> Bool                     -- ^ Was the resource modified?+            -> Logger                   -- ^ Logger+            -> IO CompileRule           -- ^ Resulting item+runCompiler compiler identifier provider routes store modified logger = do+    -- Run the compiler job+    result <-+        runCompilerJob compiler identifier provider routes store modified logger++    -- Inspect the result+    case result of+        -- In case we compiled an item, we will store a copy in the cache first,+        -- before we return control. This makes sure the compiled item can later+        -- be accessed by e.g. require.+        CompileRule (CompiledItem x) ->+            storeSet store "Hakyll.Core.Compiler.runCompiler" identifier x++        -- Otherwise, we do nothing here+        _ -> return ()++    return result++-- | Get the identifier of the item that is currently being compiled+--+getIdentifier :: Compiler a Identifier+getIdentifier = fromJob $ const $ CompilerM $ compilerIdentifier <$> ask++-- | Get the route we are using for this item+--+getRoute :: Compiler a (Maybe FilePath)+getRoute = getIdentifier >>> getRouteFor++-- | Get the route for a specified item+--+getRouteFor :: Compiler Identifier (Maybe FilePath)+getRouteFor = fromJob $ \identifier -> CompilerM $ do+    routes <- compilerRoutes <$> ask+    return $ runRoutes routes identifier++-- | Get the resource we are compiling as a string+--+getResourceString :: Compiler Resource String+getResourceString = fromJob $ \resource -> CompilerM $ do+    provider <- compilerResourceProvider <$> ask+    liftIO $ resourceString provider resource++-- | Auxiliary: get a dependency+--+getDependency :: (Binary a, Writable a, Typeable a)+              => Identifier -> CompilerM a+getDependency identifier = CompilerM $ do+    store <- compilerStore <$> ask+    fmap (fromMaybe error') $ liftIO $+        storeGet store "Hakyll.Core.Compiler.runCompiler" identifier+  where+    error' = error $  "Hakyll.Core.Compiler.getDependency: "+                   ++ show identifier+                   ++ " not found in the cache, the cache might be corrupted or"+                   ++ " the item you are referring to might not exist"+++-- | Variant of 'require' which drops the current value+--+require_ :: (Binary a, Typeable a, Writable a)+         => Identifier+         -> Compiler b a+require_ identifier =+    fromDependency identifier >>> fromJob (const $ getDependency identifier)++-- | Require another target. Using this function ensures automatic handling of+-- dependencies+--+require :: (Binary a, Typeable a, Writable a)+        => Identifier+        -> (b -> a -> c)+        -> Compiler b c+require identifier = requireA identifier . arr . uncurry++-- | Arrow-based variant of 'require'+--+requireA :: (Binary a, Typeable a, Writable a)+         => Identifier+         -> Compiler (b, a) c+         -> Compiler b c+requireA identifier = (id &&& require_ identifier >>>)++-- | Variant of 'requireAll' which drops the current value+--+requireAll_ :: (Binary a, Typeable a, Writable a)+            => Pattern+            -> Compiler b [a]+requireAll_ pattern = fromDependencies (const getDeps) >>> fromJob requireAll_'+  where+    getDeps = matches pattern . map unResource . resourceList+    requireAll_' = const $ CompilerM $ do+        deps <- getDeps . compilerResourceProvider <$> ask+        mapM (unCompilerM . getDependency) deps++-- | Require a number of targets. Using this function ensures automatic handling+-- of dependencies+--+requireAll :: (Binary a, Typeable a, Writable a)+           => Pattern+           -> (b -> [a] -> c)+           -> Compiler b c+requireAll pattern = requireAllA pattern . arr . uncurry++-- | Arrow-based variant of 'requireAll'+--+requireAllA :: (Binary a, Typeable a, Writable a)+            => Pattern+            -> Compiler (b, [a]) c+            -> Compiler b c+requireAllA pattern = (id &&& requireAll_ pattern >>>)++cached :: (Binary a, Typeable a, Writable a)+       => String+       -> Compiler Resource a+       -> Compiler Resource a+cached name (Compiler d j) = Compiler d $ const $ CompilerM $ do+    logger <- compilerLogger <$> ask+    identifier <- compilerIdentifier <$> ask+    store <- compilerStore <$> ask+    modified <- compilerResourceModified <$> ask+    report logger $ "Checking cache: " ++ if modified then "modified" else "OK"+    if modified+        then do v <- unCompilerM $ j $ Resource identifier+                liftIO $ storeSet store name identifier v+                return v+        else do v <- liftIO $ storeGet store name identifier+                case v of Just v' -> return v'+                          Nothing -> error'+  where+    error' = error "Hakyll.Core.Compiler.cached: Cache corrupt!"++-- | Create an unsafe compiler from a function in IO+--+unsafeCompiler :: (a -> IO b)   -- ^ Function to lift+               -> Compiler a b  -- ^ Resulting compiler+unsafeCompiler f = fromJob $ CompilerM . liftIO . f++-- | Compiler for debugging purposes+--+traceShowCompiler :: Show a => Compiler a a+traceShowCompiler = fromJob $ \x -> CompilerM $ do+    logger <- compilerLogger <$> ask+    report logger $ show x+    return x++-- | Map over a compiler+--+mapCompiler :: Compiler a b+            -> Compiler [a] [b]+mapCompiler (Compiler d j) = Compiler d $ mapM j++-- | Log and time a compiler+--+timedCompiler :: String        -- ^ Message+              -> Compiler a b  -- ^ Compiler to time+              -> Compiler a b  -- ^ Resulting compiler+timedCompiler msg (Compiler d j) = Compiler d $ \x -> CompilerM $ do+    logger <- compilerLogger <$> ask+    timed logger msg $ unCompilerM $ j x++-- | Choose a compiler by extension+--+-- Example:+--+-- > route   "css/*" $ setExtension "css"+-- > compile "css/*" $ byExtension (error "Not a (S)CSS file")+-- >     [ (".css",  compressCssCompiler)+-- >     , (".scss", sass)+-- >     ]+--+-- This piece of code will select the @compressCssCompiler@ for @.css@ files,+-- and the @sass@ compiler (defined elsewhere) for @.scss@ files.+--+byExtension :: Compiler a b              -- ^ Default compiler+            -> [(String, Compiler a b)]  -- ^ Choices+            -> Compiler a b              -- ^ Resulting compiler+byExtension defaultCompiler choices = Compiler deps job+  where+    -- Lookup the compiler, give an error when it is not found+    lookup' identifier =+        let extension = takeExtension $ toFilePath identifier+        in fromMaybe defaultCompiler $ lookup extension choices+    -- Collect the dependencies of the choice+    deps = do+        identifier <- dependencyIdentifier <$> ask+        compilerDependencies $ lookup' identifier+    -- Collect the job of the choice+    job x = CompilerM $ do+        identifier <- compilerIdentifier <$> ask+        unCompilerM $ compilerJob (lookup' identifier) x
+ src/Hakyll/Core/Compiler/Internal.hs view
@@ -0,0 +1,146 @@+-- | Internally used compiler module+--+{-# LANGUAGE GeneralizedNewtypeDeriving #-}+module Hakyll.Core.Compiler.Internal+    ( Dependencies+    , DependencyEnvironment (..)+    , CompilerEnvironment (..)+    , CompilerM (..)+    , Compiler (..)+    , runCompilerJob+    , runCompilerDependencies+    , fromJob+    , fromDependencies+    , fromDependency+    ) where++import Prelude hiding ((.), id)+import Control.Applicative (Applicative, pure, (<*>), (<$>))+import Control.Monad.Reader (ReaderT, Reader, ask, runReaderT, runReader)+import Control.Monad ((<=<), liftM2)+import Data.Set (Set)+import qualified Data.Set as S+import Control.Category (Category, (.), id)+import Control.Arrow (Arrow, ArrowChoice, arr, first, left)++import Hakyll.Core.Identifier+import Hakyll.Core.ResourceProvider+import Hakyll.Core.Store+import Hakyll.Core.Routes+import Hakyll.Core.Logger++-- | A set of dependencies+--+type Dependencies = Set Identifier++-- | Environment in which the dependency analyzer runs+--+data DependencyEnvironment = DependencyEnvironment+    { -- | Target identifier+      dependencyIdentifier       :: Identifier+    , -- | Resource provider+      dependencyResourceProvider :: ResourceProvider+    }++-- | Environment in which a compiler runs+--+data CompilerEnvironment = CompilerEnvironment+    { -- | Target identifier+      compilerIdentifier       :: Identifier+    , -- | Resource provider+      compilerResourceProvider :: ResourceProvider+    , -- | Site routes+      compilerRoutes           :: Routes+    , -- | Compiler store+      compilerStore            :: Store+    , -- | Flag indicating if the underlying resource was modified+      compilerResourceModified :: Bool+    , -- | Logger+      compilerLogger           :: Logger+    }++-- | The compiler monad+--+newtype CompilerM a = CompilerM+    { unCompilerM :: ReaderT CompilerEnvironment IO a+    } deriving (Monad, Functor, Applicative)++-- | The compiler arrow+--+data Compiler a b = Compiler+    { compilerDependencies :: Reader DependencyEnvironment Dependencies+    , compilerJob          :: a -> CompilerM b+    }++instance Functor (Compiler a) where+    fmap f ~(Compiler d j) = Compiler d $ fmap f . j++instance Applicative (Compiler a) where+    pure = Compiler (return S.empty) . const . return+    ~(Compiler d1 f) <*> ~(Compiler d2 j) =+        Compiler (liftM2 S.union d1 d2) $ \x -> f x <*> j x++instance Category Compiler where+    id = Compiler (return S.empty) return+    ~(Compiler d1 j1) . ~(Compiler d2 j2) =+        Compiler (liftM2 S.union d1 d2) (j1 <=< j2)++instance Arrow Compiler where+    arr f = Compiler (return S.empty) (return . f)+    first ~(Compiler d j) = Compiler d $ \(x, y) -> do+        x' <- j x+        return (x', y)++instance ArrowChoice Compiler where+    left ~(Compiler d j) = Compiler d $ \e -> case e of+        Left l  -> Left  <$> j l+        Right r -> Right <$> return r++-- | Run a compiler, yielding the resulting target and it's dependencies+--+runCompilerJob :: Compiler () a     -- ^ Compiler to run+               -> Identifier        -- ^ Target identifier+               -> ResourceProvider  -- ^ Resource provider+               -> Routes            -- ^ Route+               -> Store             -- ^ Store+               -> Bool              -- ^ Was the resource modified?+               -> Logger            -- ^ Logger+               -> IO a+runCompilerJob compiler identifier provider route store modified logger =+    runReaderT (unCompilerM $ compilerJob compiler ()) env+  where+    env = CompilerEnvironment+            { compilerIdentifier       = identifier+            , compilerResourceProvider = provider+            , compilerRoutes           = route+            , compilerStore            = store+            , compilerResourceModified = modified+            , compilerLogger           = logger+            }++runCompilerDependencies :: Compiler () a+                        -> Identifier+                        -> ResourceProvider+                        -> Dependencies+runCompilerDependencies compiler identifier provider =+    runReader (compilerDependencies compiler) env+  where+    env = DependencyEnvironment+            { dependencyIdentifier       = identifier+            , dependencyResourceProvider = provider+            }++fromJob :: (a -> CompilerM b)+        -> Compiler a b+fromJob = Compiler (return S.empty)++fromDependencies :: (Identifier -> ResourceProvider -> [Identifier])+                 -> Compiler b b+fromDependencies collectDeps = flip Compiler return $ do+    DependencyEnvironment identifier provider <- ask+    return $ S.fromList $ collectDeps identifier provider++-- | Wait until another compiler has finished before running this compiler+--+fromDependency :: Identifier -> Compiler a a+fromDependency = fromDependencies . const . const . return
+ src/Hakyll/Core/Configuration.hs view
@@ -0,0 +1,44 @@+-- | Exports a datastructure for the top-level hakyll configuration+--+module Hakyll.Core.Configuration+    ( HakyllConfiguration (..)+    , defaultHakyllConfiguration+    ) where++import System.FilePath (takeFileName)+import Data.List (isPrefixOf, isSuffixOf)++data HakyllConfiguration = HakyllConfiguration+    { -- | Directory in which the output written+      destinationDirectory :: FilePath+    , -- | Directory where hakyll's internal store is kept+      storeDirectory       :: FilePath+    , -- | Function to determine ignored files+      --+      -- In 'defaultHakyllConfiguration', the following files are ignored:+      --+      -- * files starting with a @.@+      --+      -- * files ending with a @~@+      --+      -- * files ending with @.swp@+      --+      ignoreFile           :: FilePath -> Bool+    }++-- | Default configuration for a hakyll application+--+defaultHakyllConfiguration :: HakyllConfiguration+defaultHakyllConfiguration = HakyllConfiguration+    { destinationDirectory = "_site"+    , storeDirectory       = "_cache"+    , ignoreFile           = ignoreFile'+    }+  where+    ignoreFile' path+        | "." `isPrefixOf` fileName = True+        | "~" `isSuffixOf` fileName = True+        | ".swp" `isSuffixOf` fileName = True+        | otherwise = False+      where+        fileName = takeFileName path
+ src/Hakyll/Core/CopyFile.hs view
@@ -0,0 +1,29 @@+-- | Exports simple compilers to just copy files+--+{-# LANGUAGE GeneralizedNewtypeDeriving, DeriveDataTypeable #-}+module Hakyll.Core.CopyFile+    ( CopyFile (..)+    , copyFileCompiler+    ) where++import Control.Arrow ((>>^))+import System.Directory (copyFile)++import Data.Typeable (Typeable)+import Data.Binary (Binary)++import Hakyll.Core.ResourceProvider+import Hakyll.Core.Writable+import Hakyll.Core.Compiler+import Hakyll.Core.Identifier++-- | Newtype construct around 'FilePath' which will copy the file directly+--+newtype CopyFile = CopyFile {unCopyFile :: FilePath}+                 deriving (Show, Eq, Ord, Binary, Typeable)++instance Writable CopyFile where+    write dst (CopyFile src) = copyFile src dst++copyFileCompiler :: Compiler Resource CopyFile+copyFileCompiler = getIdentifier >>^ CopyFile . toFilePath
+ src/Hakyll/Core/DirectedGraph.hs view
@@ -0,0 +1,85 @@+-- | Representation of a directed graph. In Hakyll, this is used for dependency+-- tracking.+--+module Hakyll.Core.DirectedGraph+    ( DirectedGraph+    , fromList+    , member+    , nodes+    , neighbours+    , reverse+    , reachableNodes+    , sanitize+    ) where++import Prelude hiding (reverse)+import Data.Monoid (mconcat)+import Data.Set (Set)+import Data.Maybe (fromMaybe)+import qualified Data.Map as M+import qualified Data.Set as S++import Hakyll.Core.DirectedGraph.Internal++-- | Construction of directed graphs+--+fromList :: Ord a+         => [(a, Set a)]     -- ^ List of (node, reachable neighbours)+         -> DirectedGraph a  -- ^ Resulting directed graph+fromList = DirectedGraph . M.fromList . map (\(t, d) -> (t, Node t d))++-- | Check if a node lies in the given graph+--+member :: Ord a+       => a                -- ^ Node to check for+       -> DirectedGraph a  -- ^ Directed graph to check in+       -> Bool             -- ^ If the node lies in the graph+member n = M.member n . unDirectedGraph++-- | Get all nodes in the graph+--+nodes :: Ord a+      => DirectedGraph a  -- ^ Graph to get the nodes from+      -> Set a            -- ^ All nodes in the graph+nodes = M.keysSet . unDirectedGraph++-- | Get a set of reachable neighbours from a directed graph+--+neighbours :: Ord a+           => a                -- ^ Node to get the neighbours of+           -> DirectedGraph a  -- ^ Graph to search in+           -> Set a            -- ^ Set containing the neighbours+neighbours x = fromMaybe S.empty . fmap nodeNeighbours+             . M.lookup x . unDirectedGraph++-- | Reverse a directed graph (i.e. flip all edges)+--+reverse :: Ord a+        => DirectedGraph a+        -> DirectedGraph a+reverse = mconcat . map reverse' . M.toList . unDirectedGraph+  where+    reverse' (id', Node _ neighbours') = fromList $+        zip (S.toList neighbours') $ repeat $ S.singleton id'++-- | Find all reachable nodes from a given set of nodes in the directed graph+--+reachableNodes :: Ord a => Set a -> DirectedGraph a -> Set a+reachableNodes set graph = reachable (setNeighbours set) set+  where+    reachable next visited+        | S.null next = visited+        | otherwise = reachable (sanitize' neighbours') (next `S.union` visited)+      where+        sanitize' = S.filter (`S.notMember` visited)+        neighbours' = setNeighbours (sanitize' next)++    setNeighbours = S.unions . map (`neighbours` graph) . S.toList+    +-- | Remove all dangling pointers, i.e. references to notes that do +-- not actually exist in the graph.+--+sanitize :: Ord a => DirectedGraph a -> DirectedGraph a+sanitize (DirectedGraph graph) = DirectedGraph $ M.map sanitize' graph+  where+    sanitize' (Node t n) = Node t $ S.filter (`M.member` graph) n
+ src/Hakyll/Core/DirectedGraph/DependencySolver.hs view
@@ -0,0 +1,70 @@+-- | Given a dependency graph, this module provides a function that will+-- generate an order in which the graph can be visited, so that all the+-- dependencies of a given node have been visited before the node itself is+-- visited.+--+module Hakyll.Core.DirectedGraph.DependencySolver+    ( solveDependencies+    ) where++import Prelude+import qualified Prelude as P+import Data.Set (Set)+import Data.Maybe (mapMaybe)+import qualified Data.Map as M+import qualified Data.Set as S++import Hakyll.Core.DirectedGraph+import Hakyll.Core.DirectedGraph.Internal++-- | Solve a dependency graph. This function returns an order to run the+-- different nodes+--+solveDependencies :: Ord a+                  => DirectedGraph a  -- ^ Graph+                  -> [a]              -- ^ Resulting plan+solveDependencies = P.reverse . order [] [] S.empty++-- | Produce a reversed order using a stack+--+order :: Ord a+      => [a]              -- ^ Temporary result+      -> [Node a]         -- ^ Backtrace stack+      -> Set a            -- ^ Items in the stack+      -> DirectedGraph a  -- ^ Graph+      -> [a]              -- ^ Ordered result+order temp stack set graph@(DirectedGraph graph')+    -- Empty graph - return our current result+    | M.null graph' = temp+    | otherwise = case stack of++        -- Empty stack - pick a node, and add it to the stack+        [] ->+            let (tag, node) = M.findMin graph'+            in order temp (node : stack) (S.insert tag set) graph++        -- At least one item on the stack - continue using this item+        (node : stackTail) ->+            -- Check which dependencies are still in the graph+            let tag = nodeTag node+                deps = S.toList $ nodeNeighbours node+                unsatisfied = mapMaybe (`M.lookup` graph') deps+            in case unsatisfied of+                +                -- All dependencies for node are satisfied, we can return it and+                -- remove it from the graph+                [] -> order (tag : temp) stackTail (S.delete tag set)+                            (DirectedGraph $ M.delete tag graph')++                -- There is at least one dependency left. We need to solve that+                -- one first...+                (dep : _) -> if nodeTag dep `S.member` set+                    -- The dependency is already in our stack - cycle detected!+                    then cycleError+                    -- Continue with the dependency+                    else order temp (dep : node : stackTail)+                                    (S.insert (nodeTag dep) set)+                                    graph+  where+    cycleError = error $  "Hakyll.Core.DirectedGraph.DependencySolver.order: "+                       ++ "Cycle detected!"  -- TODO: Dump cycle
+ src/Hakyll/Core/DirectedGraph/Dot.hs view
@@ -0,0 +1,30 @@+-- | Dump a directed graph in dot format. Used for debugging purposes+--+module Hakyll.Core.DirectedGraph.Dot+    ( toDot+    , writeDot+    ) where++import Hakyll.Core.DirectedGraph+import qualified Data.Set as S++-- | Convert a directed graph into dot format for debugging purposes+--+toDot :: Ord a+      => (a -> String)    -- ^ Convert nodes to dot names+      -> DirectedGraph a  -- ^ Graph to dump+      -> String           -- ^ Resulting string+toDot showTag graph = unlines $ concat+    [ return "digraph dependencies {"+    , concatMap showNode (S.toList $ nodes graph)+    , return "}"+    ]+  where+    showNode node = map (showEdge node) $ S.toList $ neighbours node graph+    showEdge x y = "    \"" ++ showTag x ++ "\" -> \"" ++ showTag y ++ "\";"++-- | Write out the @.dot@ file to a given file path. See 'toDot' for more+-- information.+--+writeDot :: Ord a => FilePath -> (a -> String) -> DirectedGraph a -> IO ()+writeDot path showTag = writeFile path . toDot showTag
+ src/Hakyll/Core/DirectedGraph/Internal.hs view
@@ -0,0 +1,43 @@+-- | Internal structure of the DirectedGraph type. Not exported outside of the+-- library.+--+module Hakyll.Core.DirectedGraph.Internal+    ( Node (..)+    , DirectedGraph (..)+    ) where++import Prelude hiding (reverse, filter)+import Data.Monoid (Monoid, mempty, mappend)+import Data.Set (Set)+import Data.Map (Map)+import qualified Data.Map as M+import qualified Data.Set as S++-- | A node in the directed graph+--+data Node a = Node+    { nodeTag        :: a      -- ^ Tag identifying the node+    , nodeNeighbours :: Set a  -- ^ Edges starting at this node+    } deriving (Show)++-- | Append two nodes. Useful for joining graphs.+--+appendNodes :: Ord a => Node a -> Node a -> Node a+appendNodes (Node t1 n1) (Node t2 n2)+    | t1 /= t2 = error'+    | otherwise = Node t1 (n1 `S.union` n2)+  where+    error' = error $  "Hakyll.Core.DirectedGraph.Internal.appendNodes: "+                   ++ "Appending differently tagged nodes"++-- | Type used to represent a directed graph+--+newtype DirectedGraph a = DirectedGraph {unDirectedGraph :: Map a (Node a)}+                        deriving (Show)++-- | Allow users to concatenate different graphs+--+instance Ord a => Monoid (DirectedGraph a) where+    mempty = DirectedGraph M.empty+    mappend (DirectedGraph m1) (DirectedGraph m2) = DirectedGraph $+        M.unionWith appendNodes m1 m2
+ src/Hakyll/Core/Identifier.hs view
@@ -0,0 +1,59 @@+-- | An identifier is a type used to uniquely identify a resource, target...+--+-- One can think of an identifier as something similar to a file path. An+-- identifier is a path as well, with the different elements in the path+-- separated by @/@ characters. Examples of identifiers are:+--+-- * @posts/foo.markdown@+--+-- * @index@+--+-- * @error/404@+--+-- The most important difference between an 'Identifier' and a file path is that+-- the identifier for an item is not necesserily the file path.+--+-- For example, we could have an @index@ identifier, generated by Hakyll. The+-- actual file path would be @index.html@, but we identify it using @index@.+--+-- @posts/foo.markdown@ could be an identifier of an item that is rendered to+-- @posts/foo.html@. In this case, the identifier is the name of the source+-- file of the page.+--+{-# LANGUAGE GeneralizedNewtypeDeriving #-}+module Hakyll.Core.Identifier+    ( Identifier (..)+    , parseIdentifier+    , toFilePath+    ) where++import Control.Arrow (second)+import Data.Monoid (Monoid)++import GHC.Exts (IsString, fromString)+import System.FilePath (joinPath)++-- | An identifier used to uniquely identify a value+--+newtype Identifier = Identifier {unIdentifier :: [String]}+                   deriving (Eq, Ord, Monoid)++instance Show Identifier where+    show = toFilePath++instance IsString Identifier where+    fromString = parseIdentifier++-- | Parse an identifier from a string+--+parseIdentifier :: String -> Identifier+parseIdentifier = Identifier . filter (not . null) . split'+  where+    split' [] = [[]]+    split' str = let (pre, post) = second (drop 1) $ break (== '/') str+                 in pre : split' post++-- | Convert an identifier to a relative 'FilePath'+--+toFilePath :: Identifier -> FilePath+toFilePath = joinPath . unIdentifier
+ src/Hakyll/Core/Identifier/Pattern.hs view
@@ -0,0 +1,160 @@+-- | Module providing pattern matching and capturing on 'Identifier's.+--+-- A very simple pattern could be, for example, @foo\/bar@. This pattern will+-- only match the exact @foo\/bar@ identifier.+--+-- To match more than one identifier, there are different captures that one can+-- use:+--+-- * @*@: matches exactly one element of an identifier;+--+-- * @**@: matches one or more elements of an identifier.+--+-- Some examples:+--+-- * @foo\/*@ will match @foo\/bar@ and @foo\/foo@, but not @foo\/bar\/qux@ nor+--   @foo@;+--+-- * @**@ will match any non-empty identifier;+--+-- * @foo\/**@ will match @foo\/bar@ and @foo\/bar\/qux@, but not @bar\/foo@ nor+--   @foo@;+--+-- A small warning: patterns are not globs. Using @foo\/*.markdown@ will not do+-- what you probably intended, as it will only match the file which is literally+-- called @foo\/*.markdown@. Remember that these captures only work on elements+-- of identifiers as a whole; not on parts of these elements.+--+-- Furthermore, the 'match' function allows the user to get access to the+-- elements captured by the capture elements in the pattern.+--+module Hakyll.Core.Identifier.Pattern+    ( Pattern+    , parsePattern+    , match+    , doesMatch+    , matches+    , fromCapture+    , fromCaptureString+    , fromCaptures+    ) where++import Data.List (intercalate)+import Control.Monad (msum)+import Data.Maybe (isJust)+import Data.Monoid (mempty, mappend)++import GHC.Exts (IsString, fromString)++import Hakyll.Core.Identifier++-- | One base element of a pattern+--+data PatternComponent = CaptureOne+                      | CaptureMany+                      | Literal String+                      deriving (Eq)++instance Show PatternComponent where+    show CaptureOne = "*"+    show CaptureMany = "**"+    show (Literal s) = s++-- | Type that allows matching on identifiers+--+newtype Pattern = Pattern {unPattern :: [PatternComponent]}+                deriving (Eq)++instance Show Pattern where+    show = intercalate "/" . map show . unPattern++instance IsString Pattern where+    fromString = parsePattern++-- | Parse a pattern from a string+--+parsePattern :: String -> Pattern+parsePattern = Pattern . map toPattern . unIdentifier . parseIdentifier+  where+    toPattern x | x == "*"  = CaptureOne+                | x == "**" = CaptureMany+                | otherwise = Literal x++-- | Match an identifier against a pattern, generating a list of captures+--+match :: Pattern -> Identifier -> Maybe [Identifier]+match (Pattern p) (Identifier i) = fmap (map Identifier) $ match' p i++-- | Check if an identifier matches a pattern+--+doesMatch :: Pattern -> Identifier -> Bool+doesMatch p = isJust . match p++-- | Given a list of identifiers, retain only those who match the given pattern+--+matches :: Pattern -> [Identifier] -> [Identifier]+matches p = filter (doesMatch p)++-- | Split a list at every possible point, generate a list of (init, tail) cases+--+splits :: [a] -> [([a], [a])]+splits ls = reverse $ splits' [] ls+  where+    splits' lx ly = (lx, ly) : case ly of+        []       -> []+        (y : ys) -> splits' (lx ++ [y]) ys++-- | Internal verion of 'match'+--+match' :: [PatternComponent] -> [String] -> Maybe [[String]]+match' [] [] = Just []  -- An empty match+match' [] _ = Nothing   -- No match+match' _ [] = Nothing   -- No match+match' (m : ms) (s : ss) = case m of+    -- Take one string and one literal, fail on mismatch+    Literal l -> if s == l then match' ms ss else Nothing+    -- Take one string and one capture+    CaptureOne -> fmap ([s] :) $ match' ms ss+    -- Take one string, and one or many captures+    CaptureMany ->+        let take' (i, t) = fmap (i :) $ match' ms t+        in msum $ map take' $ splits (s : ss)++-- | Create an identifier from a pattern by filling in the captures with a given+-- string+--+-- Example:+--+-- > fromCapture (parsePattern "tags/*") (parseIdentifier "foo")+--+-- Result:+--+-- > "tags/foo"+--+fromCapture :: Pattern -> Identifier -> Identifier+fromCapture pattern = fromCaptures pattern . repeat++-- | Simplified version of 'fromCapture' which takes a 'String' instead of an+-- 'Identifier'+--+-- > fromCaptureString (parsePattern "tags/*") "foo"+--+-- Result:+--+-- > "tags/foo"+--+fromCaptureString :: Pattern -> String -> Identifier+fromCaptureString pattern = fromCapture pattern . parseIdentifier++-- | Create an identifier from a pattern by filling in the captures with the+-- given list of strings+--+fromCaptures :: Pattern -> [Identifier] -> Identifier+fromCaptures (Pattern []) _ = mempty+fromCaptures (Pattern (m : ms)) [] = case m of+    Literal l -> Identifier [l] `mappend` fromCaptures (Pattern ms) []+    _         -> error $  "Hakyll.Core.Identifier.Pattern.fromCaptures: "+                       ++ "identifier list exhausted"+fromCaptures (Pattern (m : ms)) ids@(i : is) = case m of+    Literal l -> Identifier [l] `mappend` fromCaptures (Pattern ms) ids+    _         -> i `mappend` fromCaptures (Pattern ms) is
+ src/Hakyll/Core/Logger.hs view
@@ -0,0 +1,90 @@+-- | Produce pretty, thread-safe logs+--+{-# LANGUAGE BangPatterns #-}+module Hakyll.Core.Logger+    ( Logger+    , makeLogger+    , flushLogger+    , section+    , timed+    , report+    ) where++import Control.Monad (forever)+import Control.Monad.Trans (MonadIO, liftIO)+import Control.Applicative ((<$>), (<*>))+import Control.Concurrent (forkIO)+import Control.Concurrent.Chan.Strict (Chan, newChan, readChan, writeChan)+import Control.Concurrent.MVar.Strict (MVar, newEmptyMVar, takeMVar, putMVar)+import Text.Printf (printf)++import Data.Time (getCurrentTime, diffUTCTime)++-- | Logger structure. Very complicated.+--+data Logger = Logger+    { loggerChan :: Chan (Maybe String)  -- Nothing marks the end+    , loggerSync :: MVar ()              -- Used for sync on quit+    }++-- | Create a new logger+--+makeLogger :: IO Logger+makeLogger = do+    logger <- Logger <$> newChan <*> newEmptyMVar+    _ <- forkIO $ loggerThread logger+    return logger+  where+    loggerThread logger = forever $ do+        msg <- readChan $ loggerChan logger+        case msg of+            -- Stop: sync+            Nothing -> putMVar (loggerSync logger) ()+            -- Print and continue+            Just m  -> putStrLn m++-- | Flush the logger (blocks until flushed)+--+flushLogger :: Logger -> IO ()+flushLogger logger = do+    writeChan (loggerChan logger) Nothing+    () <- takeMVar $ loggerSync logger+    return ()++-- | Send a raw message to the logger+--+message :: Logger -> String -> IO ()+message logger = writeChan (loggerChan logger) . Just++-- | Start a section in the log+--+section :: MonadIO m+        => Logger  -- ^ Logger+        -> String  -- ^ Section name+        -> m ()    -- ^ No result+section logger = liftIO . message logger++-- | Execute a monadic action and log the duration+--+timed :: MonadIO m+      => Logger  -- ^ Logger+      -> String  -- ^ Message+      -> m a     -- ^ Action+      -> m a     -- ^ Timed and logged action+timed logger msg action = do+    start <- liftIO getCurrentTime+    !result <- action+    stop <- liftIO getCurrentTime+    let diff = fromEnum $ diffUTCTime stop start+        ms = diff `div` 10 ^ (9 :: Int)+        formatted = printf "  [%4dms] %s" ms msg+    liftIO $ message logger formatted+    return result++-- | Log something at the same level as 'timed', but without the timing+--+report :: MonadIO m+       => Logger  -- ^ Logger+       -> String  -- ^ Message+       -> m ()    -- ^ No result+report logger msg = liftIO $ message logger $ "  [      ] " ++ msg
+ src/Hakyll/Core/ResourceProvider.hs view
@@ -0,0 +1,75 @@+-- | This module provides an API for resource providers. Resource providers+-- allow Hakyll to get content from resources; the type of resource depends on+-- the concrete instance.+--+-- A resource is represented by the 'Resource' type. This is basically just a+-- newtype wrapper around 'Identifier' -- but it has an important effect: it+-- guarantees that a resource with this identifier can be provided by one or+-- more resource providers.+--+-- Therefore, it is not recommended to read files directly -- you should use the+-- provided 'Resource' methods.+--+module Hakyll.Core.ResourceProvider+    ( Resource (..)+    , ResourceProvider (..)+    , resourceExists+    , resourceDigest+    , resourceModified+    ) where++import Control.Monad ((<=<))+import Data.Word (Word8)++import qualified Data.ByteString.Lazy as LB+import OpenSSL.Digest.ByteString.Lazy (digest)+import OpenSSL.Digest (MessageDigest (MD5))++import Hakyll.Core.Identifier+import Hakyll.Core.Store++-- | A resource+--+-- Invariant: the resource specified by the given identifier must exist+--+newtype Resource = Resource {unResource :: Identifier}+                 deriving (Eq, Show, Ord)++-- | A value responsible for retrieving and listing resources+--+data ResourceProvider = ResourceProvider+    { -- | A list of all resources this provider is able to provide+      resourceList           :: [Resource]+    , -- | Retrieve a certain resource as string+      resourceString         :: Resource -> IO String+    , -- | Retrieve a certain resource as lazy bytestring+      resourceLazyByteString :: Resource -> IO LB.ByteString+    }++-- | Check if a given identifier has a resource+--+resourceExists :: ResourceProvider -> Identifier -> Bool+resourceExists provider = flip elem $ map unResource $ resourceList provider++-- | Retrieve a digest for a given resource+--+resourceDigest :: ResourceProvider -> Resource -> IO [Word8]+resourceDigest provider = digest MD5 <=< resourceLazyByteString provider++-- | Check if a resource was modified+--+resourceModified :: ResourceProvider -> Resource -> Store -> IO Bool+resourceModified provider resource store = do+    -- Get the latest seen digest from the store+    lastDigest <- storeGet store itemName $ unResource resource+    -- Calculate the digest for the resource+    newDigest <- resourceDigest provider resource+    -- Check digests+    if Just newDigest == lastDigest+        -- All is fine, not modified+        then return False+        -- Resource modified; store new digest+        else do storeSet store itemName (unResource resource) newDigest+                return True+  where+    itemName = "Hakyll.Core.ResourceProvider.resourceModified"
+ src/Hakyll/Core/ResourceProvider/FileResourceProvider.hs view
@@ -0,0 +1,29 @@+-- | A concrete 'ResourceProvider' that gets it's resources from the filesystem+--+module Hakyll.Core.ResourceProvider.FileResourceProvider+    ( fileResourceProvider+    ) where++import Control.Applicative ((<$>))++import qualified Data.ByteString.Lazy as LB++import Hakyll.Core.ResourceProvider+import Hakyll.Core.Identifier+import Hakyll.Core.Util.File+import Hakyll.Core.Configuration++-- | Create a filesystem-based 'ResourceProvider'+--+fileResourceProvider :: HakyllConfiguration -> IO ResourceProvider+fileResourceProvider configuration = do+    -- Retrieve a list of identifiers+    list <- map parseIdentifier . filter (not . ignoreFile configuration) <$>+        getRecursiveContents False "."++    -- Construct a resource provider+    return ResourceProvider+        { resourceList           = map Resource list+        , resourceString         = readFile . toFilePath . unResource+        , resourceLazyByteString = LB.readFile . toFilePath . unResource+        }
+ src/Hakyll/Core/Routes.hs view
@@ -0,0 +1,96 @@+-- | Once a target is compiled, the user usually wants to save it to the disk.+-- This is where the 'Routes' type comes in; it determines where a certain+-- target should be written.+--+-- Suppose we have an item @foo\/bar.markdown@. We can render this to+-- @foo\/bar.html@ using:+--+-- > route "foo/bar.markdown" (setExtension ".html")+--+-- If we do not want to change the extension, we can use 'idRoute', the simplest+-- route available:+--+-- > route "foo/bar.markdown" idRoute+--+-- That will route @foo\/bar.markdown@ to @foo\/bar.markdown@.+--+-- Note that the extension says nothing about the content! If you set the+-- extension to @.html@, it is your own responsibility to ensure that the+-- content is indeed HTML.+--+-- Finally, some special cases:+--+-- * If there is no route for an item, this item will not be routed, so it will+--   not appear in your site directory.+--+-- * If an item matches multiple routes, the first rule will be chosen.+--+module Hakyll.Core.Routes+    ( Routes+    , runRoutes+    , idRoute+    , setExtension+    , ifMatch+    , customRoute+    ) where++import Data.Monoid (Monoid, mempty, mappend)+import Control.Monad (mplus)+import System.FilePath (replaceExtension)++import Hakyll.Core.Identifier+import Hakyll.Core.Identifier.Pattern++-- | Type used for a route+--+newtype Routes = Routes {unRoutes :: Identifier -> Maybe FilePath}++instance Monoid Routes where+    mempty = Routes $ const Nothing+    mappend (Routes f) (Routes g) = Routes $ \id' -> f id' `mplus` g id'++-- | Apply a route to an identifier+--+runRoutes :: Routes -> Identifier -> Maybe FilePath+runRoutes = unRoutes++-- | A route that uses the identifier as filepath. For example, the target with+-- ID @foo\/bar@ will be written to the file @foo\/bar@.+--+idRoute :: Routes+idRoute = Routes $ Just . toFilePath++-- | Set (or replace) the extension of a route.+--+-- Example:+--+-- > runRoute (setExtension "html") "foo/bar"+--+-- Result:+--+-- > Just "foo/bar.html"+--+-- Example:+--+-- > runRoute (setExtension "html") "posts/the-art-of-trolling.markdown"+--+-- Result:+--+-- > Just "posts/the-art-of-trolling.html"+--+setExtension :: String -> Routes+setExtension extension = Routes $ fmap (`replaceExtension` extension)+                                . unRoutes idRoute++-- | Modify a route: apply the route if the identifier matches the given+-- pattern, fail otherwise.+--+ifMatch :: Pattern -> Routes -> Routes+ifMatch pattern (Routes route) = Routes $ \id' ->+    if doesMatch pattern id' then route id'+                             else Nothing++-- | Create a custom route. This should almost always be used with 'ifMatch'.+--+customRoute :: (Identifier -> FilePath) -> Routes+customRoute f = Routes $ Just . f
+ src/Hakyll/Core/Rules.hs view
@@ -0,0 +1,161 @@+-- | This module provides a declarative DSL in which the user can specify the+-- different rules used to run the compilers.+--+-- The convention is to just list all items in the 'RulesM' monad, routes and+-- compilation rules.+--+-- A typical usage example would be:+--+-- > main = hakyll $ do+-- >     route   "posts/*" (setExtension "html")+-- >     compile "posts/*" someCompiler+--+{-# LANGUAGE GeneralizedNewtypeDeriving, OverloadedStrings #-}+module Hakyll.Core.Rules+    ( RulesM+    , Rules+    , compile+    , create+    , route+    , metaCompile+    , metaCompileWith+    ) where++import Control.Applicative ((<$>))+import Control.Monad.Writer (tell)+import Control.Monad.Reader (ask)+import Control.Arrow (second, (>>>), arr, (>>^))+import Control.Monad.State (get, put)+import Data.Monoid (mempty)+import qualified Data.Set as S++import Data.Typeable (Typeable)+import Data.Binary (Binary)++import Hakyll.Core.ResourceProvider+import Hakyll.Core.Identifier+import Hakyll.Core.Identifier.Pattern+import Hakyll.Core.Compiler.Internal+import Hakyll.Core.Routes+import Hakyll.Core.CompiledItem+import Hakyll.Core.Writable+import Hakyll.Core.Rules.Internal+import Hakyll.Core.Util.Arrow++-- | Add a route+--+tellRoute :: Routes -> Rules+tellRoute route' = RulesM $ tell $ RuleSet route' mempty mempty++-- | Add a number of compilers+--+tellCompilers :: (Binary a, Typeable a, Writable a)+             => [(Identifier, Compiler () a)]+             -> Rules+tellCompilers compilers = RulesM $ tell $ RuleSet mempty compilers' mempty+  where+    compilers' = map (second boxCompiler) compilers+    boxCompiler = (>>> arr compiledItem >>> arr CompileRule)++-- | Add resources+--+tellResources :: [Resource]+              -> Rules+tellResources resources = RulesM $ tell $ RuleSet mempty mempty $ S.fromList resources++-- | Add a compilation rule to the rules.+--+-- This instructs all resources matching the given pattern to be compiled using+-- the given compiler. When no resources match the given pattern, nothing will+-- happen. In this case, you might want to have a look at 'create'.+--+compile :: (Binary a, Typeable a, Writable a)+        => Pattern -> Compiler Resource a -> Rules+compile pattern compiler = RulesM $ do+    identifiers <- matches pattern . map unResource . resourceList <$> ask+    unRulesM $ do+        tellCompilers $ flip map identifiers $ \identifier ->+            (identifier, constA (Resource identifier) >>> compiler)+        tellResources $ map Resource identifiers++-- | Add a compilation rule+--+-- This sets a compiler for the given identifier. No resource is needed, since+-- we are creating the item from scratch. This is useful if you want to create a+-- page on your site that just takes content from other items -- but has no+-- actual content itself.+--+create :: (Binary a, Typeable a, Writable a)+       => Identifier -> Compiler () a -> Rules+create identifier compiler = tellCompilers [(identifier, compiler)]++-- | Add a route.+--+-- This adds a route for all items matching the given pattern.+--+route :: Pattern -> Routes -> Rules+route pattern route' = tellRoute $ ifMatch pattern route'++-- | Apart from regular compilers, one is also able to specify metacompilers.+-- Metacompilers are a special class of compilers: they are compilers which+-- produce other compilers.+--+-- This is needed when the list of compilers depends on something we cannot know+-- before actually running other compilers. The most typical example is if we+-- have a blogpost using tags.+--+-- Every post has a collection of tags. For example,+--+-- > post1: code, haskell+-- > post2: code, random+--+-- Now, we want to create a list of posts for every tag. We cannot write this+-- down in our 'Rules' DSL directly, since we don't know what tags the different+-- posts will have -- we depend on information that will only be available when+-- we are actually compiling the pages.+--+-- The solution is simple, using 'metaCompile', we can add a compiler that will+-- parse the pages and produce the compilers needed for the different tag pages.+--+-- And indeed, we can see that the first argument to 'metaCompile' is a+-- 'Compiler' which produces a list of ('Identifier', 'Compiler') pairs. The+-- idea is simple: 'metaCompile' produces a list of compilers, and the+-- corresponding identifiers.+--+-- For simple hakyll systems, it is no need for this construction. More+-- formally, it is only needed when the content of one or more items determines+-- which items must be rendered.+--+metaCompile :: (Binary a, Typeable a, Writable a)+            => Compiler () [(Identifier, Compiler () a)]   +            -- ^ Compiler generating the other compilers+            -> Rules+            -- ^ Resulting rules+metaCompile compiler = RulesM $ do+    -- Create an identifier from the state+    state <- get+    let index = rulesMetaCompilerIndex state+        id' = fromCaptureString "Hakyll.Core.Rules.metaCompile/*" (show index)++    -- Update the state with a new identifier+    put $ state {rulesMetaCompilerIndex = index + 1}++    -- Fallback to 'metaCompileWith' with now known identifier+    unRulesM $ metaCompileWith id' compiler++-- | Version of 'metaCompile' that allows you to specify a custom identifier for+-- the metacompiler.+--+metaCompileWith :: (Binary a, Typeable a, Writable a)+                => Identifier+                -- ^ Identifier for this compiler+                -> Compiler () [(Identifier, Compiler () a)]   +                -- ^ Compiler generating the other compilers+                -> Rules+                -- ^ Resulting rules+metaCompileWith identifier compiler = RulesM $ tell $+    RuleSet mempty compilers mempty+  where+    makeRule = MetaCompileRule . map (second box)+    compilers = [(identifier, compiler >>> arr makeRule )]+    box = (>>> fromDependency identifier >>^ CompileRule . compiledItem)
+ src/Hakyll/Core/Rules/Internal.hs view
@@ -0,0 +1,75 @@+-- | Internal rules module for types which are not exposed to the user+--+{-# LANGUAGE GeneralizedNewtypeDeriving #-}+module Hakyll.Core.Rules.Internal+    ( CompileRule (..)+    , RuleSet (..)+    , RuleState (..)+    , RulesM (..)+    , Rules+    , runRules+    ) where++import Control.Applicative (Applicative)+import Control.Monad.Writer (WriterT, execWriterT)+import Control.Monad.Reader (ReaderT, runReaderT)+import Control.Monad.State (State, evalState)+import Data.Monoid (Monoid, mempty, mappend)+import Data.Set (Set)++import Hakyll.Core.ResourceProvider+import Hakyll.Core.Identifier+import Hakyll.Core.Compiler.Internal+import Hakyll.Core.Routes+import Hakyll.Core.CompiledItem++-- | Output of a compiler rule+--+-- * The compiler will produce a simple item. This is the most common case.+--+-- * The compiler will produce more compilers. These new compilers need to be+--   added to the runtime if possible, since other items might depend upon them.+--+data CompileRule = CompileRule CompiledItem+                 | MetaCompileRule [(Identifier, Compiler () CompileRule)]++-- | A collection of rules for the compilation process+--+data RuleSet = RuleSet+    { -- | Routes used in the compilation structure+      rulesRoutes    :: Routes+    , -- | Compilation rules+      rulesCompilers :: [(Identifier, Compiler () CompileRule)]+    , -- | A list of the used resources+      rulesResources :: Set Resource+    }++instance Monoid RuleSet where+    mempty = RuleSet mempty mempty mempty+    mappend (RuleSet r1 c1 s1) (RuleSet r2 c2 s2) =+        RuleSet (mappend r1 r2) (mappend c1 c2) (mappend s1 s2)++-- | Rule state+--+data RuleState = RuleState+    { rulesMetaCompilerIndex :: Int+    } deriving (Show)++-- | The monad used to compose rules+--+newtype RulesM a = RulesM+    { unRulesM :: ReaderT ResourceProvider (WriterT RuleSet (State RuleState)) a+    } deriving (Monad, Functor, Applicative)++-- | Simplification of the RulesM type; usually, it will not return any+-- result.+--+type Rules = RulesM ()++-- | Run a Rules monad, resulting in a 'RuleSet'+--+runRules :: Rules -> ResourceProvider -> RuleSet+runRules rules provider =+    evalState (execWriterT $ runReaderT (unRulesM rules) provider) state+  where+    state = RuleState {rulesMetaCompilerIndex = 0}
+ src/Hakyll/Core/Run.hs view
@@ -0,0 +1,207 @@+-- | This is the module which binds it all together+--+{-# LANGUAGE GeneralizedNewtypeDeriving #-}+module Hakyll.Core.Run+    ( run+    ) where++import Prelude hiding (reverse)+import Control.Monad (filterM)+import Control.Monad.Trans (liftIO)+import Control.Applicative (Applicative, (<$>))+import Control.Monad.Reader (ReaderT, runReaderT, ask)+import Control.Monad.State.Strict (StateT, evalStateT, get, modify)+import Control.Arrow ((&&&))+import qualified Data.Map as M+import Data.Monoid (mempty, mappend)+import System.FilePath ((</>))+import Data.Set (Set)+import qualified Data.Set as S++import Hakyll.Core.Routes+import Hakyll.Core.Identifier+import Hakyll.Core.Util.File+import Hakyll.Core.Compiler+import Hakyll.Core.Compiler.Internal+import Hakyll.Core.ResourceProvider+import Hakyll.Core.ResourceProvider.FileResourceProvider+import Hakyll.Core.Rules.Internal+import Hakyll.Core.DirectedGraph+import Hakyll.Core.DirectedGraph.DependencySolver+import Hakyll.Core.Writable+import Hakyll.Core.Store+import Hakyll.Core.Configuration+import Hakyll.Core.Logger++-- | Run all rules needed, return the rule set used+--+run :: HakyllConfiguration -> Rules -> IO RuleSet+run configuration rules = do+    logger <- makeLogger++    section logger "Initialising"+    store <- timed logger "Creating store" $+        makeStore $ storeDirectory configuration+    provider <- timed logger "Creating provider" $+        fileResourceProvider configuration++    let ruleSet = runRules rules provider+        compilers = rulesCompilers ruleSet++        -- Extract the reader/state+        reader = unRuntime $ addNewCompilers [] compilers+        state' = runReaderT reader $ env logger ruleSet provider store++    evalStateT state' state++    -- Flush and return+    flushLogger logger+    return ruleSet+  where+    env logger ruleSet provider store = RuntimeEnvironment+        { hakyllLogger           = logger+        , hakyllConfiguration    = configuration+        , hakyllRoutes           = rulesRoutes ruleSet+        , hakyllResourceProvider = provider+        , hakyllStore            = store+        }++    state = RuntimeState+        { hakyllModified = S.empty+        , hakyllGraph    = mempty+        }++data RuntimeEnvironment = RuntimeEnvironment+    { hakyllLogger           :: Logger+    , hakyllConfiguration    :: HakyllConfiguration+    , hakyllRoutes           :: Routes+    , hakyllResourceProvider :: ResourceProvider+    , hakyllStore            :: Store+    }++data RuntimeState = RuntimeState+    { hakyllModified :: Set Identifier+    , hakyllGraph    :: DirectedGraph Identifier+    }++newtype Runtime a = Runtime+    { unRuntime :: ReaderT RuntimeEnvironment (StateT RuntimeState IO) a+    } deriving (Functor, Applicative, Monad)++-- | Return a set of modified identifiers+--+modified :: ResourceProvider     -- ^ Resource provider+         -> Store                -- ^ Store+         -> [Identifier]         -- ^ Identifiers to check+         -> IO (Set Identifier)  -- ^ Modified resources+modified provider store ids = fmap S.fromList $ flip filterM ids $ \id' ->+    if resourceExists provider id'+        then resourceModified provider (Resource id') store+        else return False++-- | Add a number of compilers and continue using these compilers+--+addNewCompilers :: [(Identifier, Compiler () CompileRule)]+                -- ^ Remaining compilers yet to be run+                -> [(Identifier, Compiler () CompileRule)]+                -- ^ Compilers to add+                -> Runtime ()+addNewCompilers oldCompilers newCompilers = Runtime $ do+    -- Get some information+    logger <- hakyllLogger <$> ask+    section logger "Adding new compilers"+    provider <- hakyllResourceProvider <$> ask+    store <- hakyllStore <$> ask++    let -- All compilers+        compilers = oldCompilers ++ newCompilers++        -- Get all dependencies for the compilers+        dependencies = flip map compilers $ \(id', compiler) ->+            let deps = runCompilerDependencies compiler id' provider+            in (id', deps)++        -- Create a compiler map (Id -> Compiler)+        compilerMap = M.fromList compilers++        -- Create the dependency graph+        currentGraph = fromList dependencies++    -- Find the old graph and append the new graph to it. This forms the+    -- complete graph+    completeGraph <- timed logger "Creating graph" $+        mappend currentGraph . hakyllGraph <$> get++    orderedCompilers <- timed logger "Solving dependencies" $ do+        -- Check which items are up-to-date. This only needs to happen for the new+        -- compilers+        oldModified <- hakyllModified <$> get +        newModified <- liftIO $ modified provider store $ map fst newCompilers++        let modified' = oldModified `S.union` newModified+            +            -- Find obsolete items. Every item that is reachable from a modified+            -- item is considered obsolete. From these obsolete items, we are only+            -- interested in ones that are in the current subgraph.+            obsolete = S.filter (`member` currentGraph)+                     $ reachableNodes modified' $ reverse completeGraph++            -- Solve the graph and retain only the obsolete items+            ordered = filter (`S.member` obsolete) $ solveDependencies currentGraph++        -- Update the state+        modify $ updateState modified' completeGraph++        -- Join the order with the compilers again+        return $ map (id &&& (compilerMap M.!)) ordered++    -- Now run the ordered list of compilers+    unRuntime $ runCompilers orderedCompilers+  where+    -- Add the modified information for the new compilers+    updateState modified' graph state = state+        { hakyllModified = modified'+        , hakyllGraph    = graph+        }++runCompilers :: [(Identifier, Compiler () CompileRule)]+             -- ^ Ordered list of compilers+             -> Runtime ()+             -- ^ No result+runCompilers [] = return ()+runCompilers ((id', compiler) : compilers) = Runtime $ do+    -- Obtain information+    logger <- hakyllLogger <$> ask+    routes <- hakyllRoutes <$> ask+    provider <- hakyllResourceProvider <$> ask+    store <- hakyllStore <$> ask+    modified' <- hakyllModified <$> get++    section logger $ "Compiling " ++ show id'++    let -- Check if the resource was modified+        isModified = id' `S.member` modified'++    -- Run the compiler+    result <- timed logger "Total compile time" $ liftIO $+        runCompiler compiler id' provider routes store isModified logger++    case result of+        -- Compile rule for one item, easy stuff+        CompileRule compiled -> do+            case runRoutes routes id' of+                Nothing  -> return ()+                Just url -> timed logger ("Routing to " ++ url) $ do+                    destination <-+                        destinationDirectory . hakyllConfiguration <$> ask+                    let path = destination </> url+                    liftIO $ makeDirectories path+                    liftIO $ write path compiled++            -- Continue for the remaining compilers+            unRuntime $ runCompilers compilers ++        -- Metacompiler, slightly more complicated+        MetaCompileRule newCompilers ->+            -- Actually I was just kidding, it's not hard at all+            unRuntime $ addNewCompilers compilers newCompilers
+ src/Hakyll/Core/Store.hs view
@@ -0,0 +1,88 @@+-- | A store for stroing and retreiving items+--+module Hakyll.Core.Store+    ( Store+    , makeStore+    , storeSet+    , storeGet+    ) where++import Control.Concurrent.MVar (MVar, newMVar, readMVar, modifyMVar_)+import System.FilePath ((</>))+import System.Directory (doesFileExist)+import Data.Map (Map)+import qualified Data.Map as M++import Data.Binary (Binary, encodeFile, decodeFile)+import Data.Typeable (Typeable)++import Hakyll.Core.CompiledItem+import Hakyll.Core.Writable+import Hakyll.Core.Identifier+import Hakyll.Core.Util.File++-- | Data structure used for the store+--+data Store = Store+    { -- | All items are stored on the filesystem+      storeDirectory :: FilePath+    , -- | And some items are also kept in-memory+      storeMap :: MVar (Map FilePath CompiledItem)+    }++-- | Initialize the store+--+makeStore :: FilePath -> IO Store+makeStore directory = do+    mvar <- newMVar M.empty+    return Store+        { storeDirectory = directory+        , storeMap       = mvar+        }++-- | Auxiliary: add an item to the map+--+addToMap :: (Binary a, Typeable a, Writable a)+         => Store -> FilePath -> a -> IO ()+addToMap store path value =+    modifyMVar_ (storeMap store) $ return . M.insert path (compiledItem value)++-- | Create a path+--+makePath :: Store -> String -> Identifier -> FilePath+makePath store name identifier =+    storeDirectory store </> name </> toFilePath identifier </> ".hakyllstore"++-- | Store an item+--+storeSet :: (Binary a, Typeable a, Writable a)+         => Store -> String -> Identifier -> a -> IO ()+storeSet store name identifier value = do+    makeDirectories path+    encodeFile path value+    addToMap store path value+  where+    path = makePath store name identifier++-- | Load an item+--+storeGet :: (Binary a, Typeable a, Writable a)+         => Store -> String -> Identifier -> IO (Maybe a)+storeGet store name identifier = do+    -- First check the in-memory map+    map' <- readMVar $ storeMap store+    case M.lookup path map' of+        -- Found in the in-memory map+        Just c -> return $ Just $ unCompiledItem c+        -- Not found in the map, try the filesystem+        Nothing -> do+            exists <- doesFileExist path+            if not exists+                -- Not found in the filesystem either+                then return Nothing+                -- Found in the filesystem+                else do v <- decodeFile path+                        addToMap store path v+                        return $ Just v+  where+    path = makePath store name identifier
+ src/Hakyll/Core/UnixFilter.hs view
@@ -0,0 +1,76 @@+-- | A Compiler that supports unix filters.+--+module Hakyll.Core.UnixFilter+    ( unixFilter+    ) where++import Control.Concurrent (forkIO)+import System.IO (hPutStr, hClose, hGetContents)+import System.Posix.Process (executeFile, forkProcess)+import System.Posix.IO ( dupTo, createPipe, stdInput+                       , stdOutput, closeFd, fdToHandle+                       )++import Hakyll.Core.Compiler++-- | Use a unix filter as compiler. For example, we could use the 'rev' program+-- as a compiler.+--+-- > rev :: Compiler Resource String+-- > rev = getResourceString >>> unixFilter "rev" []+--+-- A more realistic example: one can use this to call, for example, the sass+-- compiler on CSS files. More information about sass can be found here:+--+-- <http://sass-lang.com/>+--+-- The code is fairly straightforward, given that we use @.scss@ for sass:+--+-- > route   "style.scss" $ setExtension "css"+-- > compile "style.scss" $+-- >     getResourceString >>> unixFilter "sass" ["-s", "--scss"]+-- >                       >>> arr compressCss+--+unixFilter :: String                  -- ^ Program name+           -> [String]                -- ^ Program args+           -> Compiler String String  -- ^ Resulting compiler+unixFilter programName args =+    timedCompiler ("Executing external program " ++ programName) $+        unsafeCompiler $ \input -> unixFilterIO programName args input++-- | Internally used function+--+unixFilterIO :: String+             -> [String]+             -> String+             -> IO String+unixFilterIO programName args input = do+    -- Create pipes+    (stdinRead, stdinWrite) <- createPipe+    (stdoutRead, stdoutWrite) <- createPipe+    +    -- Fork the child+    _ <- forkProcess $ do+        -- Copy our pipes over the regular stdin/stdout+        _ <- dupTo stdinRead stdInput+        _ <- dupTo stdoutWrite stdOutput++        -- Close the now unneeded file descriptors in the child+        mapM_ closeFd [stdinWrite, stdoutRead, stdinRead, stdoutWrite]++        -- Execute the program+        _ <- executeFile programName True args Nothing+        return ()++    -- On the parent side, close the client-side FDs.+    mapM_ closeFd [stdinRead, stdoutWrite]++    -- Write the input to the child pipe+    _ <- forkIO $ do+        stdinWriteHandle <- fdToHandle stdinWrite+        hPutStr stdinWriteHandle input+        hClose stdinWriteHandle++    -- Receive the output from the child+    stdoutReadHandle <- fdToHandle stdoutRead+    hGetContents stdoutReadHandle
+ src/Hakyll/Core/Util/Arrow.hs view
@@ -0,0 +1,25 @@+-- | Various arrow utility functions+--+module Hakyll.Core.Util.Arrow+    ( constA+    , sequenceA+    , unitA+    ) where++import Control.Arrow (Arrow, (&&&), arr, (>>^))++constA :: Arrow a+       => c+       -> a b c+constA = arr . const++sequenceA :: Arrow a+          => [a b c]+          -> a b [c]+sequenceA = foldl reduce $ constA []+  where+    reduce la xa = xa &&& la >>^ arr (uncurry (:))++unitA :: Arrow a+      => a b ()+unitA = constA ()
+ src/Hakyll/Core/Util/File.hs view
@@ -0,0 +1,90 @@+-- | A module containing various file utility functions+--+module Hakyll.Core.Util.File+    ( makeDirectories+    , getRecursiveContents+    , isFileObsolete+    , isFileInternal+    ) where++import Control.Applicative ((<$>))+import System.Time (ClockTime)+import Control.Monad (forM, filterM)+import Data.List (isPrefixOf)+import System.Directory ( createDirectoryIfMissing, doesDirectoryExist+                        , doesFileExist, getModificationTime+                        , getDirectoryContents+                        )+import System.FilePath ( normalise, takeDirectory, splitPath+                       , dropTrailingPathSeparator, (</>)+                       )++import Hakyll.Core.Configuration++-- | Given a path to a file, try to make the path writable by making+--   all directories on the path.+--+makeDirectories :: FilePath -> IO ()+makeDirectories = createDirectoryIfMissing True . takeDirectory++-- | Get all contents of a directory. Note that files starting with a dot (.)+-- will be ignored.+--+getRecursiveContents :: Bool           -- ^ Include directories?+                     -> FilePath       -- ^ Directory to search+                     -> IO [FilePath]  -- ^ List of files found+getRecursiveContents includeDirs topdir = do+    topdirExists <- doesDirectoryExist topdir+    if not topdirExists+        then return []+        else do+            names <- filter isProper <$> getDirectoryContents topdir+            paths <- forM names $ \name -> do+                let path = normalise $ topdir </> name+                isDirectory <- doesDirectoryExist path+                if isDirectory then getRecursiveContents includeDirs path+                               else return [path]+            return $ if includeDirs then topdir : concat paths+                                    else concat paths+  where+    isProper = not . (== ".") . take 1++-- | Check if a timestamp is obsolete compared to the timestamps of a number of+-- files. When they are no files, it is never obsolete.+--+isObsolete :: ClockTime    -- ^ The time to check.+           -> [FilePath]   -- ^ Dependencies of the cached file.+           -> IO Bool+isObsolete _ [] = return False+isObsolete timeStamp depends = do+    depends' <- filterM doesFileExist depends+    dependsModified <- mapM getModificationTime depends'+    return (timeStamp < maximum dependsModified)++-- | Check if a file is obsolete, given it's dependencies. When the file does+-- not exist, it is always obsolete. Other wise, it is obsolete if any of it's+-- dependencies has a more recent modification time than the file.+--+isFileObsolete :: FilePath    -- ^ The cached file+               -> [FilePath]  -- ^ Dependencies of the cached file+               -> IO Bool+isFileObsolete file depends = do+    exists <- doesFileExist file+    if not exists+        then return True+        else do timeStamp <- getModificationTime file+                isObsolete timeStamp depends++-- | Check if a file is meant for Hakyll internal use, i.e. if it is located in+-- the destination or store directory+--+isFileInternal :: HakyllConfiguration  -- ^ Configuration+               -> FilePath             -- ^ File to check+               -> Bool                 -- ^ If the given file is internal+isFileInternal configuration file =+    any (`isPrefixOf` split file) dirs+  where+    split = map dropTrailingPathSeparator . splitPath+    dirs = map (split . ($ configuration)) [ destinationDirectory+                                           , storeDirectory+                                           ]
+ src/Hakyll/Core/Writable.hs view
@@ -0,0 +1,22 @@+-- | Describes writable items; items that can be saved to the disk+--+{-# LANGUAGE FlexibleInstances #-}+module Hakyll.Core.Writable+    ( Writable (..)+    ) where++import Data.Word (Word8)++import qualified Data.ByteString as SB++-- | Describes an item that can be saved to the disk+--+class Writable a where+    -- | Save an item to the given filepath+    write :: FilePath -> a -> IO ()++instance Writable [Char] where+    write = writeFile++instance Writable [Word8] where+    write p = SB.writeFile p . SB.pack
+ src/Hakyll/Main.hs view
@@ -0,0 +1,113 @@+-- | Module providing the main hakyll function and command-line argument parsing+--+module Hakyll.Main+    ( hakyll+    , hakyllWith+    ) where++import Control.Concurrent (forkIO)+import Control.Monad (when)+import System.Environment (getProgName, getArgs)+import System.Directory (doesDirectoryExist, removeDirectoryRecursive)++import Hakyll.Core.Configuration+import Hakyll.Core.Run+import Hakyll.Core.Rules+import Hakyll.Core.Rules.Internal+import Hakyll.Web.Preview.Poll+import Hakyll.Web.Preview.Server++-- | This usualy is the function with which the user runs the hakyll compiler+--+hakyll :: Rules -> IO ()+hakyll = hakyllWith defaultHakyllConfiguration++-- | A variant of 'hakyll' which allows the user to specify a custom+-- configuration+--+hakyllWith :: HakyllConfiguration -> Rules -> IO ()+hakyllWith configuration rules = do+    args <- getArgs+    case args of+        ["build"]      -> build configuration rules+        ["clean"]      -> clean configuration+        ["help"]       -> help+        ["preview"]    -> preview configuration rules 8000+        ["preview", p] -> preview configuration rules (read p)+        ["rebuild"]    -> rebuild configuration rules+        ["server"]     -> server configuration 8000+        ["server", p]  -> server configuration (read p)+        _              -> help++-- | Build the site+--+build :: HakyllConfiguration -> Rules -> IO ()+build configuration rules = do+    _ <- run configuration rules+    return ()++-- | Remove the output directories+--+clean :: HakyllConfiguration -> IO ()+clean configuration = do+    remove $ destinationDirectory configuration+    remove $ storeDirectory configuration+  where+    remove dir = do+        putStrLn $ "Removing " ++ dir ++ "..."+        exists <- doesDirectoryExist dir+        when exists $ removeDirectoryRecursive dir++-- | Show usage information.+--+help :: IO ()+help = do+    name <- getProgName+    mapM_ putStrLn+        [ "ABOUT"+        , ""+        , "This is a Hakyll site generator program. You should always"+        , "run it from the project root directory."+        , ""+        , "USAGE"+        , ""+        , name ++ " build           Generate the site"+        , name ++ " clean           Clean up and remove cache"+        , name ++ " help            Show this message"+        , name ++ " preview [port]  Run a server and autocompile"+        , name ++ " rebuild         Clean up and build again"+        , name ++ " server [port]   Run a local test server"+        ]++-- | Preview the site+--+preview :: HakyllConfiguration -> Rules -> Int -> IO ()+preview configuration rules port = do+    -- Build once, keep the rule set+    ruleSet <- run configuration rules++    -- Get the resource list and a callback for the preview poll+    let resources = rulesResources ruleSet+        callback = build configuration rules++    -- Fork a thread polling for changes+    _ <- forkIO $ previewPoll configuration resources callback+    +    -- Run the server in the main thread+    server configuration port++-- | Rebuild the site+--+rebuild :: HakyllConfiguration -> Rules -> IO ()+rebuild configuration rules = do+    clean configuration+    build configuration rules++-- | Start a server+--+server :: HakyllConfiguration -> Int -> IO ()+server configuration port = do+    let destination = destinationDirectory configuration+    staticServer destination preServeHook port+  where+    preServeHook _ = return ()
+ src/Hakyll/Web/CompressCss.hs view
@@ -0,0 +1,51 @@+-- | Module used for CSS compression. The compression is currently in a simple+-- state, but would typically reduce the number of bytes by about 25%.+--+module Hakyll.Web.CompressCss+    ( compressCssCompiler+    , compressCss+    ) where++import Data.Char (isSpace)+import Data.List (isPrefixOf)+import Control.Arrow ((>>^))++import Hakyll.Core.Compiler+import Hakyll.Core.ResourceProvider+import Hakyll.Web.Util.String++-- | Compiler form of 'compressCss'+--+compressCssCompiler :: Compiler Resource String+compressCssCompiler = getResourceString >>^ compressCss++-- | Compress CSS to speed up your site.+--+compressCss :: String -> String+compressCss = compressSeparators+            . stripComments+            . compressWhitespace++-- | Compresses certain forms of separators.+--+compressSeparators :: String -> String+compressSeparators = replaceAll "; *}" (const "}")+                   . replaceAll " *([{};:]) *" (take 1 . dropWhile isSpace)+                   . replaceAll ";;*" (const ";")++-- | Compresses all whitespace.+--+compressWhitespace :: String -> String+compressWhitespace = replaceAll "[ \t\n][ \t\n]*" (const " ")++-- | Function that strips CSS comments away.+--+stripComments :: String -> String+stripComments [] = []+stripComments str+    | isPrefixOf "/*" str = stripComments $ eatComments $ drop 2 str+    | otherwise = head str : stripComments (drop 1 str)+  where+    eatComments str' | null str' = []+                     | isPrefixOf "*/" str' = drop 2 str'+                     | otherwise = eatComments $ drop 1 str'
+ src/Hakyll/Web/Feed.hs view
@@ -0,0 +1,123 @@+-- | A Module that allows easy rendering of RSS feeds.+--+-- The main rendering functions (@renderRss@, @renderAtom@) all assume that+-- you pass the list of items so that the most recent entry in the feed is the+-- first item in the list.+--+-- Also note that the pages should have (at least) the following fields to+-- produce a correct feed:+--+-- - @$title@: Title of the item+--+-- - @$description@: Description to appear in the feed+--+-- - @$url@: URL to the item - this is usually set automatically.+--+-- In addition, the posts should be named according to the rules for+-- 'Hakyll.Page.Metadata.renderDateField'.+--+module Hakyll.Web.Feed+    ( FeedConfiguration (..)+    , renderRss+    , renderAtom+    ) where++import Prelude hiding (id)+import Control.Category (id)+import Control.Arrow ((>>>), arr, (&&&))+import Control.Monad ((<=<))+import Data.Maybe (fromMaybe, listToMaybe)++import Hakyll.Core.Compiler+import Hakyll.Web.Page+import Hakyll.Web.Page.Metadata+import Hakyll.Web.Template+import Hakyll.Web.Template.Read.Hakyll (readTemplate)++import Paths_hakyll++-- | This is a data structure to keep the configuration of a feed.+data FeedConfiguration = FeedConfiguration+    { -- | Title of the feed.+      feedTitle       :: String+    , -- | Description of the feed.+      feedDescription :: String+    , -- | Name of the feed author.+      feedAuthorName  :: String+    , -- | Absolute root URL of the feed site (e.g. @http://jaspervdj.be@)+      feedRoot        :: String+    }++-- | This is an auxiliary function to create a listing that is, in fact, a feed.+-- The items should be sorted on date. The @$timestamp@ field should be set.+--+createFeed :: Template           -- ^ Feed template+           -> Template           -- ^ Item template+           -> String             -- ^ URL of the feed+           -> FeedConfiguration  -- ^ Feed configuration+           -> [Page String]      -- ^ Items to include+           -> String             -- ^ Resulting feed+createFeed feedTemplate itemTemplate url configuration items =+    pageBody $ applyTemplate feedTemplate+             $ setField "timestamp"   timestamp+             $ setField "title"       (feedTitle configuration)+             $ setField "description" (feedDescription configuration)+             $ setField "authorName"  (feedDescription configuration)+             $ setField "root"        (feedRoot configuration)+             $ setField "url"         url+             $ fromBody body+  where+    -- Preprocess items+    items' = flip map items $ applyTemplate itemTemplate+                            . setField "root" (feedRoot configuration)++    -- Body: concatenated items+    body = concat $ map pageBody items'++    -- Take the first timestamp, which should be the most recent+    timestamp = fromMaybe "Unknown" $ do+        p <- listToMaybe items+        return $ getField "timestamp" p+++-- | Abstract function to render any feed.+--+renderFeed :: FilePath                       -- ^ Feed template+           -> FilePath                       -- ^ Item template+           -> FeedConfiguration              -- ^ Feed configuration+           -> Compiler [Page String] String  -- ^ Feed compiler+renderFeed feedTemplate itemTemplate configuration =+    id &&& getRoute >>> renderFeed'+  where+    -- Arrow rendering the feed from the items and the URL+    renderFeed' = unsafeCompiler $ \(items, url) -> do+        feedTemplate' <- loadTemplate feedTemplate+        itemTemplate' <- loadTemplate itemTemplate+        let url' = fromMaybe noUrl url+        return $ createFeed feedTemplate' itemTemplate' url' configuration items++    -- Auxiliary: load a template from a datafile+    loadTemplate = fmap readTemplate . readFile <=< getDataFileName++    -- URL is required to have a valid field+    noUrl = error "Hakyll.Web.Feed.renderFeed: no route specified"++-- | Render an RSS feed with a number of items.+--+renderRss :: FeedConfiguration              -- ^ Feed configuration+          -> Compiler [Page String] String  -- ^ Feed compiler+renderRss configuration = arr (map renderDate)+    >>> renderFeed "templates/rss.xml" "templates/rss-item.xml" configuration+  where+    renderDate = renderDateField "timestamp" "%a, %d %b %Y %H:%M:%S UT"+                                 "No date found."++-- | Render an Atom feed with a number of items.+--+renderAtom :: FeedConfiguration              -- ^ Feed configuration+           -> Compiler [Page String] String  -- ^ Feed compiler+renderAtom configuration = arr (map renderDate)+    >>> renderFeed "templates/atom.xml" "templates/atom-item.xml" configuration+  where+    renderDate = renderDateField "timestamp" "%Y-%m-%dT%H:%M:%SZ"+                                 "No date found."
+ src/Hakyll/Web/FileType.hs view
@@ -0,0 +1,55 @@+-- | A module dealing with common file extensions and associated file types.+--+module Hakyll.Web.FileType+    ( FileType (..)+    , fileType+    , getFileType+    ) where++import System.FilePath (takeExtension)+import Control.Arrow ((>>^))++import Hakyll.Core.Identifier+import Hakyll.Core.Compiler++-- | Datatype to represent the different file types Hakyll can deal with by+-- default+--+data FileType+    = Html+    | LaTeX+    | LiterateHaskell FileType+    | Markdown+    | Rst+    | PlainText+    | Css+    | Binary+    deriving (Eq, Ord, Show, Read)++-- | Get the file type for a certain file. The type is determined by extension.+--+fileType :: FilePath -> FileType+fileType = fileType' . takeExtension+  where+    fileType' ".htm"      = Html+    fileType' ".html"     = Html+    fileType' ".lhs"      = LiterateHaskell Markdown+    fileType' ".markdown" = Markdown+    fileType' ".md"       = Markdown+    fileType' ".mdn"      = Markdown+    fileType' ".mdown"    = Markdown+    fileType' ".mdwn"     = Markdown+    fileType' ".mkd"      = Markdown+    fileType' ".mkdwn"    = Markdown+    fileType' ".page"     = Markdown+    fileType' ".rst"      = Rst+    fileType' ".tex"      = LaTeX+    fileType' ".text"     = PlainText+    fileType' ".txt"      = PlainText+    fileType' ".css"      = Css+    fileType' _           = Binary  -- Treat unknown files as binary++-- | Get the file type for the current file+--+getFileType :: Compiler a FileType+getFileType = getIdentifier >>^ fileType . toFilePath
+ src/Hakyll/Web/Page.hs view
@@ -0,0 +1,124 @@+-- | A page is a key-value mapping, representing a page on your site+--+-- A page is an important concept in Hakyll. It is a key-value mapping, and has+-- one field with an arbitrary type. A 'Page' thus consists of+--+-- * a key-value mapping (of the type @Map String String@);+--+-- * a value (of the type @a@).+--+-- Usually, the value will be a 'String' as well, and the value will be the body+-- of the page.+--+-- Pages can be constructed using Haskell, but they are usually parsed from a+-- file. The file format for pages is pretty straightforward.+--+-- > This is a simple page+-- > consisting of two lines.+--+-- This is a valid page with two lines. If we load this in Hakyll, there would+-- be no metadata, and the body would be the given text. Let's look at a page+-- with some metadata.+--+-- > ---+-- > title: Alice's Adventures in Wonderland+-- > author: Lewis Caroll+-- > year: 1865+-- > ---+-- >+-- > Chapter I+-- > =========+-- >+-- > Down the Rabbit-Hole+-- > --------------------+-- >+-- > Alice was beginning to get very tired of sitting by her sister on the bank,+-- > and of having nothing to do: once or twice she had peeped into the book her+-- > sister was reading, but it had no pictures or conversations in it, "and+-- > what is the use of a book," thought Alice "without pictures or+-- > conversation?"+-- >+-- > ...+--+-- As you can see, we construct a metadata header in Hakyll using @---@. Then,+-- we simply list all @key: value@ pairs, and end with @---@ again. This page+-- contains three metadata fields and a body. The body is given in markdown+-- format, which can be easily rendered to HTML by Hakyll, using pandoc.+--+{-# LANGUAGE DeriveDataTypeable #-}+module Hakyll.Web.Page+    ( Page (..)+    , fromBody+    , fromMap+    , toMap+    , readPageCompiler+    , pageCompiler+    , addDefaultFields+    , sortByBaseName+    ) where++import Prelude hiding (id)+import Control.Category (id)+import Control.Arrow (arr, (>>^), (&&&), (>>>))+import System.FilePath (takeBaseName, takeDirectory)+import qualified Data.Map as M+import Data.List (sortBy)+import Data.Ord (comparing)++import Hakyll.Core.Identifier+import Hakyll.Core.Compiler+import Hakyll.Core.ResourceProvider+import Hakyll.Web.Page.Internal+import Hakyll.Web.Page.Read+import Hakyll.Web.Page.Metadata+import Hakyll.Web.Pandoc+import Hakyll.Web.Template+import Hakyll.Web.Util.String++-- | Create a page from a body, without metadata+--+fromBody :: a -> Page a+fromBody = Page M.empty++-- | Read a page (do not render it)+--+readPageCompiler :: Compiler Resource (Page String)+readPageCompiler = getResourceString >>^ readPage++-- | Read a page, add default fields, substitute fields and render using pandoc+--+pageCompiler :: Compiler Resource (Page String)+pageCompiler = cached "Hakyll.Web.Page.pageCompiler" $+    readPageCompiler >>> addDefaultFields >>> arr applySelf >>> pageRenderPandoc++-- | Add a number of default metadata fields to a page. These fields include:+--+-- * @$url@+--+-- * @$category@+--+-- * @$title@+--+-- * @$path@+--+addDefaultFields :: Compiler (Page a) (Page a)+addDefaultFields =   (getRoute &&& id >>^ uncurry addRoute)+                 >>> (getIdentifier &&& id >>^ uncurry addIdentifier)+  where+    -- Add root and url, based on route+    addRoute Nothing  = id+    addRoute (Just r) = setField "url" (toUrl r)++    -- Add title and category, based on identifier+    addIdentifier i = setField "title" (takeBaseName p)+                    . setField "category" (takeBaseName $ takeDirectory p)+                    . setField "path" p+      where+        p = toFilePath i++-- | Sort posts based on the basename of the post. This is equivalent to a+-- chronologival sort, because of the @year-month-day-title.extension@ naming+-- convention in Hakyll.+--+sortByBaseName :: [Page a] -> [Page a]+sortByBaseName = sortBy $ comparing $ takeBaseName . getField "path"
+ src/Hakyll/Web/Page/Internal.hs view
@@ -0,0 +1,50 @@+-- | Internal representation of the page datatype+--+{-# LANGUAGE DeriveDataTypeable #-}+module Hakyll.Web.Page.Internal+    ( Page (..)+    , fromMap+    , toMap+    ) where++import Control.Applicative ((<$>), (<*>))+import Data.Monoid (Monoid, mempty, mappend)++import Data.Map (Map)+import Data.Binary (Binary, get, put)+import Data.Typeable (Typeable)+import qualified Data.Map as M++import Hakyll.Core.Writable++-- | Type used to represent pages+--+data Page a = Page+    { pageMetadata :: Map String String+    , pageBody     :: a+    } deriving (Eq, Show, Typeable)++instance Monoid a => Monoid (Page a) where+    mempty = Page M.empty mempty+    mappend (Page m1 b1) (Page m2 b2) =+        Page (M.union m1 m2) (mappend b1 b2)++instance Functor Page where+    fmap f (Page m b) = Page m (f b)++instance Binary a => Binary (Page a) where+    put (Page m b) = put m >> put b+    get = Page <$> get <*> get++instance Writable a => Writable (Page a) where+    write p (Page _ b) = write p b++-- | Create a metadata page, without a body+--+fromMap :: Monoid a => Map String String -> Page a+fromMap m = Page m mempty++-- | Convert a page to a map. The body will be placed in the @body@ key.+--+toMap :: Page String -> Map String String+toMap (Page m b) = M.insert "body" b m
+ src/Hakyll/Web/Page/Metadata.hs view
@@ -0,0 +1,131 @@+-- | Provides various functions to manipulate the metadata fields of a page+--+module Hakyll.Web.Page.Metadata+    ( getField+    , getFieldMaybe+    , setField+    , setFieldA+    , renderField+    , changeField+    , copyField+    , renderDateField+    , renderDateFieldWith+    ) where++import Prelude hiding (id)+import Control.Category (id)+import Control.Arrow (Arrow, (>>>), (***), arr)+import Data.List (intercalate)+import Data.Maybe (fromMaybe)+import Data.Time.Clock (UTCTime)+import Data.Time.Format (parseTime, formatTime)+import qualified Data.Map as M+import System.FilePath (takeFileName)+import System.Locale (TimeLocale, defaultTimeLocale)++import Hakyll.Web.Page.Internal+import Hakyll.Web.Util.String++-- | Get a metadata field. If the field does not exist, the empty string is+-- returned.+--+getField :: String  -- ^ Key+         -> Page a  -- ^ Page+         -> String  -- ^ Value+getField key = fromMaybe "" . getFieldMaybe key++-- | Get a field in a 'Maybe' wrapper+--+getFieldMaybe :: String        -- ^ Key+              -> Page a        -- ^ Page+              -> Maybe String  -- ^ Value, if found+getFieldMaybe key = M.lookup key . pageMetadata++-- | Add a metadata field. If the field already exists, it is not overwritten.+--+setField :: String  -- ^ Key+         -> String  -- ^ Value+         -> Page a  -- ^ Page to add it to+         -> Page a  -- ^ Resulting page+setField k v (Page m b) = Page (M.insertWith (flip const) k v m) b++-- | Arrow-based variant of 'setField'. Because of it's type, this function is+-- very usable together with the different 'require' functions.+--+setFieldA :: Arrow a+          => String                  -- ^ Key+          -> a x String              -- ^ Value arrow+          -> a (Page b, x) (Page b)  -- ^ Resulting arrow+setFieldA k v = id *** v >>> arr (uncurry $ flip $ setField k)++-- | Do something with a metadata value, but keep the old value as well. If the+-- key given is not present in the metadata, nothing will happen. If the source+-- and destination keys are the same, the value will be changed (but you should+-- use 'changeField' for this purpose).+--+renderField :: String              -- ^ Key of which the value should be copied+            -> String              -- ^ Key the value should be copied to+            -> (String -> String)  -- ^ Function to apply on the value+            -> Page a              -- ^ Page on which this should be applied+            -> Page a              -- ^ Resulting page+renderField src dst f page = case M.lookup src (pageMetadata page) of+    Nothing    -> page+    Just value -> setField dst (f value) page++-- | Change a metadata value.+--+-- > import Data.Char (toUpper)+-- > changeField "title" (map toUpper)+--+-- Will put the title in UPPERCASE.+--+changeField :: String              -- ^ Key to change.+            -> (String -> String)  -- ^ Function to apply on the value.+            -> Page a              -- ^ Page to change+            -> Page a              -- ^ Resulting page+changeField key = renderField key key++-- | Make a copy of a metadata field (put the value belonging to a certain key+-- under some other key as well)+--+copyField :: String  -- ^ Key to copy+          -> String  -- ^ Destination to copy to+          -> Page a  -- ^ Page on which this should be applied+          -> Page a  -- ^ Resulting page+copyField src dst = renderField src dst id++-- | When the metadata has a field called @path@ in a+-- @folder/yyyy-mm-dd-title.extension@ format (the convention for pages),+-- this function can render the date.+--+-- > renderDate "date" "%B %e, %Y" "Date unknown"+--+-- Will render something like @January 32, 2010@.+--+renderDateField :: String  -- ^ Key in which the rendered date should be placed+                -> String  -- ^ Format to use on the date+                -> String  -- ^ Default value, in case the date cannot be parsed+                -> Page a  -- ^ Page on which this should be applied+                -> Page a  -- ^ Resulting page+renderDateField = renderDateFieldWith defaultTimeLocale++-- | This is an extended version of 'renderDateField' that allows you to+-- specify a time locale that is used for outputting the date. For more+-- details, see 'renderDateField'.+--+renderDateFieldWith :: TimeLocale  -- ^ Output time locale+                    -> String      -- ^ Destination key+                    -> String      -- ^ Format to use on the date+                    -> String      -- ^ Default value+                    -> Page a      -- ^ Target page+                    -> Page a      -- ^ Resulting page+renderDateFieldWith locale key format defaultValue =+    renderField "path" key renderDate'+  where+    renderDate' filePath = fromMaybe defaultValue $ do+        let dateString = intercalate "-" $ take 3+                       $ splitAll "-" $ takeFileName filePath+        time <- parseTime defaultTimeLocale+                          "%Y-%m-%d"+                          dateString :: Maybe UTCTime+        return $ formatTime locale format time
+ src/Hakyll/Web/Page/Read.hs view
@@ -0,0 +1,60 @@+-- | Module providing a function to parse a page from a file+--+module Hakyll.Web.Page.Read+    ( readPage+    ) where++import Control.Applicative ((<$>), (<*>))+import Control.Arrow (second, (***))+import Control.Monad.State (State, get, put, evalState)+import Data.List (isPrefixOf)+import Data.Map (Map)+import qualified Data.Map as M++import Hakyll.Web.Page.Internal+import Hakyll.Web.Util.String++-- | We're using a simple state monad as parser+--+type LineParser = State [String]++-- | Read the metadata section from a page+--+parseMetadata :: LineParser (Map String String)+parseMetadata = get >>= \content -> case content of+    -- No lines means no metadata+    [] -> return M.empty+    -- Check if the file begins with a delimiter+    (l : ls) -> if not (isPossibleDelimiter l)+        then -- No delimiter means no metadata+             return M.empty+        else do -- Break the metadata section+                let (metadata, rest) = second (drop 1) $ break (== l) ls+                -- Put the rest back+                put rest+                -- Parse the metadata+                return $ M.fromList $ map parseMetadata' metadata+  where+    -- Check if a line can be a delimiter+    isPossibleDelimiter = isPrefixOf "---"++    -- Parse a "key: value" string to a (key, value) tupple+    parseMetadata' = (trim *** trim . drop 1) . break (== ':')++-- | Read the body section of a page+--+parseBody :: LineParser String+parseBody = do+    body <- get+    put []+    return $ unlines body++-- | Read an entire page+--+parsePage :: LineParser (Page String)+parsePage = Page <$> parseMetadata <*> parseBody++-- | Read a page from a string+--+readPage :: String -> Page String+readPage = evalState parsePage . lines
+ src/Hakyll/Web/Pandoc.hs view
@@ -0,0 +1,110 @@+-- | Module exporting pandoc bindings+--+module Hakyll.Web.Pandoc+    ( -- * The basic building blocks+      readPandoc+    , readPandocWith+    , writePandoc+    , writePandocWith++      -- * Functions working on pages/compilers+    , pageReadPandoc+    , pageReadPandocWith+    , pageRenderPandoc+    , pageRenderPandocWith++      -- * Default options+    , defaultHakyllParserState+    , defaultHakyllWriterOptions+    ) where++import Prelude hiding (id)+import Control.Applicative ((<$>))+import Control.Arrow ((>>^), (&&&))+import Control.Category (id)++import Text.Pandoc++import Hakyll.Core.Compiler+import Hakyll.Web.FileType+import Hakyll.Web.Page.Internal++-- | Read a string using pandoc, with the default options+--+readPandoc :: FileType  -- ^ File type, determines how parsing happens+           -> String    -- ^ String to read+           -> Pandoc    -- ^ Resulting document+readPandoc = readPandocWith defaultHakyllParserState++-- | Read a string using pandoc, with the supplied options+--+readPandocWith :: ParserState  -- ^ Parser options+               -> FileType     -- ^ File type, determines how parsing happens+               -> String       -- ^ String to read+               -> Pandoc       -- ^ Resulting document+readPandocWith state fileType' = case fileType' of+    Html              -> readHtml state+    LaTeX             -> readLaTeX state+    LiterateHaskell t -> readPandocWith state {stateLiterateHaskell = True} t+    Markdown          -> readMarkdown state+    Rst               -> readRST state+    t                 -> error $+        "Hakyll.Web.readPandocWith: I don't know how to read " ++ show t++-- | Write a document (as HTML) using pandoc, with the default options+--+writePandoc :: Pandoc  -- ^ Document to write+            -> String  -- ^ Resulting HTML+writePandoc = writePandocWith defaultHakyllWriterOptions++-- | Write a document (as HTML) using pandoc, with the supplied options+--+writePandocWith :: WriterOptions  -- ^ Writer options for pandoc+                -> Pandoc         -- ^ Document to write+                -> String         -- ^ Resulting HTML+writePandocWith = writeHtmlString++-- | Read the resource using pandoc+--+pageReadPandoc :: Compiler (Page String) (Page Pandoc)+pageReadPandoc = pageReadPandocWith defaultHakyllParserState++-- | Read the resource using pandoc+--+pageReadPandocWith :: ParserState -> Compiler (Page String) (Page Pandoc)+pageReadPandocWith state =+    id &&& getFileType >>^ pageReadPandocWith'+  where+    pageReadPandocWith' (p, t) = readPandocWith state t <$> p++-- | Render the resource using pandoc+--+pageRenderPandoc :: Compiler (Page String) (Page String)+pageRenderPandoc =+    pageRenderPandocWith defaultHakyllParserState defaultHakyllWriterOptions++-- | Render the resource using pandoc+--+pageRenderPandocWith :: ParserState+                     -> WriterOptions+                     -> Compiler (Page String) (Page String)+pageRenderPandocWith state options =+    pageReadPandocWith state >>^ fmap (writePandocWith options)++-- | The default reader options for pandoc parsing in hakyll+--+defaultHakyllParserState :: ParserState+defaultHakyllParserState = defaultParserState+    { -- The following option causes pandoc to read smart typography, a nice+      -- and free bonus.+      stateSmart = True+    }++-- | The default writer options for pandoc rendering in hakyll+--+defaultHakyllWriterOptions :: WriterOptions+defaultHakyllWriterOptions = defaultWriterOptions+    { -- This option causes literate haskell to be written using '>' marks in+      -- html, which I think is a good default.+      writerLiterateHaskell = True+    }
+ src/Hakyll/Web/Preview/Server.hs view
@@ -0,0 +1,72 @@+-- | Implements a basic static file server for previewing options+--+{-# LANGUAGE OverloadedStrings #-}+module Hakyll.Web.Preview.Server+    ( staticServer+    ) where++import Control.Monad.Trans (liftIO)+import Control.Applicative ((<$>))+import Codec.Binary.UTF8.String+import System.FilePath ((</>))+import System.Directory (doesFileExist)++import qualified Data.ByteString as SB+import Snap.Util.FileServe (serveFile)+import Snap.Types (Snap, rqURI, getRequest, writeBS)+import Snap.Http.Server ( httpServe, setAccessLog, setErrorLog, addListen+                        , ConfigListen (..), emptyConfig+                        )++import Hakyll.Web.Util.String (replaceAll)++-- | The first file in the list that actually exists is returned+--+findFile :: [FilePath] -> IO (Maybe FilePath)+findFile [] = return Nothing+findFile (x : xs) = do+    exists <- doesFileExist x+    if exists then return (Just x) else findFile xs++-- | Serve a given directory+--+static :: FilePath             -- ^ Directory to serve+       -> (FilePath -> IO ())  -- ^ Pre-serve hook+       -> Snap ()+static directory preServe = do+    -- Obtain the path+    uri <- rqURI <$> getRequest+    let filePath = replaceAll "\\?$"    (const "")  -- Remove trailing ?+                 $ replaceAll "#[^#]*$" (const "")  -- Remove #section+                 $ replaceAll "^/"      (const "")  -- Remove leading /+                 $ decode $ SB.unpack uri++    -- Try to find the requested file+    r <- liftIO $ findFile $ map (directory </>) $+        [ filePath+        , filePath </> "index.htm"+        , filePath </> "index.html"+        ]++    case r of+        -- Not found, error+        Nothing -> writeBS "Not found"+        -- Found, serve+        Just f  -> do+            liftIO $ preServe f+            serveFile f++-- | Main method, runs a static server in the given directory+--+staticServer :: FilePath             -- ^ Directory to serve+             -> (FilePath -> IO ())  -- ^ Pre-serve hook+             -> Int                  -- ^ Port to listen on+             -> IO ()                -- ^ Blocks forever+staticServer directory preServe port =+    httpServe config $ static directory preServe+  where+    -- Snap server config+    config = addListen (ListenHttp "0.0.0.0" port)+           $ setAccessLog Nothing+           $ setErrorLog Nothing+           $ emptyConfig
+ src/Hakyll/Web/RelativizeUrls.hs view
@@ -0,0 +1,62 @@+-- | This module exposes a function which can relativize URL's on a webpage.+--+-- This means that one can deploy the resulting site on+-- @http:\/\/example.com\/@, but also on @http:\/\/example.com\/some-folder\/@+-- without having to change anything (simply copy over the files).+--+-- To use it, you should use absolute URL's from the site root everywhere. For+-- example, use+--+-- > <img src="/images/lolcat.png" alt="Funny zomgroflcopter" />+--+-- in a blogpost. When running this through the relativize URL's module, this+-- will result in (suppose your blogpost is located at @\/posts\/foo.html@:+--+-- > <img src="../images/lolcat.png" alt="Funny zomgroflcopter" />+--+module Hakyll.Web.RelativizeUrls+    ( relativizeUrlsCompiler+    , relativizeUrls+    ) where++import Prelude hiding (id)+import Control.Category (id)+import Control.Arrow ((&&&), (>>^))+import Data.List (isPrefixOf)+import qualified Data.Set as S++import Text.HTML.TagSoup++import Hakyll.Core.Compiler+import Hakyll.Web.Page+import Hakyll.Web.Util.String++-- | Compiler form of 'compressCss' which automatically picks the right root+-- path+--+relativizeUrlsCompiler :: Compiler (Page String) (Page String)+relativizeUrlsCompiler = getRoute &&& id >>^ uncurry relativize+  where+    relativize Nothing = id+    relativize (Just r) = fmap (relativizeUrls $ toSiteRoot r)++-- | Relativize URL's in HTML+--+relativizeUrls :: String  -- ^ Path to the site root+               -> String  -- ^ HTML to relativize+               -> String  -- ^ Resulting HTML+relativizeUrls root = renderTags . map relativizeUrls' . parseTags+  where+    relativizeUrls' (TagOpen s a) = TagOpen s $ map (relativizeUrlsAttrs root) a+    relativizeUrls' x = x++-- | Relativize URL's in attributes+--+relativizeUrlsAttrs :: String            -- ^ Path to the site root+                    -> Attribute String  -- ^ Attribute to relativize+                    -> Attribute String  -- ^ Resulting attribute+relativizeUrlsAttrs root (key, value)+    | key `S.member` urls && "/" `isPrefixOf` value = (key, root ++ value)+    | otherwise = (key, value)+  where+    urls = S.fromList ["src", "href"]
+ src/Hakyll/Web/Tags.hs view
@@ -0,0 +1,179 @@+-- | Module containing some specialized functions to deal with tags.+-- This Module follows certain conventions. My advice is to stick with them if+-- possible.+--+-- More concrete: all functions in this module assume that the tags are+-- located in the @tags@ field, and separated by commas. An example file+-- @foo.markdown@ could look like:+--+-- > ---+-- > author: Philip K. Dick+-- > title: Do androids dream of electric sheep?+-- > tags: future, science fiction, humanoid+-- > ---+-- > The novel is set in a post-apocalyptic near future, where the Earth and+-- > its populations have been damaged greatly by Nuclear...+--+-- All the following functions would work with such a format. In addition to+-- tags, Hakyll also supports categories. The convention when using categories+-- is to place pages in subdirectories.+--+-- An example, the page @posts\/coding\/2010-01-28-hakyll-categories.markdown@+-- Tags or categories are read using the @readTags@ and @readCategory@+-- functions. This module only provides functions to work with tags:+-- categories are represented as tags. This is perfectly possible: categories+-- only have an additional restriction that a page can only have one category+-- (instead of multiple tags).+--+{-# LANGUAGE DeriveDataTypeable, OverloadedStrings, Arrows #-}+module Hakyll.Web.Tags+    ( Tags (..)+    , readTagsWith+    , readTags+    , readCategory+    , renderTagCloud+    , renderTagsField+    , renderCategoryField+    ) where++import Prelude hiding (id)+import Control.Category (id)+import Control.Applicative ((<$>))+import Data.Map (Map)+import qualified Data.Map as M+import Data.List (intersperse)+import Control.Arrow (arr, (&&&), (>>>), (***), (<<^), returnA)+import Data.Maybe (catMaybes, fromMaybe)+import Data.Monoid (mconcat)++import Data.Typeable (Typeable)+import Data.Binary (Binary, get, put)+import Text.Blaze.Renderer.String (renderHtml)+import Text.Blaze ((!), toHtml, toValue)+import qualified Text.Blaze.Html5 as H+import qualified Text.Blaze.Html5.Attributes as A++import Hakyll.Web.Page+import Hakyll.Web.Page.Metadata+import Hakyll.Web.Util.String+import Hakyll.Core.Writable+import Hakyll.Core.Identifier+import Hakyll.Core.Compiler++-- | Data about tags+--+data Tags a = Tags+    { tagsMap :: Map String [Page a]+    } deriving (Show, Typeable)++instance Binary a => Binary (Tags a) where+    get = Tags <$> get+    put (Tags m) = put m++instance Writable (Tags a) where+    write _ _ = return ()++-- | Obtain tags from a page+--+getTags :: Page a -> [String]+getTags = map trim . splitAll "," . getField "tags"++-- | Obtain categories from a page+--+getCategory :: Page a -> [String]+getCategory = return . getField "category"++-- | Higher-level function to read tags+--+readTagsWith :: (Page a -> [String])  -- ^ Function extracting tags from a page+             -> [Page a]              -- ^ Pages+             -> Tags a                -- ^ Resulting tags+readTagsWith f pages = Tags+    { tagsMap = foldl (M.unionWith (++)) M.empty (map readTagsWith' pages)+    }+  where+    -- Create a tag map for one page+    readTagsWith' page =+        let tags = f page+        in M.fromList $ zip tags $ repeat [page]++-- | Read a tagmap using the @tags@ metadata field+--+readTags :: [Page a] -> Tags a+readTags = readTagsWith getTags++-- | Read a tagmap using the @category@ metadata field+--+readCategory :: [Page a] -> Tags a+readCategory = readTagsWith getCategory++-- | Render a tag cloud in HTML+--+renderTagCloud :: (String -> Identifier)    -- ^ Produce a link for a tag+               -> Double                    -- ^ Smallest font size, in percent+               -> Double                    -- ^ Biggest font size, in percent+               -> Compiler (Tags a) String  -- ^ Tag cloud renderer+renderTagCloud makeUrl minSize maxSize = proc (Tags tags) -> do+    -- In tags' we create a list: [((tag, route), count)]+    tags' <- mapCompiler ((id &&& (getRouteFor <<^ makeUrl)) *** arr length)+                -< M.toList tags++    let -- Absolute frequencies of the pages+        freqs = map snd tags'++        -- Find out the relative count of a tag: on a scale from 0 to 1+        relative count = (fromIntegral count - min') / (1 + max' - min')++        -- Show the relative size of one 'count' in percent+        size count =+            let size' = floor $ minSize + relative count * (maxSize - minSize)+            in show (size' :: Int) ++ "%"++        -- The minimum and maximum count found, as doubles+        (min', max')+            | null freqs = (0, 1)+            | otherwise = (minimum &&& maximum) $ map fromIntegral freqs++        -- Create a link for one item+        makeLink ((tag, url), count) =+            H.a ! A.style (toValue $ "font-size: " ++ size count)+                ! A.href (toValue $ fromMaybe "/" url)+                $ toHtml tag++    -- Render and return the HTML+    returnA -< renderHtml $ mconcat $ intersperse " " $ map makeLink tags'++-- | Render tags with links+--+renderTagsFieldWith :: (Page a -> [String])        -- ^ Function to get the tags+                    -> String                      -- ^ Destination key+                    -> (String -> Identifier)      -- ^ Create a link for a tag+                    -> Compiler (Page a) (Page a)  -- ^ Resulting compiler+renderTagsFieldWith tags destination makeUrl =+    id &&& arr tags >>> setFieldA destination renderTags+  where+    -- Compiler creating a comma-separated HTML string for a list of tags+    renderTags :: Compiler [String] String+    renderTags =   arr (map $ id &&& makeUrl)+               >>> mapCompiler (id *** getRouteFor)+               >>> arr (map $ uncurry renderLink)+               >>> arr (renderHtml . mconcat . intersperse ", " . catMaybes)++    -- Render one tag link+    renderLink _   Nothing         = Nothing+    renderLink tag (Just filePath) = Just $+        H.a ! A.href (toValue $ toUrl filePath) $ toHtml tag++-- | Render tags with links+--+renderTagsField :: String                      -- ^ Destination key+                -> (String -> Identifier)      -- ^ Create a link for a tag+                -> Compiler (Page a) (Page a)  -- ^ Resulting compiler+renderTagsField = renderTagsFieldWith getTags++-- | Render the category in a link+--+renderCategoryField :: String                      -- ^ Destination key+                    -> (String -> Identifier)      -- ^ Create a category link+                    -> Compiler (Page a) (Page a)  -- ^ Resulting compiler+renderCategoryField = renderTagsFieldWith getCategory
+ src/Hakyll/Web/Template.hs view
@@ -0,0 +1,109 @@+-- | This module provides means for reading and applying 'Template's.+--+-- Templates are tools to convert data (pages) into a string. They are+-- perfectly suited for laying out your site.+--+-- Let's look at an example template:+--+-- > <html>+-- >     <head>+-- >         <title>My crazy homepage - $title$</title>+-- >     </head>+-- >     <body>+-- >         <div id="header">+-- >             <h1>My crazy homepage - $title$</h1>+-- >         </div>+-- >         <div id="content">+-- >             $body$+-- >         </div>+-- >         <div id="footer">+-- >             By reading this you agree that I now own your soul+-- >         </div>+-- >     </body>+-- > </html>+--+-- We can use this template to render a 'Page' which has a body and a @$title$@+-- metadata field.+--+-- As you can see, the format is very simple -- @$key$@ is used to render the+-- @$key$@ field from the page, everything else is literally copied. If you want+-- to literally insert @\"$key$\"@ into your page (for example, when you're+-- writing a Hakyll tutorial) you can use+--+-- > <p>+-- >     A literal $$key$$.+-- > </p>+--+-- Because of it's simplicity, these templates can be used for more than HTML:+-- you could make, for example, CSS or JS templates as well.+--+-- In addition to the native format, Hakyll also supports hamlet templates. For+-- more information on hamlet templates, please refer to:+-- <http://hackage.haskell.org/package/hamlet>.+--+module Hakyll.Web.Template+    ( Template+    , applyTemplate+    , applySelf+    , templateCompiler+    , templateCompilerWith+    , applyTemplateCompiler+    ) where++import Control.Arrow+import Data.Maybe (fromMaybe)+import System.FilePath (takeExtension)+import qualified Data.Map as M++import Text.Hamlet (HamletSettings, defaultHamletSettings)++import Hakyll.Core.Compiler+import Hakyll.Core.Identifier+import Hakyll.Core.ResourceProvider+import Hakyll.Web.Template.Internal+import Hakyll.Web.Template.Read+import Hakyll.Web.Page.Internal++-- | Substitutes @$identifiers@ in the given @Template@ by values from the given+-- "Page". When a key is not found, it is left as it is. You can specify+-- the characters used to replace escaped dollars (@$$@) here.+--+applyTemplate :: Template -> Page String -> Page String+applyTemplate template page =+    fmap (const $ substitute =<< unTemplate template) page+  where+    map' = toMap page+    substitute (Chunk chunk) = chunk+    substitute (Key key) = fromMaybe ("$" ++ key ++ "$") $ M.lookup key map'+    substitute (Escaped) = "$"++-- | Apply a page as it's own template. This is often very useful to fill in+-- certain keys like @$root@ and @$url@.+--+applySelf :: Page String -> Page String+applySelf page = applyTemplate (readTemplate $ pageBody page) page++-- | Read a template. If the extension of the file we're compiling is+-- @.hml@ or @.hamlet@, it will be considered as a Hamlet template, and parsed+-- as such.+--+templateCompiler :: Compiler Resource Template+templateCompiler = templateCompilerWith defaultHamletSettings++-- | Version of 'templateCompiler' that enables custom settings.+--+templateCompilerWith :: HamletSettings -> Compiler Resource Template+templateCompilerWith settings =+    cached "Hakyll.Web.Template.templateCompilerWith" $+        getIdentifier &&& getResourceString >>^ uncurry read'+  where+    read' identifier string =+        if takeExtension (toFilePath identifier) `elem` [".hml", ".hamlet"]+            -- Hamlet template+            then readHamletTemplateWith settings string+            -- Hakyll template+            else readTemplate string++applyTemplateCompiler :: Identifier                            -- ^ Template+                      -> Compiler (Page String) (Page String)  -- ^ Compiler+applyTemplateCompiler identifier = require identifier (flip applyTemplate)
+ src/Hakyll/Web/Template/Internal.hs view
@@ -0,0 +1,45 @@+-- | Module containing the template data structure+--+{-# LANGUAGE GeneralizedNewtypeDeriving, DeriveDataTypeable #-}+module Hakyll.Web.Template.Internal+    ( Template (..)+    , TemplateElement (..)+    ) where++import Control.Applicative ((<$>))++import Data.Binary (Binary, get, getWord8, put, putWord8)+import Data.Typeable (Typeable)++import Hakyll.Core.Writable++-- | Datatype used for template substitutions.+--+newtype Template = Template+    { unTemplate :: [TemplateElement]+    }+    deriving (Show, Eq, Binary, Typeable)++instance Writable Template where+    -- Writing a template is impossible+    write _ _ = return ()++-- | Elements of a template.+--+data TemplateElement+    = Chunk String+    | Key String+    | Escaped+    deriving (Show, Eq, Typeable)++instance Binary TemplateElement where+    put (Chunk string)    = putWord8 0 >> put string+    put (Key key)  = putWord8 1 >> put key+    put (Escaped) = putWord8 2++    get = getWord8 >>= \tag -> case tag of+            0 -> Chunk <$> get+            1 -> Key   <$> get+            2 -> return Escaped+            _ -> error $  "Hakyll.Web.Template.Internal: "+                       ++ "Error reading cached template"
+ src/Hakyll/Web/Template/Read.hs view
@@ -0,0 +1,10 @@+-- | Re-exports all different template reading modules+--+module Hakyll.Web.Template.Read+    ( readTemplate+    , readHamletTemplate+    , readHamletTemplateWith+    ) where++import Hakyll.Web.Template.Read.Hakyll+import Hakyll.Web.Template.Read.Hamlet
+ src/Hakyll/Web/Template/Read/Hakyll.hs view
@@ -0,0 +1,35 @@+-- | Read templates in Hakyll's native format+--+module Hakyll.Web.Template.Read.Hakyll+    ( readTemplate+    ) where++import Data.List (isPrefixOf)+import Data.Char (isAlphaNum)++import Hakyll.Web.Template.Internal++-- | Construct a @Template@ from a string.+--+readTemplate :: String -> Template+readTemplate = Template . readTemplate'+  where+    readTemplate' [] = []+    readTemplate' string+        | "$$" `isPrefixOf` string =+            Escaped : readTemplate' (drop 2 string)+        | "$" `isPrefixOf` string =+            case readKey (drop 1 string) of+                Just (key, rest) -> Key key : readTemplate' rest+                Nothing          -> Chunk "$" : readTemplate' (drop 1 string)+        | otherwise =+            let (chunk, rest) = break (== '$') string+            in Chunk chunk : readTemplate' rest++    -- Parse an key into (key, rest) if it's valid, and return+    -- Nothing otherwise+    readKey string =+        let (key, rest) = span isAlphaNum string+        in if not (null key) && "$" `isPrefixOf` rest+            then Just (key, drop 1 rest)+            else Nothing
+ src/Hakyll/Web/Template/Read/Hamlet.hs view
@@ -0,0 +1,46 @@+-- | Read templates in the hamlet format+--+{-# LANGUAGE MultiParamTypeClasses #-}+module Hakyll.Web.Template.Read.Hamlet+    ( readHamletTemplate+    , readHamletTemplateWith+    ) where++import Text.Hamlet (HamletSettings (..), defaultHamletSettings)+import Text.Hamlet.RT++import Hakyll.Web.Template.Internal++-- | Read a hamlet template using the default settings+--+readHamletTemplate :: String -> Template+readHamletTemplate = readHamletTemplateWith defaultHamletSettings++-- | Read a hamlet template using the specified settings+--+readHamletTemplateWith :: HamletSettings -> String -> Template+readHamletTemplateWith settings string =+    let result = parseHamletRT settings string+    in case result of+        Just hamlet -> fromHamletRT hamlet+        Nothing     -> error+            "Hakyll.Web.Template.Read.Hamlet.readHamletTemplateWith: \+            \Could not parse Hamlet file"++-- | Convert a 'HamletRT' to a 'Template'+--+fromHamletRT :: HamletRT  -- ^ Hamlet runtime template+             -> Template  -- ^ Hakyll template+fromHamletRT (HamletRT sd) = Template $ map fromSimpleDoc sd+  where+    fromSimpleDoc :: SimpleDoc -> TemplateElement+    fromSimpleDoc (SDRaw chunk) = Chunk chunk+    fromSimpleDoc (SDVar [var]) = Key var+    fromSimpleDoc (SDVar _) = error+        "Hakyll.Web.Template.Read.Hamlet.fromHamletRT: \+        \Hakyll does not support '.' in identifier names when using \+        \hamlet templates."+    fromSimpleDoc _ = error+        "Hakyll.Web.Template.Read.Hamlet.fromHamletRT: \+        \Only simple $key$ identifiers are allowed when using hamlet \+        \templates."
+ src/Hakyll/Web/Util/String.hs view
@@ -0,0 +1,73 @@+-- | Miscellaneous string manipulation functions.+--+module Hakyll.Web.Util.String+    ( trim+    , replaceAll+    , splitAll+    , toUrl+    , toSiteRoot+    ) where++import Data.Char (isSpace)+import Data.Maybe (listToMaybe)++import System.FilePath (splitPath, takeDirectory, joinPath)+import Text.Regex.PCRE ((=~~))++-- | Trim a string (drop spaces, tabs and newlines at both sides).+--+trim :: String -> String+trim = reverse . trim' . reverse . trim'+  where+    trim' = dropWhile isSpace++-- | A simple (but inefficient) regex replace funcion+--+replaceAll :: String              -- ^ Pattern+           -> (String -> String)  -- ^ Replacement (called on capture)+           -> String              -- ^ Source string+           -> String              -- ^ Result+replaceAll pattern f source = replaceAll' source+  where+    replaceAll' src = case listToMaybe (src =~~ pattern) of+        Nothing     -> src+        Just (o, l) ->+            let (before, tmp) = splitAt o src+                (capture, after) = splitAt l tmp+            in before ++ f capture ++ replaceAll' after++-- | A simple regex split function. The resulting list will contain no empty+-- strings.+--+splitAll :: String    -- ^ Pattern+         -> String    -- ^ String to split+         -> [String]  -- ^ Result+splitAll pattern = filter (not . null) . splitAll'+  where+    splitAll' src = case listToMaybe (src =~~ pattern) of+        Nothing     -> [src]+        Just (o, l) ->+            let (before, tmp) = splitAt o src+            in before : splitAll' (drop l tmp)++-- | Convert a filepath to an URL starting from the site root+--+-- Example:+--+-- > toUrl "foo/bar.html"+--+-- Result:+--+-- > "/foo/bar.html"+--+toUrl :: FilePath -> String+toUrl = ('/' :)++-- | Get the relative url to the site root, for a given (absolute) url+--+toSiteRoot :: String -> String+toSiteRoot = emptyException . joinPath . map parent . splitPath . takeDirectory+  where+    parent = const ".."+    emptyException [] = "."+    emptyException x  = x
− src/Network/Hakyll/SimpleServer.hs
@@ -1,215 +0,0 @@--- | Module containing a small, simple http file server for testing and preview---   purposes.-module Network.Hakyll.SimpleServer-    ( simpleServer-    ) where--import Prelude hiding (log)-import Control.Monad (forever)-import Control.Monad.Reader (ReaderT, runReaderT, ask, liftIO)-import Network-import System.IO-import System.Directory (doesFileExist, doesDirectoryExist)-import Control.Concurrent (forkIO)-import Control.Concurrent.Chan (Chan, newChan, readChan, writeChan)-import System.FilePath (takeExtension)-import qualified Data.Map as M-import Data.List (intercalate)--import Text.Hakyll.Util-import Text.Hakyll.Regex---- | Function to log from a chan.-log :: Chan String -> IO ()-log logChan = forever (readChan logChan >>= hPutStrLn stderr)---- | General server configuration.-data ServerConfig = ServerConfig { documentRoot :: FilePath-                                 , portNumber :: PortNumber-                                 , logChannel :: Chan String-                                 }---- | Custom monad stack.-type Server = ReaderT ServerConfig IO---- | Simple representation of a HTTP request.-data Request = Request { requestMethod :: String-                       , requestURI :: String-                       , requestVersion :: String-                       } deriving (Ord, Eq)--instance Show Request where-    show request =  requestMethod request ++ " "-                 ++ requestURI request ++ " "-                 ++ requestVersion request---- | Read a HTTP request from a 'Handle'. For now, this will ignore the request---   headers and body.-readRequest :: Handle -> Server Request-readRequest handle = do-    requestLine <- liftIO $ hGetLine handle-    let [method, uri, version] = map trim $ splitRegex " " requestLine-        request = Request { requestMethod = method-                          , requestURI = uri-                          , requestVersion = version-                          }-    return request---- | Simple representation of the HTTP response we send back.-data Response = Response { responseVersion :: String-                         , responseStatusCode :: Int-                         , responsePhrase :: String-                         , responseHeaders :: M.Map String String-                         , responseBody :: String-                         } deriving (Ord, Eq)--instance Show Response where-    show response =  responseVersion response ++ " "-                  ++ show (responseStatusCode response) ++ " "-                  ++ responsePhrase response---- | A default response.-defaultResponse :: Response-defaultResponse = Response { responseVersion = "HTTP/1.1"-                           , responseStatusCode = 0-                           , responsePhrase = ""-                           , responseHeaders = M.empty-                           , responseBody = ""-                           }---- | Create a response for a given HTTP request.-createResponse :: Request -> Server Response-createResponse request-    | requestMethod request == "GET" = createGetResponse request-    | otherwise = return $ createErrorResponse 501 "Not Implemented"---- | Create a simple error response.-createErrorResponse :: Int      -- ^ Error code.-                    -> String   -- ^ Error phrase.-                    -> Response -- ^ Result.-createErrorResponse statusCode phrase = defaultResponse-    { responseStatusCode = statusCode-    , responsePhrase = phrase-    , responseHeaders = M.singleton "Content-Type" "text/html"-    , responseBody =-           "<html> <head> <title>" ++ show statusCode ++ "</title> </head>"-        ++ "<body> <h1>" ++ show statusCode ++ "</h1>\n"-        ++ "<p>" ++ phrase ++ "</p> </body> </html>"-    }---- | Create a simple get response.-createGetResponse :: Request -> Server Response-createGetResponse request = do-    -- Construct the complete fileName of the requested resource.-    config <- ask-    let -- Drop everything after a '?'.-        uri = takeWhile ((/=) '?') $ requestURI request-        log' = writeChan (logChannel config)-    isDirectory <- liftIO $ doesDirectoryExist $ documentRoot config ++ uri-    let fileName =-            documentRoot config ++ if isDirectory then uri ++ "/index.html"-                                                  else uri--        create200 = do-            h <- openBinaryFile fileName ReadMode-            contentLength <- hFileSize h-            body <- hGetContents h-            let mimeHeader = getMIMEHeader fileName-                headers = ("Content-Length", show contentLength) : mimeHeader-            return $ defaultResponse-                { responseStatusCode = 200-                , responsePhrase = "OK"-                , responseHeaders = responseHeaders defaultResponse-                    `M.union` M.fromList headers-                , responseBody = body-                }--        -- Called when an error occurs during the creation of a 200 response.-        create500 e = do-            log' $ "Internal Error: " ++ show e-            return $ createErrorResponse 500 "Internal Server Error"--    -- Send back the page if found.-    exists <- liftIO $ doesFileExist fileName-    if exists-        then liftIO $ catch create200 create500-        else do liftIO $ log' $ "Not Found: " ++ fileName-                return $ createErrorResponse 404 "Not Found"---- | Get the mime header for a certain filename. This is based on the extension---   of the given 'FilePath'.-getMIMEHeader :: FilePath -> [(String, String)]-getMIMEHeader fileName =-    case result of (Just x) -> [("Content-Type", x)]-                   Nothing  -> []-  where-    result = lookup (takeExtension fileName) [ (".css", "text/css")-                                             , (".gif", "image/gif")-                                             , (".htm", "text/html")-                                             , (".html", "text/html")-                                             , (".jpeg", "image/jpeg")-                                             , (".jpg", "image/jpeg")-                                             , (".js", "text/javascript")-                                             , (".png", "image/png")-                                             , (".txt", "text/plain")-                                             , (".xml", "text/xml")-                                             ]---- | Respond to an incoming request.-respond :: Handle -> Server ()-respond handle = do-    -- Read the request and create a response.-    request <- readRequest handle-    response <- createResponse request--    -- Generate some output.-    config <- ask-    liftIO $ writeChan (logChannel config)-           $ show request ++ " => " ++ show response--    -- Send the response back to the handle.-    liftIO $ putResponse response-  where-    putResponse response = do hPutStr handle $ intercalate " "-                                  [ responseVersion response-                                  , show $ responseStatusCode response-                                  , responsePhrase response-                                  ]-                              hPutStr handle "\r\n"-                              mapM_ putHeader-                                    (M.toList $ responseHeaders response)-                              hPutStr handle "\r\n"-                              hPutStr handle $ responseBody response-                              hPutStr handle "\r\n"-                              hClose handle--    putHeader (key, value) =-        hPutStr handle $ key ++ ": " ++ value ++ "\r\n"---- | Start a simple http server on the given 'PortNumber', serving the given---   directory.----simpleServer :: PortNumber  -- ^ Port to listen on.-             -> FilePath    -- ^ Root directory to serve.-             -> IO ()       -- ^ Optional pre-respond action.-             -> IO ()-simpleServer port root preRespond = do-    -- Channel to send logs to-    logChan <- newChan--    let config = ServerConfig { documentRoot = root-                              , portNumber = port-                              , logChannel = logChan-                              }--          -- When a client connects, respond in a separate thread.-        listen socket = do (handle, _, _) <- accept socket-                           preRespond-                           forkIO (runReaderT (respond handle) config)--    -- Handle logging in a separate thread-    _ <- forkIO (log logChan)--    writeChan logChan $ "Starting hakyll server on port " ++ show port ++ "..."-    socket <- listenOn (PortNumber port)-    forever (listen socket)
− src/Text/Hakyll.hs
@@ -1,185 +0,0 @@--- | This is the main Hakyll module, exporting the important @hakyll@ function.------   Most configurations would use this @hakyll@ function more or less as the---   main function:------   > main = hakyll $ do---   >     directory css "css"---   >     directory static "images"----module Text.Hakyll-    ( defaultHakyllConfiguration-    , hakyll-    , hakyllWithConfiguration-    , runDefaultHakyll--    , module Text.Hakyll.Context-    , module Text.Hakyll.ContextManipulations-    , module Text.Hakyll.CreateContext-    , module Text.Hakyll.File-    , module Text.Hakyll.HakyllMonad-    , module Text.Hakyll.Regex-    , module Text.Hakyll.Render-    , module Text.Hakyll.HakyllAction-    , module Text.Hakyll.Paginate-    , module Text.Hakyll.Page-    , module Text.Hakyll.Pandoc-    , module Text.Hakyll.Util-    , module Text.Hakyll.Tags-    , module Text.Hakyll.Feed-    , module Text.Hakyll.Configurations.Static-    ) where--import Control.Concurrent (forkIO, threadDelay)-import Control.Monad.Reader (runReaderT, liftIO, ask)-import Control.Monad (when)-import Data.Monoid (mempty)-import System.Environment (getArgs, getProgName)-import System.Directory (doesDirectoryExist, removeDirectoryRecursive)-import System.Time (getClockTime)--import Text.Pandoc-import Text.Hamlet (defaultHamletSettings)--import Network.Hakyll.SimpleServer (simpleServer)-import Text.Hakyll.Context-import Text.Hakyll.ContextManipulations-import Text.Hakyll.CreateContext-import Text.Hakyll.File-import Text.Hakyll.HakyllMonad-import Text.Hakyll.Regex-import Text.Hakyll.Render-import Text.Hakyll.HakyllAction-import Text.Hakyll.Paginate-import Text.Hakyll.Page-import Text.Hakyll.Pandoc-import Text.Hakyll.Util-import Text.Hakyll.Tags-import Text.Hakyll.Feed-import Text.Hakyll.Configurations.Static---- | The default reader options for pandoc parsing.----defaultPandocParserState :: ParserState-defaultPandocParserState = defaultParserState-    { -- The following option causes pandoc to read smart typography, a nice-      -- and free bonus.-      stateSmart = True-    }---- | The default writer options for pandoc rendering.----defaultPandocWriterOptions :: WriterOptions-defaultPandocWriterOptions = defaultWriterOptions-    { -- This option causes literate haskell to be written using '>' marks in-      -- html, which I think is a good default.-      writerLiterateHaskell = True-    }---- | The default hakyll configuration.----defaultHakyllConfiguration :: String               -- ^ Absolute site URL.-                           -> HakyllConfiguration  -- ^ Default config.-defaultHakyllConfiguration absoluteUrl' = HakyllConfiguration-    { absoluteUrl         = absoluteUrl'-    , additionalContext   = mempty-    , siteDirectory       = "_site"-    , cacheDirectory      = "_cache"-    , enableIndexUrl      = False-    , previewMode         = BuildOnRequest-    , pandocParserState   = defaultPandocParserState-    , pandocWriterOptions = defaultPandocWriterOptions-    , hamletSettings      = defaultHamletSettings-    }---- | Main function to run Hakyll with the default configuration. The--- absolute URL is only used in certain cases, for example RSS feeds et--- cetera.----hakyll :: String    -- ^ Absolute URL of your site. Used in certain cases.-       -> Hakyll () -- ^ You code.-       -> IO ()-hakyll absolute = hakyllWithConfiguration configuration-  where-    configuration = defaultHakyllConfiguration absolute---- | Main function to run hakyll with a custom configuration.----hakyllWithConfiguration :: HakyllConfiguration -> Hakyll () -> IO ()-hakyllWithConfiguration configuration buildFunction = do-    args <- getArgs-    let f = case args of ["build"]      -> buildFunction-                         ["clean"]      -> clean-                         ["preview", p] -> preview (read p) -                         ["preview"]    -> preview defaultPort-                         ["rebuild"]    -> clean >> buildFunction-                         ["server", p]  -> server (read p) (return ())-                         ["server"]     -> server defaultPort (return ())-                         _              -> help-    runReaderT f configuration-  where-    preview port = case previewMode configuration of-        BuildOnRequest  -> server port buildFunction-        BuildOnInterval -> do-            let pIO = runReaderT (previewThread buildFunction) configuration-            _ <- liftIO $ forkIO pIO-            server port (return ())--    defaultPort = 8000---- | A preview thread that periodically recompiles the site.----previewThread :: Hakyll ()  -- ^ Build function-              -> Hakyll ()  -- ^ Result-previewThread buildFunction = run =<< liftIO getClockTime-  where-    delay = 1000000-    run time = do liftIO $ threadDelay delay-                  contents <- getRecursiveContents "."-                  valid <- isMoreRecent time contents-                  when valid buildFunction-                  run =<< liftIO getClockTime---- | Clean up directories.----clean :: Hakyll ()-clean = do askHakyll siteDirectory >>= remove'-           askHakyll cacheDirectory >>= remove'-  where-    remove' dir = liftIO $ do putStrLn $ "Removing " ++ dir ++ "..."-                              exists <- doesDirectoryExist dir-                              when exists $ removeDirectoryRecursive dir---- | Show usage information.----help :: Hakyll ()-help = liftIO $ do-    name <- getProgName-    putStrLn $  "This is a Hakyll site generator program. You should always\n"-             ++ "run it from the project root directory.\n"-             ++ "\n"-             ++ "Usage:\n"-             ++ name ++ " build           Generate the site.\n"-             ++ name ++ " clean           Clean up and remove cache.\n"-             ++ name ++ " help            Show this message.\n"-             ++ name ++ " preview [port]  Run a server and autocompile.\n"-             ++ name ++ " rebuild         Clean up and build again.\n"-             ++ name ++ " server [port]   Run a local test server.\n"---- | Start a server at the given port number.----server :: Integer    -- ^ Port number to serve on.-       -> Hakyll ()  -- ^ Pre-respond action.-       -> Hakyll ()-server port preRespond = do -    configuration <- ask-    root <- askHakyll siteDirectory-    let preRespondIO = runReaderT preRespond configuration-    liftIO $ simpleServer (fromIntegral port) root preRespondIO---- | Run a Hakyll action with default settings. This is mostly aimed at testing--- code.----runDefaultHakyll :: Hakyll a -> IO a-runDefaultHakyll f =-    runReaderT f $ defaultHakyllConfiguration "http://example.com"
− src/Text/Hakyll/Configurations/Static.hs
@@ -1,59 +0,0 @@--- | Module for a simple static configuration of a website.------ The configuration works like this:------ * The @templates/@ directory should contain one template.------ * Renderable files in the directory tree are rendered using this template.------ * The @static/@ directory is copied entirely (if it exists).------ * All files in the @css/@ directory are compressed.----module Text.Hakyll.Configurations.Static-    ( staticConfiguration-    ) where--import Control.Applicative ((<$>))-import Control.Monad (filterM, forM_)--import Text.Hakyll.File ( getRecursiveContents, inDirectory, inHakyllDirectory-                        , directory )-import Text.Hakyll.Internal.FileType (isRenderableFile)-import Text.Hakyll.HakyllMonad (Hakyll, logHakyll)-import Text.Hakyll.Render (renderChain, css, static)-import Text.Hakyll.CreateContext (createPage)---- | A simple configuration for an entirely static website.----staticConfiguration :: Hakyll ()-staticConfiguration = do-    -- Find all files not in _site or _cache.-    files <- filterM isRenderableFile' =<< getRecursiveContents "."--    -- Find a main template to use-    mainTemplate <- take 1 <$> getRecursiveContents templateDir-    logHakyll $ case mainTemplate of [] ->      "Using no template"-                                     (x : _) -> "Using template " ++ x--    -- Render all files using this template-    forM_ files $ renderChain mainTemplate . createPage--    -- Render a static directory-    directory static staticDir--    -- Render a css directory-    directory css cssDir-  where-    -- A file should have a renderable extension and not be in a hakyll-    -- directory, and not in a special directory.-    isRenderableFile' file = do-        inHakyllDirectory' <- inHakyllDirectory file-        return $  isRenderableFile file-               && not (any (inDirectory file) [templateDir, cssDir, staticDir])-               && not inHakyllDirectory'--    -- Directories-    templateDir = "templates"-    cssDir = "css"-    staticDir = "static"
− src/Text/Hakyll/Context.hs
@@ -1,16 +0,0 @@--- | This (quite small) module exports the datatype used for contexts. A---   @Context@ is a simple key-value mapping. You can render these @Context@s---   with templates, and manipulate them in various ways.-{-# LANGUAGE GeneralizedNewtypeDeriving #-}-module Text.Hakyll.Context-    ( Context (..)-    ) where--import Data.Monoid (Monoid)-import Data.Map (Map)-import Data.Binary (Binary)---- | Datatype used for key-value mappings.-newtype Context = Context { -- | Extract the context.-                            unContext :: Map String String-                          } deriving (Show, Monoid, Binary)
− src/Text/Hakyll/ContextManipulations.hs
@@ -1,124 +0,0 @@--- | This module exports a number of functions that produce @HakyllAction@s to---   manipulate @Context@s.-module Text.Hakyll.ContextManipulations-    ( renderValue-    , changeValue-    , changeUrl-    , copyValue-    , renderDate-    , renderDateWithLocale-    , changeExtension-    , renderBody-    , takeBody-    ) where--import Control.Monad (liftM)-import Control.Arrow (arr)-import System.Locale (TimeLocale, defaultTimeLocale)-import System.FilePath (takeFileName, addExtension, dropExtension)-import Data.Time.Format (parseTime, formatTime)-import Data.Time.Clock (UTCTime)-import Data.Maybe (fromMaybe)-import qualified Data.Map as M--import Text.Hakyll.Regex (substituteRegex)-import Text.Hakyll.HakyllAction (HakyllAction (..))-import Text.Hakyll.Context (Context (..))---- | Do something with a value in a @Context@, but keep the old value as well.---   If the key given is not present in the @Context@, nothing will happen.----renderValue :: String              -- ^ Key of which the value should be copied.-            -> String              -- ^ Key the value should be copied to.-            -> (String -> String)  -- ^ Function to apply on the value.-            -> HakyllAction Context Context-renderValue source destination f = arr $ \(Context context) -> Context $-    case M.lookup source context of-        Nothing      -> context-        (Just value) -> M.insert destination (f value) context---- | Change a value in a @Context@.------   > import Data.Char (toUpper)---   > changeValue "title" (map toUpper)------   Will put the title in UPPERCASE.-changeValue :: String             -- ^ Key to change.-            -> (String -> String) -- ^ Function to apply on the value.-            -> HakyllAction Context Context-changeValue key = renderValue key key---- | Change the URL of a page. This requires a special function, so dependency--- handling can happen correctly.--- -changeUrl :: (String -> String)            -- ^ Function to change URL with.-          -> HakyllAction Context Context  -- ^ Resulting action.-changeUrl f = let action = changeValue "url" f-              in action {actionUrl = Right $ liftM f}---- | Copy a value from one key to another in a @Context@.-copyValue :: String -- ^ Source key.-          -> String -- ^ Destination key.-          -> HakyllAction Context Context-copyValue source destination = renderValue source destination id---- | When the context has a key called @path@ in a---   @folder/yyyy-mm-dd-title.extension@ format (the convention for pages),---   this function can render the date.------   > renderDate "date" "%B %e, %Y" "Date unknown"------   Will render something like @January 32, 2010@.----renderDate :: String -- ^ Key in which the rendered date should be placed.-           -> String -- ^ Format to use on the date.-           -> String -- ^ Default key, in case the date cannot be parsed.-           -> HakyllAction Context Context-renderDate = renderDateWithLocale defaultTimeLocale---- | This is an extended version of 'renderDate' that allows you to specify a--- time locale that is used for outputting the date. For more details, see--- 'renderDate'.----renderDateWithLocale :: TimeLocale  -- ^ Output time locale.-                     -> String      -- ^ Destination key.-                     -> String      -- ^ Format to use on the date.-                     -> String      -- ^ Default key.-                     -> HakyllAction Context Context-renderDateWithLocale locale key format defaultValue =-    renderValue "path" key renderDate'-  where-    renderDate' filePath = fromMaybe defaultValue $ do-        let dateString = substituteRegex "^([0-9]*-[0-9]*-[0-9]*).*" "\\1"-                                         (takeFileName filePath)-        time <- parseTime defaultTimeLocale-                          "%Y-%m-%d"-                          dateString :: Maybe UTCTime-        return $ formatTime locale format time---- | Change the extension of a file. This is only needed when you want to---   render, for example, mardown to @.php@ files instead of @.html@ files.------   > changeExtension "php"------   Will render @test.markdown@ to @test.php@ instead of @test.html@.-changeExtension :: String -- ^ Extension to change to.-                -> HakyllAction Context Context-changeExtension extension = changeValue "url" changeExtension'-  where-    changeExtension' = flip addExtension extension . dropExtension---- | Change the body of a file using a certain manipulation.------   > import Data.Char (toUpper)---   > renderBody (map toUpper)------   Will put the entire body of the page in UPPERCASE.-renderBody :: (String -> String)-           -> HakyllAction Context Context-renderBody = renderValue "body" "body"---- | Get the resulting body text from a context----takeBody :: HakyllAction Context String-takeBody = arr $ fromMaybe "" . M.lookup "body" . unContext
− src/Text/Hakyll/CreateContext.hs
@@ -1,114 +0,0 @@--- | A module that provides different ways to create a @Context@. These---   functions all use the @HakyllAction@ arrow, so they produce values of the---   type @HakyllAction () Context@.-module Text.Hakyll.CreateContext-    ( createPage-    , createCustomPage-    , createListing-    , addField-    , combine-    , combineWithUrl-    ) where--import Prelude hiding (id)--import qualified Data.Map as M-import Control.Arrow (second, arr, (&&&), (***))-import Control.Monad (liftM2)-import Control.Applicative ((<$>))-import Control.Arrow ((>>>))-import Control.Category (id)--import Text.Hakyll.Context-import Text.Hakyll.HakyllAction-import Text.Hakyll.Render-import Text.Hakyll.Page-import Text.Hakyll.Pandoc-import Text.Hakyll.Internal.Cache---- | Create a @Context@ from a page file stored on the disk. This is probably---   the most common way to create a @Context@.-createPage :: FilePath -> HakyllAction () Context-createPage path = cacheAction "pages" $ readPageAction path >>> renderAction---- | Create a custom page @Context@.---   ---   The association list given maps keys to values for substitution. Note---   that as value, you can either give a @String@ or a---   @HakyllAction () String@. The latter is preferred for more complex data,---   since it allows dependency checking. A @String@ is obviously more simple---   to use in some cases.----createCustomPage :: FilePath-                 -> [(String, Either String (HakyllAction () String))]-                 -> HakyllAction () Context-createCustomPage url association = HakyllAction-    { actionDependencies = dataDependencies-    , actionUrl          = Left $ return url-    , actionFunction     = \_ -> Context . M.fromList <$> assoc'-    }-  where-    mtuple (a, b) = b >>= \b' -> return (a, b')-    toHakyllString = second (either return runHakyllAction)-    assoc' = mapM (mtuple . toHakyllString) $ ("url", Left url) : association-    dataDependencies = map snd association >>= getDependencies-    getDependencies (Left _) = []-    getDependencies (Right x) = actionDependencies x---- | A @createCustomPage@ function specialized in creating listings.------   This function creates a listing of a certain list of @Context@s. Every---   item in the list is created by applying the given template to every---   renderable. You can also specify additional context to be included in the---   @CustomPage@.-createListing :: FilePath                  -- ^ Destination of the page.-              -> [FilePath]                -- ^ Templates to render items with.-              -> [HakyllAction () Context] -- ^ Renderables in the list.-              -> [(String, Either String (HakyllAction () String))]-              -> HakyllAction () Context-createListing url templates renderables additional =-    createCustomPage url context-  where-    context = ("body", Right concatenation) : additional-    concatenation = renderAndConcat templates renderables---- | Add a field to a 'Context'.----addField :: String                                  -- ^ Key-         -> Either String (HakyllAction () String)  -- ^ Value-         -> HakyllAction Context Context            -- ^ Result-addField key value =   arr (const ()) &&& id-                   >>> value' *** id-                   >>> arr (uncurry insert)-  where-    value' = arr (const ()) >>> either (arr . const) id value-    insert v = Context . M.insert key v . unContext---- | Combine two @Context@s. The url will always be taken from the first---   @Renderable@. Also, if a `$key` is present in both renderables, the---   value from the first @Context@ will be taken as well.------   You can see this as a this as a @union@ between two mappings.-combine :: HakyllAction () Context -> HakyllAction () Context-        -> HakyllAction () Context-combine x y = HakyllAction-    { actionDependencies = actionDependencies x ++ actionDependencies y-    , actionUrl          = actionUrl x-    , actionFunction     = \_ ->-        Context <$> liftM2 (M.union) (unContext <$> runHakyllAction x)-                                     (unContext <$> runHakyllAction y)-    }---- | Combine two @Context@s and set a custom URL. This behaves like @combine@,---   except that for the @url@ field, the given URL is always chosen.-combineWithUrl :: FilePath-               -> HakyllAction () Context-               -> HakyllAction () Context-               -> HakyllAction () Context-combineWithUrl url x y = combine'-    { actionUrl          = Left $ return url-    , actionFunction     = \_ ->-        Context . M.insert "url" url . unContext <$> runHakyllAction combine'-    }-  where-    combine' = combine x y
− src/Text/Hakyll/Feed.hs
@@ -1,112 +0,0 @@--- | A Module that allows easy rendering of RSS feeds. If you use this module,---   you must make sure you set the `absoluteUrl` field in the main Hakyll---   configuration.------   Apart from that, the main rendering functions (@renderRss@, @renderAtom@) ---   all assume that you pass the list of items so that the most recent entry---   in the feed is the first item in the list.------   Also note that the @Context@s should have (at least) the following---   fields to produce a correct feed:------   - @$title@: Title of the item.------   - @$description@: Description to appear in the feed.------   - @$url@: URL to the item - this is usually set automatically.------   Furthermore, the feed will be generated, but will be incorrect (it won't---   validate) if an empty list is passed.----module Text.Hakyll.Feed-    ( FeedConfiguration (..)-    , renderRss-    , renderAtom-    ) where--import Control.Arrow ((>>>), second)-import Control.Monad.Reader (liftIO)-import Data.Maybe (fromMaybe)-import qualified Data.Map as M--import Text.Hakyll.Context (Context (..))-import Text.Hakyll.CreateContext (createListing)-import Text.Hakyll.ContextManipulations (renderDate)-import Text.Hakyll.HakyllMonad (Hakyll)-import Text.Hakyll.Render (render, renderChain)-import Text.Hakyll.HakyllAction--import Paths_hakyll---- | This is a data structure to keep the configuration of a feed.-data FeedConfiguration = FeedConfiguration-    { -- | Url of the feed (relative to site root). For example, @rss.xml@.-      feedUrl         :: String-    , -- | Title of the feed.-      feedTitle       :: String-    , -- | Description of the feed.-      feedDescription :: String-    , -- | Name of the feed author.-      feedAuthorName  :: String-    }---- | This is an auxiliary function to create a listing that is, in fact, a feed.---   The items should be sorted on date.-createFeed :: FeedConfiguration         -- ^ Feed configuration.-           -> [HakyllAction () Context] -- ^ Items to include.-           -> FilePath                  -- ^ Feed template.-           -> FilePath                  -- ^ Item template.-           -> HakyllAction () Context-createFeed configuration renderables template itemTemplate =-    listing >>> render template-  where-    listing = createListing (feedUrl configuration)-                            [itemTemplate] renderables additional--    additional = map (second $ Left . ($ configuration))-        [ ("title", feedTitle)-        , ("description", feedDescription)-        , ("authorName", feedAuthorName)-        ] ++ updated--    -- Take the first timestamp, which should be the most recent.-    updated = let action = createHakyllAction $ return . fromMaybe "foo" -                                              . M.lookup "timestamp" . unContext-                  toTuple r = ("timestamp", Right $ r >>> action)-              in map toTuple $ take 1 renderables-            ---- | Abstract function to render any feed.-renderFeed :: FeedConfiguration         -- ^ Feed configuration.-           -> [HakyllAction () Context] -- ^ Items to include in the feed.-           -> FilePath                  -- ^ Feed template.-           -> FilePath                  -- ^ Item template.-           -> Hakyll ()-renderFeed configuration renderables template itemTemplate = do-    template' <- liftIO $ getDataFileName template-    itemTemplate' <- liftIO $ getDataFileName itemTemplate-    let renderFeed' = createFeed configuration renderables-                                 template' itemTemplate'-    renderChain [] renderFeed'---- | Render an RSS feed with a number of items.-renderRss :: FeedConfiguration         -- ^ Feed configuration.-          -> [HakyllAction () Context] -- ^ Items to include in the feed.-          -> Hakyll ()-renderRss configuration renderables =-    renderFeed configuration (map (>>> renderRssDate) renderables)-               "templates/rss.xml" "templates/rss-item.xml"-  where-    renderRssDate = renderDate "timestamp" "%a, %d %b %Y %H:%M:%S UT"-                               "No date found."---- | Render an Atom feed with a number of items.-renderAtom :: FeedConfiguration         -- ^ Feed configuration.-           -> [HakyllAction () Context] -- ^ Items to include in the feed.-           -> Hakyll ()-renderAtom configuration renderables =-    renderFeed configuration (map (>>> renderAtomDate) renderables)-               "templates/atom.xml" "templates/atom-item.xml"-  where-    renderAtomDate = renderDate "timestamp" "%Y-%m-%dT%H:%M:%SZ"-                                            "No date found."
− src/Text/Hakyll/File.hs
@@ -1,199 +0,0 @@--- | A module containing various function for manipulating and examinating---   files and directories.-module Text.Hakyll.File-    ( toDestination-    , toCache-    , toUrl-    , toRoot-    , inDirectory-    , inHakyllDirectory-    , removeSpaces-    , makeDirectories-    , getRecursiveContents-    , sortByBaseName-    , havingExtension-    , directory-    , isMoreRecent-    , isFileMoreRecent-    ) where--import System.Directory-import Control.Applicative ((<$>))-import System.FilePath-import System.Time (ClockTime)-import Control.Monad-import Data.List (isPrefixOf, sortBy)-import Data.Ord (comparing)-import Control.Monad.Reader (liftIO)--import Text.Hakyll.HakyllMonad-import Text.Hakyll.Internal.FileType (isRenderableFile)---- | Auxiliary function to remove pathSeparators form the start. We don't deal---   with absolute paths here. We also remove $root from the start.-removeLeadingSeparator :: FilePath -> FilePath-removeLeadingSeparator [] = []-removeLeadingSeparator path-    | head path' `elem` pathSeparators = drop 1 path'-    | otherwise                        = path'-  where-    path' = if "$root" `isPrefixOf` path then drop 5 path-                                         else path---- | Convert a relative URL to a filepath in the destination---   (default: @_site@).-toDestination :: FilePath -> Hakyll FilePath-toDestination url = do dir <- askHakyll siteDirectory-                       toFilePath dir url---- | Convert a relative URL to a filepath in the cache---   (default: @_cache@).-toCache :: FilePath -> Hakyll FilePath-toCache path = do dir <- askHakyll cacheDirectory-                  toFilePath dir path---- | Implementation of toDestination/toCache----toFilePath :: String           -- ^ Directory (site or cache)-           -> String           -- ^ URL-           -> Hakyll FilePath  -- ^ Resulting file path-toFilePath dir url = do-   enableIndexUrl' <- askHakyll enableIndexUrl-   let destination = if enableIndexUrl' && separatorEnd-           then dir </> noSeparator </> "index.html"-           else dir </> noSeparator-   return destination-  where-    noSeparator = removeLeadingSeparator url-    separatorEnd = not (null url) && last url == '/'---- | Get the url for a given page. For most extensions, this would be the path---   itself. It's only for rendered extensions (@.markdown@, @.rst@, @.lhs@ this---   function returns a path with a @.html@ extension instead.-toUrl :: FilePath -> Hakyll FilePath-toUrl path = do enableIndexUrl' <- askHakyll enableIndexUrl-                -- If the file does not have a renderable extension, like for-                -- example favicon.ico, we don't have to change it at all.-                return $ if not (isRenderableFile path)-                            then path-                            -- If index url's are enabled, we create pick it-                            -- unless the page is an index already.-                            else if enableIndexUrl' && not isIndex-                                then indexUrl-                                else withSimpleHtmlExtension-  where-    isIndex = dropExtension (takeFileName path) == "index"-    withSimpleHtmlExtension = flip addExtension ".html" $ dropExtension path-    indexUrl = dropExtension path ++ "/"-                            ---- | Get the relative url to the site root, for a given (absolute) url-toRoot :: FilePath -> FilePath-toRoot = emptyException . joinPath . map parent-       . filter relevant . splitPath . takeDirectory-  where-    parent = const ".."-    emptyException [] = "."-    emptyException x  = x-    relevant "." = False-    relevant "/" = False-    relevant _   = True---- | Check if a file is in a given directory.----inDirectory :: FilePath  -- ^ File path-            -> FilePath  -- ^ Directory-            -> Bool      -- ^ Result-inDirectory path dir = case splitDirectories path of-    [] -> False-    (x : _) -> x == dir---- | Check if a file is in a Hakyll directory. With a Hakyll directory, we mean--- a directory that should be "ignored" such as the @_site@ or @_cache@--- directory.------ Example:------ > inHakyllDirectory "_cache/pages/index.html"------ Result:------ > True----inHakyllDirectory :: FilePath -> Hakyll Bool-inHakyllDirectory path =-    or <$> mapM (liftM (inDirectory path) . askHakyll)-                [siteDirectory, cacheDirectory]---- | Swaps spaces for '-'.-removeSpaces :: FilePath -> FilePath-removeSpaces = map swap-  where-    swap ' ' = '-'-    swap x   = x---- | Given a path to a file, try to make the path writable by making---   all directories on the path.-makeDirectories :: FilePath -> Hakyll ()-makeDirectories path = liftIO $ createDirectoryIfMissing True dir-  where-    dir = takeDirectory path---- | Get all contents of a directory. Note that files starting with a dot (.)---   will be ignored.----getRecursiveContents :: FilePath -> Hakyll [FilePath]-getRecursiveContents topdir = do-    topdirExists <- liftIO $ doesDirectoryExist topdir-    if topdirExists-        then do names <- liftIO $ getDirectoryContents topdir-                let properNames = filter isProper names-                paths <- forM properNames $ \name -> do-                    let path = topdir </> name-                    isDirectory <- liftIO $ doesDirectoryExist path-                    if isDirectory-                        then getRecursiveContents path-                        else return [normalise path]-                return (concat paths)-        else return []-  where-    isProper = not . (== '.') . head---- | Sort a list of filenames on the basename.-sortByBaseName :: [FilePath] -> [FilePath]-sortByBaseName = sortBy compareBaseName-  where-    compareBaseName = comparing takeFileName---- | A filter that takes all file names with a given extension. Prefix the---   extension with a dot:------   > havingExtension ".markdown" [ "index.markdown"---   >                             , "style.css"---   >                             ] == ["index.markdown"]-havingExtension :: String -> [FilePath] -> [FilePath]-havingExtension extension = filter ((==) extension . takeExtension)---- | Perform a Hakyll action on every file in a given directory.-directory :: (FilePath -> Hakyll ()) -> FilePath -> Hakyll ()-directory action dir = getRecursiveContents dir >>= mapM_ action---- | Check if a timestamp is newer then a number of given files.-isMoreRecent :: ClockTime  -- ^ The time to check.-             -> [FilePath] -- ^ Dependencies of the cached file.-             -> Hakyll Bool-isMoreRecent _ [] = return True-isMoreRecent timeStamp depends = do-    dependsModified <- liftIO $ mapM getModificationTime depends-    return (timeStamp >= maximum dependsModified)---- | Check if a file is newer then a number of given files.-isFileMoreRecent :: FilePath   -- ^ The cached file.-                 -> [FilePath] -- ^ Dependencies of the cached file.-                 -> Hakyll Bool-isFileMoreRecent file depends = do-    exists <- liftIO $ doesFileExist file-    if not exists-        then return False-        else do timeStamp <- liftIO $ getModificationTime file-                isMoreRecent timeStamp depends
− src/Text/Hakyll/HakyllAction.hs
@@ -1,98 +0,0 @@--- | This is the module which exports @HakyllAction@.-module Text.Hakyll.HakyllAction-    ( HakyllAction (..)-    , createHakyllAction-    , createSimpleHakyllAction-    , createFileHakyllAction-    , chain-    , runHakyllAction-    , runHakyllActionIfNeeded-    ) where--import Control.Arrow-import Control.Category-import Control.Monad ((<=<), unless)-import Prelude hiding ((.), id)--import Text.Hakyll.File (toDestination, isFileMoreRecent)-import Text.Hakyll.HakyllMonad---- | Type used for rendering computations that carry along dependencies.-data HakyllAction a b = HakyllAction-    { -- | Dependencies of the @HakyllAction@.-      actionDependencies :: [FilePath]-    , -- | URL pointing to the result of this @HakyllAction@.-      actionUrl          :: Either (Hakyll FilePath)-                                   (Hakyll FilePath -> Hakyll FilePath)-    , -- | The actual render function.-      actionFunction     :: a -> Hakyll b-    }---- | Create a @HakyllAction@ from a function.-createHakyllAction :: (a -> Hakyll b)  -- ^ Function to execute.-                   -> HakyllAction a b-createHakyllAction f = id { actionFunction = f }---- | Create a @HakyllAction@ from a simple @Hakyll@ value.-createSimpleHakyllAction :: Hakyll b -- ^ Hakyll value to pass on.-                         -> HakyllAction () b-createSimpleHakyllAction = createHakyllAction . const---- | Create a @HakyllAction@ that operates on one file.-createFileHakyllAction :: FilePath          -- ^ File to operate on.-                       -> Hakyll b          -- ^ Value to pass on.-                       -> HakyllAction () b -- ^ The resulting action.-createFileHakyllAction path action = HakyllAction-    { actionDependencies = [path]-    , actionUrl          = Left $ return path-    , actionFunction     = const action-    }---- | Run a @HakyllAction@ now.-runHakyllAction :: HakyllAction () a -- ^ Render action to run.-                -> Hakyll a          -- ^ Result of the action.-runHakyllAction action = actionFunction action ()---- | Run a @HakyllAction@, but only when it is out-of-date. At this point, the---   @actionUrl@ field must be set.-runHakyllActionIfNeeded :: HakyllAction () () -- ^ Action to run.-                        -> Hakyll ()          -- ^ Empty result.-runHakyllActionIfNeeded action = do-    url <- case actionUrl action of-        Left u  -> u-        Right _ -> error "No url when checking dependencies."-    destination <- toDestination url-    valid <- isFileMoreRecent destination $ actionDependencies action-    unless valid $ do logHakyll $ "Rendering " ++ destination-                      runHakyllAction action---- | Chain a number of @HakyllAction@ computations.-chain :: [HakyllAction a a] -- ^ Actions to chain.-      -> HakyllAction a a   -- ^ Resulting action.-chain []   = id-chain list = foldl1 (>>>) list--instance Category HakyllAction where-    id = HakyllAction-        { actionDependencies = []-        , actionUrl          = Right id-        , actionFunction     = return-        }--    x . y = HakyllAction-        { actionDependencies = actionDependencies x ++ actionDependencies y-        , actionUrl          = case actionUrl x of-            Left ux  -> Left ux-            Right fx -> case actionUrl y of-                Left uy  -> Left (fx uy)-                Right fy -> Right (fx . fy)-        , actionFunction     = actionFunction x <=< actionFunction y-        }--instance Arrow HakyllAction where-    arr f = id { actionFunction = return . f }--    first x = x-        { actionFunction = \(y, z) -> do y' <- actionFunction x y-                                         return (y', z)-        }
− src/Text/Hakyll/HakyllMonad.hs
@@ -1,99 +0,0 @@--- | Module describing the Hakyll monad stack.-module Text.Hakyll.HakyllMonad-    ( HakyllConfiguration (..)-    , PreviewMode (..)-    , Hakyll-    , askHakyll-    , getAdditionalContext-    , logHakyll-    , forkHakyllWait-    , concurrentHakyll-    ) where--import Control.Monad.Trans (liftIO)-import Control.Concurrent.MVar (MVar, putMVar, newEmptyMVar, readMVar)-import Control.Monad.Reader (ReaderT, ask, runReaderT)-import Control.Monad (liftM, forM, forM_)-import qualified Data.Map as M-import System.IO (hPutStrLn, stderr)--import Text.Pandoc (ParserState, WriterOptions)-import Text.Hamlet (HamletSettings)--import Text.Hakyll.Context (Context (..))---- | Our custom monad stack.----type Hakyll = ReaderT HakyllConfiguration IO---- | Preview mode.----data PreviewMode = BuildOnRequest-                 | BuildOnInterval-                 deriving (Show, Eq, Ord)---- | Hakyll global configuration type.----data HakyllConfiguration = HakyllConfiguration-    { -- | Absolute URL of the site.-      absoluteUrl         :: String-    , -- | An additional context to use when rendering. This additional context-      --   is used globally.-      additionalContext   :: Context-    , -- | Directory where the site is placed.-      siteDirectory       :: FilePath-    , -- | Directory for cache files.-      cacheDirectory      :: FilePath-    , -- | Enable index links.-      enableIndexUrl      :: Bool-    , -- | The preview mode used-      previewMode         :: PreviewMode-    , -- | Pandoc parsing options-      pandocParserState   :: ParserState-    , -- | Pandoc writer options-      pandocWriterOptions :: WriterOptions-    , -- | Hamlet settings (if you use hamlet for templates)-      hamletSettings      :: HamletSettings-    }---- | Simplified @ask@ function for the Hakyll monad stack.------   Usage would typically be something like:------   > doSomething :: a -> b -> Hakyll c---   > doSomething arg1 arg2 = do---   >     siteDirectory' <- askHakyll siteDirectory---   >     ...----askHakyll :: (HakyllConfiguration -> a) -> Hakyll a-askHakyll = flip liftM ask---- | Obtain the globally available, additional context.----getAdditionalContext :: HakyllConfiguration -> Context-getAdditionalContext configuration =-    let (Context c) = additionalContext configuration-    in Context $ M.insert "absolute" (absoluteUrl configuration) c---- | Write some log information.----logHakyll :: String -> Hakyll ()-logHakyll = liftIO . hPutStrLn stderr---- | Perform a concurrent hakyll action. Returns an MVar you can wait on----forkHakyllWait :: Hakyll () -> Hakyll (MVar ())-forkHakyllWait action = do-    mvar <- liftIO newEmptyMVar-    config <- ask-    liftIO $ do-        runReaderT action config-        putMVar mvar ()-    return mvar---- | Perform a number of concurrent hakyll actions, and waits for them to finish----concurrentHakyll :: [Hakyll ()] -> Hakyll ()-concurrentHakyll actions = do-    mvars <- forM actions forkHakyllWait-    forM_ mvars (liftIO . readMVar)
− src/Text/Hakyll/Internal/Cache.hs
@@ -1,53 +0,0 @@-module Text.Hakyll.Internal.Cache-    ( storeInCache-    , getFromCache-    , isCacheMoreRecent-    , cacheAction-    ) where--import Control.Monad ((<=<))-import Control.Monad.Reader (liftIO)-import Data.Binary-import System.FilePath ((</>))--import Text.Hakyll.File-import Text.Hakyll.HakyllMonad (Hakyll)-import Text.Hakyll.HakyllAction---- | We can store all datatypes instantiating @Binary@ to the cache. The cache---   directory is specified by the @HakyllConfiguration@, usually @_cache@.-storeInCache :: (Binary a) => a -> FilePath -> Hakyll ()-storeInCache value path = do-    cachePath <- toCache path-    makeDirectories cachePath-    liftIO $ encodeFile cachePath value---- | Get a value from the cache. The filepath given should not be located in the---   cache. This function performs a timestamp check on the filepath and the---   filepath in the cache, and only returns the cached value when it is still---   up-to-date.-getFromCache :: (Binary a) => FilePath -> Hakyll a-getFromCache = liftIO . decodeFile <=< toCache---- | Check if a file in the cache is more recent than a number of other files.-isCacheMoreRecent :: FilePath -> [FilePath] -> Hakyll Bool-isCacheMoreRecent file depends = toCache file >>= flip isFileMoreRecent depends---- | Cache an entire arrow----cacheAction :: Binary a-            => String-            -> HakyllAction () a-            -> HakyllAction () a-cacheAction key action = action { actionFunction = const cacheFunction }-  where-    cacheFunction = do-        -- Construct a filename-        fileName <- fmap (key </>) $ either id (const $ return "unknown")-                                   $ actionUrl action-        -- Check the cache-        cacheOk <- isCacheMoreRecent fileName $ actionDependencies action-        if cacheOk then getFromCache fileName-                   else do result <- actionFunction action ()-                           storeInCache result fileName-                           return result
− src/Text/Hakyll/Internal/CompressCss.hs
@@ -1,36 +0,0 @@--- | Module used for CSS compression. The compression is currently in a simple---   state, but would typically reduce the number of bytes by about 25%.-module Text.Hakyll.Internal.CompressCss-    ( compressCss-    ) where--import Data.List (isPrefixOf)--import Text.Hakyll.Regex (substituteRegex)---- | Compress CSS to speed up your site.-compressCss :: String -> String-compressCss = compressSeparators-            . stripComments-            . compressWhitespace---- | Compresses certain forms of separators.-compressSeparators :: String -> String-compressSeparators = substituteRegex "; *}" "}" -                   . substituteRegex " *([{};:]) *" "\\1"-                   . substituteRegex ";;*" ";"---- | Compresses all whitespace.-compressWhitespace :: String -> String-compressWhitespace = substituteRegex "[ \t\n][ \t\n]*" " "---- | Function that strips CSS comments away.-stripComments :: String -> String-stripComments [] = []-stripComments str-    | isPrefixOf "/*" str = stripComments $ eatComments $ drop 2 str-    | otherwise = head str : stripComments (drop 1 str)-  where-    eatComments str' | null str' = []-                     | isPrefixOf "*/" str' = drop 2 str'-                     | otherwise = eatComments $ drop 1 str'
− src/Text/Hakyll/Internal/FileType.hs
@@ -1,49 +0,0 @@--- | A module dealing with file extensions and associated file types.-module Text.Hakyll.Internal.FileType-    ( FileType (..)-    , getFileType-    , isRenderable-    , isRenderableFile-    ) where--import System.FilePath (takeExtension)---- | Datatype to represent the different file types Hakyll can deal with.-data FileType = Html-              | LaTeX-              | LiterateHaskellMarkdown-              | Markdown-              | ReStructuredText-              | Text-              | UnknownFileType-              deriving (Eq, Ord, Show, Read)---- | Get the file type for a certain file. The type is determined by extension.-getFileType :: FilePath -> FileType-getFileType = getFileType' . takeExtension-  where-    getFileType' ".htm"      = Html-    getFileType' ".html"     = Html-    getFileType' ".lhs"      = LiterateHaskellMarkdown-    getFileType' ".markdown" = Markdown-    getFileType' ".md"       = Markdown-    getFileType' ".mdn"      = Markdown-    getFileType' ".mdown"    = Markdown-    getFileType' ".mdwn"     = Markdown-    getFileType' ".mkd"      = Markdown-    getFileType' ".mkdwn"    = Markdown-    getFileType' ".page"     = Markdown-    getFileType' ".rst"      = ReStructuredText-    getFileType' ".tex"      = LaTeX-    getFileType' ".text"     = Text-    getFileType' ".txt"      = Text-    getFileType' _           = UnknownFileType---- | Check if a certain @FileType@ is renderable.-isRenderable :: FileType -> Bool-isRenderable UnknownFileType = False-isRenderable _               = True---- | Check if a certain file is renderable.-isRenderableFile :: FilePath -> Bool-isRenderableFile = isRenderable . getFileType
− src/Text/Hakyll/Internal/Template.hs
@@ -1,86 +0,0 @@-module Text.Hakyll.Internal.Template-    ( Template (..)-    , fromString-    , readTemplate-    , substitute-    , regularSubstitute-    , finalSubstitute-    ) where--import Control.Arrow ((>>>))-import Control.Applicative ((<$>))-import Data.List (isPrefixOf)-import Data.Char (isAlphaNum)-import Data.Maybe (fromMaybe)-import System.FilePath ((</>))-import qualified Data.Map as M--import Text.Hakyll.Context (Context (..))-import Text.Hakyll.HakyllMonad (Hakyll)-import Text.Hakyll.HakyllAction-import Text.Hakyll.Pandoc-import Text.Hakyll.Internal.Cache-import Text.Hakyll.Page-import Text.Hakyll.ContextManipulations-import Text.Hakyll.Internal.Template.Template-import Text.Hakyll.Internal.Template.Hamlet---- | Construct a @Template@ from a string.----fromString :: String -> Template-fromString = Template . fromString'-  where-    fromString' [] = []-    fromString' string-        | "$$" `isPrefixOf` string =-            EscapeCharacter : (fromString' $ drop 2 string)-        | "$" `isPrefixOf` string =-            let (key, rest) = span isAlphaNum $ drop 1 string-            in Identifier key : fromString' rest-        | otherwise =-            let (chunk, rest) = break (== '$') string-            in Chunk chunk : fromString' rest---- | Read a @Template@ from a file. This function might fetch the @Template@---   from the cache, if available.-readTemplate :: FilePath -> Hakyll Template-readTemplate path = do-    isCacheMoreRecent' <- isCacheMoreRecent fileName [path]-    if isCacheMoreRecent'-        then getFromCache fileName-        else do-            template <- if isHamletRTFile path-                            then readHamletTemplate-                            else readDefaultTemplate-            storeInCache template fileName-            return template-  where -    fileName = "templates" </> path-    readDefaultTemplate = do-        body <- runHakyllAction $   readPageAction path-                                >>> renderAction-                                >>> takeBody-        return $ fromString body--    readHamletTemplate = fromHamletRT <$> readHamletRT path---- | Substitutes @$identifiers@ in the given @Template@ by values from the given---   "Context". When a key is not found, it is left as it is. You can specify---   the characters used to replace escaped dollars (@$$@) here.-substitute :: String -> Template -> Context -> String -substitute escaper template context = substitute' =<< unTemplate template-  where-    substitute' (Chunk chunk) = chunk-    substitute' (Identifier key) =-        fromMaybe ('$' : key) $ M.lookup key $ unContext context-    substitute' (EscapeCharacter) = escaper---- | @substitute@ for use during a chain. This will leave escaped characters as---   they are.-regularSubstitute :: Template -> Context -> String-regularSubstitute = substitute "$$"---- | @substitute@ for the end of a chain (just before writing). This renders---   escaped characters.-finalSubstitute :: Template -> Context -> String-finalSubstitute = substitute "$"
− src/Text/Hakyll/Internal/Template/Hamlet.hs
@@ -1,56 +0,0 @@--- | Support for Hamlet templates in Hakyll.----module Text.Hakyll.Internal.Template.Hamlet-    ( isHamletRTFile-    , readHamletRT-    , fromHamletRT-    ) where--import Control.Exception (try)-import Control.Monad.Trans (liftIO)-import System.FilePath (takeExtension)--import Text.Hamlet.RT--import Text.Hakyll.Internal.Template.Template-import Text.Hakyll.HakyllMonad (Hakyll, askHakyll, hamletSettings, logHakyll)---- | Determine if a file is a hamlet template by extension.----isHamletRTFile :: FilePath -> Bool-isHamletRTFile fileName = takeExtension fileName `elem` [".hamlet", ".hml"]---- | Read a 'HamletRT' by file name.----readHamletRT :: FilePath         -- ^ Filename of the template-             -> Hakyll HamletRT  -- ^ Resulting hamlet template-readHamletRT fileName = do-    settings <- askHakyll hamletSettings-    string <- liftIO $ readFile fileName-    result <- liftIO $ try $ parseHamletRT settings string-    case result of-        Left (HamletParseException s) -> error' s-        Left (HamletUnsupportedDocException d) -> error' $ show d-        Left (HamletRenderException s) -> error' s-        Right x -> return x-  where-    error' s = do-        logHakyll $ "Parse of hamlet file " ++ fileName ++ " failed."-        logHakyll s-        error "Parse failed."---- | Convert a 'HamletRT' to a 'Template'----fromHamletRT :: HamletRT  -- ^ Hamlet runtime template-             -> Template  -- ^ Hakyll template-fromHamletRT (HamletRT sd) = Template $ map fromSimpleDoc sd-  where-    fromSimpleDoc :: SimpleDoc -> TemplateElement-    fromSimpleDoc (SDRaw chunk) = Chunk chunk-    fromSimpleDoc (SDVar [var]) = Identifier var-    fromSimpleDoc (SDVar _) =-        error "Hakyll does not support '.' in identifier names when using \-              \hamlet templates."-    fromSimpleDoc _ =-        error "Only simple $key$ identifiers are allowed when using hamlet \-              \templates."
− src/Text/Hakyll/Internal/Template/Template.hs
@@ -1,34 +0,0 @@--- | Module containing the template data structure.----{-# LANGUAGE GeneralizedNewtypeDeriving #-}-module Text.Hakyll.Internal.Template.Template-    ( Template (..)-    , TemplateElement (..)-    ) where--import Control.Applicative ((<$>))--import Data.Binary (Binary, get, getWord8, put, putWord8)---- | Datatype used for template substitutions.----newtype Template = Template { unTemplate :: [TemplateElement] }-                 deriving (Show, Eq, Binary)---- | Elements of a template.----data TemplateElement = Chunk String-                     | Identifier String-                     | EscapeCharacter-                     deriving (Show, Eq)--instance Binary TemplateElement where-    put (Chunk string)    = putWord8 0 >> put string-    put (Identifier key)  = putWord8 1 >> put key-    put (EscapeCharacter) = putWord8 2--    get = getWord8 >>= \tag ->-         case tag of 0 -> Chunk <$> get-                     1 -> Identifier <$> get-                     2 -> return EscapeCharacter-                     _ -> error "Error reading cached template"
− src/Text/Hakyll/Page.hs
@@ -1,108 +0,0 @@--- | A module for dealing with @Page@s. This module is mostly internally used.-module Text.Hakyll.Page-    ( PageSection (..)-    , readPage-    , readPageAction-    ) where--import Data.List (isPrefixOf)-import Data.Char (isSpace)-import Control.Monad.Reader (liftIO)-import System.FilePath-import Control.Monad.State (State, evalState, get, put)--import Text.Hakyll.File-import Text.Hakyll.HakyllMonad-import Text.Hakyll.HakyllAction-import Text.Hakyll.Regex (substituteRegex, matchesRegex)-import Text.Hakyll.Util (trim)---- | A page is first parsed into a number of page sections. A page section--- consists of:------ * A key------ * A value------ * A 'Bool' flag, indicating if the value is applicable for rendering----data PageSection = PageSection {unPageSection :: (String, String, Bool)}-                 deriving (Show)---- | Split a page into sections.----splitAtDelimiters :: [String] -> State (Maybe String) [[String]]-splitAtDelimiters [] = return []-splitAtDelimiters ls@(x:xs) = do-    delimiter <- get-    if not (isDelimiter delimiter x)-        then return [ls]-        else do let proper = takeWhile (== '-') x-                    (content, rest) = break (isDelimiter $ Just proper) xs-                put $ Just proper-                rest' <- splitAtDelimiters rest-                return $ (x : content) : rest'-  where-    isDelimiter old = case old of-        Nothing  -> isPossibleDelimiter-        (Just d) -> (== d) . takeWhile (== '-')---- | Check if the given string is a metadata delimiter.-isPossibleDelimiter :: String -> Bool-isPossibleDelimiter = isPrefixOf "---"---- | Read one section of a page.----readSection :: Bool -- ^ If this section is the first section in the page.-            -> [String] -- ^ Lines in the section.-            -> [PageSection] -- ^ Key-values extracted.-readSection _ [] = []-readSection isFirst ls-    | not isDelimiter' = [body ls]-    | isNamedDelimiter = readSectionMetaData ls-    | isFirst = readSimpleMetaData (drop 1 ls)-    | otherwise = [body (drop 1 ls)]-  where-    isDelimiter' = isPossibleDelimiter (head ls)-    isNamedDelimiter = head ls `matchesRegex` "^----*  *[a-zA-Z0-9][a-zA-Z0-9]*"-    body ls' = PageSection ("body", unlines ls', True)--    readSimpleMetaData = map readPair . filter (not . all isSpace)-    readPair = trimPair . break (== ':')-    trimPair (key, value) = PageSection (trim key, trim (drop 1 value), False)--    readSectionMetaData [] = []-    readSectionMetaData (header:value) =-        let key = substituteRegex "[^a-zA-Z0-9]" "" header-        in [PageSection (key, unlines value, True)]---- | Read a page from a file. Metadata is supported.----readPage :: FilePath -> Hakyll [PageSection]-readPage path = do-    let sectionFunctions = map readSection $ True : repeat False--    -- Read file.-    contents <- liftIO $ readFile path-    url <- toUrl path-    let sections = evalState (splitAtDelimiters $ lines contents) Nothing-        sectionsData = concat $ zipWith ($) sectionFunctions sections--    -- Note that url, path etc. are listed first, which means can be overwritten-    -- by section data-    return $ PageSection ("url", url, False)-           : PageSection ("path", path, False)-           : PageSection ("title", takeBaseName path, False)-           : (category ++ sectionsData)-  where-    category = let dirs = splitDirectories $ takeDirectory path-               in [PageSection ("category", last dirs, False) | not (null dirs)]---- | Read a page from a file. Metadata is supported.----readPageAction :: FilePath -> HakyllAction () [PageSection]-readPageAction path = HakyllAction-    { actionDependencies = [path]-    , actionUrl          = Left $ toUrl path-    , actionFunction     = const $ readPage path-    }
− src/Text/Hakyll/Paginate.hs
@@ -1,94 +0,0 @@--- | Module aimed to paginate web pages.----module Text.Hakyll.Paginate-    ( PaginateConfiguration (..)-    , defaultPaginateConfiguration-    , paginate-    ) where--import Control.Applicative ((<$>))--import Text.Hakyll.Context (Context)-import Text.Hakyll.CreateContext-import Text.Hakyll.HakyllAction-import Text.Hakyll.Util (link)---- | A configuration for a pagination.----data PaginateConfiguration = PaginateConfiguration-    { -- | Label for the link to the previous page.-      previousLabel :: String-    , -- | Label for the link to the next page.-      nextLabel     :: String-    , -- | Label for the link to the first page.-      firstLabel    :: String-    , -- | Label for the link to the last page.-      lastLabel     :: String-    }---- | A simple default configuration for pagination.----defaultPaginateConfiguration :: PaginateConfiguration-defaultPaginateConfiguration = PaginateConfiguration-    { previousLabel = "Previous"-    , nextLabel     = "Next"-    , firstLabel    = "First"-    , lastLabel     = "Last"-    }---- | The most important function for pagination. This function operates on a---   list of @Context@s (the pages), and basically just adds fields to them---   by combining them with a custom page.------   The following metadata fields will be added:------   - @$previous@: A link to the previous page.------   - @$next@: A link to the next page.------   - @$first@: A link to the first page.------   - @$last@: A link to the last page.------   - @$index@: 1-based index of the current page.------   - @$length@: Total number of pages.------   When @$previous@ or @$next@ are not available, they will be just a label---   without a link. The same goes for when we are on the first or last page for---   @$first@ and @$last@.----paginate :: PaginateConfiguration-         -> [HakyllAction () Context]-         -> [HakyllAction () Context]-paginate configuration renderables = paginate' Nothing renderables (1 :: Int)-  where-    -- Create a link with a given label, taken from the configuration.-    linkWithLabel f r = Right $ case actionUrl r of-        Left l  -> createSimpleHakyllAction $-            link (f configuration) . ("$root/" ++) <$> l-        Right _ -> error "No link found for pagination."--    -- The main function that creates combined renderables by recursing over-    -- the list of items.-    paginate' _ [] _ = []-    paginate' maybePrev (x:xs) index = -        let (previous, first) = case maybePrev of-                (Just r) -> ( linkWithLabel previousLabel r-                            , linkWithLabel firstLabel (head renderables) )-                Nothing  -> ( Left $ previousLabel configuration-                            , Left $ firstLabel configuration )-            (next, last') = case xs of-                (n:_) -> ( linkWithLabel nextLabel n-                         , linkWithLabel lastLabel (last renderables) )-                []    -> ( Left $ nextLabel configuration-                         , Left $ lastLabel configuration )-            customPage = createCustomPage ""-                [ ("previous", previous)-                , ("next", next)-                , ("first", first)-                , ("last", last')-                , ("index", Left $ show index)-                , ("length", Left $ show $ length renderables)-                ]-        in (x `combine` customPage) : paginate' (Just x) xs (index + 1)
− src/Text/Hakyll/Pandoc.hs
@@ -1,57 +0,0 @@--- | Module exporting a pandoc arrow----module Text.Hakyll.Pandoc-    ( renderAction-    , renderActionWith-    ) where--import Data.Maybe (fromMaybe)-import qualified Data.Map as M-import Control.Arrow (second, (>>>), arr, (&&&))--import Text.Pandoc--import Text.Hakyll.Internal.FileType-import Text.Hakyll.Page-import Text.Hakyll.HakyllMonad-import Text.Hakyll.HakyllAction-import Text.Hakyll.Context---- | Get a render function for a given extension.----getRenderFunction :: HakyllAction FileType (String -> String)-getRenderFunction = createHakyllAction $ \fileType -> case fileType of-    Html -> return id-    Text -> return id-    UnknownFileType -> return id-    _ -> do parserState <- askHakyll pandocParserState-            writerOptions <- askHakyll pandocWriterOptions-            return $ writeHtmlString writerOptions-                   . readFunction fileType (readOptions parserState fileType)-  where-    readFunction ReStructuredText        = readRST-    readFunction LaTeX                   = readLaTeX-    readFunction Markdown                = readMarkdown-    readFunction LiterateHaskellMarkdown = readMarkdown-    readFunction t                       = error $ "Cannot render " ++ show t--    readOptions options LiterateHaskellMarkdown = options-        { stateLiterateHaskell = True }-    readOptions options _                       = options---- | An action that renders the list of page sections to a context using pandoc----renderAction :: HakyllAction [PageSection] Context-renderAction = (arr id &&& (getFileType' >>> getRenderFunction))-             >>> renderActionWith-  where-    getFileType' = arr $ getFileType . fromMaybe "unknown" . lookup "path" -                       . map (\(x, y, _) -> (x, y)) . map unPageSection---- | An action to render pages, offering just a little more flexibility----renderActionWith :: HakyllAction ([PageSection], String -> String) Context-renderActionWith = createHakyllAction $ \(sections, render') -> return $-    Context $ M.fromList $ map (renderTriple render' . unPageSection) sections-  where-    renderTriple render' (k, v, r) = second (if r then render' else id) (k, v)
− src/Text/Hakyll/Regex.hs
@@ -1,77 +0,0 @@--- | A module that exports a simple regex interface. This code is mostly copied---   from the regex-compat package at hackage. I decided to write this module---   because I want to abstract the regex package used.-module Text.Hakyll.Regex-    ( splitRegex-    , substituteRegex-    , matchesRegex-    ) where--import Text.Regex.TDFA---- | Match a regular expression against a string, returning more information---   about the match.-matchRegexAll :: Regex -> String -> Maybe (String, String, String, [String])-matchRegexAll = matchM---- | Replaces every occurance of the given regexp with the replacement string.-subRegex :: Regex  -- ^ Search pattern-         -> String -- ^ Input string-         -> String -- ^ Replacement text-         -> String -- ^ Output string-subRegex _ "" _ = ""-subRegex regexp inp replacement =-  let -- bre matches a backslash then capture either a backslash or some digits-      bre = makeRegex "\\\\(\\\\|[0-9]+)"-      lookup' _ [] _ = []-      lookup' [] _ _ = []-      lookup' match' repl groups =-        case matchRegexAll bre repl of-          Nothing -> repl-          Just (lead, _, trail, bgroups) ->-            let newval =-                 if head bgroups == "\\"-                   then "\\"-                   else let index :: Int-                            index = read (head bgroups) - 1-                        in if index == -1-                             then match'-                             else groups !! index-            in lead ++ newval ++ lookup' match' trail groups-  in case matchRegexAll regexp inp of-       Nothing -> inp-       Just (lead, match', trail, groups) ->-         lead ++ lookup' match' replacement groups-              ++ subRegex regexp trail replacement---- | Splits a string based on a regular expression.  The regular expression---   should identify one delimiter.-splitRegex' :: Regex -> String -> [String]-splitRegex' _ [] = []-splitRegex' delim strIn = loop strIn where-  loop str = case matchOnceText delim str of-                Nothing -> [str]-                Just (firstline, _, remainder) ->-                  if null remainder-                    then [firstline,""]-                    else firstline : loop remainder---- | Split a list at a certain element.-splitRegex :: String -> String -> [String]-splitRegex pattern = filter (not . null)-                   . splitRegex' (makeRegex pattern)---- | Substitute a regex. Simplified interface. This function performs a global---   substitution.-substituteRegex :: String -- ^ Pattern to replace (regex).-                -> String -- ^ Replacement string.-                -> String -- ^ Input string.-                -> String -- ^ Result.-substituteRegex pattern replacement string =-    subRegex (makeRegex pattern) string replacement---- | Simple regex matching.-matchesRegex :: String -- ^ Input string.-             -> String -- ^ Pattern to match.-             -> Bool-matchesRegex = (=~)
− src/Text/Hakyll/Render.hs
@@ -1,126 +0,0 @@--- | Module containing rendering functions. All these functions are used to---   render files to the @_site@ directory.-module Text.Hakyll.Render -    ( render-    , renderAndConcat-    , renderChain-    , static-    , css-    , writePage-    ) where--import Control.Arrow ((>>>))-import Control.Applicative ((<$>))-import Control.Monad.Reader (liftIO)-import System.Directory (copyFile)-import Data.Maybe (fromMaybe)-import qualified Data.Map as M--import Text.Hakyll.Context (Context (..))-import Text.Hakyll.HakyllMonad (Hakyll, askHakyll, getAdditionalContext)-import Text.Hakyll.File-import Text.Hakyll.HakyllAction-import Text.Hakyll.ContextManipulations-import Text.Hakyll.Internal.CompressCss-import Text.Hakyll.Internal.Template---- | A pure render function - used internally.-pureRender :: Template -- ^ Template to use for rendering.-           -> Context  -- ^ Renderable object to render with given template.-           -> Context  -- ^ The body of the result will contain the render.-pureRender template (Context c) =-    -- Ignore $root when substituting here. We will only replace that in the-    -- final render (just before writing).-    let contextIgnoringRoot = Context $ M.insert "root" "$root" c-        body = regularSubstitute template $ contextIgnoringRoot-    in Context $ M.insert "body" body c---- | This is the most simple render action. You render a @Context@ with a---   template, and get back the result.-render :: FilePath                     -- ^ Template to use for rendering.-       -> HakyllAction Context Context -- ^ The render computation.-render templatePath = HakyllAction-    { actionDependencies = [templatePath]-    , actionUrl          = Right id-    , actionFunction     = \context ->-        flip pureRender context <$> readTemplate templatePath-    }---- | Render each @Context@ with the given templates, then concatenate the---   result. So, basically this function:------   - Takes every @Context@.------   - Renders every @Context@ with all given templates. This is comparable---     with a renderChain action.------   - Concatenates the result and returns that as a @String@.-renderAndConcat :: [FilePath]-                -> [HakyllAction () Context]-                -> HakyllAction () String-renderAndConcat templatePaths renderables = HakyllAction-    { actionDependencies = renders >>= actionDependencies-    , actionUrl          = Right id-    , actionFunction     = actionFunction'-    }-  where-    render' = chain (map render templatePaths)-    renders = map (>>> render') renderables--    actionFunction' _ = do-        contexts <- mapM (runHakyllAction . (>>> takeBody)) renders-        return $ concat contexts---- | Chain a render action for a page with a number of templates. This will---   also write the result to the site destination. This is the preferred way---   to do general rendering.------   > renderChain [ "templates/notice.html"---   >             , "templates/default.html"---   >             ] $ createPagePath "warning.html"------   This code will first render @warning.html@ using @templates/notice.html@,---   and will then render the result with @templates/default.html@.-renderChain :: [FilePath]-            -> HakyllAction () Context-            -> Hakyll ()-renderChain templatePaths initial =-    runHakyllActionIfNeeded renderChainWith'-  where-    renderChainWith' = initial >>> chain' >>> writePage-    chain' = chain $ map render templatePaths---- | Mark a certain file as static, so it will just be copied when the site is---   generated.-static :: FilePath -> Hakyll ()-static source = runHakyllActionIfNeeded static'-  where-    static' = createFileHakyllAction source $ do-        destination <- toDestination source-        makeDirectories destination-        liftIO $ copyFile source destination---- | Render a css file, compressing it.-css :: FilePath -> Hakyll ()-css source = runHakyllActionIfNeeded css'-  where-    css' = createFileHakyllAction source $ do-        contents <- liftIO $ readFile source-        destination <- toDestination source-        makeDirectories destination-        liftIO $ writeFile destination (compressCss contents)---- | Write a page to the site destination. Final action after render---   chains and such.-writePage :: HakyllAction Context ()-writePage = createHakyllAction $ \(Context initialContext) -> do-    additionalContext' <- unContext <$> askHakyll getAdditionalContext-    let url = fromMaybe (error "No url defined at write time.")-                        (M.lookup "url" initialContext)-        body = fromMaybe "" (M.lookup "body" initialContext)-    let context = additionalContext' `M.union` M.singleton "root" (toRoot url)-    destination <- toDestination url-    makeDirectories destination-    -- Substitute $root here, just before writing.-    liftIO $ writeFile destination $ finalSubstitute (fromString body)-                                                     (Context context)
− src/Text/Hakyll/Tags.hs
@@ -1,172 +0,0 @@--- | Module containing some specialized functions to deal with tags.---   This Module follows certain conventions. Stick with them.------   More concrete: all functions in this module assume that the tags are---   located in the @tags@ field, and separated by commas. An example file---   @foo.markdown@ could look like:------   > ------   > author: Philip K. Dick---   > title: Do androids dream of electric sheep?---   > tags: future, science fiction, humanoid---   > ------   > The novel is set in a post-apocalyptic near future, where the Earth and---   > its populations have been damaged greatly by Nuclear...------   All the following functions would work with such a format. In addition to---   tags, Hakyll also supports categories. The convention when using categories---   is to place pages in subdirectories.------   An example, the page @posts\/coding\/2010-01-28-hakyll-categories.markdown@---   Tags or categories are read using the @readTagMap@ and @readCategoryMap@---   functions. Because categories are implemented using tags - categories can---   be seen as tags, with the restriction that a page can only have one---   category - all functions for tags also work with categories.------   When reading a @TagMap@ (which is also used for category maps) using the---   @readTagMap@ or @readCategoryMap@ function, you also have to give a unique---   identifier to it. This identifier is simply for caching reasons, so Hakyll---   can tell different maps apart; it has no other use.----module Text.Hakyll.Tags-    ( TagMap-    , readTagMap-    , readCategoryMap-    , withTagMap-    , renderTagCloud-    , renderTagLinks-    ) where--import qualified Data.Map as M-import Data.List (intercalate)-import Data.Maybe (fromMaybe, maybeToList)-import Control.Arrow (second, (>>>))-import Control.Applicative ((<$>))-import System.FilePath--import Text.Blaze.Renderer.String (renderHtml)-import Text.Blaze.Html5 ((!), string, stringValue)-import qualified Text.Blaze.Html5 as H-import qualified Text.Blaze.Html5.Attributes as A--import Text.Hakyll.Context (Context (..))-import Text.Hakyll.ContextManipulations (changeValue)-import Text.Hakyll.CreateContext (createPage)-import Text.Hakyll.HakyllMonad (Hakyll)-import Text.Hakyll.Regex-import Text.Hakyll.HakyllAction-import Text.Hakyll.Util-import Text.Hakyll.Internal.Cache---- | Type for a tag map.------   This is a map associating tags or categories to the appropriate pages---   using that tag or category. In the case of categories, each path will only---   appear under one category - this is not the case with tags.-type TagMap = M.Map String [HakyllAction () Context]---- | Read a tag map. This is a internally used function that can be used for---   tags as well as for categories.-readMap :: (Context -> [String]) -- ^ Function to get tags from a context.-        -> String -- ^ Unique identifier for the tagmap.-        -> [FilePath]-        -> HakyllAction () TagMap-readMap getTagsFunction identifier paths = HakyllAction-    { actionDependencies = paths-    , actionUrl          = Right id-    , actionFunction     = actionFunction'-    } -  where-    fileName = "tagmaps" </> identifier--    actionFunction' _ = do-        isCacheMoreRecent' <- isCacheMoreRecent fileName paths-        assocMap <- if isCacheMoreRecent'-                        then M.fromAscList <$> getFromCache fileName-                        else do assocMap' <- readTagMap'-                                storeInCache (M.toAscList assocMap') fileName-                                return assocMap'-        return $ M.map (map createPage) assocMap--    -- TODO: preserve order-    readTagMap' :: Hakyll (M.Map String [FilePath])-    readTagMap' = do-        pairs' <- concat <$> mapM pairs paths-        return $ M.fromListWith (flip (++)) pairs'--    -- | Read a page, and return an association list where every tag is-    -- associated with some paths. Of course, this will always be just one-    -- @FilePath@ here.-    pairs :: FilePath -> Hakyll [(String, [FilePath])]-    pairs path = do-        context <- runHakyllAction $ createPage path-        let tags = getTagsFunction context-        return $ map (\tag -> (tag, [path])) tags---- | Read a @TagMap@, using the @tags@ metadata field.-readTagMap :: String     -- ^ Unique identifier for the map.-           -> [FilePath] -- ^ Paths to get tags from.-           -> HakyllAction () TagMap-readTagMap = readMap getTagsFunction-  where-    getTagsFunction = map trim . splitRegex ","-                    . fromMaybe [] . M.lookup "tags" . unContext---- | Read a @TagMap@, using the subdirectories the pages are placed in.-readCategoryMap :: String     -- ^ Unique identifier for the map.-                -> [FilePath] -- ^ Paths to get tags from.-                -> HakyllAction () TagMap-readCategoryMap = readMap $ maybeToList . M.lookup "category" . unContext---- | Perform a @Hakyll@ action on every item in the tag----withTagMap :: HakyllAction () TagMap-           -> (String -> [HakyllAction () Context] -> Hakyll ())-           -> Hakyll ()-withTagMap tagMap function = runHakyllAction (tagMap >>> action)-  where-    action = createHakyllAction (mapM_ (uncurry function) . M.toList)---- | Render a tag cloud.-renderTagCloud :: (String -> String) -- ^ Function to produce an url for a tag.-               -> Float              -- ^ Smallest font size, in percent.-               -> Float              -- ^ Biggest font size, in percent.-               -> HakyllAction TagMap String-renderTagCloud urlFunction minSize maxSize = createHakyllAction renderTagCloud'-  where-    renderTagCloud' tagMap =-        return $ intercalate " " $ map (renderTag tagMap) (tagCount tagMap)--    renderTag tagMap (tag, count) = renderHtml $-        H.a ! A.style (stringValue $ "font-size: " ++ sizeTag tagMap count)-            ! A.href (stringValue $ urlFunction tag)-            $ string tag-        -    sizeTag tagMap count = show (size' :: Int) ++ "%"-      where-        size' = floor $ minSize + relative tagMap count * (maxSize - minSize)--    minCount = minimum . map snd . tagCount-    maxCount = maximum . map snd . tagCount-    relative tagMap count = (count - minCount tagMap) /-                            (1 + maxCount tagMap - minCount tagMap)--    tagCount = map (second $ fromIntegral . length) . M.toList---- | Render all tags to links.---   ---   On your site, it is nice if you can display the tags on a page, but---   naturally, most people would expect these are clickable.------   So, this function takes a function to produce an url for a given tag, and---   applies it on all tags.------   Note that it is your own responsibility to ensure a page with such an url---   exists.-renderTagLinks :: (String -> String) -- ^ Function to produce an url for a tag.-               -> HakyllAction Context Context-renderTagLinks urlFunction = changeValue "tags" renderTagLinks'-  where-    renderTagLinks' = intercalate ", "-                    . map ((\t -> link t $ urlFunction t) . trim)-                    . splitRegex ","
− src/Text/Hakyll/Util.hs
@@ -1,34 +0,0 @@--- | Miscellaneous text manipulation functions.-module Text.Hakyll.Util -    ( trim-    , stripHtml-    , link-    ) where--import Data.Char (isSpace)--import Text.Blaze.Html5 ((!), string, stringValue, a)-import Text.Blaze.Html5.Attributes (href)-import Text.Blaze.Renderer.String (renderHtml)---- | Trim a string (drop spaces, tabs and newlines at both sides).-trim :: String -> String-trim = reverse . trim' . reverse . trim'-  where-    trim' = dropWhile isSpace---- | Strip html tags from the given string.-stripHtml :: String -> String-stripHtml []  = []-stripHtml str = let (beforeTag, rest) = break (== '<') str-                    (_, afterTag)     = break (== '>') rest-                in beforeTag ++ stripHtml (drop 1 afterTag)---- | Make a HTML link.------   > link "foo" "bar.html" == "<a href='bar.html'>foo</a>"-link :: String -- ^ Link text.-     -> String -- ^ Link destination.-     -> String-link text destination = renderHtml $ a ! href (stringValue destination)-                                       $ string text