packages feed

patat 0.15.1.0 → 0.15.2.0

raw patch · 11 files changed

+330/−172 lines, 11 filesdep +case-insensitivedep ~QuickCheckdep ~containersdep ~hashablePVP: major bump suggested

API removals or changes: PVP suggests a major version bump

Dependencies added: case-insensitive

Dependency ranges changed: QuickCheck, containers, hashable, pandoc, random

API changes (from Hackage documentation)

- Patat.Eval.Internal: [ebAttr] :: EvalBlock -> !Attr
- Patat.Presentation.Internal: type EvalSettingsMap = HashMap Text EvalSettings
- Patat.Presentation.Settings: type EvalSettingsMap = HashMap Text EvalSettings
+ Patat.Eval.Internal: [ebClasses] :: EvalBlock -> ![CI Text]
+ Patat.Presentation.Internal: EvalSettingsMap :: HashMap (CI Text) EvalSettings -> EvalSettingsMap
+ Patat.Presentation.Internal: newtype EvalSettingsMap
+ Patat.Presentation.Settings: EvalSettingsMap :: HashMap (CI Text) EvalSettings -> EvalSettingsMap
+ Patat.Presentation.Settings: instance Data.Aeson.Types.FromJSON.FromJSON Patat.Presentation.Settings.EvalSettingsMap
+ Patat.Presentation.Settings: instance GHC.Base.Semigroup Patat.Presentation.Settings.EvalSettingsMap
+ Patat.Presentation.Settings: instance GHC.Classes.Eq Patat.Presentation.Settings.EvalSettingsMap
+ Patat.Presentation.Settings: instance GHC.Show.Show Patat.Presentation.Settings.EvalSettingsMap
+ Patat.Presentation.Settings: newtype EvalSettingsMap
+ Patat.Theme: CenterHeaderAlign :: HeaderAlign
+ Patat.Theme: HeaderTheme :: !Maybe Style -> !Maybe Text -> !Maybe Text -> !Maybe HeaderAlign -> HeaderTheme
+ Patat.Theme: HeaderThemes :: Map String HeaderTheme -> HeaderThemes
+ Patat.Theme: LeftHeaderAlign :: HeaderAlign
+ Patat.Theme: [htAlign] :: HeaderTheme -> !Maybe HeaderAlign
+ Patat.Theme: [htPrefix] :: HeaderTheme -> !Maybe Text
+ Patat.Theme: [htStyle] :: HeaderTheme -> !Maybe Style
+ Patat.Theme: [htUnderline] :: HeaderTheme -> !Maybe Text
+ Patat.Theme: [themeHeaders] :: Theme -> !Maybe HeaderThemes
+ Patat.Theme: data HeaderAlign
+ Patat.Theme: data HeaderTheme
+ Patat.Theme: instance Data.Aeson.Types.FromJSON.FromJSON Patat.Theme.HeaderAlign
+ Patat.Theme: instance Data.Aeson.Types.FromJSON.FromJSON Patat.Theme.HeaderTheme
+ Patat.Theme: instance Data.Aeson.Types.FromJSON.FromJSON Patat.Theme.HeaderThemes
+ Patat.Theme: instance Data.Aeson.Types.ToJSON.ToJSON Patat.Theme.HeaderAlign
+ Patat.Theme: instance Data.Aeson.Types.ToJSON.ToJSON Patat.Theme.HeaderTheme
+ Patat.Theme: instance Data.Aeson.Types.ToJSON.ToJSON Patat.Theme.HeaderThemes
+ Patat.Theme: instance GHC.Base.Semigroup Patat.Theme.HeaderTheme
+ Patat.Theme: instance GHC.Base.Semigroup Patat.Theme.HeaderThemes
+ Patat.Theme: instance GHC.Classes.Eq Patat.Theme.HeaderAlign
+ Patat.Theme: instance GHC.Classes.Eq Patat.Theme.HeaderTheme
+ Patat.Theme: instance GHC.Classes.Eq Patat.Theme.HeaderThemes
+ Patat.Theme: instance GHC.Show.Show Patat.Theme.HeaderAlign
+ Patat.Theme: instance GHC.Show.Show Patat.Theme.HeaderTheme
+ Patat.Theme: instance GHC.Show.Show Patat.Theme.HeaderThemes
+ Patat.Theme: newtype HeaderThemes
+ Patat.Theme: themeForHeader :: Int -> Theme -> HeaderTheme
- Patat.Eval.Internal: EvalBlock :: !EvalSettings -> !Attr -> !Text -> !Maybe (Async ()) -> EvalBlock
+ Patat.Eval.Internal: EvalBlock :: !EvalSettings -> ![CI Text] -> !Text -> !Maybe (Async ()) -> EvalBlock
- Patat.Presentation.Display.CodeBlock: prettyCodeBlock :: DisplaySettings -> [Text] -> Text -> Doc
+ Patat.Presentation.Display.CodeBlock: prettyCodeBlock :: DisplaySettings -> [CI Text] -> Text -> Doc
- Patat.Presentation.Internal: EvalSettings :: !Text -> !Bool -> !Bool -> !EvalSettingsContainer -> !Bool -> !Maybe Text -> EvalSettings
+ Patat.Presentation.Internal: EvalSettings :: !Text -> !Bool -> !Bool -> !EvalSettingsContainer -> !Bool -> !Maybe (CI Text) -> EvalSettings
- Patat.Presentation.Internal: [evalSyntax] :: EvalSettings -> !Maybe Text
+ Patat.Presentation.Internal: [evalSyntax] :: EvalSettings -> !Maybe (CI Text)
- Patat.Presentation.Settings: EvalSettings :: !Text -> !Bool -> !Bool -> !EvalSettingsContainer -> !Bool -> !Maybe Text -> EvalSettings
+ Patat.Presentation.Settings: EvalSettings :: !Text -> !Bool -> !Bool -> !EvalSettingsContainer -> !Bool -> !Maybe (CI Text) -> EvalSettings
- Patat.Presentation.Settings: [evalSyntax] :: EvalSettings -> !Maybe Text
+ Patat.Presentation.Settings: [evalSyntax] :: EvalSettings -> !Maybe (CI Text)
- Patat.Presentation.Syntax: CodeBlock :: !Attr -> !Text -> Block
+ Patat.Presentation.Syntax: CodeBlock :: ![CI Text] -> !Text -> Block
- Patat.Theme: Theme :: !Maybe Style -> !Maybe Style -> !Maybe Style -> !Maybe Style -> !Maybe Text -> !Maybe Style -> !Maybe Style -> !Maybe Style -> !Maybe Style -> !Maybe Style -> !Maybe Style -> !Maybe Style -> !Maybe Style -> !Maybe Style -> !Maybe Style -> !Maybe Style -> !Maybe Style -> !Maybe Style -> !Maybe Style -> !Maybe Style -> !Maybe Style -> !Maybe Style -> !Maybe Style -> !Maybe SyntaxHighlighting -> Theme
+ Patat.Theme: Theme :: !Maybe Style -> !Maybe Style -> !Maybe HeaderThemes -> !Maybe Style -> !Maybe Style -> !Maybe Text -> !Maybe Style -> !Maybe Style -> !Maybe Style -> !Maybe Style -> !Maybe Style -> !Maybe Style -> !Maybe Style -> !Maybe Style -> !Maybe Style -> !Maybe Style -> !Maybe Style -> !Maybe Style -> !Maybe Style -> !Maybe Style -> !Maybe Style -> !Maybe Style -> !Maybe Style -> !Maybe Style -> !Maybe SyntaxHighlighting -> Theme

