packages feed

errata 0.1.0.0 → 0.2.0.0

raw patch · 7 files changed

+210/−116 lines, 7 filesPVP ok

version bump matches the API change (PVP)

API changes (from Hackage documentation)

+ Errata: [blockHeader] :: Block -> Maybe Header
+ Errata.Types: [blockHeader] :: Block -> Maybe Header
+ Errata.Types: type Body = Text
+ Errata.Types: type Column = Int
+ Errata.Types: type Header = Text
+ Errata.Types: type Label = Text
+ Errata.Types: type Line = Int
- Errata: Block :: Style -> (FilePath, Int, Int) -> [Pointer] -> Maybe Text -> Block
+ Errata: Block :: Style -> (FilePath, Line, Column) -> Maybe Header -> [Pointer] -> Maybe Body -> Block
- Errata: Errata :: Maybe Text -> Block -> [Block] -> Maybe Text -> Errata
+ Errata: Errata :: Maybe Header -> Block -> [Block] -> Maybe Body -> Errata
- Errata: Pointer :: Int -> Int -> Int -> Bool -> Maybe Text -> Pointer
+ Errata: Pointer :: Line -> Column -> Column -> Bool -> Maybe Label -> Pointer
- Errata: Style :: ((FilePath, Int, Int) -> Text) -> (Int -> Text) -> ([(Int, Int)] -> Text -> Text) -> Text -> Text -> Text -> Text -> Text -> Text -> Text -> Text -> Style
+ Errata: Style :: ((FilePath, Line, Column) -> Text) -> (Line -> Text) -> ([(Column, Column)] -> Text -> Text) -> Text -> Text -> Text -> Text -> Text -> Text -> Text -> Text -> Style
- Errata: [blockBody] :: Block -> Maybe Text
+ Errata: [blockBody] :: Block -> Maybe Body
- Errata: [blockLocation] :: Block -> (FilePath, Int, Int)
+ Errata: [blockLocation] :: Block -> (FilePath, Line, Column)
- Errata: [errataBody] :: Errata -> Maybe Text
+ Errata: [errataBody] :: Errata -> Maybe Body
- Errata: [errataHeader] :: Errata -> Maybe Text
+ Errata: [errataHeader] :: Errata -> Maybe Header
- Errata: [pointerColEnd] :: Pointer -> Int
+ Errata: [pointerColEnd] :: Pointer -> Column
- Errata: [pointerColStart] :: Pointer -> Int
+ Errata: [pointerColStart] :: Pointer -> Column
- Errata: [pointerLabel] :: Pointer -> Maybe Text
+ Errata: [pointerLabel] :: Pointer -> Maybe Label
- Errata: [pointerLine] :: Pointer -> Int
+ Errata: [pointerLine] :: Pointer -> Line
- Errata: [styleLine] :: Style -> [(Int, Int)] -> Text -> Text
+ Errata: [styleLine] :: Style -> [(Column, Column)] -> Text -> Text
- Errata: [styleLocation] :: Style -> (FilePath, Int, Int) -> Text
+ Errata: [styleLocation] :: Style -> (FilePath, Line, Column) -> Text
- Errata: [styleNumber] :: Style -> Int -> Text
+ Errata: [styleNumber] :: Style -> Line -> Text
- Errata: blockConnected :: Style -> FilePath -> (Int, Int, Int, Maybe Text) -> (Int, Int, Int, Maybe Text) -> Maybe Text -> Block
+ Errata: blockConnected :: Style -> FilePath -> Maybe Header -> (Line, Column, Column, Maybe Label) -> (Line, Column, Column, Maybe Label) -> Maybe Body -> Block
- Errata: blockConnected' :: Style -> FilePath -> (Int, Int, Maybe Text) -> (Int, Int, Maybe Text) -> Maybe Text -> Block
+ Errata: blockConnected' :: Style -> FilePath -> Maybe Header -> (Line, Column, Maybe Label) -> (Line, Column, Maybe Label) -> Maybe Body -> Block
- Errata: blockMerged :: Style -> FilePath -> (Int, Int, Int, Maybe Text) -> (Int, Int, Int, Maybe Text) -> Maybe Text -> Maybe Text -> Block
+ Errata: blockMerged :: Style -> FilePath -> Maybe Header -> (Line, Column, Column, Maybe Label) -> (Line, Column, Column, Maybe Label) -> Maybe Label -> Maybe Body -> Block
- Errata: blockMerged' :: Style -> FilePath -> (Int, Int, Maybe Text) -> (Int, Int, Maybe Text) -> Maybe Text -> Maybe Text -> Block
+ Errata: blockMerged' :: Style -> FilePath -> Maybe Header -> (Line, Column, Maybe Label) -> (Line, Column, Maybe Label) -> Maybe Label -> Maybe Body -> Block
- Errata: blockSimple :: Style -> FilePath -> Int -> (Int, Int) -> Maybe Text -> Maybe Text -> Block
+ Errata: blockSimple :: Style -> FilePath -> Maybe Header -> (Line, Column, Column, Maybe Label) -> Maybe Body -> Block
- Errata: blockSimple' :: Style -> FilePath -> Int -> Int -> Maybe Text -> Maybe Text -> Block
+ Errata: blockSimple' :: Style -> FilePath -> Maybe Header -> (Line, Column, Maybe Label) -> Maybe Body -> Block
- Errata: errataSimple :: Maybe Text -> Block -> Maybe Text -> Errata
+ Errata: errataSimple :: Maybe Header -> Block -> Maybe Body -> Errata
- Errata.Internal.Render: renderBlock :: Source source => Seq source -> Block -> Builder
+ Errata.Internal.Render: renderBlock :: Source source => Array Int source -> Block -> Builder
- Errata.Internal.Render: renderErrata :: Source source => Seq source -> Errata -> Builder
+ Errata.Internal.Render: renderErrata :: Source source => Array Int source -> Errata -> Builder
- Errata.Internal.Render: renderSourceLines :: Source source => Seq source -> Block -> NonEmpty Pointer -> Builder
+ Errata.Internal.Render: renderSourceLines :: Source source => Array Int source -> Block -> NonEmpty Pointer -> Builder
- Errata.Types: Block :: Style -> (FilePath, Int, Int) -> [Pointer] -> Maybe Text -> Block
+ Errata.Types: Block :: Style -> (FilePath, Line, Column) -> Maybe Header -> [Pointer] -> Maybe Body -> Block
- Errata.Types: Errata :: Maybe Text -> Block -> [Block] -> Maybe Text -> Errata
+ Errata.Types: Errata :: Maybe Header -> Block -> [Block] -> Maybe Body -> Errata
- Errata.Types: Pointer :: Int -> Int -> Int -> Bool -> Maybe Text -> Pointer
+ Errata.Types: Pointer :: Line -> Column -> Column -> Bool -> Maybe Label -> Pointer
- Errata.Types: Style :: ((FilePath, Int, Int) -> Text) -> (Int -> Text) -> ([(Int, Int)] -> Text -> Text) -> Text -> Text -> Text -> Text -> Text -> Text -> Text -> Text -> Style
+ Errata.Types: Style :: ((FilePath, Line, Column) -> Text) -> (Line -> Text) -> ([(Column, Column)] -> Text -> Text) -> Text -> Text -> Text -> Text -> Text -> Text -> Text -> Text -> Style
- Errata.Types: [blockBody] :: Block -> Maybe Text
+ Errata.Types: [blockBody] :: Block -> Maybe Body
- Errata.Types: [blockLocation] :: Block -> (FilePath, Int, Int)
+ Errata.Types: [blockLocation] :: Block -> (FilePath, Line, Column)
- Errata.Types: [errataBody] :: Errata -> Maybe Text
+ Errata.Types: [errataBody] :: Errata -> Maybe Body
- Errata.Types: [errataHeader] :: Errata -> Maybe Text
+ Errata.Types: [errataHeader] :: Errata -> Maybe Header
- Errata.Types: [pointerColEnd] :: Pointer -> Int
+ Errata.Types: [pointerColEnd] :: Pointer -> Column
- Errata.Types: [pointerColStart] :: Pointer -> Int
+ Errata.Types: [pointerColStart] :: Pointer -> Column
- Errata.Types: [pointerLabel] :: Pointer -> Maybe Text
+ Errata.Types: [pointerLabel] :: Pointer -> Maybe Label
- Errata.Types: [pointerLine] :: Pointer -> Int
+ Errata.Types: [pointerLine] :: Pointer -> Line
- Errata.Types: [styleLine] :: Style -> [(Int, Int)] -> Text -> Text
+ Errata.Types: [styleLine] :: Style -> [(Column, Column)] -> Text -> Text
- Errata.Types: [styleLocation] :: Style -> (FilePath, Int, Int) -> Text
+ Errata.Types: [styleLocation] :: Style -> (FilePath, Line, Column) -> Text
- Errata.Types: [styleNumber] :: Style -> Int -> Text
+ Errata.Types: [styleNumber] :: Style -> Line -> Text
- Errata.Types: highlight :: Text -> Text -> [(Int, Int)] -> Text -> Text
+ Errata.Types: highlight :: Text -> Text -> [(Column, Column)] -> Text -> Text
- Errata.Types: pointerColumns :: Pointer -> (Int, Int)
+ Errata.Types: pointerColumns :: Pointer -> (Line, Column)

