pretty-diff (empty) → 0.1.0.0
raw patch · 6 files changed
+479/−0 lines, 6 filesdep +Diffdep +basedep +data-default
Dependencies added: Diff, base, data-default, pretty-diff, tasty, tasty-hunit, tasty-test-reporter, text
Files
- CHANGELOG.md +3/−0
- LICENSE +29/−0
- README.md +23/−0
- pretty-diff.cabal +62/−0
- src/Pretty/Diff.hs +189/−0
- test/Main.hs +173/−0
+ CHANGELOG.md view
@@ -0,0 +1,3 @@+# 0.1.0.0++- Initial implementation.
+ LICENSE view
@@ -0,0 +1,29 @@+BSD 3-Clause License++Copyright (c) 2020, Christoph Hermann+All rights reserved.++Redistribution and use in source and binary forms, with or without+modification, are permitted provided that the following conditions are met:++1. Redistributions of source code must retain the above copyright notice, this+ list of conditions and the following disclaimer.++2. 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.++3. Neither the name of the copyright holder nor the names of its+ 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 HOLDER 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.
+ README.md view
@@ -0,0 +1,23 @@+# pretty-diff++Pretty printing a diff of two values.++## Usage++```hs+import qualified Pretty.Diff as Diff+import Data.Default (def)++Diff.pretty def "1234" "_23"+```+Will create a string that looks like this:++```+ ▼ ▼+"1234"+╷+│+╵+"_23"+ ▲+```
+ pretty-diff.cabal view
@@ -0,0 +1,62 @@+cabal-version: 1.18++-- This file has been generated from package.yaml by hpack version 0.33.0.+--+-- see: https://github.com/sol/hpack+--+-- hash: 4f4d19546099374e472ed7d6dd40094526c70cb5b1e0b7c2033232726b349f95++name: pretty-diff+version: 0.1.0.0+synopsis: Pretty printing a simple diff of two values.+description: Please see the README at <https://github.com/stoeffel/pretty-diff>.+category: Diffing+homepage: https://github.com/stoeffel/pretty-diff#readme+bug-reports: https://github.com/stoeffel/pretty-diff/issues+author: Christoph Hermann+maintainer: schtoeffel@gmail.com+copyright: 2020 Christoph Hermann+license: BSD3+license-file: LICENSE+build-type: Simple+extra-doc-files:+ README.md+ CHANGELOG.md++source-repository head+ type: git+ location: https://github.com/stoeffel/pretty-diff++library+ exposed-modules:+ Pretty.Diff+ other-modules:+ Paths_pretty_diff+ hs-source-dirs:+ src+ build-depends:+ Diff >=0.3 && <0.4+ , base >=4.10.1.0 && <5+ , data-default >=0.7 && <0.8+ , text >=1.2 && <1.3+ default-language: Haskell2010++test-suite spec+ type: exitcode-stdio-1.0+ main-is: Main.hs+ other-modules:+ Pretty.Diff+ Paths_pretty_diff+ hs-source-dirs:+ test+ src+ build-depends:+ Diff >=0.3 && <0.4+ , base+ , data-default >=0.7 && <0.8+ , pretty-diff+ , tasty >=1.1 && <1.3+ , tasty-hunit+ , tasty-test-reporter+ , text >=1.2 && <1.3+ default-language: Haskell2010
+ src/Pretty/Diff.hs view
@@ -0,0 +1,189 @@+{-# LANGUAGE NamedFieldPuns #-}+{-# LANGUAGE OverloadedStrings #-}++-- |+-- Printing nice and simple diffs of two values.+--+-- @+-- import qualified Pretty.Diff as Diff+-- import Data.Default (def)+--+-- Diff.pretty def "1234" "_23"+-- @+--+-- Will create a string that looks like this:+--+-- @+-- ▼ ▼+-- "1234"+-- ╷+-- │+-- ╵+-- "_23"+-- ▲+-- @+module Pretty.Diff+ ( -- * Configuration+ Config (Config, separatorText, wrapping),+ Wrapping (Wrap, NoWrap),++ -- * pretty printing+ pretty,+ actual,+ expected,+ )+where++import qualified Data.Algorithm.Diff as Diff+import Data.Default (Default, def)+import Data.Function ((&))+import Data.List (transpose)+import Data.Maybe (fromMaybe, mapMaybe)+import Data.String (IsString)+import qualified Data.Text as Text+import Data.Text (Text)+import Prelude++-- | Configuration for `Pretty.Diff.pretty`.+data Config+ = Config+ { -- | Text that gets displayed inbetween the diffed values+ --+ -- @+ -- Diff.pretty def { Diff.separatorText = "differing" } "1234" "_23"+ -- @+ --+ -- Will create a string that looks like this:+ --+ -- @+ -- ▼ ▼+ -- "1234"+ -- ╷+ -- │ differing+ -- ╵+ -- "_23"+ -- ▲+ -- @+ separatorText :: Maybe Text,+ -- | Wrapping text to multiple lines if they are longer than the provided length.+ -- This is useful in combination with [terminal-size](https://hackage.haskell.org/package/terminal-size).+ --+ -- @+ -- Diff.pretty def { Diff.wrapping = Diff.Wrap 6 } "0900000000" "9000000000"+ -- @+ --+ -- Will create a string that looks like this:+ --+ -- @+ -- ▼+ -- "09000+ -- 00000"+ -- ╷+ -- │+ -- ╵+ -- "90000+ -- 00000"+ -- ▲+ -- @+ wrapping :: Wrapping+ }++instance Default Config where+ def = Config {separatorText = Nothing, wrapping = NoWrap}++-- | Define whether or not to wrap the diffing lines.+data Wrapping+ = Wrap Int+ | NoWrap++-- | Printing a full diff of both values separated by some pipes.+pretty :: Show a => Config -> a -> a -> Text+pretty Config {separatorText, wrapping} x y =+ [ actual wrapping x y,+ separator separatorText,+ expected wrapping x y+ ]+ & mconcat++-- | Printing The first value and the diff indicator above.+--+-- @+-- Diff.actual Diff.NoWrap "1234" "_23"+-- @+--+-- @+-- ▼ ▼+-- "1234"+-- @+actual :: Show a => Wrapping -> a -> a -> Text+actual wrapping x y =+ wrap wrapping [diffLine First down x y, Text.pack (show x)]+ & filterEmptyLines+ & Text.unlines++-- | Printing The second value and the diff indicator below.+--+-- @+-- Diff.expected Diff.NoWrap "1234" "_23"+-- @+--+-- @+-- "_23"+-- ▲+-- @+expected :: Show a => Wrapping -> a -> a -> Text+expected wrapping x y =+ wrap wrapping [Text.pack (show y), diffLine Second up x y]+ & filterEmptyLines+ & Text.unlines++wrap :: Wrapping -> [Text] -> [Text]+wrap wrapping text =+ case wrapping of+ Wrap n ->+ text+ & fmap (Text.chunksOf n)+ & interleaveLists+ NoWrap -> text++down :: Char+down = '▼'++up :: Char+up = '▲'++data Position = First | Second++diffLine :: Show a => Position -> Char -> a -> a -> Text.Text+diffLine pos differ a b =+ Diff.getDiff+ (show a)+ (show b)+ & mapMaybe (toDiffLine pos differ)+ & Text.pack+ & Text.stripEnd++toDiffLine :: Position -> Char -> Diff.Diff a -> Maybe Char+toDiffLine pos c d =+ case d of+ Diff.First _ -> case pos of+ First -> Just c+ Second -> Nothing+ Diff.Second _ -> case pos of+ First -> Nothing+ Second -> Just c+ Diff.Both _ _ -> Just ' '++separator :: Maybe Text -> Text+separator maybeComparison =+ [ "╷",+ "│" <> (fromMaybe "" $ ((<>) " ") <$> maybeComparison),+ "╵"+ ]+ & Text.unlines++interleaveLists :: [[a]] -> [a]+interleaveLists = mconcat . transpose++filterEmptyLines :: [Text] -> [Text]+filterEmptyLines = filter (not . Text.null . Text.strip)
+ test/Main.hs view
@@ -0,0 +1,173 @@+{-# LANGUAGE OverloadedStrings #-}+{-# LANGUAGE ScopedTypeVariables #-}++module Main+ ( main,+ )+where++import Control.Exception (catch, throw)+import Data.Default (def)+import Data.Function ((&))+import qualified Data.Text as Text+import qualified Data.Text.IO as Text.IO+import qualified Pretty.Diff as Diff+import Test.Tasty+import Test.Tasty.HUnit+import qualified Test.Tasty.Runners.Reporter as Reporter++main = defaultMainWithIngredients [Reporter.ingredient] tests++tests :: TestTree+tests =+ testGroup+ "Pretty.Diff"+ [ testCase "Comparing two equal strings" $+ expectDiffToEqual+ ( Diff.pretty+ def+ "Hello"+ "Hello"+ )+ [ "Hello" & showText,+ "╷",+ "│",+ "╵",+ "Hello" & showText+ ],+ testCase "Removed characters" $+ expectDiffToEqual+ ( Diff.pretty+ def+ "Hello"+ "Hel"+ )+ [ " ▼▼" & forEscapedString,+ "Hello" & showText,+ "╷",+ "│",+ "╵",+ "Hel" & showText+ ],+ testCase "Added characters" $+ expectDiffToEqual+ ( Diff.pretty+ def+ "Hel"+ "Hello"+ )+ [ "Hel" & showText,+ "╷",+ "│",+ "╵",+ "Hello" & showText,+ " ▲▲" & forEscapedString+ ],+ testCase "Changed characters" $+ expectDiffToEqual+ ( Diff.pretty+ def+ "Axxx"+ "Bxxx"+ )+ [ "▼" & forEscapedString,+ "Axxx" & showText,+ "╷",+ "│",+ "╵",+ "Bxxx" & showText,+ "▲" & forEscapedString+ ],+ testCase "Mixed changes" $+ expectDiffToEqual+ ( Diff.pretty+ def+ "12345"+ "1004"+ )+ [ " ▼▼ ▼" & forEscapedString,+ "12345" & showText,+ "╷",+ "│",+ "╵",+ "1004" & showText,+ " ▲▲" & forEscapedString+ ],+ testCase "Multiline changes (on first and last line)" $+ expectDiffToEqual+ ( Diff.pretty+ def+ { Diff.wrapping = Diff.Wrap $ 5 + 1 -- + 1 because of "+ }+ "0900000000"+ "9000000000"+ )+ [ "▼" & forEscapedString,+ "09000" & showTextPrefix,+ "00000" & showTextPostfix,+ "╷",+ "│",+ "╵",+ "90000" & showTextPrefix,+ "00000" & showTextPostfix,+ " ▲"+ ],+ testCase "Multiline changes (inbetween)" $+ expectDiffToEqual+ ( Diff.pretty+ def+ { Diff.wrapping = Diff.Wrap $ 5 + 1 -- + 1 because of "+ }+ "0000090000"+ "0090000000"+ )+ [ "00000" & showTextPrefix,+ "▼",+ "90000" & showTextPostfix,+ "╷",+ "│",+ "╵",+ "00900" & showTextPrefix,+ " ▲" & forEscapedString,+ "00000" & showTextPostfix+ ],+ testCase "With separator text" $+ expectDiffToEqual+ ( Diff.pretty+ Diff.Config+ { Diff.separatorText = Just "equals",+ Diff.wrapping = Diff.Wrap $ 5 + 1 -- + 1 because of "+ }+ "0000090000"+ "0090000000"+ )+ [ "00000" & showTextPrefix,+ "▼",+ "90000" & showTextPostfix,+ "╷",+ "│ equals",+ "╵",+ "00900" & showTextPrefix,+ " ▲" & forEscapedString,+ "00000" & showTextPostfix+ ]+ ]++expectDiffToEqual actual expected_ = do+ let expected = Text.unlines expected_+ (actual @?= expected)+ `catch` ( \(e :: HUnitFailure) -> do+ Text.IO.putStrLn "Actual was:"+ Text.IO.putStrLn actual+ Text.IO.putStrLn "Expected was:"+ Text.IO.putStrLn expected+ throw e+ )++forEscapedString x = " " <> x++showText = Text.pack . show++showTextPrefix x = "\"" <> x++showTextPostfix x = x <> "\""