Files

CHANGELOG.md view
@@ -1,5 +1,28 @@ # Changelog +## 0.15.2.0 (2025-08-06)++ *  Fix case-insensitivity of class names for `eval` code blocks (#192).+    Class names are case-insensitive (like in HTML), but we were treating them+    as case-sensitive.++ *  Under `theme:`, individual headers can now also be customized (#162).++    The configuration blocks under `headers` accepts a `style` list, a `prefix`+    string and an `underline` string that is repeated match the width of the+    header. `align: center` can be used to horizontally center the header.++    ```yaml+    patat:+      theme:+        headers:+          h3:+            style: [vividRed]+            prefix: '### '+            underline: '-~-~'+            align: center+    ```+ ## 0.15.1.0 (2025-04-26)   *  Add a `syntax` option for `eval` blocks (#187).  This allows you to set
README.md view
@@ -527,6 +527,24 @@         strong: [underline, underlineVividBlue] ``` +#### Theming headers++In addition to `header`, individual headers can also be customized.  The+configuration blocks under `headers` accepts a `style` list, a `prefix` string+and an `underline` string that is repeated match the width of the header.+`align: center` can be used to horizontally center the header.++```yaml+patat:+  theme:+    headers:+      h3:+        style: [vividRed]+        prefix: '### '+        underline: '-~-~'+        align: center+```+ ### Syntax Highlighting  `patat` uses [Kate] Syntax Highlighting files.  `patat` ships with support for
lib/Patat/Eval.hs view
@@ -16,6 +16,8 @@ import           Control.Monad               (foldM, when) import           Control.Monad.State         (StateT, runStateT, state) import           Control.Monad.Writer        (Writer, runWriter, tell)+import           Data.CaseInsensitive        (CI)+import qualified Data.CaseInsensitive        as CI import           Data.Foldable               (for_) import qualified Data.HashMap.Strict         as HMS import qualified Data.IORef                  as IORef@@ -48,8 +50,8 @@   ---------------------------------------------------------------------------------lookupSettings :: [T.Text] -> EvalSettingsMap -> [EvalSettings]-lookupSettings classes settings = do+lookupSettings :: [CI T.Text] -> EvalSettingsMap -> [EvalSettings]+lookupSettings classes (EvalSettingsMap settings) = do     c <- classes     maybeToList $ HMS.lookup c settings @@ -73,10 +75,10 @@ evalBlock     :: EvalSettingsMap -> Block     -> ExtractEvalM [Block]-evalBlock settings orig@(CodeBlock attr@(_, classes, _) txt)+evalBlock settings orig@(CodeBlock classes txt)     | [s@EvalSettings {..}] <- lookupSettings classes settings = do         var <- Var <$> state freshUnique-        tell $ HMS.singleton var $ EvalBlock s attr txt Nothing+        tell $ HMS.singleton var $ EvalBlock s classes txt Nothing         case (evalReveal, evalReplace) of             (False, True) -> pure [VarBlock var]             (False, False) -> pure [orig, VarBlock var]@@ -98,8 +100,8 @@                     ]     | _ : _ : _ <- lookupSettings classes settings =         let msg = "patat eval matched multiple settings for " <>-                T.intercalate "," classes in-        pure [CodeBlock attr msg]+                T.intercalate "," (map CI.original classes) in+        pure [CodeBlock classes msg] evalBlock _ block =     pure [block] 
lib/Patat/Eval/Internal.hs view
@@ -8,12 +8,12 @@   ---------------------------------------------------------------------------------import qualified Control.Concurrent.Async       as Async-import qualified Data.HashMap.Strict            as HMS-import qualified Data.Text                      as T+import qualified Control.Concurrent.Async    as Async+import           Data.CaseInsensitive        (CI)+import qualified Data.HashMap.Strict         as HMS+import qualified Data.Text                   as T import           Patat.Presentation.Settings import           Patat.Presentation.Syntax-import qualified Text.Pandoc                    as Pandoc   --------------------------------------------------------------------------------@@ -24,7 +24,7 @@ -- | Block that needs to be evaluated. data EvalBlock = EvalBlock     { ebSettings :: !EvalSettings-    , ebAttr     :: !Pandoc.Attr+    , ebClasses  :: ![CI T.Text]     , ebInput    :: !T.Text     , ebAsync    :: !(Maybe (Async.Async ()))     }@@ -33,7 +33,7 @@ -------------------------------------------------------------------------------- renderEvalBlock :: EvalBlock -> T.Text -> [Block] renderEvalBlock EvalBlock {..} out = case evalContainer ebSettings of-    EvalContainerCode   -> [CodeBlock ("", classes, []) out]+    EvalContainerCode   -> [CodeBlock classes out]     EvalContainerNone   -> [RawBlock fmt out]     EvalContainerInline -> [Plain [RawInline fmt out]]   where@@ -41,6 +41,4 @@      -- The classes for the new code block are copied from the old one if     -- unspecified, or the syntax specified in the eval settings.-    classes = case evalSyntax ebSettings of-        Nothing        -> let (_, cs, _) = ebAttr in cs-        Just outSyntax -> [outSyntax]+    classes = maybe ebClasses pure $ evalSyntax ebSettings
lib/Patat/Presentation/Display.hs view
@@ -29,7 +29,7 @@ import           Patat.Presentation.Settings import qualified Patat.Presentation.SpeakerNotes      as SpeakerNotes import           Patat.Presentation.Syntax-import           Patat.PrettyPrint                    ((<$$>), (<+>))+import           Patat.PrettyPrint                    ((<$$>)) import qualified Patat.PrettyPrint                    as PP import           Patat.Size import           Patat.Theme                          (Theme (..))@@ -191,15 +191,16 @@         refs ->             let doc0        = PP.vcat refs                 size@(r, _) = PP.dimensions doc0 in-            [(horizontalIndent size $ horizontalWrap doc0, r)]+            [(horizontalIndent size gmargins $ horizontalWrap gmargins doc0, r)]   where     Size rows columns = dsSize ds-    Margins {..} = dsMargins ds+    gmargins = dsMargins ds      -- For every block, calculate the size based on its last fragment.     blockSize block =-        let revealState = blocksRevealLastStep [block] in-        PP.dimensions $ deindent $ horizontalWrap $+        let revealState = blocksRevealLastStep [block]+            bmargins    = marginsFor block+        in PP.dimensions $ deindent bmargins $ horizontalWrap bmargins $             prettyBlock ds {dsRevealState = revealState} block      -- Vertically align some blocks by adding spaces in front of it.@@ -208,7 +209,7 @@     vertical :: [(PP.Doc, Int)] -> PP.Doc     vertical docs0 = mconcat (replicate top PP.hardline) <> doc       where-        top = case mTop of+        top = case mTop gmargins of             Auto      -> (rows - actual) `div` 2             NotAuto x -> x @@ -236,13 +237,18 @@                 revealToBlocks (dsRevealState ds) ConcatWrapper reveal in         (PP.vcat fblocks, fst (blockSize b))     horizontal block =-        let size@(r, _) = blockSize block in-        (horizontalIndent size $ horizontalWrap $ prettyBlock ds block, r)+        let size@(r, _) = blockSize block+            bmargins    = marginsFor block in+        ( horizontalIndent size bmargins $ horizontalWrap bmargins $+            prettyBlock ds block+        , r+        ) -    horizontalIndent :: (Int, Int) -> PP.Doc -> PP.Doc-    horizontalIndent (_, dcols) doc0 = PP.indent indentation indentation doc1+    horizontalIndent :: (Int, Int) -> Margins -> PP.Doc -> PP.Doc+    horizontalIndent (_, dcols) dmargins@Margins {..} doc0 =+        PP.indent indentation indentation doc1       where-        doc1 = deindent doc0+        doc1 = deindent dmargins doc0         left = case mLeft of             NotAuto x -> x             Auto      -> case mRight of@@ -251,13 +257,13 @@         indentation = PP.Indentation left mempty      -- Strip leading spaces to horizontally align code blocks etc.-    deindent doc0 = case (mLeft, mRight) of+    deindent Margins {..} doc0 = case (mLeft, mRight) of         (Auto, Auto) -> PP.deindent doc0         _            -> doc0      -- Rearranges lines to fit into the wrap settings.-    horizontalWrap :: PP.Doc -> PP.Doc-    horizontalWrap doc0 = case dsWrap ds of+    horizontalWrap :: Margins -> PP.Doc -> PP.Doc+    horizontalWrap Margins {..} doc0 = case dsWrap ds of         NoWrap     -> doc0         AutoWrap   -> PP.wrapAt (Just $ columns - right - left) doc0         WrapAt col -> PP.wrapAt (Just col) doc0@@ -269,7 +275,16 @@             Auto      -> 0             NotAuto x -> x +    -- Find the right margins for the specific block.  Currently, only headers+    -- can have different margins.+    marginsFor :: Block -> Margins+    marginsFor (Header n _ _) = fromMaybe gmargins $ do+        align <- Theme.htAlign $ Theme.themeForHeader n (dsTheme ds)+        guard $ align == Theme.CenterHeaderAlign+        pure gmargins {mLeft = Auto, mRight = Auto}+    marginsFor _ = gmargins + -------------------------------------------------------------------------------- prettyBlock :: DisplaySettings -> Block -> PP.Doc @@ -278,11 +293,24 @@ prettyBlock ds (Para inlines) =     prettyInlines ds inlines <> PP.hardline -prettyBlock ds (Header i _ inlines) =-    themed ds themeHeader (PP.string (replicate i '#') <+> prettyInlines ds inlines) <>-    PP.hardline+prettyBlock ds (Header n _ inlines) =+    themed ds style content <> PP.hardline <>+    (case underline of+        Just t | t /= "" ->+            themed ds style (PP.string $ take cols $ cycle $ T.unpack t) <>+            PP.hardline+        _ -> mempty)+  where+    prefix    = fromMaybe mempty $ Theme.htPrefix headerTheme+    content   = PP.text prefix <> prettyInlines ds inlines+    (_, cols) = PP.dimensions content+    underline = Theme.htUnderline headerTheme -prettyBlock ds (CodeBlock (_, classes, _) txt) =+    headerTheme = Theme.themeForHeader n (dsTheme ds)++    style _ = Theme.htStyle headerTheme++prettyBlock ds (CodeBlock classes txt) =     prettyCodeBlock ds classes txt  prettyBlock ds (BulletList bss) = PP.vcat
lib/Patat/Presentation/Display/CodeBlock.hs view
@@ -8,6 +8,8 @@   --------------------------------------------------------------------------------+import           Data.CaseInsensitive                (CI)+import qualified Data.CaseInsensitive                as CI import           Data.Char.WCWidth.Extended          (wcstrwidth, wcwidth) import           Data.Maybe                          (mapMaybe) import qualified Data.Text                           as T@@ -20,7 +22,8 @@  -------------------------------------------------------------------------------- highlight-    :: Skylighting.SyntaxMap -> [T.Text] -> T.Text -> [Skylighting.SourceLine]+    :: Skylighting.SyntaxMap -> [CI T.Text] -> T.Text+    -> [Skylighting.SourceLine] highlight extraSyntaxMap classes rawCodeBlock =     case mapMaybe getSyntax classes of         []        -> zeroHighlight rawCodeBlock@@ -29,8 +32,9 @@                 Left  _  -> zeroHighlight rawCodeBlock                 Right sl -> sl   where-    getSyntax :: T.Text -> Maybe Skylighting.Syntax-    getSyntax c = Skylighting.lookupSyntax c syntaxMap+    -- Note that SyntaxMap always uses lowercase keys.+    getSyntax :: CI T.Text -> Maybe Skylighting.Syntax+    getSyntax c = Skylighting.lookupSyntax (T.toLower $ CI.original c) syntaxMap      config :: Skylighting.TokenizerConfig     config = Skylighting.TokenizerConfig@@ -70,7 +74,7 @@   ---------------------------------------------------------------------------------prettyCodeBlock :: DisplaySettings -> [T.Text] -> T.Text -> PP.Doc+prettyCodeBlock :: DisplaySettings -> [CI T.Text] -> T.Text -> PP.Doc prettyCodeBlock ds classes rawCodeBlock =     PP.vcat (map blockified sourceLines) <> PP.hardline   where
lib/Patat/Presentation/Internal.hs view
@@ -17,7 +17,7 @@      , ImageSettings (..) -    , EvalSettingsMap+    , EvalSettingsMap (..)     , EvalSettings (..)      , Slide (..)
lib/Patat/Presentation/Settings.hs view
@@ -16,7 +16,7 @@      , ImageSettings (..) -    , EvalSettingsMap+    , EvalSettingsMap (..)     , EvalSettingsContainer (..)     , EvalSettings (..) @@ -35,6 +35,9 @@ import           Control.Monad          (mplus, unless) import qualified Data.Aeson.Extended    as A import qualified Data.Aeson.TH.Extended as A+import           Data.Bifunctor         (first)+import           Data.CaseInsensitive   (CI)+import qualified Data.CaseInsensitive   as CI import qualified Data.Foldable          as Foldable import           Data.Function          (on) import qualified Data.HashMap.Strict    as HMS@@ -227,10 +230,18 @@   ---------------------------------------------------------------------------------type EvalSettingsMap = HMS.HashMap T.Text EvalSettings+newtype EvalSettingsMap = EvalSettingsMap (HMS.HashMap (CI T.Text) EvalSettings)+    deriving (Eq, Show, Semigroup)   --------------------------------------------------------------------------------+instance A.FromJSON EvalSettingsMap where+    parseJSON =+        fmap (EvalSettingsMap . HMS.fromList . map (first CI.mk) . HMS.toList) .+        A.parseJSON+++-------------------------------------------------------------------------------- data EvalSettingsContainer     = EvalContainerCode     | EvalContainerNone@@ -257,7 +268,7 @@     , evalReveal    :: !Bool     , evalContainer :: !EvalSettingsContainer     , evalStderr    :: !Bool-    , evalSyntax    :: !(Maybe T.Text)+    , evalSyntax    :: !(Maybe (CI T.Text))     } deriving (Eq, Show)  @@ -269,7 +280,7 @@         <*> deprecated "fragment" "reveal" True o         <*> deprecated "wrap" "container" EvalContainerCode o         <*> o A..:? "stderr" A..!= True-        <*> o A..:? "syntax"+        <*> (fmap CI.mk <$> o A..:? "syntax")       where         deprecated old new def obj = do             mo <- obj A..:? old
lib/Patat/Presentation/Syntax.hs view
@@ -38,6 +38,8 @@ import           Control.Monad.Identity      (runIdentity) import           Control.Monad.State         (State, execState, modify) import           Control.Monad.Writer        (Writer, execWriter, tell)+import           Data.CaseInsensitive        (CI)+import qualified Data.CaseInsensitive        as CI import           Data.Hashable               (Hashable) import qualified Data.HashSet                as HS import           Data.List                   (foldl')@@ -68,7 +70,7 @@     = Plain ![Inline]     | Para ![Inline]     | LineBlock ![[Inline]]-    | CodeBlock !Pandoc.Attr !T.Text+    | CodeBlock ![CI T.Text] !T.Text     | RawBlock !Pandoc.Format !T.Text     | BlockQuote ![Block]     | OrderedList !Pandoc.ListAttributes ![[Block]]@@ -194,7 +196,8 @@ fromPandocBlock (Pandoc.Para xs) = [Para (fromPandocInlines xs)] fromPandocBlock (Pandoc.LineBlock xs) =     [LineBlock (map fromPandocInlines xs)]-fromPandocBlock (Pandoc.CodeBlock attrs body) = [CodeBlock attrs body]+fromPandocBlock (Pandoc.CodeBlock (_, classes, _) body) =+    [CodeBlock (map CI.mk classes) body] fromPandocBlock (Pandoc.RawBlock fmt body)     -- Parse config blocks.     | fmt == "html"
lib/Patat/Theme.hs view
@@ -3,10 +3,13 @@ {-# LANGUAGE OverloadedStrings          #-} {-# LANGUAGE TemplateHaskell            #-} module Patat.Theme-    ( Theme (..)+    ( Style (..)+    , HeaderAlign (..)+    , HeaderTheme (..)+    , HeaderThemes (..)+    , Theme (..)     , defaultTheme--    , Style (..)+    , themeForHeader      , SyntaxHighlighting (..)     , defaultSyntaxHighlighting@@ -32,9 +35,189 @@   --------------------------------------------------------------------------------+newtype Style = Style {unStyle :: [Ansi.SGR]}+    deriving (Eq, Monoid, Semigroup, Show)+++--------------------------------------------------------------------------------+instance A.ToJSON Style where+    toJSON = A.toJSON . mapMaybe sgrToString . unStyle+++--------------------------------------------------------------------------------+instance A.FromJSON Style where+    parseJSON val = do+        names <- A.parseJSON val+        sgrs  <- mapM toSgr names+        return $! Style sgrs+      where+        toSgr name = case stringToSgr name of+            Just sgr -> return sgr+            Nothing  -> fail $!+                "Unknown style: " ++ show name ++ ". Known styles are: " +++                intercalate ", " (map show $ M.keys namedSgrs) +++                ", or \"rgb#RrGgBb\" and \"onRgb#RrGgBb\" where 'Rr', " +++                "'Gg' and 'Bb' are hexadecimal bytes (e.g. \"rgb#f08000\")."+++--------------------------------------------------------------------------------+stringToSgr :: String -> Maybe Ansi.SGR+stringToSgr s+    | "rgb#"   `isPrefixOf` s = rgbToSgr Ansi.Foreground $ drop 4 s+    | "onRgb#" `isPrefixOf` s = rgbToSgr Ansi.Background $ drop 6 s+    | otherwise               = M.lookup s namedSgrs+++--------------------------------------------------------------------------------+rgbToSgr :: Ansi.ConsoleLayer -> String -> Maybe Ansi.SGR+rgbToSgr layer rgbHex =+    case sRGB24reads rgbHex of+        [(color, "")] -> Just $ Ansi.SetRGBColor layer color+        _             -> Nothing+++--------------------------------------------------------------------------------+sgrToString :: Ansi.SGR -> Maybe String+sgrToString sgr = case sgr of+    Ansi.SetColor layer intensity color -> Just $ layerPrefix layer $+        (case intensity of+            Ansi.Dull  -> "dull"+            Ansi.Vivid -> "vivid") +++        (case color of+            Ansi.Black   -> "Black"+            Ansi.Red     -> "Red"+            Ansi.Green   -> "Green"+            Ansi.Yellow  -> "Yellow"+            Ansi.Blue    -> "Blue"+            Ansi.Magenta -> "Magenta"+            Ansi.Cyan    -> "Cyan"+            Ansi.White   -> "White")++    Ansi.SetUnderlining Ansi.SingleUnderline -> Just "underline"++    Ansi.SetConsoleIntensity Ansi.BoldIntensity -> Just "bold"++    Ansi.SetItalicized True -> Just "italic"++    Ansi.SetRGBColor layer color -> Just $ layerPrefix layer $+        "rgb#" ++ (toRGBHex $ toSRGB24 color)++    _ -> Nothing+  where+    toRGBHex (RGB r g b) = concat $ map toHexByte [r, g, b]+    toHexByte x = showHex2 x ""+    showHex2 x | x <= 0xf = ("0" ++) . showHex x+               | otherwise = showHex x++    layerPrefix layer str = case layer of+        Ansi.Foreground -> str+        Ansi.Background -> "on" ++ capitalize str+        Ansi.Underlining -> "underline" ++ capitalize str+++--------------------------------------------------------------------------------+nameForTokenType :: Skylighting.TokenType -> String+nameForTokenType =+    unCapitalize . dropTok . show+  where+    unCapitalize (x : xs) = toLower x : xs+    unCapitalize xs       = xs++    dropTok :: String -> String+    dropTok str+        | "Tok" `isSuffixOf` str = take (length str - 3) str+        | otherwise              = str+++--------------------------------------------------------------------------------+nameToTokenType :: String -> Maybe Skylighting.TokenType+nameToTokenType = readMaybe . capitalize . (++ "Tok")+++--------------------------------------------------------------------------------+capitalize :: String -> String+capitalize ""       = ""+capitalize (x : xs) = toUpper x : xs+++--------------------------------------------------------------------------------+namedSgrs :: M.Map String Ansi.SGR+namedSgrs = M.fromList+    [ (name, sgr)+    | sgr  <- knownSgrs+    , name <- maybeToList (sgrToString sgr)+    ]+  where+    -- It doesn't really matter if we generate "too much" SGRs here since+    -- 'sgrToString' will only pick the ones we support.+    knownSgrs =+        [ Ansi.SetColor l i c+        | l <- [minBound .. maxBound]+        , i <- [minBound .. maxBound]+        , c <- [minBound .. maxBound]+        ] +++        [Ansi.SetUnderlining      u | u <- [minBound .. maxBound]] +++        [Ansi.SetConsoleIntensity c | c <- [minBound .. maxBound]] +++        [Ansi.SetItalicized       i | i <- [minBound .. maxBound]]+++--------------------------------------------------------------------------------+data HeaderAlign = LeftHeaderAlign | CenterHeaderAlign+    deriving (Eq, Show)+++--------------------------------------------------------------------------------+instance A.ToJSON HeaderAlign where+    toJSON LeftHeaderAlign   = "left"+    toJSON CenterHeaderAlign = "center"+++--------------------------------------------------------------------------------+instance A.FromJSON HeaderAlign where+    parseJSON = A.withText "FromJSON HeaderAlign" $ \txt -> case txt of+        "left"   -> pure LeftHeaderAlign+        "center" -> pure CenterHeaderAlign+        _        -> fail $ "Unknown align: " ++ show txt+++--------------------------------------------------------------------------------+data HeaderTheme = HeaderTheme+    { htStyle     :: !(Maybe Style)+    , htPrefix    :: !(Maybe T.Text)+    , htUnderline :: !(Maybe T.Text)+    , htAlign     :: !(Maybe HeaderAlign)+    } deriving (Eq, Show)+++--------------------------------------------------------------------------------+$(A.deriveJSON A.dropPrefixOptions ''HeaderTheme)+++--------------------------------------------------------------------------------+instance Semigroup HeaderTheme where+    l <> r = HeaderTheme+        { htStyle     = htStyle     l `mplus` htStyle     r+        , htPrefix    = htPrefix    l `mplus` htPrefix    r+        , htUnderline = htUnderline l `mplus` htUnderline r+        , htAlign     = htAlign     l `mplus` htAlign     r+        }+++--------------------------------------------------------------------------------+newtype HeaderThemes = HeaderThemes (M.Map String HeaderTheme)+    deriving (Eq, Show, A.FromJSON, A.ToJSON)+++--------------------------------------------------------------------------------+instance Semigroup HeaderThemes where+    HeaderThemes l <> HeaderThemes r = HeaderThemes $ M.unionWith (<>) l r+++-------------------------------------------------------------------------------- data Theme = Theme     { themeBorders            :: !(Maybe Style)     , themeHeader             :: !(Maybe Style)+    , themeHeaders            :: !(Maybe HeaderThemes)     , themeCodeBlock          :: !(Maybe Style)     , themeBulletList         :: !(Maybe Style)     , themeBulletListMarkers  :: !(Maybe T.Text)@@ -65,6 +248,7 @@     l <> r = Theme         { themeBorders            = mplusOn   themeBorders         , themeHeader             = mplusOn   themeHeader+        , themeHeaders            = mappendOn themeHeaders         , themeCodeBlock          = mplusOn   themeCodeBlock         , themeBulletList         = mplusOn   themeBulletList         , themeBulletListMarkers  = mplusOn   themeBulletListMarkers@@ -99,13 +283,17 @@     mempty  = Theme         Nothing Nothing Nothing Nothing Nothing Nothing Nothing Nothing Nothing         Nothing Nothing Nothing Nothing Nothing Nothing Nothing Nothing Nothing-        Nothing Nothing Nothing Nothing Nothing Nothing+        Nothing Nothing Nothing Nothing Nothing Nothing Nothing  -------------------------------------------------------------------------------- defaultTheme :: Theme defaultTheme = Theme     { themeBorders            = dull Ansi.Yellow     , themeHeader             = dull Ansi.Blue+    , themeHeaders            = Just $ HeaderThemes $ M.fromList $ do+        n <- [1 .. 6]+        let prefix = T.replicate n "#" <> " "+        pure ("h" <> show n, HeaderTheme Nothing (Just prefix) Nothing Nothing)     , themeCodeBlock          = dull Ansi.White `mappend` ondull Ansi.Black     , themeBulletList         = dull Ansi.Magenta     , themeBulletListMarkers  = Just "-*"@@ -137,105 +325,12 @@   ---------------------------------------------------------------------------------newtype Style = Style {unStyle :: [Ansi.SGR]}-    deriving (Eq, Monoid, Semigroup, Show)------------------------------------------------------------------------------------instance A.ToJSON Style where-    toJSON = A.toJSON . mapMaybe sgrToString . unStyle------------------------------------------------------------------------------------instance A.FromJSON Style where-    parseJSON val = do-        names <- A.parseJSON val-        sgrs  <- mapM toSgr names-        return $! Style sgrs-      where-        toSgr name = case stringToSgr name of-            Just sgr -> return sgr-            Nothing  -> fail $!-                "Unknown style: " ++ show name ++ ". Known styles are: " ++-                intercalate ", " (map show $ M.keys namedSgrs) ++-                ", or \"rgb#RrGgBb\" and \"onRgb#RrGgBb\" where 'Rr', " ++-                "'Gg' and 'Bb' are hexadecimal bytes (e.g. \"rgb#f08000\")."------------------------------------------------------------------------------------stringToSgr :: String -> Maybe Ansi.SGR-stringToSgr s-    | "rgb#"   `isPrefixOf` s = rgbToSgr Ansi.Foreground $ drop 4 s-    | "onRgb#" `isPrefixOf` s = rgbToSgr Ansi.Background $ drop 6 s-    | otherwise               = M.lookup s namedSgrs------------------------------------------------------------------------------------rgbToSgr :: Ansi.ConsoleLayer -> String -> Maybe Ansi.SGR-rgbToSgr layer rgbHex =-    case sRGB24reads rgbHex of-        [(color, "")] -> Just $ Ansi.SetRGBColor layer color-        _             -> Nothing------------------------------------------------------------------------------------sgrToString :: Ansi.SGR -> Maybe String-sgrToString sgr = case sgr of-    Ansi.SetColor layer intensity color -> Just $ layerPrefix layer $-        (case intensity of-            Ansi.Dull  -> "dull"-            Ansi.Vivid -> "vivid") ++-        (case color of-            Ansi.Black   -> "Black"-            Ansi.Red     -> "Red"-            Ansi.Green   -> "Green"-            Ansi.Yellow  -> "Yellow"-            Ansi.Blue    -> "Blue"-            Ansi.Magenta -> "Magenta"-            Ansi.Cyan    -> "Cyan"-            Ansi.White   -> "White")--    Ansi.SetUnderlining Ansi.SingleUnderline -> Just "underline"--    Ansi.SetConsoleIntensity Ansi.BoldIntensity -> Just "bold"--    Ansi.SetItalicized True -> Just "italic"--    Ansi.SetRGBColor layer color -> Just $ layerPrefix layer $-        "rgb#" ++ (toRGBHex $ toSRGB24 color)--    _ -> Nothing-  where-    toRGBHex (RGB r g b) = concat $ map toHexByte [r, g, b]-    toHexByte x = showHex2 x ""-    showHex2 x | x <= 0xf = ("0" ++) . showHex x-               | otherwise = showHex x--    layerPrefix layer str = case layer of-        Ansi.Foreground -> str-        Ansi.Background -> "on" ++ capitalize str-        Ansi.Underlining -> "underline" ++ capitalize str------------------------------------------------------------------------------------namedSgrs :: M.Map String Ansi.SGR-namedSgrs = M.fromList-    [ (name, sgr)-    | sgr  <- knownSgrs-    , name <- maybeToList (sgrToString sgr)-    ]+themeForHeader :: Int -> Theme -> HeaderTheme+themeForHeader n theme = maybe def (<> def) $ do+    HeaderThemes m <- themeHeaders theme+    M.lookup ("h" ++ show n) m   where-    -- It doesn't really matter if we generate "too much" SGRs here since-    -- 'sgrToString' will only pick the ones we support.-    knownSgrs =-        [ Ansi.SetColor l i c-        | l <- [minBound .. maxBound]-        , i <- [minBound .. maxBound]-        , c <- [minBound .. maxBound]-        ] ++-        [Ansi.SetUnderlining      u | u <- [minBound .. maxBound]] ++-        [Ansi.SetConsoleIntensity c | c <- [minBound .. maxBound]] ++-        [Ansi.SetItalicized       i | i <- [minBound .. maxBound]]+    def = HeaderTheme (themeHeader theme) Nothing Nothing Nothing   --------------------------------------------------------------------------------@@ -287,31 +382,6 @@      mkSyntaxHighlighting ls = SyntaxHighlighting $         M.fromList [(nameForTokenType tt, s) | (tt, s) <- ls]------------------------------------------------------------------------------------nameForTokenType :: Skylighting.TokenType -> String-nameForTokenType =-    unCapitalize . dropTok . show-  where-    unCapitalize (x : xs) = toLower x : xs-    unCapitalize xs       = xs--    dropTok :: String -> String-    dropTok str-        | "Tok" `isSuffixOf` str = take (length str - 3) str-        | otherwise              = str------------------------------------------------------------------------------------nameToTokenType :: String -> Maybe Skylighting.TokenType-nameToTokenType = readMaybe . capitalize . (++ "Tok")------------------------------------------------------------------------------------capitalize :: String -> String-capitalize ""       = ""-capitalize (x : xs) = toUpper x : xs   --------------------------------------------------------------------------------
patat.cabal view
@@ -1,5 +1,5 @@ Name:          patat-Version:       0.15.1.0+Version:       0.15.2.0 Synopsis:      Terminal-based presentations using Pandoc Description:   Terminal-based presentations using Pandoc. License:       GPL-2@@ -39,6 +39,7 @@     base                 >= 4.9   && < 5,     base64-bytestring    >= 1.0   && < 1.3,     bytestring           >= 0.10  && < 0.13,+    case-insensitive     >= 1.2   && < 1.3,     colour               >= 2.3   && < 2.4,     containers           >= 0.5   && < 0.7,     directory            >= 1.2   && < 1.4,