diff --git a/.gitignore b/.gitignore index e1b55c8..b68d71a 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,4 @@ dist result .*.sw[a-p] +dist-newstyle/ diff --git a/cabal.project b/cabal.project index 61d88ea..c8f1255 100644 --- a/cabal.project +++ b/cabal.project @@ -8,3 +8,4 @@ packages: di-df1/ di-handle/ di-monad/ + df1-html/ diff --git a/df1-html/CHANGELOG.md b/df1-html/CHANGELOG.md new file mode 100644 index 0000000..fe26e36 --- /dev/null +++ b/df1-html/CHANGELOG.md @@ -0,0 +1,4 @@ +# Version 0.1 + +* Initial version + diff --git a/df1-html/LICENSE.txt b/df1-html/LICENSE.txt new file mode 100644 index 0000000..656bc4f --- /dev/null +++ b/df1-html/LICENSE.txt @@ -0,0 +1,30 @@ +Copyright (c) 2020, Renzo Carbonara + +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + + * Redistributions in binary form must reproduce the above + copyright notice, this list of conditions and the following + disclaimer in the documentation and/or other materials provided + with the distribution. + + * Neither the name of Renzo Carbonara nor the names of other + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/df1-html/README.md b/df1-html/README.md new file mode 100644 index 0000000..dc333d0 --- /dev/null +++ b/df1-html/README.md @@ -0,0 +1,7 @@ +# df1-html + +Render and parse logs from [df1] (https://hackage.haskell.org/package/df1) as HTML using the library [xmlbf] (https://hackage.haskell.org/package/xmlbf). + +See the [BSD3 LICENSE](https://github.com/k0001/di/blob/master/df1-html/LICENSE.txt) +file to learn about the legal terms and conditions for this library. + diff --git a/df1-html/Setup.hs b/df1-html/Setup.hs new file mode 100755 index 0000000..374b966 --- /dev/null +++ b/df1-html/Setup.hs @@ -0,0 +1,4 @@ +#! /usr/bin/env nix-shell +#! nix-shell ./shell.nix -i runghc +import Distribution.Simple +main = defaultMain diff --git a/df1-html/df1-html.cabal b/df1-html/df1-html.cabal new file mode 100644 index 0000000..36d3c33 --- /dev/null +++ b/df1-html/df1-html.cabal @@ -0,0 +1,56 @@ +name: df1-html +version: 0.1 +author: Melisa Laura Diaz +maintainer: renĪ»ren.zone +copyright: Renzo Carbonara 2020 +license: BSD3 +license-file: LICENSE.txt +extra-source-files: + README.md + CHANGELOG.md + theme-solarized-dark.css + theme-solarized-dark.png + theme-solarized-light.css + theme-solarized-light.png +category: Logging +build-type: Simple +cabal-version: >=1.18 +synopsis: Render and parse df1 logs as HTML +description: Render and parse df1 logs as HTML +homepage: https://github.com/k0001/di +bug-reports: https://github.com/k0001/di/issues + +library + hs-source-dirs: lib + default-language: Haskell2010 + exposed-modules: Df1.Html.Render, Df1.Html.Parse + build-depends: + attoparsec, + base >=4.9 && <5.0, + bytestring, + containers, + df1, + text, + time, + xmlbf + ghcjs-options: -Wall -O3 + ghc-options: -Wall -O2 + +test-suite test + default-language: Haskell2010 + ghc-options: -threaded + type: exitcode-stdio-1.0 + hs-source-dirs: test + main-is: Main.hs + build-depends: + base, + containers, + df1, + df1-html, + QuickCheck, + tasty, + tasty-hunit, + tasty-quickcheck, + text, + time, + xmlbf diff --git a/df1-html/lib/Df1/Html/Parse.hs b/df1-html/lib/Df1/Html/Parse.hs new file mode 100644 index 0000000..2b247c7 --- /dev/null +++ b/df1-html/lib/Df1/Html/Parse.hs @@ -0,0 +1,88 @@ +{-# LANGUAGE OverloadedLists #-} +{-# LANGUAGE OverloadedStrings #-} +{-# LANGUAGE ScopedTypeVariables #-} + +module Df1.Html.Parse (log) where + +import Control.Applicative +import qualified Data.Attoparsec.ByteString as AB +import qualified Data.ByteString.Lazy as BL +import qualified Data.Text as T +import qualified Data.Text.Lazy as TL +import qualified Data.Text.Lazy.Encoding as TLE +import qualified Df1 as D +import qualified Xmlbf as X +import Prelude hiding (log) + +-- | An "Xmlbf" parser for a 'D.Log' rendered as HTML as 'Df1.Html.Render.log' renders it. +-- +-- Notice that this parser will not ignore leading and trailing white space in the HTML. +-- It will become part of the parsed 'D.Key', 'D.Value', 'D.Segment', 'D.Message'. +log :: X.Parser D.Log +log = X.pElement "div" $ do + attrClass "df1-log" + t <- parseTime + p <- parsePaths + l <- parseLevel + m <- parseMessage + let raw = BL.toStrict $ TLE.encodeUtf8 $ TL.intercalate " " [t, p, l, m] + case AB.parseOnly D.parse raw of + Left _ -> fail "Could not parse Log." + Right a -> pure a + +attrClass :: T.Text -> X.Parser () +attrClass t = do + attrs <- X.pAttr "class" + case elem t (T.words attrs) of + False -> fail ("Expected \"class\" value to contain " <> show t <> ".") + True -> pure () + +parseTime :: X.Parser TL.Text +parseTime = X.pElement "span" $ do + attrClass "df1-time" + X.pText + +parseLevel :: X.Parser TL.Text +parseLevel = X.pElement "span" $ do + attrClass "df1-level" + X.pText + +parsePaths :: X.Parser TL.Text +parsePaths = X.pElement "span" $ do + attrClass "df1-path" + TL.intercalate " " <$> many (parsePush <|> parseAttr) + +parsePush :: X.Parser TL.Text +parsePush = X.pElement "span" $ do + attrClass "df1-push" + t <- X.pText + s <- parseSeg + pure (t <> s) + +parseSeg :: X.Parser TL.Text +parseSeg = X.pElement "span" $ do + attrClass "df1-seg" + X.pText <|> pure "" + +parseAttr :: X.Parser TL.Text +parseAttr = X.pElement "span" $ do + attrClass "df1-attr" + k <- parseKey + eq <- X.pText + v <- parseValue + pure (k <> eq <> v) + +parseKey :: X.Parser TL.Text +parseKey = X.pElement "span" $ do + attrClass "df1-key" + X.pText <|> pure "" + +parseValue :: X.Parser TL.Text +parseValue = X.pElement "span" $ do + attrClass "df1-value" + X.pText <|> pure "" + +parseMessage :: X.Parser TL.Text +parseMessage = X.pElement "span" $ do + attrClass "df1-msg" + X.pText <|> pure "" \ No newline at end of file diff --git a/df1-html/lib/Df1/Html/Render.hs b/df1-html/lib/Df1/Html/Render.hs new file mode 100644 index 0000000..31ae86f --- /dev/null +++ b/df1-html/lib/Df1/Html/Render.hs @@ -0,0 +1,137 @@ +{-# LANGUAGE OverloadedLists #-} +{-# LANGUAGE OverloadedStrings #-} + +module Df1.Html.Render + ( log, + -- * Themes + -- + -- $themes + ) +where + +import qualified Data.ByteString.Builder as BB +import qualified Data.ByteString.Lazy as BL +import Data.Foldable (toList) +import Data.List (intercalate) +import qualified Data.Sequence as Seq +import qualified Data.Text as T +import qualified Data.Text.Encoding as TE +import qualified Data.Text.Lazy as TL +import qualified Data.Time.Clock.System as Time +import qualified Df1 as D +import qualified Df1.Render as DR +import qualified Xmlbf as X +import Prelude hiding (log) + +-- | Converts 'D.Log' into a list of 'X.Node's from "Xmlbf" to render it as HTML. +-- +-- Example log: +-- @1999-12-20T07:11:39.230553031Z \/foo x=a y=b \/bar \/qux z=c z=d WARNING Something@ +-- +-- The generated HTML matches the following CSS selectors: +-- +-- [@.df1-log.df1-debug@]: +-- +-- [@.df1-log.df1-info@]: +-- +-- [@.df1-log.df1-notice@]: +-- +-- [@.df1-log.df1-warning@]: +-- +-- [@.df1-log.df1-error@]: +-- +-- [@.df1-log.df1-critical@]: +-- +-- [@.df1-log.df1-alert@]: +-- +-- [@.df1-log.df1-emergency@]: Top level container for a 'D.Log' entry of a particular 'D.Level'. +-- +-- [@.df1-log .df1-time@]: Timestamp - Example: @1999-12-20T07:11:39.230553031Z@ +-- +-- [@.df1-log .df1-path@]: Full list of 'D.Path's - Example: @\/foo x=a y=b \/bar \/qux z=c z=d@ +-- +-- [@.df1-log .df1-path .df1-push@]: Single 'D.Push' - Examples: @\/foo@, @\/bar@, @\/qux@ +-- +-- [@.df1-log .df1-path .df1-push .df1-seg@]: Single 'D.Segment' - Example: @foo@ +-- +-- [@.df1-log .df1-path .df1-attr@]: Single 'D.Attr' - Example: @x=a@, @y=b@, @z=c@, @z=d@ +-- +-- [@.df1-log .df1-path .df1-attr .df1-key@]: Single 'D.Key' - Example: @x@, @y@, @z@, @z@ +-- +-- [@.df1-log .df1-path .df1-attr .df1-value@]: Single 'D.Value' - Example: @a@, @b@, @c@, @d@ +-- +-- [@.df1-log .df1-level@]: 'D.Level' - Example: @WARNING@ +-- +-- [@.df1-log .df1-msg@]: 'D.Message' - Example: @Something@ +-- +log :: D.Log -> [X.Node] +log x = + X.element "div" [("class", "df1-log " <> levelClass (D.log_level x))] $ + mconcat + [ timeHtml (D.log_time x), + X.text " ", + pathsHtml (D.log_path x), + X.text " ", + levelHtml (D.log_level x), + X.text " ", + messageHtml (D.log_message x) + ] + +levelClass :: D.Level -> T.Text +levelClass l = "df1-" <> TL.toStrict (TL.toLower (levelToText l)) + +timeHtml :: Time.SystemTime -> [X.Node] +timeHtml t = spanClass "df1-time" (X.text (textLazyFromBuilder (DR.iso8601 t))) + +textLazyFromBuilder :: BB.Builder -> TL.Text +textLazyFromBuilder b = TL.fromStrict (TE.decodeUtf8 (BL.toStrict (BB.toLazyByteString b))) + +levelHtml :: D.Level -> [X.Node] +levelHtml l = spanClass "df1-level" (X.text (levelToText l)) + +levelToText :: D.Level -> TL.Text +levelToText l = + case l of + D.Debug -> "DEBUG" + D.Info -> "INFO" + D.Notice -> "NOTICE" + D.Warning -> "WARNING" + D.Error -> "ERROR" + D.Critical -> "CRITICAL" + D.Alert -> "ALERT" + D.Emergency -> "EMERGENCY" + +messageHtml :: D.Message -> [X.Node] +messageHtml m = spanClass "df1-msg" (X.text (textLazyFromBuilder (DR.message m))) + +pathsHtml :: Seq.Seq D.Path -> [X.Node] +pathsHtml ps = spanClass "df1-path" (intercalate (X.text " ") (fmap pathHtml (toList ps))) + +pathHtml :: D.Path -> [X.Node] +pathHtml p = case p of + D.Push seg -> spanClass "df1-push" (X.text "/" <> segmentHtml seg) + D.Attr key val -> spanClass "df1-attr" (keyHtml key <> X.text "=" <> valueHtml val) + +segmentHtml :: D.Segment -> [X.Node] +segmentHtml s = spanClass "df1-seg" (X.text (textLazyFromBuilder (DR.segment s))) + +keyHtml :: D.Key -> [X.Node] +keyHtml k = spanClass "df1-key" (X.text (textLazyFromBuilder (DR.key k))) + +valueHtml :: D.Value -> [X.Node] +valueHtml v = spanClass "df1-value" (X.text (textLazyFromBuilder (DR.value v))) + +spanClass :: T.Text -> [X.Node] -> [X.Node] +spanClass t = X.element "span" [("class", t)] + +-- $themes +-- +-- If you need to style the rendered HTML, you can use some of the themes shipped with this library. +-- +-- == [theme-solarized-dark.css](https://github.com/k0001/di/blob/html/df1-html/theme-solarized-dark.css?raw=true) +-- +-- ![theme-solarized-dark](https://github.com/k0001/di/blob/html/df1-html/theme-solarized-dark.png?raw=true) +-- +-- == [theme-solarized-light.css](https://github.com/k0001/di/blob/html/df1-html/theme-solarized-light.css?raw=true) +-- +-- ![theme-solarized-light](https://github.com/k0001/di/blob/html/df1-html/theme-solarized-light.png?raw=true) diff --git a/df1-html/test/Main.hs b/df1-html/test/Main.hs new file mode 100644 index 0000000..8bf773c --- /dev/null +++ b/df1-html/test/Main.hs @@ -0,0 +1,159 @@ +{-# LANGUAGE OverloadedLists #-} +{-# LANGUAGE OverloadedStrings #-} + +module Main where + +import qualified Data.Sequence as Seq +import Data.String (fromString) +import qualified Data.Text.Lazy as TL +import qualified Data.Time.Clock.System as Time +import qualified Df1 as D +import qualified Df1.Html.Parse as DHP +import qualified Df1.Html.Render as DHR +import qualified Test.Tasty as Tasty +import qualified Test.Tasty.HUnit as HU +import Test.Tasty.HUnit ((@=?)) +import Test.Tasty.QuickCheck ((===)) +import qualified Test.Tasty.QuickCheck as QC +import qualified Test.Tasty.Runners as Tasty +import qualified Xmlbf as X + +-------------------------------------------------------------------------------- + +main :: IO () +main = + Tasty.defaultMainWithIngredients + [ Tasty.consoleTestReporter, + Tasty.listingTests + ] + tt + +tt :: Tasty.TestTree +tt = + Tasty.testGroup + "df1-html" + [ HU.testCase "Given a D.Log, render it as HTML" $ do + expected1 @=? DHR.log log1, + + HU.testCase "Parse that HTML to reobtain the D.Log" $ do + case X.runParser DHP.log expected1 of + Left _ -> fail "Could not parse Log." + Right a -> log1 @=? a, + + Tasty.localOption (QC.QuickCheckTests 2000) $ + QC.testProperty "Render/Parse roundtrip" $ do + QC.forAllShrink QC.arbitrary QC.shrink $ \log0 -> do + let html = DHR.log log0 + Right log0 === X.runParser DHP.log html + ] + + +log1 :: D.Log +log1 = + D.Log + { D.log_time = Time.MkSystemTime 355 43, + D.log_level = D.Warning, + D.log_path = examplePath, + D.log_message = D.message ("example" :: String) + } + +examplePath :: Seq.Seq D.Path +examplePath = + [ D.Push (D.segment ("foo" :: String)), + D.Attr (D.key ("=" :: String)) (D.value ("a" :: String)), + D.Attr (D.key ("y" :: String)) (D.value ("b" :: String)), + D.Push (D.segment ("bar" :: String)), + D.Push (D.segment ("qux" :: String)), + D.Attr (D.key ("z" :: String)) (D.value ("c" :: String)), + D.Attr (D.key ("z" :: String)) (D.value ("d" :: String)) + ] + + +expected1 :: [X.Node] +expected1 = + X.element + "div" + [("class", "df1-log df1-warning")] + $ mconcat + [ X.element "span" [("class", "df1-time")] (X.text "1970-01-01T00:05:55.000000043Z"), + X.text " ", + X.element "span" [("class", "df1-path")] $ + mconcat + [ X.element "span" [("class", "df1-push")] $ mconcat [X.text "/", X.element "span" [("class", "df1-seg")] (X.text "foo")], + X.text " ", + X.element "span" [("class", "df1-attr")] $ + mconcat + [ X.element "span" [("class", "df1-key")] (X.text "%3d"), + X.text "=", + X.element "span" [("class", "df1-value")] (X.text "a") + ], + X.text " ", + X.element "span" [("class", "df1-attr")] $ + mconcat + [ X.element "span" [("class", "df1-key")] (X.text "y"), + X.text "=", + X.element "span" [("class", "df1-value")] (X.text "b") + ], + X.text " ", + X.element "span" [("class", "df1-push")] $ mconcat [X.text "/", X.element "span" [("class", "df1-seg")] (X.text "bar")], + X.text " ", + X.element "span" [("class", "df1-push")] $ mconcat [X.text "/", X.element "span" [("class", "df1-seg")] (X.text "qux")], + X.text " ", + X.element "span" [("class", "df1-attr")] $ + mconcat + [ X.element "span" [("class", "df1-key")] (X.text "z"), + X.text "=", + X.element "span" [("class", "df1-value")] (X.text "c") + ], + X.text " ", + X.element "span" [("class", "df1-attr")] $ + mconcat + [ X.element "span" [("class", "df1-key")] (X.text "z"), + X.text "=", + X.element "span" [("class", "df1-value")] (X.text "d") + ] + ], + X.text " ", + X.element "span" [("class", "df1-level")] (X.text "WARNING"), + X.text " ", + X.element "span" [("class", "df1-msg")] (X.text "example") + ] + + +instance QC.Arbitrary D.Log where + arbitrary = D.Log + <$> genSystemTime <*> QC.arbitrary <*> QC.arbitrary <*> QC.arbitrary + shrink (D.Log a b c d) = D.Log + <$> pure a <*> QC.shrink b <*> QC.shrink c <*> QC.shrink d + +instance QC.Arbitrary D.Path where + arbitrary = QC.oneof + [ D.Push <$> QC.arbitrary + , D.Attr <$> QC.arbitrary <*> QC.arbitrary ] + shrink (D.Push s) = D.Push <$> QC.shrink s + shrink (D.Attr k v) = D.Attr <$> QC.shrink k <*> QC.shrink v + +instance QC.Arbitrary D.Level where + arbitrary = QC.elements [minBound .. minBound] + +instance QC.Arbitrary D.Segment where + arbitrary = fromString <$> QC.arbitrary + shrink = map fromString . QC.shrink . TL.unpack . D.unSegment + +instance QC.Arbitrary D.Key where + arbitrary = fromString <$> QC.arbitrary + shrink = map fromString . QC.shrink . TL.unpack . D.unKey + +instance QC.Arbitrary D.Value where + arbitrary = fromString <$> QC.arbitrary + shrink = map fromString . QC.shrink . TL.unpack . D.unValue + +instance QC.Arbitrary D.Message where + arbitrary = fromString <$> QC.arbitrary + shrink = map fromString . QC.shrink . TL.unpack . D.unMessage + +genSystemTime :: QC.Gen Time.SystemTime +genSystemTime = do + a <- QC.choose (0, 253402300799) -- up to 4 digit years + b <- QC.choose (0, 1000000000) + pure (Time.MkSystemTime a b) \ No newline at end of file diff --git a/df1-html/theme-solarized-dark.css b/df1-html/theme-solarized-dark.css new file mode 100644 index 0000000..6ea5e32 --- /dev/null +++ b/df1-html/theme-solarized-dark.css @@ -0,0 +1,63 @@ +.df1-theme-solarized-dark .df1-log { + font-family: monospace; +} + +.df1-theme-solarized-dark .df1-debug, +.df1-theme-solarized-dark .df1-info, +.df1-theme-solarized-dark .df1-notice, +.df1-theme-solarized-dark .df1-warning { + background-color: #002b36; + color: #93a1a1; +} + +.df1-theme-solarized-dark .df1-debug .df1-push, +.df1-theme-solarized-dark .df1-info .df1-push, +.df1-theme-solarized-dark .df1-notice .df1-push, +.df1-theme-solarized-dark .df1-warning .df1-push, +.df1-theme-solarized-dark .df1-error .df1-push { + color: #268bd2; +} + +.df1-theme-solarized-dark .df1-debug .df1-key, +.df1-theme-solarized-dark .df1-info .df1-key, +.df1-theme-solarized-dark .df1-notice .df1-key, +.df1-theme-solarized-dark .df1-warning .df1-key, +.df1-theme-solarized-dark .df1-error .df1-key, +.df1-theme-solarized-dark .df1-critical .df1-key, +.df1-theme-solarized-dark .df1-alert .df1-key, +.df1-theme-solarized-dark .df1-emergency .df1-key { + color: #2aa198; +} + +.df1-theme-solarized-dark .df1-notice .df1-level { + color: #859900; +} + +.df1-theme-solarized-dark .df1-warning .df1-level { + color: #b58900; +} + +.df1-theme-solarized-dark .df1-error { + background-color: #fdf6e3; + color: #002b36; +} + +.df1-theme-solarized-dark .df1-error .df1-level { + color: #dc322f; +} + +.df1-theme-solarized-dark .df1-critical, +.df1-theme-solarized-dark .df1-alert, +.df1-theme-solarized-dark .df1-emergency { + background-color: #dc322f; + color: #002b36; +} + +.df1-theme-solarized-dark .df1-critical .df1-push, +.df1-theme-solarized-dark .df1-alert .df1-push, +.df1-theme-solarized-dark .df1-emergency .df1-push, +.df1-theme-solarized-dark .df1-critical .df1-level, +.df1-theme-solarized-dark .df1-alert .df1-level, +.df1-theme-solarized-dark .df1-emergency .df1-level { + color: #fdf6e3; +} \ No newline at end of file diff --git a/df1-html/theme-solarized-dark.png b/df1-html/theme-solarized-dark.png new file mode 100644 index 0000000..e20ec00 Binary files /dev/null and b/df1-html/theme-solarized-dark.png differ diff --git a/df1-html/theme-solarized-light.css b/df1-html/theme-solarized-light.css new file mode 100644 index 0000000..a450a81 --- /dev/null +++ b/df1-html/theme-solarized-light.css @@ -0,0 +1,63 @@ +.df1-theme-solarized-light .df1-log { + font-family: monospace; +} + +.df1-theme-solarized-light .df1-debug, +.df1-theme-solarized-light .df1-info, +.df1-theme-solarized-light .df1-notice, +.df1-theme-solarized-light .df1-warning { + background-color: #fdf6e3; + color: #93a1a1; +} + +.df1-theme-solarized-light .df1-debug .df1-push, +.df1-theme-solarized-light .df1-info .df1-push, +.df1-theme-solarized-light .df1-notice .df1-push, +.df1-theme-solarized-light .df1-warning .df1-push, +.df1-theme-solarized-light .df1-error .df1-push { + color: #268bd2; +} + +.df1-theme-solarized-light .df1-debug .df1-key, +.df1-theme-solarized-light .df1-info .df1-key, +.df1-theme-solarized-light .df1-notice .df1-key, +.df1-theme-solarized-light .df1-warning .df1-key, +.df1-theme-solarized-light .df1-error .df1-key, +.df1-theme-solarized-light .df1-critical .df1-key, +.df1-theme-solarized-light .df1-alert .df1-key, +.df1-theme-solarized-light .df1-emergency .df1-key { + color: #2aa198; +} + +.df1-theme-solarized-light .df1-notice .df1-level { + color: #859900; +} + +.df1-theme-solarized-light .df1-warning .df1-level { + color: #b58900; +} + +.df1-theme-solarized-light .df1-error { + background-color: #073642; + color: #eee8d5; +} + +.df1-theme-solarized-light .df1-error .df1-level { + color: #dc322f; +} + +.df1-theme-solarized-light .df1-critical, +.df1-theme-solarized-light .df1-alert, +.df1-theme-solarized-light .df1-emergency { + background-color: #dc322f; + color: #002b36; +} + +.df1-theme-solarized-light .df1-critical .df1-push, +.df1-theme-solarized-light .df1-alert .df1-push, +.df1-theme-solarized-light .df1-emergency .df1-push, +.df1-theme-solarized-light .df1-critical .df1-level, +.df1-theme-solarized-light .df1-alert .df1-level, +.df1-theme-solarized-light .df1-emergency .df1-level { + color: #fdf6e3; +} \ No newline at end of file diff --git a/df1-html/theme-solarized-light.png b/df1-html/theme-solarized-light.png new file mode 100644 index 0000000..94de5c6 Binary files /dev/null and b/df1-html/theme-solarized-light.png differ diff --git a/hs-overlay.nix b/hs-overlay.nix index 24dacf6..e4397fc 100644 --- a/hs-overlay.nix +++ b/hs-overlay.nix @@ -10,11 +10,12 @@ in self: super: { di-df1 = super.callPackage ./di-df1/pkg.nix { }; di-handle = super.callPackage ./di-handle/pkg.nix { }; di-monad = super.callPackage ./di-monad/pkg.nix { }; + df1-html = super.callCabal2nix "df1-html" ./df1-html {}; _shell = self.shellFor { withHoogle = true; # hoogle dependencies don't compile packages = p: [ - p.df1 p.di p.di-core p.di-df1 p.di-handle p.di-monad + p.df1 p.di p.di-core p.di-df1 p.di-handle p.di-monad p.df1-html ]; }; }