aboutsummaryrefslogtreecommitdiffstats
path: root/app/Render.hs
diff options
context:
space:
mode:
Diffstat (limited to 'app/Render.hs')
-rw-r--r--app/Render.hs196
1 files changed, 196 insertions, 0 deletions
diff --git a/app/Render.hs b/app/Render.hs
new file mode 100644
index 0000000..2220fb8
--- /dev/null
+++ b/app/Render.hs
@@ -0,0 +1,196 @@
+{-# LANGUAGE DefaultSignatures #-}
+{-# LANGUAGE UndecidableInstances #-}
+
+-- | This module is an experimental wrapper around the prettyprinter module.
+module Render
+ ( -- * Re-exports
+ module Prettyprinter,
+ module Prettyprinter.Render.Terminal,
+
+ -- * Render
+ Render (..),
+ (<<<),
+ styled,
+
+ -- * Reporting styles
+ Detailed (..),
+ Summarized (..),
+ Linked (..),
+
+ -- * Markdown
+ Markdown (..),
+ renderAsMarkdown,
+
+ -- * Additional symbols
+ plus,
+ minus,
+ )
+where
+
+import CMark qualified as D
+import Data.List (isPrefixOf, isSuffixOf, intersperse)
+import Data.Maybe (catMaybes)
+import Data.Text qualified as T
+import Data.Time.Calendar (Day)
+import Prettyprinter
+import Prettyprinter.Render.Terminal
+
+-- | The render class is a superclass of `Pretty`. It exists to facilitate
+-- reporting styles (see below), as well as decomposing aggregate entities into composable parts (for instance `IssueTitle`, `IssueTags` of `Issue`).
+--
+-- Renderable entities are expected to define `Render (Detailed a)` and default `Render a` to that instance.
+class Render a where
+ render :: a -> Doc AnsiStyle
+ default render :: Pretty a => a -> Doc AnsiStyle
+ render = pretty
+
+instance Render (Doc AnsiStyle) where
+ render = id
+
+instance Render T.Text
+
+instance Render String
+
+instance Render Day where
+ render = render . show
+
+-- | The `(<<<)` combinator concatenates renderables. It takes care of inserting spaces between non-empty renderables automatically, obsoleting prettyprinter's `(<+>)` and `(<>)`.
+(<<<) :: (Render a, Render b) => a -> b -> Doc AnsiStyle
+(<<<) a' b' =
+ case (nonEmpty a', nonEmpty b') of
+ (Nothing, Nothing) -> emptyDoc
+ (Just a, Nothing) -> a
+ (Nothing, Just b) -> b
+ (Just a, Just b) ->
+ if endsWithNL a || startsWithNL b
+ then a <> b
+ else a <> space <> b
+ where
+ nonEmpty x' =
+ let x = render x'
+ in if not (null (show x)) then Just (render x) else Nothing
+ startsWithNL = ("\n" `isPrefixOf`) . show . render
+ endsWithNL = ("\n" `isSuffixOf`) . show . render
+
+-- | `styled` allows to annotate a document with multiple annotations. It obsoletes prettyprinter's `annotate`.
+styled :: [AnsiStyle] -> Doc AnsiStyle -> Doc AnsiStyle
+styled [] doc = doc
+styled as doc = foldl1 (.) (map annotate as) $ doc
+
+-- | The detailed report should present all information, corresponding to the semantics of the `show` command.
+--
+-- It should be the default instance for all data types.
+newtype Detailed a = Detailed {unDetailed :: a}
+
+-- | The summarized report should present most relevant information in a concise way, corresponding to the semantics of the `list` command.
+newtype Summarized a = Summarized {unSummarized :: a}
+
+-- | The linked report should present strictly necessary information. It should be used when one entity refers to another.
+newtype Linked a = Linked {unLinked :: a}
+
+newtype Markdown = Markdown {unMarkdown :: D.Node}
+
+renderAsMarkdown :: T.Text -> Doc AnsiStyle
+renderAsMarkdown = render . Markdown . D.commonmarkToNode []
+
+instance Render Markdown where
+ render = maybe emptyDoc go . rec . unMarkdown
+ where
+ rec (D.Node _ D.SOFTBREAK _) = Nothing
+ rec (D.Node p t ns) = Just $ D.Node p t (catMaybes $ map rec ns)
+
+ go (D.Node _ D.DOCUMENT ns) = vsep (intersperse (pretty ("" :: T.Text)) (map go ns))
+ go (D.Node _ D.THEMATIC_BREAK _) = pretty ("***" :: T.Text)
+ go (D.Node _ D.PARAGRAPH ns) = fillSep $ map go ns
+ go (D.Node _ D.BLOCK_QUOTE ns) =
+ styled [color Black] . fillSep $
+ pretty (">" :: T.Text)
+ : [ align . fillSep $
+ map go ns
+ ++ [pretty ("\n" :: T.Text)]
+ ]
+ go (D.Node _ (D.HTML_BLOCK s) _) =
+ styled [color Yellow] $
+ hsep
+ [ pretty (T.strip s),
+ pretty ("\n" :: T.Text)
+ ]
+ go (D.Node _ (D.CUSTOM_BLOCK _ _) ns) =
+ styled [color Yellow] $
+ hsep $
+ map go ns ++ [pretty ("\n" :: T.Text)]
+ go (D.Node _ (D.CODE_BLOCK _ s) _) =
+ hcat
+ [ styled [color Green] (pretty ("────────────────────\n" :: T.Text)),
+ styled [color Yellow]
+ . vsep
+ . map (styled [color Yellow] . pretty)
+ $ T.lines s,
+ pretty ("\n" :: T.Text),
+ styled [color Green] (pretty ("────────────────────" :: T.Text))
+ ]
+ go (D.Node _ (D.HEADING c) ns) =
+ styled [color Magenta] . fillSep $
+ pretty (T.replicate c "#") : map go ns
+ go (D.Node _ (D.LIST _) ns) =
+ hsep (map ((pretty ("- " :: T.Text)) <>) (map go ns))
+ go (D.Node _ D.ITEM ns) = fillSep (map go ns)
+ go (D.Node _ (D.TEXT s) _) =
+ fillSep . map (pretty . T.strip) $
+ T.words s
+ go (D.Node _ D.LINEBREAK _) = hardline
+ go (D.Node _ (D.HTML_INLINE s) _) =
+ styled [color Yellow] $ pretty (T.strip s)
+ go (D.Node _ (D.CUSTOM_INLINE _ _) ns) =
+ styled [color Yellow] . fillSep $ map go ns
+ go (D.Node _ (D.CODE s) _) =
+ styled [color Yellow] $ pretty (T.strip s)
+ go (D.Node _ D.EMPH ns) = styled [italicized] $ fillSep (map go ns)
+ go (D.Node _ D.STRONG ns) = styled [bold] $ fillSep (map go ns)
+ go (D.Node _ D.SOFTBREAK _) = emptyDoc
+ go (D.Node _ (D.LINK url "") ns) =
+ styled [color Blue] $
+ hcat
+ [ pretty ("[" :: T.Text),
+ hsep (map go ns),
+ pretty ("](" :: T.Text),
+ pretty url,
+ pretty (")" :: T.Text)
+ ]
+ go (D.Node _ (D.LINK url title) ns) =
+ styled [color Blue] $
+ hcat
+ [ pretty ("[" :: T.Text),
+ hsep (map go ns),
+ pretty ("](" :: T.Text),
+ pretty url,
+ pretty (" \"" :: T.Text),
+ pretty title,
+ pretty ("\")" :: T.Text)
+ ]
+ go (D.Node _ (D.IMAGE url "") ns) =
+ styled [color Blue] $
+ hcat
+ [ pretty ("![" :: T.Text),
+ hsep (map go ns),
+ pretty ("](" :: T.Text),
+ pretty url,
+ pretty (")" :: T.Text)
+ ]
+ go (D.Node _ (D.IMAGE url title) ns) =
+ styled [color Blue] $
+ hcat
+ [ pretty ("![" :: T.Text),
+ hsep (map go ns),
+ pretty ("](" :: T.Text),
+ pretty url,
+ pretty (" \"" :: T.Text),
+ pretty title,
+ pretty ("\")" :: T.Text)
+ ]
+
+plus :: Doc ann
+plus = pretty ("+" :: T.Text)
+
+minus :: Doc ann
+minus = pretty ("-" :: T.Text)