Lykah (empty) → 0.0.1
raw patch · 13 files changed
+1859/−0 lines, 13 filesdep +ListLikedep +arraydep +base
Dependencies added: ListLike, array, base, blaze-markup, blazeT, clay, containers, directory, filepath, filesystem-trees, formatting, mtl, old-locale, pandoc, regex-compat, regex-posix, safe, split, template-haskell, text, time, transformers
Files
- LICENSE +21/−0
- Lykah.cabal +96/−0
- README.md +39/−0
- src/Lykah/Assets.hs +239/−0
- src/Lykah/BlazeTH.hs +39/−0
- src/Lykah/BuildingBlocks.hs +36/−0
- src/Lykah/EHtml.hs +161/−0
- src/Lykah/Formatting.hs +6/−0
- src/Lykah/Structure.hs +184/−0
- src/Lykah/Style.hs +438/−0
- src/Lykah/Theme2.hs +188/−0
- src/main.hs +401/−0
- stack.yaml +11/−0
+ LICENSE view
@@ -0,0 +1,21 @@+MIT License++Copyright (c) 2016 Johannes Gerer++Permission is hereby granted, free of charge, to any person obtaining a copy+of this software and associated documentation files (the "Software"), to deal+in the Software without restriction, including without limitation the rights+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell+copies of the Software, and to permit persons to whom the Software is+furnished to do so, subject to the following conditions:++The above copyright notice and this permission notice shall be included in all+copies or substantial portions of the Software.++THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE+SOFTWARE.
+ Lykah.cabal view
@@ -0,0 +1,96 @@+name: Lykah+version: 0.0.1+cabal-version: >=1.10+build-type: Simple+license: MIT+license-file: LICENSE+maintainer: Johannes Gerer <oss@johannesgerer.com>+stability: Experimental+homepage: http://johannesgerer.com/buchhaltung+bug-reports: http://github.com/johannesgerer/buchhaltung/issues+synopsis: A static website and blog generator+description:+ Build websites using the full power and flexibility of Haskell.+ .+ See <https://github.com/johannesgerer/Lykah Readme> on Github.+category: Web+author: Johannes Gerer+extra-source-files:+ README.md+ stack.yaml++library+ exposed-modules:+ Lykah.Assets+ Lykah.BlazeTH+ Lykah.BuildingBlocks+ Lykah.EHtml+ Lykah.Structure+ Lykah.Style+ Lykah.Theme2+ Lykah.Formatting+ build-depends:+ base >=4.9.0.0 && <4.10,+ blaze-markup >=0.7.1 && <0.8,+ ListLike ==4.5.*,+ pandoc >=1.17.1 && <1.18,+ safe >=0.3.10 && <0.4,+ split >=0.2.3.1 && <0.3,+ text >=1.2.2.1 && <1.3,+ time >=1.6.0.1 && <1.7,+ transformers >=0.5.2.0 && <0.6,+ mtl >=2.2.1 && <2.3,+ containers >=0.5.5.1 && <0.6,+ array >=0.5.0.0 && <0.6,+ filepath >=1.4.1.0 && <1.5,+ template-haskell >=2.11.0.0 && <2.12,+ regex-posix >=0.95.2 && <0.96,+ regex-compat >=0.95.1 && <0.96,+ clay ==0.12.*,+ old-locale >=1.0.0.7 && <1.1,+ blazeT >=0.0.5 && <0.1,+ directory >=1.2.6.2 && <1.3,+ filesystem-trees >=0.1.0.6 && <0.2,+ formatting >=6.2.4 && <6.3+ default-language: Haskell2010+ hs-source-dirs: src+ ghc-options: -O2++executable lykah+ main-is: main.hs+ build-depends:+ base >=4.9.0.0 && <4.10,+ blaze-markup >=0.7.1 && <0.8,+ ListLike ==4.5.*,+ pandoc >=1.17.1 && <1.18,+ safe >=0.3.10 && <0.4,+ split >=0.2.3.1 && <0.3,+ text >=1.2.2.1 && <1.3,+ time >=1.6.0.1 && <1.7,+ transformers >=0.5.2.0 && <0.6,+ mtl >=2.2.1 && <2.3,+ containers >=0.5.5.1 && <0.6,+ array >=0.5.0.0 && <0.6,+ filepath >=1.4.1.0 && <1.5,+ template-haskell >=2.11.0.0 && <2.12,+ regex-posix >=0.95.2 && <0.96,+ regex-compat >=0.95.1 && <0.96,+ clay ==0.12.*,+ old-locale >=1.0.0.7 && <1.1,+ blazeT >=0.0.5 && <0.1,+ directory >=1.2.6.2 && <1.3,+ filesystem-trees >=0.1.0.6 && <0.2,+ formatting >=6.2.4 && <6.3+ default-language: Haskell2010+ hs-source-dirs: src+ other-modules:+ Lykah.Assets+ Lykah.BlazeTH+ Lykah.BuildingBlocks+ Lykah.EHtml+ Lykah.Structure+ Lykah.Style+ Lykah.Theme2+ Lykah.Formatting+ ghc-options: -O2 -threaded -rtsopts -with-rtsopts=-N -H1200M+
+ README.md view
@@ -0,0 +1,39 @@+# Lykah [](https://travis-ci.org/johannesgerer/Lykah) [](https://hackage.haskell.org/package/Lykah)++100% native Haskell, static (for now) website and blog generator library.++Build websites in a HTML monad with access to the site's and other context information (powered by [BlazeT](https://github.com/johannesgerer/blazet)).++Among other things, this enables *smart links*. The software will automatically include files that were actually referenced in the HTML; making broken (internal) links impossible.++## Comparison with other projects++In contrast to [yst](https://hackage.haskell.org/package/yst), there is no (yaml) configuration, instead the website is build in actual Haskell, as a value of type [Website](https://hackage.haskell.org/package/Lykah/docs/Lykah-Structure.html#t:Website). ++[hakyll](https://jaspervdj.be/hakyll/) treats the content — be it HTML or any other format — as a blackbox, which leaves a lot of Haskell's potential untapped. Lykah could, however, profit from hakyll's comfortable routing and file copying.++## Current state++It is an experimental state and only powers my own website [johannesgerer.com](http://johannesgerer.com). I would be delighted to hear back from other users.++Currently, it tries to find a good balance between generality and conciseness. Other things, like processing of external (static) files, were of no priority to me, but can be easily accomplished since the website can to anything you can to with Haskell ...++At this stage, code says more than a thousand READMEs, so simply check it out.+++## Usage and Example++```+stack build #only once to install dependencies++stack runghc -- -isrc src/main+```++This will build the website [johannesgerer.com](http://johannesgerer.com) in the `output` folder.++## Documentation++See source and docs on [Hackage](https://hackage.haskell.org/package/Lykah).+++
+ src/Lykah/Assets.hs view
@@ -0,0 +1,239 @@+{-# LANGUAGE OverloadedStrings+, TemplateHaskell+, MultiParamTypeClasses+, FlexibleInstances+, NoMonomorphismRestriction+, StandaloneDeriving+, ExistentialQuantification+, FlexibleContexts+, DeriveFunctor+, DeriveTraversable+, DeriveFoldable+ #-}+{-# OPTIONS_HADDOCK ignore-exports #-}+-- | Low-level module for Assets that are used to build websites.+module Lykah.Assets+ (+ Identifier+ ,Pathed (..)+ ,Env(ePaths,eIDs,eCur)+ ,Asset(..)+ ,strip+ ,IDO+ ,uniquify+ ,root+ ,isRoot+ ,getId+ ,reference+ ,Assets+ ,lookupId+ ,lookupNamedId+ ,generateAssets+ ,onlyUsed+ ,Text+ ,module Control.Applicative+ ,module Control.Arrow+ ,module Control.Monad+ ,module Control.Monad.Reader+ ,module Control.Monad.Trans+ ,module Control.Monad.RWS+ ,module Data.Char+ ,module Data.Either+ ,module Data.Function+ ,module Data.List+ ,module Data.Tuple+ ,module Data.Maybe+ ,module Data.Monoid+ ,module Data.Ord+ ,module Data.String+ ,module Data.Time.Calendar+ ,module Data.Time.Format+ ,module Data.Time.LocalTime+ ,module System.Directory+ ,module System.FilePath+ ,module Text.Printf+ ) where++import Control.Applicative+import Control.Arrow+import Control.Monad+import Control.Monad.Identity+import Control.Monad.RWS+import Control.Monad.Reader+import Control.Monad.State+import Control.Monad.Trans+import Data.Char+import Data.Either+import Data.Foldable hiding (mapM_)+import Data.Function+import Data.List+import qualified Data.Map.Strict as M+import Data.Maybe+import Data.Monoid+import Data.Ord+import Data.String+import Data.Text.Lazy (Text)+import qualified Data.Text.Lazy as T+import qualified Data.Text.Lazy.IO as T+import Data.Time.Calendar+import Data.Time.Format+import Data.Time.LocalTime+import Data.Tuple+import System.Directory+import System.File.Tree (getDirectory, copyTo_)+import System.FilePath+import Text.Printf+ +-- copyDirectory source target = getDirectory source >>= copyTo_ target+++type Identifier = String++data Pathed c = Pathed { aPath :: FilePath+ , aId :: Identifier+ , aName :: Maybe Text+ , aParent :: Maybe (Pathed ())+ , aContent :: c+ }+ deriving (Show,Functor) ++deriving instance Traversable Pathed+deriving instance Foldable Pathed++-- | Environment for the currently rendered Ehtml's asset+data Env a = Env { ePaths :: M.Map Identifier (Pathed ())+ -- ^ all available resources by ID+ , eIDs :: M.Map FilePath Identifier + -- ^ the reverse: mapping Path to IDs (unused so far) + , eCur :: a+ -- ^ the environment of the currently rendered+ -- asset+ }+ deriving Functor+++class IDO a where+ getId :: a -> Identifier++instance IDO String where+ getId = id++instance IDO (Pathed a) where+ getId = aId++-- | returns top-most parent+root :: Pathed a -> Pathed ()+root p@Pathed{aParent=Nothing} = strip p+root Pathed{aParent=(Just p)} = root p++isRoot :: Pathed c -> Bool+isRoot = isNothing . aParent++lookupNamedId :: (MonadWriter [Identifier] m,MonadReader (Env a) m)+ => Identifier -> m (Pathed ())+lookupNamedId = fmap f . lookupId+ where f p@Pathed{aName=Just _} = p+ f p@Pathed{aName=Nothing} =+ error $ printf "Identifier %s provides no name." $ aId p++-- | reference a Pathed value to ensure it is included in output+reference+ :: (IDO a, MonadWriter [Identifier] m, MonadTrans t) => a -> t m ()+reference i = lift $ tell [getId i]+ +lookupId :: (MonadWriter [Identifier] m,MonadReader (Env a) m)+ => Identifier -> m (Pathed ())+lookupId x = do+ paths <- reader ePaths+ maybe (error $ printf "Identifier %s not found in %s." x $+ unlines $ show <$> M.elems paths)+ (\p -> tell [x] >> return p) $ x `M.lookup` paths+ +-- | +-- is the result of the 'render' method from the 'Content' class+data Asset = Copy FilePath -- ^ the asset can be generated by copying+ -- this file+ | CopyDir FilePath-- ^ the asset can be generated by copying+ -- this directory+ | Write Text -- ^ the asset can be generated by writing+ -- this text+ | WriteIO (IO Text) -- ^ the asset can be generated by writing+ -- the result of an IO action+ | Concat [Asset] -- ^ the asset can be generated by concatenating+ -- the contents described by a list of assets++instance Monoid Asset where+ mempty = Concat mempty+ mappend a b = Concat $ on (++) toConcatList a b++toConcatList :: Asset -> [Asset]+toConcatList (Concat x) = x+toConcatList x = [x]++type Assets = [Pathed Asset]+++strip :: Pathed a -> Pathed ()+strip = fmap (const ())++-- | generate a map of Pathed objects with unique IDs and paths+uniquify :: [Pathed ()] -> Env ()+uniquify x = Env (fst $ execState action (M.empty,M.empty)) undefined ()+ where action = modify $ \(ids,paths) ->+ (M.unionWithKey check1 ids $ M.fromListWithKey check1 p1+ ,M.unionWithKey check2 paths $ M.fromListWithKey check2 p2)+ p1 = fmap (\p -> (aId p, p)) x+ p2 = fmap (\p -> (aId p, aPath p)) x+ check name iid a b = error $ printf+ ("Conflicting %s for '%s':\n%v\n%v")+ (name :: String) iid (show a) $ show b+ check1 = check "IDs"+ check2 = check "Paths"++ +-- | Generates all assets and their depending assets in the target dir+-- (careful: removes preexisting target dir)+generateAssets :: Bool -- ^ Debug+ -> FilePath -- ^ target directory+ -> Assets -> IO ()+generateAssets debug dir assets = do+ ef1 <- doesFileExist dir+ ef2 <- doesDirectoryExist dir+ when (ef1 || ef2) $ removeDirectoryRecursive dir+ when debug $ printf "Output dir: %s\n" dir+ mapM_ (performeAction . modifyAss) assets+ where+ modifyAss a@(Pathed{aPath = f}) = a{aPath=f'}+ where f' = if hasTrailingPathSeparator f then+ f </> "index.html"+ else f+ performeAction p@(Pathed{aPath=target, aId=aid}) = do+ createDirectoryIfMissing True $ takeDirectory at+ action' $ aContent p+ where+ action' (WriteIO action) = action >>= action' . Write+ action' (Write content) = do+ when debug $ printf "Writing content of '%s' to file %s\n" aid at+ T.writeFile at content+ action' (Copy source) = do+ when debug $ printf "Copying '%s' from %s to %s\n" source aid at+ copyFile source at+ action' (CopyDir source) = do+ when debug $ printf "Recursively copying '%s' from %s to %s\n" source aid at+ getDirectory source >>= copyTo_ at + action' x@(Concat _) = do+ when debug $ printf "Writing %s to %s\n" aid at+ extractContent x >>= T.writeFile at+ extractContent (Write text) = return text+ extractContent (WriteIO action) = action+ extractContent (Copy source) = T.readFile source+ extractContent (Concat xs) = T.concat <$> mapM extractContent xs+ extractContent (CopyDir _) = error "extractConcat (CopyDir _) not implemented"+ at = dir </> target+++-- | Extract only these assets of a list of IDs that are actually used+onlyUsed :: [Identifier] -> Assets -> Assets+onlyUsed used ass = M.elems $ M.intersection+ (M.fromList $ (\x -> (aId x, x)) <$> ass)+ $M.fromList $ (\x -> (x,())) <$> used
+ src/Lykah/BlazeTH.hs view
@@ -0,0 +1,39 @@+{-# LANGUAGE TemplateHaskell+,FlexibleInstances+,Rank2Types+, MultiParamTypeClasses+, OverloadedStrings+ #-}+module Lykah.BlazeTH where++import Language.Haskell.TH+import Text.BlazeT+++-- usage:+--+-- `dash [|toValue|] undefined ['href]`+--+-- defines `href' x = href $ toValue x` +--+dash :: Q Exp -> Q Type -- ^ this is (currently) ignored+ -> [Name] -> Q [Dec]+dash f _ = mapM $+ \n -> funD (mkName $ nameBase n ++ "'")+ [clause [varP x] --pattern+ (normalB --body+ [| $(varE n) $ $f $(varE x) -- :: $t , where t is the second argument above+ |]) []]+ where x = mkName "x"++dashAttr :: [Name] -> Q [Dec]+dashAttr = dash [|toValue|] [t|Attribute|]++dashTag :: [Name] -> Q [Dec]+dashTag = dash [|toMarkup|] [t|forall m. Monad m => MarkupT m ()|]+++-- instance Attributable (a -> MarkupM b) where+-- h ! f = (B.! f) . h+ +-- asd = html $ "asd"
+ src/Lykah/BuildingBlocks.hs view
@@ -0,0 +1,36 @@+{-# LANGUAGE OverloadedStrings+ #-}+module Lykah.BuildingBlocks+ where++import Lykah.Structure+import Lykah.EHtml+import Text.BlazeT+import qualified Text.BlazeT as B+import qualified Text.BlazeT.Html5 as H+import Text.BlazeT.Html5 hiding (head,link)+import qualified Text.BlazeT.Html5.Attributes as A+import Text.BlazeT.Html5.Attributes hiding (id)++more :: Html+more = ((tell "...") >>) $ H.a ! href "#" ! class_ "more" $ "(more)" ++clear :: Html+clear = H.div ! A.style "clear:both" $ ""+ +loremMore i = mapM_ (p.(>> more).tell) $ take i lorem++autoRelLink :: Identifier -> EHtml (Website,PathedPage a) ()+autoRelLink id = do Pathed{aPath=p, aName=n} <- lift $ lookupId id + readPathed aId+ maybe (error $ printf "Identifier %s provides no name." id)+ (link' p . (mappend $ fromString p )) $ n+++-- | Correct base meta tag pointing to top directory+autoBase :: SHtml a+autoBase = do+ curPath <- readPathed aPath+ let curBase = concat $ replicate+ (pred $ length $ takeWhile (/= ".") $ iterate takeDirectory curPath) "../"+ base ! href' curBase
+ src/Lykah/EHtml.hs view
@@ -0,0 +1,161 @@+{-# LANGUAGE FlexibleContexts #-}+{-# LANGUAGE OverloadedStrings+, TypeSynonymInstances+, FlexibleInstances+, TemplateHaskell+, NoMonomorphismRestriction+ #-}+{-# OPTIONS_HADDOCK ignore-exports #-}++-- | A writer monad that writes the assets needed for the current+-- asset (internal monad) while producing the current asset's HTML+-- (monad transformer)+module Lykah.EHtml+ (module Lykah.EHtml+ ,module Text.BlazeT.Renderer.Text+ )+ where++import qualified Data.ListLike as L+import qualified Data.ListLike.String as L+import qualified Data.Text.Lazy as T+import Lykah.Assets+import Lykah.BlazeTH+import qualified Text.Blaze as Bl+import Text.Blaze.Internal (Attributable)+import Text.BlazeT+import qualified Text.BlazeT as B+import qualified Text.BlazeT.Html5 as H+import Text.BlazeT.Html5 hiding (link)+import qualified Text.BlazeT.Html5.Attributes as A+import Text.BlazeT.Html5.Attributes hiding (id)+import Text.BlazeT.Renderer.Text+++-- | Html monad with inner Reader (See Env) and Writer Monad writing a list of identifiers of actually used assets+type EHtml self a = HtmlT (RWS (Env self) [Identifier] ()) a++readWebsite f = lift $ reader $ f . fst . eCur+readPathed f = lift $ reader $ f . snd. eCur+readPage f = readPathed $ f . aContent++askPathed :: EHtml (a,b) (b)+askPathed = lift $ reader $ snd . eCur+ +localEHtml :: (a -> b) -> EHtml b c -> EHtml a c+localEHtml f = mapMarkupT $ withRWS $ \r s -> (f <$> r,s) ++-- | render the given EHtml to the given path+renderEHtml :: (env,Pathed a) -- ^ current env and current asset to be rendered+ -> [Pathed ()] -- ^ All pathed entities that can be referenced+ -> EHtml (env,Pathed a) () -> (Assets,[Identifier])+renderEHtml cur refs x = ([fmap (const $ Write t) $ snd cur], ids)+ where (t,ids) = evalRWS (execWith renderHtml x) (uniquify refs){eCur=cur} ()+ +poster :: AttributeValue -> Attribute+poster = customAttribute "poster"++stylesheet x = H.link ! rel "stylesheet" ! type_ "text/css" ! href x+javascript x = script ! src x $ mempty++dashAttr ['href,'A.id,'A.style,'A.type_,'A.src,'poster,'A.for,'value,'class_, 'stylesheet, 'javascript]+dashTag ['a,'b,'h1,'video,'H.label, 'H.title,'script]++link' :: (ToValue h, Bl.ToMarkup t, Monad m) => h -> t -> MarkupT m ()+link' url = a' ! href' url ++link :: Monad m => AttributeValue -> MarkupT m () -> MarkupT m ()+link url = a ! href url++linkB' :: (ToValue h, Bl.ToMarkup t, Monad m) => h -> t -> MarkupT m ()+linkB' url = blank . link' url++linkB :: Monad m => AttributeValue -> MarkupT m () -> MarkupT m ()+linkB url = blank . link url++path' :: IDO a => a -> EHtml b FilePath+path' = path . getId++autoLink' :: IDO a1 => a1 -> EHtml a ()+autoLink' = autoLink . getId+ +path :: Identifier -> EHtml b FilePath+path = lift . fmap aPath . lookupId++-- | Link with given name and href from identifier plus optional+-- fragment+namedLinkF :: Maybe String -- ^ Fragment+ -> EHtml a ()+ -> Identifier+ -> EHtml a ()+namedLinkF fr name pId = do+ o <- lift $ lookupId pId + link (toValue $ maybe id (flip (#)) fr $ aPath o) name++namedLink :: EHtml a () -> Identifier -> EHtml a ()+namedLink = namedLinkF Nothing++namedLinkB :: EHtml a () -> Identifier -> EHtml a ()+namedLinkB n = blank . namedLink n++namedLinkF' :: IDO b => String -- ^ Fragment+ -> EHtml a () -> b -> EHtml a ()+namedLinkF' fr n = namedLinkF (Just fr) n . getId++namedLink' :: IDO b => EHtml a () -> b -> EHtml a ()+namedLink' n = namedLink n . getId++namedLinkB' :: IDO b => EHtml a () -> b -> EHtml a ()+namedLinkB' n = blank . namedLink' n++-- | creates a link with name from object+autoLink :: Identifier -> EHtml a ()+autoLink id = do+ Pathed{aPath=p, aName=(Just n)} <- lift $ lookupNamedId id + link' p n+ +anchor :: Monad m => AttributeValue -> MarkupT m ()+anchor n = H.div ! class_ "anchor" $ a ! name n $ preEscapedText " " ++(!#) :: Attributable h => h -> AttributeValue -> h+a !# b= a ! A.id b++(!$) :: (ToValue a, Attributable h) => h -> a -> h+a !$ b= a ! id' b++-- | append fragment+(#)+ :: (L.ListLike full item, IsString full) => full -> full -> full+a # b = L.concat [a,"#",b]++quote :: Monad m => MarkupT m () -> MarkupT m ()+quote x = mconcat [preEscapedText "“"+ ,x+ ,preEscapedText "”"+ ]+++lorem = [" Lorem ipsum dolor sit amet, consectetur adipiscing elit. Proin a nunc tortor. Praesent sagittis accumsan felis. Curabitur venenatis purus mollis augue consectetur euismod. Quisque mattis, tortor sit amet imperdiet vulputate, massa enim vestibulum nulla, eget aliquam nisi libero eu dui. Morbi imperdiet tortor eu augue lacinia, quis tristique diam placerat. Nunc quis lobortis est, quis adipiscing risus. Pellentesque nisi sem, convallis at posuere egestas, bibendum id turpis. Ut vestibulum sodales massa, quis egestas velit aliquam non."+ ,"Suspendisse eleifend quis quam pulvinar molestie. Fusce viverra blandit neque, nec fermentum odio ullamcorper in. Vivamus vel purus at mi imperdiet semper. Suspendisse sodales, elit in aliquet sodales, erat tortor gravida ipsum, sit amet consectetur mi ipsum sed justo. Etiam nisl justo, varius quis dolor a, adipiscing pharetra tortor. Aenean ultrices, est et volutpat pellentesque, nisi quam malesuada massa, vel bibendum nisl dui non neque. Nullam eu lacus a nibh vestibulum iaculis venenatis id diam. Aenean congue lacus et leo faucibus consectetur. Pellentesque id sem eget sem pellentesque sagittis. Integer non pretium nulla. Donec pellentesque urna in suscipit varius. Curabitur sollicitudin fermentum faucibus. Nunc sollicitudin ornare neque nec vestibulum. Nunc gravida consectetur lacinia. Duis semper aliquam vestibulum. Sed eleifend, nisi vel ultricies volutpat, dui tellus dictum orci, nec pulvinar turpis leo sed felis."+ ,"Interdum et malesuada fames ac ante ipsum primis in faucibus. Donec dignissim, urna quis aliquam pretium, magna justo consequat sem, eu dapibus odio urna a tortor. Nunc consectetur rutrum felis, ut blandit metus venenatis sit amet. Nullam blandit risus vitae imperdiet accumsan. Nam lobortis, ligula vitae hendrerit vehicula, mauris leo consequat felis, eget porttitor est ligula in dolor. Curabitur accumsan elit iaculis libero ultricies, et mattis eros pretium. Nam odio odio, dictum non nibh id, porta porta mi. Proin eu dolor cursus, ullamcorper diam ut, fringilla urna. Nulla tempor purus vel erat vulputate varius. Praesent sagittis fermentum mollis. Ut vestibulum gravida tristique. In hac habitasse platea dictumst. In eu tortor semper, pretium nisi vitae, euismod risus. Suspendisse nunc tortor, porta non aliquam ac, pulvinar et mauris. Duis at dolor sit amet elit sollicitudin mattis sed suscipit nunc. Nulla justo augue, feugiat condimentum pellentesque at, consequat vitae odio."+ ,"Donec nunc mauris, dapibus eu orci at, lacinia iaculis nulla. Maecenas in velit lacus. Duis sodales nisi eu arcu auctor volutpat. Sed id risus quis dolor fringilla commodo nec id dolor. Aenean id justo nec neque dignissim fringilla vitae sed est. Proin eu pellentesque dolor. Duis hendrerit ipsum non rhoncus euismod. Quisque elementum aliquet velit, eu fermentum augue rhoncus non. Nam eu sem tempor, ultrices libero sit amet, rutrum odio. Donec auctor, elit vitae gravida vestibulum, arcu quam tristique urna, eu pulvinar quam leo non leo."+ ,"Sed eget magna eros. Aliquam pellentesque felis quis pharetra faucibus. Suspendisse dignissim erat ut orci adipiscing, vel adipiscing mauris facilisis. Suspendisse mattis suscipit justo in scelerisque. Proin nec euismod neque, sit amet rhoncus nisl. Vestibulum in tristique velit. Mauris dignissim posuere metus, ut vehicula orci egestas sed. Quisque mattis massa eget lorem pharetra tincidunt. Interdum et malesuada fames ac ante ipsum primis in faucibus. Vivamus molestie ultrices tortor, ac consectetur velit hendrerit sed. Donec eleifend pellentesque ipsum, id euismod ante mattis cursus. Proin consequat dapibus fermentum. Nunc sagittis suscipit tortor, eu congue sem ullamcorper elementum. Cras in ipsum dictum, adipiscing felis ullamcorper, varius dui."]+++googleAnalytics id = script' $ T.concat + ["(function(i,s,o,g,r,a,m){i['GoogleAnalyticsObject']=r;i[r]=i[r]||function(){(i[r].q=i[r].q||[]).push(arguments)},i[r].l=1*new Date();a=s.createElement(o),m=s.getElementsByTagName(o)[0];a.async=1;a.src=g;m.parentNode.insertBefore(a,m)})(window,document,'script','//www.google-analytics.com/analytics.js','ga');ga('create', '"+ ,id+ ,"', 'auto');"+ -- do this after page load:+ -- ,"ga('require', 'displayfeatures');ga('send', 'pageview');"+ ]++stackExchange+ :: Monad m => AttributeValue -> AttributeValue -> MarkupT m ()+stackExchange id name = link ("http://stackexchange.com/users/" <> id) $+ img ! src' ("http://stackexchange.com/users/flair/" <> id <>".png")+ ! width "208" ! height "58" ! alt tt ! A.title tt+ where tt = "profile for " <> name <> " on Stack Exchange, a network of free, community-driven Q&A sites"+ +blank :: Attributable h => h -> h+blank x = x ! target "_blank"
+ src/Lykah/Formatting.hs view
@@ -0,0 +1,6 @@+module Lykah.Formatting+(module Formatting+,module Formatting.ShortFormatters) where++import Formatting+import Formatting.ShortFormatters
+ src/Lykah/Structure.hs view
@@ -0,0 +1,184 @@+{-# LANGUAGE ScopedTypeVariables #-}+{-# LANGUAGE FlexibleContexts #-}+{-# LANGUAGE OverloadedStrings+, TypeSynonymInstances+, FlexibleInstances+, Rank2Types+, TemplateHaskell+, MultiParamTypeClasses+-- ,NoMonomorphismRestriction+ #-}++-- | Types for the website's structure: Website, Theme, Pages, Types+module Lykah.Structure+ (module Lykah.Structure+ ,module Lykah.Assets+ ) where++import Control.Monad.Identity+import Control.Monad.Trans.Writer+import qualified Data.Map.Strict as M+import qualified Data.Text.Lazy as T+import Data.Traversable hiding (sequence)+import Lykah.Assets+import Text.BlazeT.Html (Html, string)+import Lykah.EHtml+++data Website = Website { wTheme :: Theme+ , googleAnalyticsId :: Text+ , wMenu :: [PathedPage ()]+ , wPosts :: [PathedPage Post]+ , wAssets :: Assets+ }++-- | eigentlich sollte das `HTML Template` heißen. TODO+type SHtml2 a b = EHtml (Website,PathedPage a) b+type SHtml a = SHtml2 a ()++data Theme = Theme { tAssets :: Assets+ , tPage :: forall a . SHtml a -> SHtml a+ -- ^ the first argument is an explicit+ -- `body`. This is needed because the body of+ -- current asset (in the enviromnent) should be+ -- used in a static fashion and not used for+ -- thinks like `tPage . tPost`+ , tPost :: Bool -> SHtml Post+ -- ^ Template for post rendering, with the first+ -- argument indicating full page or single blog+ -- entry+ , tMenuItem :: forall a .+ Int + -> Bool + -> PathedPage ()+ -> SHtml a+ -- ^ first arg: one-based index of the current item+ --+ -- second arg: menu item for current page?+ }++instance Show Theme where+ show a = "Theme"++data Page a = Page { pTitle :: Html+ , pBody :: SHtml a+ , pDetails :: a+ }++type PathedPage a = Pathed (Page a)++-- version ..._old : withPathed sonst sind die links z.b. auf blog posts falsch?++mapSHtml_old :: (Page b -> Page a) -> SHtml a -> SHtml b+mapSHtml_old f = localEHtml $ second $ fmap f++withPage_old :: Page a -> SHtml a -> SHtml ()+withPage_old p = mapSHtml_old (const p)+ +capture_old :: Page a -> Page ()+capture_old p = p{pDetails=()+ ,pBody= withPage_old p $ pBody p}+++ +localSHtml :: (Pathed (Page b) -> Pathed (Page a)) -> SHtml a -> SHtml b+localSHtml f = localEHtml $ second f++withPathed :: Pathed (Page a) -> SHtml a -> SHtml b+withPathed p = localSHtml (const p)++withPage :: Page a -> SHtml a -> SHtml b+withPage p x = do pathed <- askPathed+ withPathed (pathed{aContent=p}) x++-- runs a template with a given `pBody`+withBody :: SHtml a -- ^ Body+ -> SHtml a -- ^ Template that uses `Body`+ -> SHtml a -- ^ Result+withBody b x = do p <- askPage + withPage (p{pBody=b}) x+ +capture :: Pathed (Page a) -> Pathed (Page ())+capture pth = fmap capture' pth+ where capture' p = p{pDetails=()+ ,pBody= withPathed pth $ pBody p}++relax :: SHtml () -> SHtml a+relax = localSHtml capture+ +data Post = Post {poDate :: ZonedTime+ -- , poTags :: ()+ }+ deriving Show+ +renderWebsite :: Website -> Assets+renderWebsite website =+ onlyUsed used $+ assets' ++ rendered+ where s = fmap strip+ env = s (wMenu website) ++ s (wPosts website) ++ s assets'+ (rendered,used) = mconcat $ (renderPage askBody <$> (wMenu website))+ ++ (renderPage (tPost th True) <$> (wPosts website))+ renderPage :: (SHtml a) -> PathedPage a -> (Assets,[Identifier])+ renderPage templ p = renderEHtml (website,p) env $ tPage th templ+ assets' = wAssets website ++ tAssets th+ th = wTheme website++instance Show a => Show (Page a) where+ show p = "Page { pTitle = "++ T.unpack(renderHtml $ pTitle p)++", pBody, pDetails = "+ ++ show(pDetails p)++"}"++readDetails f = readPage $ f . pDetails++readTheme f = readWebsite $ f . wTheme+++askPage :: SHtml2 b (Page b)+askPage = readPathed $ aContent+++askTheme :: SHtml2 b (Theme)+askTheme = readWebsite $ wTheme++askBody :: SHtml b+askBody = join $ readPage pBody+++page ::+ Identifier+ -> Html -- ^ title+ -> FilePath -- ^ path+ -> Maybe (Pathed ()) -- ^ parent+ -> SHtml () -- ^ body+ -> PathedPage ()+page i t p pa c = Pathed p i (Just $ renderHtml t) pa $ Page t c ()++ +post ::+ Identifier+ -> FilePath -- ^ path+ -> String -- ^ zoned time in "%Y-%m-%d %H:%M %Z" format+ -> Html -- ^ title+ -> Maybe (Pathed a) -- ^ parent+ -> SHtml Post -- ^ body+ -> PathedPage Post+post i p time t pa c = Pathed p i (Just $ renderHtml t) (strip <$> pa)+ $ Page t c $+ Post $ parseTimeOrError False defaultTimeLocale "%Y-%m-%d %H:%M %Z" time++-- ^ renders a subset of posts+postList :: (Page Post -> Bool) -> SHtml ()+postList f = do pb <- readTheme $ flip tPost False+ posts <- readWebsite (filter (f . aContent) . wPosts)+ forM_ posts $ flip withPathed pb+ +++-- | Generates the Menu+menu :: SHtml a+menu = do+ theme <- readTheme tMenuItem+ cur <- readPathed $ aId . root+ let g ix pp = theme ix (cur == aId pp) pp+ zipWithM_ g [1..] =<< readWebsite wMenu+
+ src/Lykah/Style.hs view
@@ -0,0 +1,438 @@+{-# LANGUAGE OverloadedStrings+ #-}+module Lykah.Style where++import qualified Clay as C+import Clay hiding(table)+import qualified Clay.Media as M+import Clay.Stylesheet+import Control.Monad+import Data.Function hiding ((&))+import qualified Data.Text as T+import qualified Data.Text.Lazy as TL+import qualified Data.Text.Lazy.IO as TL+import Prelude hiding (div,span)++render' :: Css -> TL.Text+render' = renderWith compact []++pa :: Integer -> Clay.Stylesheet.StyleM ()+pa = (position absolute >>).zIndex+ +myCss :: T.Text+myCss = TL.toStrict $ render myCss'++smallScreen :: Css -> Css+smallScreen = queryOnly M.screen [M.maxWidth $ px threshold]++threshold :: Num a => a+threshold = 480++-- bw = True+bw = False++myCss' :: Clay.Stylesheet.StyleM ()+myCss' = do+ body ? do+ margin nil nil nil nil+ fAbel+ -- color white+ fontSize $ px 16+ fontWeight $ weight 300+ ".clearfix" & "::after" & do+ clear both+ display block+ content $ stringContent ""+ let clf w mw = do+ img ? do+ width $ px w+ maxWidth $ pct 98+ queryOnly M.screen [M.maxWidth $ px mw] $+ float none+ ".imgP" & do+ ".halfBorder" & img ? do+ borderRight solid (px 1) "#ccc"+ borderBottom solid (px 1) "#ccc"+ ".border" & img ? do+ border solid (px 1) "#ccc"+ ".clearfix" & do+ marginBottom $ px 16+ marginBottom $ px 0+ img ? do+ float floatLeft+ marginRight $ px 20+ marginBottom $ px 10+ ".normal" & clf 300 495+ ".small" & clf 220 415+ ".tiny" & clf 163 265+ ".right" & do+ textAlign $ alignSide sideRight+ marginRight $ px 5+ display block+ ".keyValue" & do+ td ? do+ firstChild & do+ width $ px 104+ verticalAlign vAlignTop+ a ? do+ paddingRight $ px 7+ "#v" & do position fixed+ left nil >> right nil >> top nil+ overflow hidden+ height $ pct 100+ video ? do pa 0+ span ? do pa 1+ bottom $ px 10+ right $ px 10+ "#content" & do+ zIndex 1+ -- left $ px 100+ marginTop $ px 100+ smallScreen $ do+ marginTop $ px 0+ marginBottom $ px 30+ -- display table+ marginLeft auto+ marginRight auto+ maxWidth $ px 670+ ".anchor" & do+ position relative+ a ? do+ position absolute+ top $ em $ -2+ visibility hidden+ ".menu" & do+ fontSize $ px 20+ fJul+ marginBottom $ px 4+ let fe x = div ? ".fixedExtra" & x+ fe $ do+ paddingBottom $ px 11+ paddingLeft $ px 9+ display none+ ".title" & do+ cursor pointer+ ".fixed" ? do+ fe $ display block+ (".collapsed" &) $ fe $ display none+ div ? ".collapsedExtra" & do+ float floatLeft+ whiteSpace nowrap+ ".title" & do+ paddingLeft $ px 5+ display none+ float none+ overflow hidden+ textOverflow overflowEllipsis+ cursor pointer+ ".hamburger" & do+ -- visibility hidden+ display none+ span ? do+ -- top $ px $ -2+ -- left $ px $ -15+ padding'' 1 4 16 4+ verticalAlign $ vAlignTop+ fontSize $ px 12+ hover & cursor pointer+ ".menuItem1" ? do+ clear clearLeft+ ".collapsed" ? do+ fontSize $ px 16+ ".menuItem1" ? do+ clear none+ span ?".preload" & do+ position fixed+ left $ px $ -100+ visibility hidden+ ".hidden" <? do+ visibility hidden+ -- a ? fontWeight bold+ -- div <? a ? firstChild & do+ -- fontWeight normal+ a ? ".name" & ".short" <? do+ display none+ ".visible" <? do+ position relative+ div <? do+ boxShadow (px 1) (px 1) (px 2) $ setA 0.85 $ white+ position absolute+ background white+ width $ pct 100+ ".fixed" & do+ zIndex 3+ top $ px 0+ position fixed+ width $ pct 100+ -- left $ px 0+ ".collapsed" & do+ a ? ".name" & do+ marginRight $ px $ -2+ ".long" <? display none+ ".short" <? display block+ a ? ".inactive" & do+ opacity 0+ paddingLeft $ px 0+ paddingRight $ px 0+ marginLeft $ px 0+ marginRight $ px 0+ a ? ".active" & do+ width auto+ marginRight $ px 5+ marginLeft $ px 5+ -- paddingRight $ px 0+ -- borderRight solid (px 1) "#ccc"+ -- content $ stringContent ""+ -- height $ px 50+ -- position relative+ -- left + div ? ".collapsedExtra" & do+ ".hamburger" & do+ -- visibility visible+ display block+ div <? div <? do+ paddingTop $ px 11+ a <? do++ queryOnly M.screen [M.maxWidth $ px threshold] $ do+ width $ px 101+ textAlign $ alignSide sideLeft+ ".finance" & do+ width $ px 83+ + ++ width auto+ textAlign center+ + whiteSpace nowrap+ + let t = 0.5+ tp = [("width", t, ease, 0)+ ,("padding", t, ease, 0)+ ,("margin", t, ease, 0)+ ,("opacity", t, easeOut, 0)+ ]+ transitions tp+ display inline+ float floatLeft+ + marginRight $ px 14+ marginLeft $ px 9+ paddingLeft $ px 0+ paddingRight $ px 0+ paddingBottom $ px 0+ marginBottom $ px 11++ firstChild & do+ width auto+ transitionDuration 0+ paddingRight $ px 8+ -- this was needed to visually align the first letter when+ -- it was still a 'J'+ -- marginLeft $ px 11+ cursor cursorDefault+ -- background $ t c19+ -- hover & do+ -- fontWeight normal+ fontWeight bold+ hover & do+ fontWeight bold+ ".active" & do+ transitionDuration 0+ fontWeight bold+ "::after" & do+ display block+ content $ attrContent "data-text"+ fontWeight bold;+ height $ px 1+ color transparent+ overflow hidden+ visibility hidden+ marginBottom $ px $ -1+ ".body" & do+ -- textAlign justify+ -- background $ rgba 139 160 68 98+ -- rgba(4, 24, 65, 0.38) -- dunkel+ -- background $ rgba 9 8 5 148+ -- rgba 138 156 73 128+ -- rgba(116, 145, 27, 0.58)+ + float floatLeft+ -- width $ px 610+ -- height $ px 800+ padding' $ px 11+ p ? marginTop (px 0)+ marginBottom $ px 4+ maxWidth $ px 550+ ".section" & do+ float floatLeft+ "#contentBody" & do+ marginTop $ px 42+ smallScreen $ do+ marginTop $ px 22+ a ? do+ textDecoration underline+ ".title" ? do+ fJul+ -- background $ setA 0.352 $ c263+ clear clearLeft+ marginRight $ px 4+ marginBottom $ px 4+ padding' $ px 11+ -- "word-break" -: "break-all"+ -- width $ px 740+ cursor cursorDefault+ -- textShadow (px $ -2) (px 4) (px 3) "#000000"+ fontWeight normal+ h1 ? ".title" & do+ fontSize $ px 41+ marginTop $ px 0+ maxWidth $ px 590+ h2 ? ".title" & do+ fontSize $ px 32+ marginTop $ px 0+ paddingLeft $ px 0+ maxWidth $ px 590+ h3 ? ".title" & do+ fontSize $ px 23+ marginTop $ px 0+ paddingLeft $ px 0+ h4 ? ".title" & do+ fontSize $ px 19+ marginTop $ px 0+ paddingLeft $ px 0+ ".none" & display none+ ".entry" & do+ paddingRight $ px 40+ float floatLeft+ position relative+ -- ".body" ? do+ ".title" ? do + fontSize $ px 35+ paddingBottom $ px 5+ ".single" & ".title" ? do + fontSize $ px 45+ ".date" ? do + fAbel+ paddingTop $ px 2+ -- pa 2+ fontSize $ px 13+ textAlign end+ + footer ? do+ fontSize $ px 11+ textAlign center+ paddingTop $ px 40+ clear both+ a ? do+ textDecoration none+ color black++ ".more" ? do+ -- color "#FF7171"+ fontWeight $ weight 700+ ++ unless bw $ + ".menu" & do+ ".menuItem1" ? color+ -- "#007a4b"+ "#00c100"+ -- "#009100"+ ".letterSpace" ? do+ letterSpacing $ px 1+ ".menuItem2" ? color+ -- "#b500b5"+ "#a100ff"+ ".menuItem3" ? color+ -- "#007faa"+ "#00b7e4"+ -- "#0083a3"+ ".menuItem4" ? color "#e00000"+ ".menuItem5" ? color -- "#d4be00"+ -- "#eabd07"+ "#deb200"+ -- "##9f8000"+ -- "#8d6708"+ -- ".aboutColor" & color (cb 1)+ -- ".financeColor" & color (cb 2)+ -- ".softwareColor" & color (cb 3)+ -- ".physicsColor" & color (cb 4)+ -- ".blogColor" & color (cb 5)+ code ? do+ padding' $ em 0.2+ quad borderRadius $ px 3 + backgroundColor "#f3f3f3"+ fontSize $ pct 85++t = setA 0.625+shad = textShadow 0 0 (px 2) "#000000"+ +juliusFace = fontFace $ do+ fontFamily ["Julius"] []+ fontFaceSrc [FontFaceSrcUrl "fonts/JuliusSansOne-Regular.ttf"+ $ Just TrueType+ ,FontFaceSrcLocal "Julius Sans One"+ ,FontFaceSrcLocal "JuliusSansOne-Regular"+ ]++fJul = + fontFamily ["Julius Sans One"] [sansSerif] -- google+ -- fontFamily ["Julius"] [sansSerif]+fAbel = fontFamily ["Merriweather"] [serif]++ +main = TL.putStr $ flip TL.snoc '\n' $ render myCss'++padding' = quad padding+quad y x = y x x x x+padding'' a b = on padding px a b `on` px+margin' x = margin x x x x++c1 = "#566f11"+c2 = "#729415"+c3 = "#3a500c"+c4 = "#1f3009"+c5 = "#8aa31b"+c6 = "#9ea856"+c7 = "#7f9b4b"+c8 = "#eff2a8"+c9 = "#f5fbe4"+c10 = "#9a722b"+c11 = "#6d754f"+c12 = "#583410"+c13 = "#dbb964"+c14 = "#e5de76"+c15 = "#afb697"+c16 = "#b9d471"+c17 = "#997b4b"+c18 = "#bfd8a5"+c19 = "#993511"+c20 = "#d86c20"+c201 ="#eb7523"+c21 = "#dbbb94"+c22 = "#d99529"+c23 = "#2f5546"+c24 = "#659488"+c25 = "#cc4444"+c26 = "#88cccc"+c261= "#00cccc"+c262= "#1480d0"+c263= "#0998f8"+ +ca 1 = "#00ff22"+ca 2 = "#0085e8"+ca 3 = "#ae00ff"+ca 4 = "#e82c0c"+ca 5 = "#ffcd00"++cb 1 = "#0cf574" +cb 2 = "#118ab2" +cb 3 = "#00ffe7" +cb 4 = "#ef476f" +cb 5 = "#ffe716" +++ +pos [t,l,w,h] = top (px t) >> left (px l) >> width (px w) >> height (px h)
+ src/Lykah/Theme2.hs view
@@ -0,0 +1,188 @@+{-# LANGUAGE FlexibleContexts #-}+{-# LANGUAGE OverloadedStrings+, TypeSynonymInstances+, FlexibleInstances+, Rank2Types+, TemplateHaskell+, MultiParamTypeClasses+-- ,NoMonomorphismRestriction+ #-}+-- | Themes are used to describe how to render a website/structure+-- into HTML+module Lykah.Theme2+ (theme+ ,sect+ ,subSect+ ,floatingImage+ ,loremMore+ ) where++import Control.Monad.Identity+import qualified Data.Map.Strict as M+import qualified Data.Text.Lazy as T+import qualified Data.Text.Lazy.IO as T+import Data.Traversable hiding (sequence)+import Lykah.Structure+import Lykah.EHtml+import Lykah.BuildingBlocks+import qualified Lykah.Style as S+import Text.BlazeT+import qualified Text.BlazeT as B+import Text.BlazeT.Html5 hiding (head,link,menu)+import qualified Text.BlazeT.Html5 as H+import Text.BlazeT.Html5.Attributes hiding (id)+import qualified Text.BlazeT.Html5.Attributes as A+++-- * Static Assets++theme :: Theme+theme = Theme ([myCss,retina,js,fonts]) t1page bentry menuItem++-- TODO: beispiel: einfach pfad geändert und alles andere automatisch+myCss = Pathed "css/style.css" "myCss" Nothing Nothing+ $ mconcat [Copy "assets/css/googlefonts.css"+ ,Write $ T.fromStrict S.myCss+ ,Copy "assets/css/glyphicons.css"+ ]++fonts = Pathed "fonts" "fonts" Nothing Nothing $ CopyDir "assets/fonts"+ +js = Pathed "main.js" "js" Nothing Nothing $ mconcat+ [Copy "assets/js.js"+ ]++retina = Pathed "retina.js" "retina" Nothing Nothing $ Copy "assets/retina.min.js"++t1page :: SHtml a -- ^ Body+ -> SHtml a+t1page body = do+ Page t _ _ <- askPage+ docTypeHtml $ do+ H.head $ do+ autoBase+ meta ! charset "UTF-8"+ meta ! name "viewport" ! content "width=device-width,initial-scale=1.0"+ --,maximum-scale=1.0"+ path' myCss >>= stylesheet'+ javascript "//code.jquery.com/jquery-1.11.1.min.js"+ path' js >>= javascript'+ googleAnalytics =<< readWebsite googleAnalyticsId + -- stylesheet "http://fonts.googleapis.com/css?family=Julius+Sans+One%7CMerriweather:300,400,700&subset=latin,latin"+ reference fonts+ + + tit <- readPage pTitle+ H.title $ string "Dr. Johannes Gerer - " >> tit+ H.body $ do+ let mh = H.div $ H.div $ do+ -- H.div ! class_ ("collapsedExtra hamburger") $ do+ -- H.span ! class_ "glyphicon glyphicon-menu-hamburger" $ ""+ a ! class_ "name" $ do+ H.span ! class_ "long" $ do+ text "D"+ H.span ! class_ "letterSpace" $ "r."+ " Johannes Gerer"+ H.span ! class_ "short" $ "J. Gerer"+ menu+ clear+ menuExtra "fixedExtra"+ H.div ! A.id "content" $ do+ H.div ! class_ "menu" $ do+ mh ! class_ "visible"+ mh ! class_ "hidden"+ clear+ H.div ! A.id "contentBody" $ do+ body+ + footer $ sequence_+ ["Site proudly generated by "+ ,link "http://johannesgerer.com/Lykah" "Lykah"+ ,"."]+ path' retina >>= javascript'+ ++menuExtra :: AttributeValue -> SHtml a+menuExtra f = do+ H.div ! class_ (f <> " title") $ H.span mempty+ +menuItem :: Int+ -> Bool -- ^ menu item for current page?+ -> PathedPage ()+ -> SHtml a+menuItem ix active p@Pathed{aName=(Just n)} = do+ -- the following ensures, that the assets is referenced and thus included+ path <- path' p+ when active $ do+ H.span ! class_ "preload glyphicon glyphicon-menu-down" $ ""+ H.div ! class_ ("collapsedExtra hamburger") $ do+ H.span ! class_ "glyphicon glyphicon-menu-down" $ ""+ (! class_' (toValue $ intercalate " " $+ [g active, aId p, "menuItem" <> show ix ])) $+ a ! href' path ! dataAttribute "text" (toValue n) $ do+ lazyText n+ when active $ menuExtra "collapsedExtra"+ where g True = "active"+ g False = "inactive"+menuItem ix a p = error $ "menuItem not supported for unnamed pages:\n"+ ++ show (ix, a, p)+++-- | Blog Entry+bentry :: Bool -- ^ full page and not single blog entry+ -> SHtml Post+bentry single = do+ Page t body (Post dat) <- askPage+ let d = string $ formatTime defaultTimeLocale "%x %R %Z" (dat ::ZonedTime)+ H.div ! class_' (postClass "entry" ::String) $ do+ H.div ! class_ "title" $ do+ postTitle t+ H.div ! class_ "date" $ d+ clear+ H.div ! class_ "body" $ body+ clear+ where+ (postClass,postTitle) = if single then ((++" single"),id)+ else (id, \n -> do+ p <- join $ readPathed path'+ H.a ! A.href (toValue p) $ n)++-- | Section+sect :: Monad m => Markup2 -> MarkupT m () -> MarkupT m () -> MarkupT m ()+sect h t b = H.div ! class_ "section" $ do+ h ! class_ "title" $ t+ H.div ! class_ "body" $ b ++subSect :: Monad m => Markup2 -> MarkupT m () -> MarkupT m () -> MarkupT m ()+subSect h title body = do+ h ! class_ "title" $ title+ body+++-- | floating image with text, terminated by a clearfix.+floatingImage :: IDO a+ => Int -- ^ size+ -> Bool -- ^ the image should be wrapped in a link to open it in blank+ -> String -- ^ classes+ -> Maybe a -- ^ the image has a downscaled version+ -> a -- ^ src+ -> Maybe (EHtml b ()) -- ^ the image should have this description and a clearfix after it+ -> EHtml b ()+floatingImage s withLink classes downscaled pId body =+ H.div ! class_' (intercalate " "+ ["imgP"+ ,sc s+ ,cl body+ ,classes+ ]) $ do+ path <- path' pId+ let a = if withLink then flip namedLinkB' pId+ else id+ path2 <- maybe (return path) path' downscaled+ a $ img ! src' path2 ! dataAttribute "rjs" (toValue path)+ fromMaybe mempty body+ where sc 1 = "small"+ sc 2 = "normal"+ sc 0 = "tiny"+ cl Nothing = ""+ cl _ = "clearfix"
+ src/main.hs view
@@ -0,0 +1,401 @@+{-# LANGUAGE FlexibleContexts #-}+{-# LANGUAGE OverloadedStrings+, TypeSynonymInstances+, FlexibleInstances+, MultiParamTypeClasses+, NoMonomorphismRestriction+ #-}++import qualified Data.Text as T+import qualified Data.Text.IO as T+import Lykah.EHtml+import Lykah.Formatting (sformat, (%), (%.))+import qualified Lykah.Formatting as F+import Lykah.Structure+import Lykah.Theme2+import Text.BlazeT+import qualified Text.BlazeT as B+import qualified Text.BlazeT.Html5 as H+import Text.BlazeT.Html5 hiding (head,link,main)+import qualified Text.BlazeT.Html5.Attributes as A+import Text.BlazeT.Html5.Attributes hiding (id)+-- import Lykah.Pandoc++-- todo: use Shake to leverage things like newCache+-- http://declaredvolatile.org/blog/2014-09-14-writing-a-simple-blog-with-shake-and-pandoc/++-- against automatic slug creation: a filename should NEVER change. so+-- produce a slug once and keep it in the meta data. do not use the+-- filename of the post or derive it from the title with (changing+-- implementations,...)++-- contents in markdown? file:///usr/share/doc/terminator/html/advancedusage.html++main :: IO ()+main = do+ posts <- myPosts+ let w = Website theme "UA-27460943-1" pages posts assets+ generateAssets True "output" $ renderWebsite w+ return ()++assets :: [Pathed Asset]+assets@(photo:photoLR:wincor:diss:diplom:tensor:dirac:memisScreen:+ haxCharts:_) =+ [Pathed "img/me.jpg" "meJpg" Nothing Nothing+ $ Copy "assets/me1.jpg"+ ,Pathed "img/me_low_res.jpg" "meJpgLowRes" Nothing Nothing+ $ Copy "assets/me2.jpg"+ ,Pathed "img/wincor_nixdorf_keyboard.jpg" "wincor" Nothing Nothing+ $ Copy "assets/wincor_nixdorf_keyboarb.jpg"+ ,Pathed "pdf/diss_gerer.pdf" "diss" Nothing Nothing+ $ Copy "assets/diss_online.pdf"+ ,Pathed "pdf/diploma_thesis_gerer.pdf" "diplom" Nothing Nothing+ $ Copy "assets/Diplom.pdf"+ ,Pathed "pdf/tensor_charge_gerer.pdf" "tensor" Nothing Nothing+ $ Copy "assets/Tensorladung.pdf"+ ,Pathed "pdf/dirac_inversion_gerer.pdf" "dirac" Nothing Nothing+ $ Copy "assets/DOInversion.pdf"+ ,Pathed "img/memis.png" "memisScreen" Nothing Nothing+ $ Copy "assets/memis.png"+ ,Pathed "img/hax_charts.png" "haxCharts" Nothing Nothing+ $ Copy "assets/hax_charts.png"+ ]+ <> fmap fst (hedgefundScreens :: [(Pathed Asset, MarkupT Maybe ())])+ <> engineScreenshots++hedgefundScreens :: Monad m => [(Pathed Asset, MarkupT m ())]+hedgefundScreens = (g *** p) <$>+ [("setup", "The marginal distributions are fitted using the " <> code "R" <> " executable and historic price data.")+ ,("preferences", "The user enters their current portfolio, transaction costs and risk measure and then starts the calculation.")+ ,("optimal_portfolios", "The result is an interactive plot of the efficient frontier. (The discontinuities are due to short selling and borrowing restrictions.)")+ ]+ where g src = Pathed ("img" <> src2) src Nothing Nothing $ Copy $ "assets" <> src2+ where src2 = "/hedgefund/" <> src <.> "jpg"++engineScreenshots :: [Pathed Asset]+engineScreenshots = g <$>+ ["2003_v1"+ ,"2004"+ ,"2005_1"+ ,"2005_2"+ ,"2006_rotation"+ ,"2007"+ ]+ where g src = Pathed ("img/61engine-" <> src <> ".png") src Nothing Nothing+ $ Copy $ "assets/screenshots/" <> src <> ".png"+ +pages@[about,finance,software,physics,blog] = [+ page "about" "About me" "./" Nothing $+ H.div ! class_ "body" $ do+ floatingImage 2 True "" (Just photoLR) photo $ Just $ mapM_ p+ ["I am a finance scholar, a software engineer and a physicist. My passion is tackling problems that can only be solved with the use of mathematics and/or technology."+ ,do+ text "Recently, I have received a PhD (Dr. rer. pol) in mathematical finance from the University of Regensburg, Germany. "+ namedLink' "See more." finance+ ,do+ text "Before that, I pursued a Master of Science degree (Diplom) in theoretical particle physics. "+ namedLink' "See more." physics+ ,do+ text "In addition to my formal education, I have gained significant experience in software development. My research both in finance and physics is computationally challenging. Furthermore, I used my skills as a freelance developer to finance my studies. "+ namedLink' "See more." software+ ]+ subSect H.h3 "Contact" $ do+ table ! class_ "keyValue" $ mapM_ (tr . mapM_ td) $+ [["Email"+ ,emailAddress+ ]+ ,["Social"+ ,sequence_ $ intersperse " "+ [linkB "https://www.linkedin.com/in/dr-hans-wurscht-8827694/"+ "LinkedIn"+ ,linkB "https://github.com/johannesgerer/"+ "Github"+ ,linkB "http://stackoverflow.com/users/578832/johannes-gerer?tab=profile"+ "StackOverflow"+ ]+ ]+ ,["Residence"+ ,"Munich, Germany"+ ]+ ]+ ,page "finance" "Finance" "finance/" Nothing $ do+ H.div ! class_ "body" $ do+ p $ do+ text "In November 2016, I received my PhD (Dr. rer. pol.) in mathematical finance summa cum laude from the "+ linkB "http://www.uni-regensburg.de/business-economics-and-management-information-systems/business-dorfleitner/index.html"+ "Department of Finance"+ " at the University of Regensburg, Germany."+ subSect (H.h2 ! dataAttribute "title" "Dissertation")+ "Dissertation: Essays on derivatives pricing in incomplete markets" $ do+ mapM_ p+ [do text "My dissertation can be downloaded "+ blank $ namedLink' "here" diss+ " (PDF, 134 Pages, 0.9 MB). It is cumulative and consists of the following four research articles:"+ ]+ publication "Time consistent pricing of options with embedded decisions"+ ["G. Dorfleitner"]+ ["Under review for publication in ", em "Mathematical Finance"] $ do+ "arst"+ publication (anchor amop <> "Optimal discrete hedging of American options")+ ["G. Dorfleitner"]+ ["Accepted for publication in ", em "Review of Derivatives Research"+ , " subject to minor revisions"+ ] $ do+ "The numerical results have been obtained using my C++ pricing software "+ <> namedLinkF' fipster "fipster" software+ <> "."+ publication "A note on utility indifference pricing"+ ["G. Dorfleitner"]+ [linkB "http://www.worldscientific.com/doi/abs/10.1142/S0219024916500370?src=recsys&"+ "Published"+ , em " in the International Journal of Theoretical and Applied Finance"+ ] $ do+ "rst"+ publication "The pricing efficiency of exchange-traded commodities"+ ["G. Dorfleitner", "A. Gerl"]+ [linkB "http://link.springer.com/article/10.1007/s11846-016-0221-0"+ "Published"+ , em " in Review of Managerial Science"+ ] $ do+ "st"+ subSect (H.h2 ! dataAttribute "title" "") "Unpublished Stuff" $ do+ "discrete hedging"+ subSect ((anchor pfa <>) . (H.h2 ! dataAttribute "title" ""))+ "Postbank Finance Award 2008" $ do+ p $ do+ text "During my physics studies, I took part in a project together with "+ <> "three finance students to gauge my interest in the field. It won the first prize of the "+ linkB "https://www.postbank.de/postbank/ka_finance_award_2008.html"+ "Postbank Finance Award 2008"+ text " and € 10.000 for the university and has been published in "+ linkB "http://epub.uni-regensburg.de/11449/"+ "Hegde-Fonds – Chancen und Risiken"+ " under the following title."+ chapter "How can private investors profit from investing in hegde funds?" $ do+ mapM_ p+ ["(original German title: Wie können Privatanleger durch den Einsatz von Hedge-Fonds profitieren?)"+ , sequence_+ ["My contribution consists of a Java program that calculates and shows optimal portfolios under different risk measures—namely Sigma, VaR, CVaR, Shortfall probability—that can be achieved by adding one hedgefund tracking product to a given portfolio. It employs a nonparametric empirical Copula Approach (Williams & Ioannidis 2007) and normal-inverse Gaussian distributions as marginal distributions fitted using "+ ,code "nigFit" <> " from the " <> code "R" <> "-package " <> code "fBasics" <> "."+ ]+ ,"Screenshots:"+ ]+ mapM_ (uncurry (floatingImage 1 True "" Nothing) . second Just) hedgefundScreens+ ,page "software" "Software" "software/" Nothing $ do+ H.div ! class_ "body" $ mapM_ p + ["Writing software is and has always been an important role in my research in physics and finance, in my side job as freelance software engineer and in my private life. I am proficient in the languages listed below."+ ,"I have contributed to a handful of open source projects. "+ <> "Some of my own projects are published under an open source license and presented on this page."+ ]+ sect H.h1 "Haskell" $ do+ mapM_ p $+ [ "After stumbling upon Haskell in 2012 it quickly became my preferred "+ <> "language for everything that cannot be done in a few lines in the shell."+ , sequence_+ ["In my opinion there is no better way to write effectful programs than by using a side effect-free functional language: compose in a pure, mathematical manner a value of type "+ ,code "Program" <> ", which in Haskell is called " <> code "IO ()"+ ,", and pass it to the runtime."]+ ]+ softwareProject "Buchhaltung" (Just "buchhaltung") $ do+ p $ "A set of tools for creating a complete ledger of all your bank and savings accounts', credit cards', and other transactions, in a "+ <> linkB "http://plaintextaccounting.org/" "text-based ledger format"+ <> ". Key features:"+ ul $ mapM_ li+ ["Import transactions via HBCI/OFX interfaces or from CSV files and handle duplicates comfortably."+ ,"Match transactions to accounts with high accuracy through the "+ <> "use of a Bayes classifier."+ ,"Manually enter transactions using an intelligent "+ <> "editor which reduces the required effort to a minimum."+ ]+ softwareProject (anchor lykah <> "Lykah") (Just "lykah") $ p $ mconcat+ ["A static website and blog generator powering "+ ,linkB "http://johannesgerer.com" "johannesgerer.com"+ ,". It allows you to create websites with the "+ ,"full power and expressivity of Haskell. "+ ,"Its name, Lykah, is " <> linkB "https://jaspervdj.be/hakyll/" "Hakyll"+ ," in reverse because it solves the same problem using quite a different approach."]+ softwareProject "BlazeT" (Just "blazet") $ p $ mconcat+ [text "A true monad (transformer) version of the great "+ ,linkB "https://jaspervdj.be/blaze/" "blaze libraries"+ ,". I started this project to be able to make "+ ,namedLinkF' lykah "Lykah" software+ ,"."+ ]+ softwareProject "Doi" (Just "doi") $ mapM_ p $+ ["After I successfully switched my research/citation management from Mendeley to "+ <> linkB "http://joostkremers.github.io/ebib/" "ebib"+ <> " (BibTeX database manager for Emacs), the system still lacked an easy way to download or extract citation information. "+ ,code "doi" <> " fills this gap. Given a DOI or URL, it will generate its BibTeX entry (both from the official source and from Bibsonomy) and can download the fulltext PDF, all automatically." + ]+ softwareProject "Google Code Jam" Nothing $+ p $ "This package makes writing concise and fast solutions to Google Code Jam problems easy, including automatic parallelization if possible. The Github "+ <> linkB "https://github.com/johannesgerer/googleCodeJam" "repository"+ <> " also contains my Code Jam submissions."+ softwareProject "Memis – efficient manual image sorting" (Just "memis") $ do+ floatingImage 1 True "halfBorder" Nothing memisScreen Nothing+ p $ "In order to make the paperless office feasable, I needed a fast way to categorize and sort the scanned documents. The existing solutions were not satisfactory, which lead to the creation of Memis."+ softwareProject "Hax" (Just "hax") $ do+ floatingImage 1 True "border" Nothing haxCharts Nothing+ p $ "A library for cash-flow and tax simulations. The type system is used to ensure correctness e.g. of double-entry accounting, and the correct calculation of taxes. The use of Haskell's " <> code "do" <> "-notation and type-classes permit an almost verbatim translation of the tax code into the program."+ p "It currently includes personal income tax (Einkommensteuer), corporate tax (Körperschaftsteuer) and trade/business tax (Gewerbesteuer), but could easily be extended to other legal systems."+ sect H.h1 "C++" $ do+ H.div $ mapM_ p $+ [ "What I appreciate about C++ is its expressiveness, while still being close enough to the machine to enable zero-cost abstractions. This makes it suitable for my use case: numerical software."+ ]+ softwareProject (anchor fipster <> "Fipster") (Just "fipster") $ do+ p $ "Option pricing software for complex financial options on arbitrary dimensional state space using finite differences. "+ <> " It powers the numerical results published in my paper on "+ <> namedLinkF' amop "American options" finance+ ul $ mapM_ li+ ["Expectation based pricing (risk neutral valuation, utility indifference pricing, etc.)"+ ,"Derive optimal/pessimal strategies for decision dependent payoffs (like exercise decisions in American options or optimal hedging decisions)"+ ,"Efficient solver for linear complementary problems"+ ,"Rannacher smoothing"+ ,"Implemented for arbitrary dimensional state space"+ ,"Automatic handling of any combination of boundary conditions"+ ,"Automatic parallelization"+ ]+ softwareProject "Enhance" (Just "enhance") $ do+ mapM_ p + ["When writing Fipster (see above) I had to write comparison operator for 20+ classes, which felt very repetitive. Then, I switched from "+ <> code "std::map" <> " to " <> code "std::unordered_map"+ <> ", which instead of comparison operators requires completely analogous hash functions for all classes."+ ,"To spare myself all this boilerplate I wrote an early version of Enhance. Over the years, Enhance was expanded in functionality, polished and published. Now, it can "+ <> quote "enhance" <> " your classes: with a minimum of code and no runtime overhead it will provide you with"+ ]+ ul $ mapM_ li+ ["comparison operators,"+ ,"assignment operators,"+ ,"hash functions,"+ ,"swap functions,"+ ,"serialization,"+ ,"pretty printing,"+ ,"etc."+ ]+ softwareProject "61 Engine" (Just "61-Engine") $ do+ p "The 61 Engine is an OpenGL based 3D (hexadecimal for 61) and basic physics (rigid body and gravitation) engine written for simple games and educational purposes. It was one of my first larger projects, started at age 15 with the goal of writing a more realistic version of GTA3. ;-)" + zipWithM_ (floatingImage 0 True "" Nothing) engineScreenshots $ repeat Nothing+ sect H.h1 "Java" $ do+ H.div $ mapM_ p $+ [ do+ quote ("I had a problem so I thought to use Java. Now I have a " <> code "ProblemFactory")+ <> br+ H.span ! class_ "right" $ "— Cpt. Picard"+ ]+ softwareProject "Portfolio Optimization" Nothing $ p $ + "My contribution to the winning entry of the "+ <> namedLinkF' pfa "Postbank Finance Award 2008" finance+ <> "."+ softwareProject "Unicenta POS — keyboard version" (Just "unicenta") $ do+ floatingImage 1 True "" Nothing wincor $ Nothing+ p $ "For a client, I changed the Unicenta point of sale software from "+ <> "touchscreen to an entirely keyboard based user interface. "+ <> "It now supports the typical POS keyboards for highest data input speeds."+ sect H.h1 "Fortran" $ do+ softwareProject "Lattice quantum chromodynamics" Nothing $ p $ do+ "For my " <> namedLink' "physics thesis" physics <> " I extended existing "+ linkB "https://en.wikipedia.org/wiki/Lattice_QCD" "Lattice QCD"+ ! target "_blank"+ "-progams written in Fortran and ran them on a high performance computing cluster."+ sect H.h1 "Miscellaneous" $ do+ softwareProject "Peekattack.com — P2P video chat platform" (Just "peekattack") $+ p "Full stack: Streaming Server in Java, Realtime Push Server in C and Javascript, Flex/ActionScript client, and of course client-side Javascript"+ softwareProject "Mathematica" (Just "MathematicaNotes") $+ p "Used for most finance/maths/physics problems during my studies."+ softwareProject "C#" Nothing $+ p "Business software, like client-side data entry of invoices and server-side centralized reports."+ softwareProject "Perl, PHP, SQL and Javascript" Nothing $+ p "Many years of web developement"+ softwareProject "Python" Nothing $+ p "I use python to hack on open-source code bases of programs that I use myself."+ softwareProject "Linux and Shell" Nothing $+ p "Since 2012, I am an Arch Linux power user that switched from Windows 7 and never looked back."+ ,page "physics" "Physics" "physics/" Nothing $ + H.div ! class_ "body" $ do+ p $ do+ text "I graduated with a Master of Science (Diplom-Physiker) from the "+ linkB "https://www.elitenetzwerk.bayern.de/elitestudiengaenge/elite-graduate-programs-according-to-fields-of-study/physics-with-integrated-doctorate-program/?L=2"+ "elite graduate program in physics "+ " at the University of Regensburg and the Univerity of Erlangen–Nuremberg, Germany. My main field of study was elementary particle physics."+ subSect (H.h2 ! dataAttribute "title" "Thesis")+ "Thesis: Is chiral symmetry effectively restored for hadrons on the lattice?" $ mapM_ p $+ [ do text "The thesis can be downloaded "+ blank $ namedLink' "here" diplom+ " (PDF, 82 Pages, 0.8 MB)."+ ,"To answer the above question, properties of the constituents of the nucleons' parity chiral doublet have to be calculated using lattice quantum chromodynamics (QCD)."+ ,"Lattice QCD calculations require the inversion of a discretized version of the "+ <> "Dirac operator"+ <> "—in my case a matrix with 1.5 million rows—and thus can only be performed on high performance computing clusters."+ ,"I extended preexisting Fortran programs that used MPI for parallelization and ran them on the 3000 core " <> linkB "http://www.uni-regensburg.de/EDV/kurs_info/brf09510/hpc/hpc13.html" "Athene cluster"+ <> " at the University of Regensburg."+ ,"Prior to this thesis I completed the following two introductory projects:"+ ]+ chapter (namedLinkB'+ "Calculating the tensor charge of the nucleon using lattice QCD"+ tensor <> br <> " (PDF, 22 pages, 0.3 MB)")+ $ mempty+ chapter (namedLinkB'+ "Parallel inversion of the dirac matrix" + dirac <> br <> " (PDF, 14 pages, 0.2 MB)")+ $ mempty+ ,page "blog" "Blog" "blog/" Nothing $ postList $ const True+ ]++fragments@[pfa,lykah,fipster,amop] =+ ["pfa"+ ,"lykah"+ ,"fipster"+ ,"amop"+ ]++softwareProject+ :: Monad m+ => MarkupT m () -- ^ title+ -> Maybe AttributeValue -- ^ github repo name+ -> MarkupT m () -- ^ body+ -> MarkupT m ()+softwareProject title repo body = subSect H.h3 title $ do+ body+ maybe mempty+ (\repo' ->+ p $ " More on " <> linkB+ ("https://github.com/johannesgerer/" <> repo') "Github.")+ repo+ +chapter :: Monad m => MarkupT m () -> MarkupT m () -> MarkupT m ()+chapter t b = subSect H.h4 t $ do+ b++myPosts = do+ -- a <- either error id <$> t2+ return+ [ -- a+ post "annBlazeT" "blog/blazet.html" "2016-06-13 20:50 CEST"+ "Anouncing: Release of BlazeT" (Just blog)+ $ loremMore 2+ ,post "annfipster2" "blog/ann_fipster.html" "2014-06-13 20:50 CEST"+ "Anouncing: Release of Fipster" (Just blog)+ $ loremMore 2+ ]+++-- | j@johannesgerer.com obfuscated using http://www.mailtoencoder.com/+emailAddress :: Markup+emailAddress = do+ script ! A.type_ "text/javascript" $+ textComment "\nvar fscuanb = ['=','o','>','<','a','o','r','<','g','f','\"','j','l','a','e','e','c','g','m','r','n','/','.','m','>','o','l','a','\"','a','e','c','j','s',' ','h','=','s',':','h','j','@','r','o','r','t',' ','s','a','i','e','h','m','a','\"','s','r','j','e','o','e','@','a','n','n','n','c','e','m','e','i','\"','l','.'];var mvfrrrw = [42,54,73,0,21,33,63,70,61,6,49,51,48,1,44,29,67,26,45,30,22,71,31,69,50,14,38,10,8,46,27,32,18,40,36,55,7,41,15,3,53,52,65,68,28,13,2,25,56,47,62,20,34,39,35,60,4,16,24,19,64,17,72,57,23,58,37,5,9,59,11,43,12,66];var bhufbnr= new Array();for(var i=0;i<mvfrrrw.length;i++){bhufbnr[mvfrrrw[i]] = fscuanb[i]; }for(var i=0;i<bhufbnr.length;i++){document.write(bhufbnr[i]);}\n"+ noscript $ "Please enable Javascript to see the email address"++publication :: Monad m+ => MarkupT m () -- ^ title+ -> [T.Text] -- ^ coauthors+ -> [MarkupT m ()] -- ^ status+ -> MarkupT m () -- ^ body+ -> MarkupT m ()+publication title coauths status body =+ chapter title $ do+ p $ do+ text $ "(with " <> T.intercalate ", " coauths <> ". "+ mconcat status+ ".)"+ body
+ stack.yaml view
@@ -0,0 +1,11 @@+flags: {}+pvp-bounds: both+extra-package-dbs: []+packages:+- '.'+extra-deps:+- blazeT-0.0.5+- cond-0.4.1.1+- filesystem-trees-0.1.0.6+- clay-0.12.1+resolver: lts-7.14