Files

CHANGELOG.md view
@@ -2,6 +2,16 @@  **Errata** uses [PVP Versioning](https://pvp.haskell.org). +## 0.2.0.0++* Added new `blockHeader` field to `Block`, which will put text underneath the location text but above the source lines. This also affects all the block helper functions, which now have an argument for the header.++* The `blockSimple` and `blockSimple'` functions are now passed tuples of positions and labels instead of `Int`s, which is more consistent with the rest of the helper functions.++* Defined type synonyms for line, column, headers, bodies, and labels, for the purpose of documentation. It should be much easier to know what is expected by just reading the types now.++* Use `GHC.Arr.Array` for keeping source lines, which should be faster for indexing and should not force the individual lines until they are needed.+ ## 0.1.0.0  * Initial release.
README.md view
@@ -4,14 +4,24 @@ [![Hackage](https://img.shields.io/hackage/v/errata.svg?logo=haskell)](https://hackage.haskell.org/package/errata) [![CI](https://github.com/1Computer1/errata/workflows/CI/badge.svg)](https://github.com/1Computer1/errata/actions?query=workflow%3ACI) -**Errata** is an extremely customizable error pretty printer that can handle many kinds of error formatting. For example, it can handle errors that are all over the source or errors that are connected to each other spanning multiple lines. You can be as simple or as fancy as you like!  +**Errata** is an extremely customizable error pretty printer that can handle many kinds of error formatting. +## Features++**Errata** can handle errors that are all over the source or errors that are connected to each other spanning multiple lines. You can be as simple or as fancy as you like!  + You can also customize the format of the printer in several ways:    - Custom messages and labels - Custom character sets for symbols - Highlighting the source, messages, and symbols -Here is an example of a pretty error message from **Errata**:  +## Examples -![Pretty error message](./errata.png)+A clean, modern error message that is trying to be helpful:++![An error message that points out that the `fold` function was not found in scope. It then asks if the user meant to use `foldl` or `foldr`](./errata_fold.png)++A busy error message with underlining and connections:++![An error message that highlights mismatching types in an `if` expression. The first section underlines the mismatching values, and the second section underlines the `if` expression](./errata_if.png)
errata.cabal view
@@ -1,6 +1,6 @@ cabal-version: 2.4 name:          errata-version:       0.1.0.0+version:       0.2.0.0 synopsis:      Source code error pretty printing description:     An extremely customizable error pretty printer that can handle many kinds of error formatting.
example/Main.hs view
@@ -22,8 +22,10 @@ toErrata :: ParseError -> Errata toErrata (ParseError fp l c unexpected expected) =     errataSimple-        (Just "error: invalid syntax")-        (blockSimple basicStyle fp l (c, c + T.length unexpected) Nothing+        (Just "an error occured!")+        (blockSimple basicStyle fp+            (Just "error: invalid syntax")+            (l, c, c + T.length unexpected, Just "this one")             (Just $ "unexpected " <> unexpected <> "\nexpected " <> T.intercalate ", " expected))         Nothing @@ -36,7 +38,7 @@  -- An ad-hoc errata. adhoc :: [Pointer] -> Errata-adhoc ps = errataSimple (Just "an error") (Block fancyRedStyle ("here", 1, 1) ps (Just "pbody")) Nothing+adhoc ps = errataSimple (Just "an error") (Block fancyRedStyle ("here", 1, 1) Nothing ps (Just "pbody")) Nothing  main :: IO () main = do@@ -108,7 +110,7 @@         (pure (ParseError "./comma.json" 2 18 "]" ["null", "true", "false", "\"", "-", "digit", "[", "{"]))     putStrLn "" -    putStrLn "Example from the readme:"+    putStrLn "Example 1 from the readme:"     putStrLn ""     TL.putStrLn $ prettyErrors @String         "foo = if 1 > 2\n    then 100\n    else \"uh oh\""@@ -117,6 +119,7 @@             (Block                 fancyRedStyle                 ("file.hs", 3, 10)+                Nothing                 [ Pointer 2 10 13 False (Just "\x1b[31mthis has type `Int`\x1b[0m")                 , Pointer 3 10 17 False (Just "\x1b[31mbut this has type `String`\x1b[0m")                 ]@@ -124,6 +127,7 @@             [ Block                 fancyYellowStyle                 ("file.hs", 1, 7)+                Nothing                 [ Pointer 1 7 9 True Nothing                 , Pointer 2 5 9 True Nothing                 , Pointer 3 5 9 True (Just "\x1b[33min this `if` expression\x1b[0m")@@ -131,5 +135,22 @@                 Nothing             ]             (Just "\x1b[33mnote: use --explain E001 to learn more\x1b[0m")+        ]+    putStrLn ""++    putStrLn "Example 2 from the readme:"+    putStrLn ""+    TL.putStrLn $ prettyErrors @String+        "sum xs = fold (+) 0 xs"+        [ Errata+            (Just "\x1b[31m─────── NAME UNKNOWN ───────\x1b[0m\n\nThe name \x1b[31mfold\x1b[0m was not found.\n")+            (Block+                fancyRedStyle { styleUnderline = " " }+                ("file.hs", 1, 10)+                Nothing+                [Pointer 1 10 14 False Nothing]+                Nothing)+            []+            (Just "Did you mean to use one of these?\n\n    \x1b[31mfoldl\x1b[0m\n    \x1b[31mfoldr\x1b[0m")         ]     putStrLn ""
src/Errata.hs view
@@ -17,6 +17,14 @@ The overall workflow to use the printer is to convert your error type to 'Errata', which entails converting your errors to 'Errata' by filling in messages and 'Block's. You can create 'Errata' and 'Block' from their constructors, or use the convenience functions for common usecases, like 'errataSimple' and 'blockSimple'.++For easier reading, we define:++> type Line = Int+> type Column = Int+> type Header = Text+> type Body = Text+> type Label = Text -} module Errata     ( -- * Error format data@@ -52,9 +60,9 @@  -- | Creates a simple error that has a single block, with an optional header or body. errataSimple-    :: Maybe T.Text -- ^ The header.+    :: Maybe Header -- ^ The header.     -> Block        -- ^ The block.-    -> Maybe T.Text -- ^ The body.+    -> Maybe Body   -- ^ The body.     -> Errata errataSimple header block body = Errata     { errataHeader = header@@ -63,66 +71,70 @@     , errataBody = body     } --- | A simple block that points to only one line and optionally has a label or a body message.+-- | A simple block that points to only one line and optionally has a label, header, or body message. blockSimple-    :: Style        -- ^ The style of the pointer.-    -> FilePath     -- ^ The filepath.-    -> Int          -- ^ The line number starting at 1.-    -> (Int, Int)   -- ^ The column span. These start at 1.-    -> Maybe T.Text -- ^ The label.-    -> Maybe T.Text -- ^ The body message.+    :: Style                               -- ^ The style of the pointer.+    -> FilePath                            -- ^ The filepath.+    -> Maybe Header                        -- ^ The header message.+    -> (Line, Column, Column, Maybe Label) -- ^ The line number and column span, starting at 1, and a label.+    -> Maybe Body                          -- ^ The body message.     -> Block-blockSimple style fp l (cs, ce) lbl m = Block+blockSimple style fp hm (l, cs, ce, lbl) bm = Block     { blockStyle = style     , blockLocation = (fp, l, cs)+    , blockHeader = hm     , blockPointers = [Pointer l cs ce False lbl]-    , blockBody = m+    , blockBody = bm     }  -- | A variant of 'blockSimple' that only points at one column. blockSimple'-    :: Style        -- ^ The style of the pointer.-    -> FilePath     -- ^ The filepath.-    -> Int          -- ^ The line number starting at 1.-    -> Int          -- ^ The column number starting at 1.-    -> Maybe T.Text -- ^ The label.-    -> Maybe T.Text -- ^ The body message.+    :: Style                       -- ^ The style of the pointer.+    -> FilePath                    -- ^ The filepath.+    -> Maybe Header                -- ^ The header message.+    -> (Line, Column, Maybe Label) -- ^ The line number and column, starting at 1, and a label.+    -> Maybe Body                  -- ^ The body message.     -> Block-blockSimple' style fp l c lbl m = Block+blockSimple' style fp hm (l, c, lbl) bm = Block     { blockStyle = style     , blockLocation = (fp, l, c)+    , blockHeader = hm     , blockPointers = [Pointer l c (c + 1) False lbl]-    , blockBody = m+    , blockBody = bm     }  -- | A block that points to two parts of the source that are visually connected together. blockConnected-    :: Style                         -- ^ The style of the pointer.-    -> FilePath                      -- ^ The filepath.-    -> (Int, Int, Int, Maybe T.Text) -- ^ The first line number and column span, starting at 1, as well as a label.-    -> (Int, Int, Int, Maybe T.Text) -- ^ The second line number and column span, starting at 1, as well as a label.-    -> Maybe T.Text                  -- ^ The body message.+    :: Style                               -- ^ The style of the pointer.+    -> FilePath                            -- ^ The filepath.+    -> Maybe Header                        -- ^ The header message.+    -> (Line, Column, Column, Maybe Label) -- ^ The first line number and column span, starting at 1, and a label.+    -> (Line, Column, Column, Maybe Label) -- ^ The second line number and column span, starting at 1, and a label.+    -> Maybe Body                          -- ^ The body message.     -> Block-blockConnected style fp (l1, cs1, ce1, lbl1) (l2, cs2, ce2, lbl2) m = Block+blockConnected style fp hm (l1, cs1, ce1, lbl1) (l2, cs2, ce2, lbl2) bm = Block     { blockStyle = style     , blockLocation = (fp, l1, cs1)+    , blockHeader = hm     , blockPointers = [Pointer l1 cs1 ce1 True lbl1, Pointer l2 cs2 ce2 True lbl2]-    , blockBody = m+    , blockBody = bm     }  -- | A variant of 'blockConnected' where the pointers point at only one column. blockConnected'-    :: Style                    -- ^ The style of the pointer.-    -> FilePath                 -- ^ The filepath.-    -> (Int, Int, Maybe T.Text) -- ^ The first line number and column, starting at 1, as well as a label.-    -> (Int, Int, Maybe T.Text) -- ^ The second line number and column, starting at 1, as well as a label.-    -> Maybe T.Text             -- ^ The body message.+    :: Style                       -- ^ The style of the pointer.+    -> FilePath                    -- ^ The filepath.+    -> Maybe Header                -- ^ The header message.+    -> (Line, Column, Maybe Label) -- ^ The first line number and column, starting at 1, and a label.+    -> (Line, Column, Maybe Label) -- ^ The second line number and column, starting at 1, and a label.+    -> Maybe Body                  -- ^ The body message.     -> Block-blockConnected' style fp (l1, c1, lbl1) (l2, c2, lbl2) m = Block+blockConnected' style fp hm (l1, c1, lbl1) (l2, c2, lbl2) bm = Block     { blockStyle = style     , blockLocation = (fp, l1, c1)+    , blockHeader = hm     , blockPointers = [Pointer l1 c1 (c1 + 1) True lbl1, Pointer l2 c2 (c2 + 1) True lbl2]-    , blockBody = m+    , blockBody = bm     }  {-|@@ -131,38 +143,42 @@ If the two parts of the source happen to be on the same line, the pointers are merged into one. -} blockMerged-    :: Style                         -- ^ The style of the pointer.-    -> FilePath                      -- ^ The filepath.-    -> (Int, Int, Int, Maybe T.Text) -- ^ The first line number and column span, starting at 1, as well as a label.-    -> (Int, Int, Int, Maybe T.Text) -- ^ The second line number and column span, starting at 1, as well as a label.-    -> Maybe T.Text                  -- ^ The label for when the two pointers are merged into one.-    -> Maybe T.Text                  -- ^ The body message.+    :: Style                               -- ^ The style of the pointer.+    -> FilePath                            -- ^ The filepath.+    -> Maybe Header                        -- ^ The header message.+    -> (Line, Column, Column, Maybe Label) -- ^ The first line number and column span, starting at 1, and a label.+    -> (Line, Column, Column, Maybe Label) -- ^ The second line number and column span, starting at 1, and a label.+    -> Maybe Label                         -- ^ The label for when the two pointers are merged into one.+    -> Maybe Body                          -- ^ The body message.     -> Block-blockMerged style fp (l1, cs1, ce1, lbl1) (l2, cs2, ce2, lbl2) lbl m = Block+blockMerged style fp hm (l1, cs1, ce1, lbl1) (l2, cs2, ce2, lbl2) lbl bm = Block     { blockStyle = style     , blockLocation = (fp, l1, cs1)+    , blockHeader = hm     , blockPointers = if l1 == l2         then [Pointer l1 cs1 ce2 False lbl]         else [Pointer l1 cs1 ce1 True lbl1, Pointer l2 cs2 ce2 True lbl2]-    , blockBody = m+    , blockBody = bm     }  -- | A variant of 'blockMerged' where the pointers point at only one column. blockMerged'-    :: Style                    -- ^ The style of the pointer.-    -> FilePath                 -- ^ The filepath.-    -> (Int, Int, Maybe T.Text) -- ^ The first line number and column, starting at 1, as well as a label.-    -> (Int, Int, Maybe T.Text) -- ^ The second line number and column, starting at 1, as well as a label.-    -> Maybe T.Text             -- ^ The label for when the two pointers are merged into one.-    -> Maybe T.Text             -- ^ The body message.+    :: Style                       -- ^ The style of the pointer.+    -> FilePath                    -- ^ The filepath.+    -> Maybe Header                -- ^ The header message.+    -> (Line, Column, Maybe Label) -- ^ The first line number and column, starting at 1, and a label.+    -> (Line, Column, Maybe Label) -- ^ The second line number and column, starting at 1, and a label.+    -> Maybe Label                 -- ^ The label for when the two pointers are merged into one.+    -> Maybe Body                  -- ^ The body message.     -> Block-blockMerged' style fp (l1, c1, lbl1) (l2, c2, lbl2) lbl m = Block+blockMerged' style fp hm (l1, c1, lbl1) (l2, c2, lbl2) lbl bm = Block     { blockStyle = style     , blockLocation = (fp, l1, c1)+    , blockHeader = hm     , blockPointers = if l1 == l2         then [Pointer l1 c1 (c2 + 1) False lbl]         else [Pointer l1 c1 (c1 + 1) True lbl1, Pointer l2 c2 (c2 + 1) True lbl2]-    , blockBody = m+    , blockBody = bm     }  {-|@@ -172,6 +188,7 @@  > error header message > --> file.ext:1:16+> block header message >   | > 1 |   line 1 foo bar do >   |  ________________^^ start label@@ -209,6 +226,7 @@  > error header message > → file.ext:1:16+> block header message >   │ > 1 │   line 1 foo bar do >   │ ┌────────────────^^ start label@@ -293,31 +311,36 @@  Then we can create a simple pretty printer like so: -> import qualified Data.List.NonEmpty as N-> import qualified Data.Text as T-> import qualified Data.Text.Lazy.IO as TL-> import           Errata->-> toErrata :: ParseError -> Errata-> toErrata (ParseError fp l c unexpected expected) =->     errataSimple->         (Just "error: invalid syntax")->         (blockSimple basicStyle fp l (c, c + T.length unexpected) Nothing->             (Just $ "unexpected " <> unexpected <> "\nexpected " <> T.intercalate ", " expected))->         Nothing->-> printErrors :: T.Text -> [ParseError] -> IO ()-> printErrors source es = TL.putStrLn $ prettyErrors source (toErrata <$> es)+@+import qualified Data.List.NonEmpty as N+import qualified Data.Text as T+import qualified Data.Text.Lazy.IO as TL+import           "Errata" +toErrata :: ParseError -> 'Errata'+toErrata (ParseError fp l c unexpected expected) =+    'errataSimple'+        (Just \"an error occured!\")+        ('blockSimple' 'basicStyle' fp+            (Just \"error: invalid syntax\")+            (l, c, c + T.length unexpected, Just \"this one\")+            (Just $ \"unexpected \" \<> unexpected \<> \"\\nexpected \" \<> T.intercalate \", \" expected))+        Nothing++printErrors :: T.Text -> [ParseError] -> IO ()+printErrors source es = TL.putStrLn $ 'prettyErrors' source (toErrata \<$> es)+@+ Note that in the above example, we have @OverloadedStrings@ enabled to reduce uses of 'Data.Text.pack'.  An example error message from this might be: -> error: invalid syntax+> an error occured! > --> ./comma.json:2:18+> error: invalid syntax >   | > 2 |     "bad": [1, 2,]->   |                  ^+>   |                  ^ this one > unexpected ] > expected null, true, false, ", -, digit, [, { -}
src/Errata/Internal/Render.hs view
@@ -21,12 +21,11 @@     , renderSourceLines     ) where +import qualified GHC.Arr as A import           Data.List import qualified Data.List.NonEmpty as N-import qualified Data.Sequence as S import qualified Data.Map.Strict as M import           Data.Maybe-import           Data.String (IsString) import qualified Data.Text as T import qualified Data.Text.Lazy.Builder as TB import           Errata.Source@@ -36,15 +35,12 @@ renderErrors :: Source source => source -> [Errata] -> TB.Builder renderErrors source errs = unsplit "\n\n" prettified     where-        sortedErrata = sortOn (\(Errata {..}) -> blockLocation errataBlock) $ errs--        -- We may arbitrarily index the source lines a lot, so a Seq is appropriate.-        -- If push comes to shove, this could be replaced with an 'Array' or a 'Vec'.-        slines = S.fromList (sourceToLines source)+        sortedErrata = sortOn (blockLocation . errataBlock) errs+        slines = let xs = sourceToLines source in A.listArray (0, length xs - 1) xs         prettified = map (renderErrata slines) sortedErrata  -- | A single pretty error from metadata and source lines.-renderErrata :: Source source => S.Seq source -> Errata -> TB.Builder+renderErrata :: Source source => A.Array Int source -> Errata -> TB.Builder renderErrata slines (Errata {..}) = errorMessage     where         errorMessage = mconcat@@ -54,11 +50,12 @@             ]  -- | A single pretty block from block data and source lines.-renderBlock :: Source source => S.Seq source -> Block -> TB.Builder+renderBlock :: Source source => A.Array Int source -> Block -> TB.Builder renderBlock slines block@(Block {..}) = blockMessage     where         blockMessage = mconcat             [ TB.fromText $ styleLocation blockStyle blockLocation+            , TB.fromText $ maybe "" ("\n" <>) blockHeader             , maybe "" ("\n" <>) (renderSourceLines slines block <$> N.nonEmpty blockPointers)             , TB.fromText $ maybe "" ("\n" <>) blockBody             ]@@ -66,7 +63,7 @@ -- | The source lines for a block. renderSourceLines     :: Source source-    => S.Seq source+    => A.Array Int source     -> Block     -> N.NonEmpty Pointer     -> TB.Builder@@ -81,8 +78,8 @@          -- Shows a line in accordance to the style.         -- We might get a line that's out-of-bounds, usually the EOF line, so we can default to empty.-        showLine :: [(Int, Int)] -> Int -> TB.Builder-        showLine hs n = TB.fromText . maybe "" id . fmap (styleLine hs . sourceToText) $ S.lookup (n - 1) slines+        showLine :: [(Column, Column)] -> Line -> TB.Builder+        showLine hs n = TB.fromText . maybe "" id . fmap (styleLine hs . sourceToText) $ indexLines slines (n - 1)          -- Generic prefix without line number.         prefix = mconcat@@ -95,7 +92,7 @@             ]          -- Prefix with a line number.-        linePrefix :: Int -> TB.Builder+        linePrefix :: Line -> TB.Builder         linePrefix n = mconcat             [ TB.fromText (styleNumber n), replicateB (padding - length (show n)) " ", " "             , TB.fromText styleLinePrefix, " "@@ -113,18 +110,18 @@         hasConnMulti = M.size (M.filter (any pointerConnect) pointersGrouped) > 1          -- Whether line /n/ has a connection to somewhere else (including the same line).-        hasConn :: Int -> Bool+        hasConn :: Line -> Bool         hasConn n = maybe False (any pointerConnect) $ M.lookup n pointersGrouped          -- Whether line /n/ has a connection to a line before or after it (but not including).-        connAround :: Int -> (Bool, Bool)+        connAround :: Line -> (Bool, Bool)         connAround n =             let (a, b) = M.split n pointersGrouped             in ((any . any) pointerConnect a, (any . any) pointerConnect b)          -- Makes the source lines.         -- We have an @extra@ parameter to keep track of extra lines when spanning multiple lines.-        makeSourceLines :: Int -> [Int] -> [TB.Builder]+        makeSourceLines :: Line -> [Line] -> [TB.Builder]          -- No lines left.         makeSourceLines _ [] = []@@ -280,11 +277,11 @@  -- | Makes a line of decorations below the source. foldDecorations-    :: (Int -> Bool -> [Pointer] -> TB.Builder) -- ^ Catch up from the previous pointer to this pointer.-    -> TB.Builder                               -- ^ Something in the middle.-    -> (Int -> TB.Builder)                      -- ^ Reach the next pointer.+    :: (Column -> Bool -> [Pointer] -> TB.Builder) -- ^ Catch up from the previous pointer to this pointer.+    -> TB.Builder                                  -- ^ Something in the middle.+    -> (Column -> TB.Builder)                      -- ^ Reach the next pointer.     -> [Pointer]-    -> (TB.Builder, Int)+    -> (TB.Builder, Column) foldDecorations catchUp something reachAfter ps =     let (decor, finalCol, _, _) = foldr             (\(Pointer {..}) (xs, c, rest, isFirst) ->@@ -309,10 +306,19 @@ parar f b (a:as) = f a (as, parar f b as)  -- | Puts text between each item.-unsplit :: (Semigroup a, IsString a) => a -> [a] -> a+unsplit :: TB.Builder -> [TB.Builder] -> TB.Builder unsplit _ [] = "" unsplit a (x:xs) = foldl' (\acc y -> acc <> a <> y) x xs+{-# INLINE unsplit #-}  -- | Replicates text into a builder. replicateB :: Int -> T.Text -> TB.Builder-replicateB n = TB.fromText . T.replicate n+replicateB n xs = TB.fromText (T.replicate n xs)+{-# INLINE replicateB #-}++-- | Index safely into an array.+indexLines :: A.Array Int s -> Int -> Maybe s+indexLines slines i = if A.inRange (A.bounds slines) i+    then Just (A.unsafeAt slines i)+    else Nothing+{-# INLINE indexLines #-}
src/Errata/Types.hs view
@@ -10,8 +10,14 @@ need some of the helper functions for making new functionality on top of Errata. -} module Errata.Types-    ( -- * Error format data-      Errata(..)+    ( -- * Type synonyms+      Line+    , Column+    , Header+    , Body+    , Label+      -- * Error format data+    , Errata(..)       -- * Blocks and pointers     , Block(..)     , Pointer(..)@@ -23,12 +29,27 @@  import qualified Data.Text as T +-- | Line number, starts at 1.+type Line = Int++-- | Column number, starts at 1.+type Column = Int++-- | Header text. Generally goes above things.+type Header = T.Text++-- | Body text. Generally goes below things.+type Body = T.Text++-- | Label text. Generally goes inline with things.+type Label = T.Text+ -- | A collection of information for pretty printing an error. data Errata = Errata-    { errataHeader :: Maybe T.Text -- ^ The message that appears above all the blocks.+    { errataHeader :: Maybe Header -- ^ The message that appears above all the blocks.     , errataBlock  :: Block        -- ^ The main error block, which will be used for sorting errors.     , errataBlocks :: [Block]      -- ^ Extra blocks in the source code to display. Blocks themselves are not sorted.-    , errataBody   :: Maybe T.Text -- ^ The message that appears below all the blocks.+    , errataBody   :: Maybe Body   -- ^ The message that appears below all the blocks.     }  {-|@@ -45,8 +66,11 @@        This is used for sorting errors, as well as to create the text that details the location.       -}-    , blockLocation :: (FilePath, Int, Int)+    , blockLocation :: (FilePath, Line, Column) +      -- | The header message for the block. This will appear below the location and above the source lines.+    , blockHeader :: Maybe Header+       {-|       The block's pointers. These are used to "point out" parts of the source code in this block. @@ -55,8 +79,8 @@       -}     , blockPointers :: [Pointer] -      -- | The message for the block. This will appear below the source lines.-    , blockBody :: Maybe T.Text+      -- | The body message for the block. This will appear below the source lines.+    , blockBody :: Maybe Body     }  {-|@@ -67,24 +91,24 @@ A pointer may also be connected to all the other pointers within the same block. -} data Pointer = Pointer-    { pointerLine     :: Int          -- ^ The line of the pointer.-    , pointerColStart :: Int          -- ^ The starting column of the pointer.-    , pointerColEnd   :: Int          -- ^ The ending column of the pointer.-    , pointerConnect  :: Bool         -- ^ Whether this pointer connects with other pointers.-    , pointerLabel    :: Maybe T.Text -- ^ An optional label for the pointer.+    { pointerLine     :: Line        -- ^ The line of the pointer.+    , pointerColStart :: Column      -- ^ The starting column of the pointer.+    , pointerColEnd   :: Column      -- ^ The ending column of the pointer.+    , pointerConnect  :: Bool        -- ^ Whether this pointer connects with other pointers.+    , pointerLabel    :: Maybe Label -- ^ An optional label for the pointer.     }  -- | Gets the column span for a 'Pointer'.-pointerColumns :: Pointer -> (Int, Int)+pointerColumns :: Pointer -> (Line, Column) pointerColumns p = (pointerColStart p, pointerColEnd p)  -- | Stylization options for a block, e.g. characters to use. data Style = Style     { -- | Shows the location of a block at a file, line, and column.-      styleLocation :: (FilePath, Int, Int) -> T.Text+      styleLocation :: (FilePath, Line, Column) -> T.Text        -- | Shows the line number /n/ for a source line. The result should visually be the same length as just @show n@.-    , styleNumber :: Int -> T.Text+    , styleNumber :: Line -> T.Text        {-|       Stylize a source line.@@ -92,7 +116,7 @@       Column pointers of the text that are being underlined are given for highlighting purposes. The result of this       should visually take up the same space as the original line.       -}-    , styleLine :: [(Int, Int)] -> T.Text -> T.Text+    , styleLine :: [(Column, Column)] -> T.Text -> T.Text        {-|       The text to use as an ellipsis in the position of line numbers for when lines are omitted. This should visually@@ -139,10 +163,10 @@  -- | Adds highlighting to spans of text by enclosing it with some text e.g ANSI escape codes. highlight-    :: T.Text       -- ^ Text to add before.-    -> T.Text       -- ^ Text to add after.-    -> [(Int, Int)] -- ^ Indices to enclose. These are column spans, starting at 1. They must not overlap.-    -> T.Text       -- ^ Text to highlight.+    :: T.Text             -- ^ Text to add before.+    -> T.Text             -- ^ Text to add after.+    -> [(Column, Column)] -- ^ Indices to enclose. These are column spans, starting at 1. They must not overlap.+    -> T.Text             -- ^ Text to highlight.     -> T.Text highlight open close = go False . concatMap (\(a, b) -> [a, b])     where