packages feed

PyF-0.11.4.0: test/Spec.hs

{-# LANGUAGE BinaryLiterals #-}
{-# LANGUAGE DataKinds #-}
{-# LANGUAGE DeriveAnyClass #-}
{-# LANGUAGE DerivingStrategies #-}
{-# LANGUAGE ExtendedDefaultRules #-}
{-# LANGUAGE FlexibleInstances #-}
{-# LANGUAGE GeneralizedNewtypeDeriving #-}
{-# LANGUAGE MultiParamTypeClasses #-}
{-# LANGUAGE OverloadedLabels #-}
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE QuasiQuotes #-}
{-# LANGUAGE ScopedTypeVariables #-}
{-# LANGUAGE TemplateHaskell #-}
{-# LANGUAGE TupleSections #-}
{-# LANGUAGE TypeApplications #-}
{-# LANGUAGE TypeFamilies #-}
{-# LANGUAGE TypeOperators #-}

-- This warning is disabled because any expression with literal leads to it.
{-# OPTIONS -Wno-type-defaults #-}

import qualified Data.ByteString
import qualified Data.ByteString.Char8
import qualified Data.ByteString.Lazy
import qualified Data.ByteString.Lazy.Char8
import qualified Data.List as List
import Data.Proxy (Proxy (..))
import qualified Data.Ratio
import qualified Data.Text
import qualified Data.Text as Text
import qualified Data.Text.Lazy
import qualified Data.Time
import GHC.OverloadedLabels
import GHC.TypeLits (KnownSymbol, Symbol, symbolVal)
import PyF
import SpecCustomDelimiters
import Test.Hspec

main :: IO ()
main = hspec $ parallel spec

newtype FooFloating t = FooFloating t
  deriving newtype (Show, RealFloat, RealFrac, Floating, Fractional, Real, Enum, Num, Ord, Eq, PyfFormatFractional)

newtype FooIntegral = FooIntegral Integer
  deriving newtype (Show, Integral, Real, Enum, Num, Ord, Eq)

data Foo = Foo

data FooDefault = FooDefault
  deriving (Show)

instance PyFToString Foo where
  pyfToString Foo = "I'm a Foo"

type instance PyFClassify Foo = 'PyFString

type instance PyFClassify (FooFloating t) = 'PyFFractional

type instance PyFClassify FooIntegral = 'PyFIntegral

type instance PyFClassify FooDefault = 'PyFString

-- Data type to test overloaded labels
data V (a :: Symbol) = V

instance (KnownSymbol a) => Show (V a) where
  show V = "V=" ++ symbolVal (Proxy @a)

instance (a ~ a') => IsLabel a (V a') where
  fromLabel = V

showV :: (KnownSymbol a) => V a -> String
showV = show

globalName :: String
globalName = "Valérian"

spec :: Spec
spec = do
  describe "simple with external variable" $ do
    let anInt = 123
        aFloat = 0.234
        aString = "hello"
    it "int" $ [fmt|{anInt}|] `shouldBe` "123"
    it "float" $ [fmt|{aFloat}|] `shouldBe` "0.234"
    it "string" $ [fmt|{aString}|] `shouldBe` "hello"
  describe "only expression" $ do
    describe "default" $ do
      it "int" $ [fmt|{123}|] `shouldBe` "123"
      it "float" $ [fmt|{0.234}|] `shouldBe` "0.234"
      it "string" $ [fmt|{"hello"}|] `shouldBe` "hello"
      it "float precision" $ [fmt|{0.234:.1}|] `shouldBe` "0.2"
      it "string precision" $ [fmt|{"hello":.1}|] `shouldBe` "h"
      it "sign +" $ [fmt|{0.234:+}|] `shouldBe` "+0.234"
      it "sign space" $ [fmt|{0.234: }|] `shouldBe` " 0.234"
      it "sign neg" $ [fmt|{-123:+}|] `shouldBe` "-123"
    describe "binary" $ do
      it "simple" $ [fmt|{123:b}|] `shouldBe` "1111011"
      it "alt" $ [fmt|{123:#b}|] `shouldBe` "0b1111011"
      it "sign" $ [fmt|{123:+#b}|] `shouldBe` "+0b1111011"
    describe "character" $
      it "simple" $
        [fmt|{123:c}|] `shouldBe` "{"
    describe "decimal" $ do
      it "simple" $ [fmt|{123:d}|] `shouldBe` "123"
      it "sign" $ [fmt|{123:+d}|] `shouldBe` "+123"
    describe "exponentiel" $ do
      it "simple > 1" $ [fmt|{234.0:e}|] `shouldBe` "2.340000e+02"
      it "precision > 1" $ [fmt|{234.0:.1e}|] `shouldBe` "2.3e+02"
      it "simple < 1" $ [fmt|{0.234:e}|] `shouldBe` "2.340000e-01"
      it "precision < 1 " $ [fmt|{0.234:.1e}|] `shouldBe` "2.3e-01"
    describe "exponentiel caps" $ do
      it "simple > 1" $ [fmt|{234.0:E}|] `shouldBe` "2.340000E+02"
      it "precision > 1" $ [fmt|{234.0:.1E}|] `shouldBe` "2.3E+02"
      it "simple < 1" $ [fmt|{0.234:E}|] `shouldBe` "2.340000E-01"
      it "precision < 1 " $ [fmt|{0.234:.1E}|] `shouldBe` "2.3E-01"
    describe "general" $ do
      it "simple small" $ [fmt|{123.02:g}|] `shouldBe` "123.020000"
      it "precision small" $ [fmt|{123.02:.1g}|] `shouldBe` "123.0"
      it "simple big" $ [fmt|{1234567890.23:g}|] `shouldBe` "1.234568e+09"
      it "precision big" $ [fmt|{1234567890.23:.1g}|] `shouldBe` "1.2e+09"
    describe "general caps" $ do
      it "simple small" $ [fmt|{123.02:G}|] `shouldBe` "123.020000"
      it "precision small" $ [fmt|{123.02:.1G}|] `shouldBe` "123.0"
      it "simple big" $ [fmt|{1234567890.23:G}|] `shouldBe` "1.234568E+09"
      it "precision big" $ [fmt|{1234567890.23:.1G}|] `shouldBe` "1.2E+09"
    describe "fixed" $ do
      it "simple" $ [fmt|{0.234:f}|] `shouldBe` "0.234000"
      it "precision" $ [fmt|{0.234:.1f}|] `shouldBe` "0.2"
    describe "fixed caps" $ do
      it "simple" $ [fmt|{0.234:F}|] `shouldBe` "0.234000"
      it "precision" $ [fmt|{0.234:.1F}|] `shouldBe` "0.2"
    describe "octal" $ do
      it "simple" $ [fmt|{123:o}|] `shouldBe` "173"
      it "alt" $ [fmt|{123:#o}|] `shouldBe` "0o173"
    describe "string" $ do
      it "string" $ [fmt|{"hello":s}|] `shouldBe` "hello"
      it "precision" $ [fmt|{"hello":.2s}|] `shouldBe` "he"
    describe "hex" $ do
      it "simple" $ [fmt|{123:x}|] `shouldBe` "7b"
      it "alt" $ [fmt|{123:#x}|] `shouldBe` "0x7b"
    describe "hex caps" $ do
      it "simple" $ [fmt|{123:X}|] `shouldBe` "7B"
      it "alt" $ [fmt|{123:#X}|] `shouldBe` "0X7B"
    describe "percent" $ do
      it "simple" $ [fmt|{0.234:%}|] `shouldBe` "23.400000%"
      it "precision" $ [fmt|{0.234:.2%}|] `shouldBe` "23.40%"
    describe "string truncating" $
      it "works" $
        [fmt|{"hello":.3}|] `shouldBe` "hel"
    describe "padding" $ do
      describe "default char" $ do
        it "left" $ [fmt|{"hello":<10}|] `shouldBe` "hello     "
        it "right" $ [fmt|{"hello":>10}|] `shouldBe` "     hello"
        it "center" $ [fmt|{"hello":^10}|] `shouldBe` "  hello   "
      describe "a char" $ do
        it "left" $ [fmt|{"hello":-<10}|] `shouldBe` "hello-----"
        it "right" $ [fmt|{"hello":->10}|] `shouldBe` "-----hello"
        it "center" $ [fmt|{"hello":-^10}|] `shouldBe` "--hello---"
      describe "inside" $ do
        it "inside" $ [fmt|{123:=+10}|] `shouldBe` "+      123"
        it "inside" $ [fmt|{123:=10}|] `shouldBe` "       123"
        it "inside" $ [fmt|{- 123:=10}|] `shouldBe` "-      123"
        it "inside" $ [fmt|{- 123:|= 10}|] `shouldBe` "-||||||123"
        it "inside" $ [fmt|{123:|= 10}|] `shouldBe` " ||||||123"
      describe "default padding" $ do
        it "floating" $ [fmt|{1:10f}|] `shouldBe` "  1.000000"
        it "integral" $ [fmt|{1:10d}|] `shouldBe` "         1"
        it "string" $ [fmt|{"h":10s}|] `shouldBe` "h         "
        it "default" $ [fmt|{1:10}|] `shouldBe` "         1"
        it "default" $ [fmt|{1.0:10}|] `shouldBe` "       1.0"
        it "default" $ [fmt|{"h":10}|] `shouldBe` "h         "
    describe "NaN" $ do
      describe "float" $ do
        let nan = 0.0 / 0 :: Float
        it "nan" $ [fmt|{nan}|] `shouldBe` "nan"
        it "nan f" $ [fmt|{nan:f}|] `shouldBe` "nan"
        it "nan e" $ [fmt|{nan:e}|] `shouldBe` "nan"
        it "nan g" $ [fmt|{nan:g}|] `shouldBe` "nan"
        it "nan F" $ [fmt|{nan:F}|] `shouldBe` "NAN"
        it "nan G" $ [fmt|{nan:G}|] `shouldBe` "NAN"
        it "nan E" $ [fmt|{nan:E}|] `shouldBe` "NAN"
      describe "double" $ do
        let nan = 0.0 / 0 :: Double
        it "nan" $ [fmt|{nan}|] `shouldBe` "nan"
        it "nan f" $ [fmt|{nan:f}|] `shouldBe` "nan"
        it "nan e" $ [fmt|{nan:e}|] `shouldBe` "nan"
        it "nan g" $ [fmt|{nan:g}|] `shouldBe` "nan"
        it "nan F" $ [fmt|{nan:F}|] `shouldBe` "NAN"
        it "nan G" $ [fmt|{nan:G}|] `shouldBe` "NAN"
        it "nan E" $ [fmt|{nan:E}|] `shouldBe` "NAN"
    describe "Infinite" $ do
      describe "float" $ do
        let inf = 1.0 / 0 :: Float
        it "infinite" $ [fmt|{inf}|] `shouldBe` "inf"
        it "infinite f" $ [fmt|{inf:f}|] `shouldBe` "inf"
        it "infinite e" $ [fmt|{inf:e}|] `shouldBe` "inf"
        it "infinite g" $ [fmt|{inf:g}|] `shouldBe` "inf"
        it "infinite F" $ [fmt|{inf:F}|] `shouldBe` "INF"
        it "infinite G" $ [fmt|{inf:G}|] `shouldBe` "INF"
        it "infinite E" $ [fmt|{inf:E}|] `shouldBe` "INF"
      describe "double" $ do
        let inf = 1.0 / 0 :: Double
        it "infinite" $ [fmt|{inf}|] `shouldBe` "inf"
        it "infinite f" $ [fmt|{inf:f}|] `shouldBe` "inf"
        it "infinite e" $ [fmt|{inf:e}|] `shouldBe` "inf"
        it "infinite g" $ [fmt|{inf:g}|] `shouldBe` "inf"
        it "infinite F" $ [fmt|{inf:F}|] `shouldBe` "INF"
        it "infinite G" $ [fmt|{inf:G}|] `shouldBe` "INF"
        it "infinite E" $ [fmt|{inf:E}|] `shouldBe` "INF"
    describe "Grouping" $ do
      it "groups int" $ [fmt|{123456789:,d}|] `shouldBe` "123,456,789"
      it "groups int with _" $ [fmt|{123456789:_d}|] `shouldBe` "123_456_789"
      it "groups float" $ [fmt|{123456789.234:,f}|] `shouldBe` "123,456,789.234000"
      it "groups bin" $ [fmt|{123456789:_b}|] `shouldBe` "111_0101_1011_1100_1101_0001_0101"
      it "groups hex" $ [fmt|{123456789:_x}|] `shouldBe` "75b_cd15"
      it "groups oct" $ [fmt|{123456789:_o}|] `shouldBe` "7_2674_6425"
    describe "negative zero" $ do
      it "f" $ [fmt|{-0.0:f}|] `shouldBe` "-0.000000"
      it "e" $ [fmt|{-0.0:e}|] `shouldBe` "-0.000000e+00"
      it "g" $ [fmt|{-0.0:g}|] `shouldBe` "-0.000000"
      it "F" $ [fmt|{-0.0:F}|] `shouldBe` "-0.000000"
      it "G" $ [fmt|{-0.0:G}|] `shouldBe` "-0.000000"
      it "E" $ [fmt|{-0.0:E}|] `shouldBe` "-0.000000E+00"
    describe "0" $ do
      it "works" $ [fmt|{123:010}|] `shouldBe` "0000000123"
      it "works with sign" $ [fmt|{-123:010}|] `shouldBe` "-000000123"
      it "accept mode override" $ [fmt|{-123:<010}|] `shouldBe` "-123000000"
      it "accept mode and char override" $ [fmt|{-123:.<010}|] `shouldBe` "-123......"
    describe "no digit no dot" $ do
      it "f" $ [fmt|{1.0:.0f}|] `shouldBe` "1"
      it "e" $ [fmt|{1.0:.0e}|] `shouldBe` "1e+00"
      it "g" $ [fmt|{1.0:.0g}|] `shouldBe` "1"
      it "E" $ [fmt|{1.0:.0E}|] `shouldBe` "1E+00"
      it "G" $ [fmt|{1.0:.0G}|] `shouldBe` "1"
      it "percent" $ [fmt|{1.0:.0%}|] `shouldBe` "100%"
    describe "no digit alt -> dot" $ do
      it "f" $ [fmt|{1.0:#.0f}|] `shouldBe` "1."
      it "e" $ [fmt|{1.0:#.0e}|] `shouldBe` "1.e+00"
      it "g" $ [fmt|{1.0:#.0g}|] `shouldBe` "1."
      it "E" $ [fmt|{1.0:#.0E}|] `shouldBe` "1.E+00"
      it "G" $ [fmt|{1.0:#.0G}|] `shouldBe` "1."
      it "percent" $ [fmt|{1.0:#.0%}|] `shouldBe` "100.%"
  describe "complex" $
    it "works with many things at once" $
      let name = "Guillaume"
          age = 31
          euroToFrancs = 6.55957
       in [fmt|hello {name} you are {age} years old and the conversion rate of euro is {euroToFrancs:.2}|] `shouldBe` "hello Guillaume you are 31 years old and the conversion rate of euro is 6.56"
  describe "error reporting" $
    pure () -- TODO: find a way to test error reporting
  describe "sub expressions" $
    it "works" $
      [fmt|2pi = {2 * pi:.2}|] `shouldBe` "2pi = 6.28"
  describe "escape strings" $
    it "works" $
      [fmt|hello \n\b|] `shouldBe` "hello \n\b"
  describe "variable precision" $
    it "works" $
      do
        let n = 3 :: Int
        [fmt|{pi:.{n}}|] `shouldBe` "3.142"
  describe "variable padding" $
    it "works" $
      do
        let n = 5 :: Integer
        [fmt|Bonjour {'a':>{n}}|] `shouldBe` "Bonjour     a"
  it "escape chars" $
    [fmt|}}{{}}{{|] `shouldBe` "}{}{"
  describe "custom delimiters" $ do
    it "works" $
      [myCustomFormatter|2 * pi = @2*pi:.2f!|] `shouldBe` "2 * pi = 6.28"
    it "escape chars" $
      [myCustomFormatter|@@!!@@!!|] `shouldBe` "@!@!"
    it "works for custom precision" $
      [myCustomFormatter|@pi:.@2!!|] `shouldBe` "3.14"
  describe "empty line" $
    it "works" $
      [fmt||] `shouldBe` ""
  describe "multi line escape" $ do
    it "works" $
      [fmt|\
- a
- b
\
|]
        `shouldBe` "- a\n- b\n"
    it "escapes in middle of line" $
      [fmt|Example goes \
here!|]
        `shouldBe` "Example goes here!"
    it "escapes a lot of things" $
      [fmt|\
I'm a line with \n and \\ and a correct line
ending, but that one is escaped\
And I'm escaping before and after: \\{pi:.3f}\\
yeah\
|]
        `shouldBe` "I'm a line with \n and \\ and a correct line\nending, but that one is escapedAnd I'm escaping before and after: \\3.142\\\nyeah"
    it "escapes" $
      [fmt|\\
- a
- b
\
|]
        `shouldBe` "\\\n- a\n- b\n"
  describe "empty trailing value" $
    it "String" $
      ( [fmt|\
{pi:.0}
|] ::
          String
      )
        `shouldBe` "3\n"
  describe "language extensions" $ do
    it "parses @Int" $
      [fmt|hello {show @Int 10}|] `shouldBe` "hello 10"
    it "parses @_" $
      [fmt|hello {show @_ 10}|] `shouldBe` "hello 10"
    it "parses BinaryLiterals" $
      [fmt|hello {0b1111}|] `shouldBe` "hello 15"
    it "OverloadedLabels works" $
      [fmt|{showV #abc}|] `shouldBe` "V=abc"
  describe "custom types" $ do
    it "works with integral" $
      [fmt|{FooIntegral 10:d}|] `shouldBe` "10"
    it "works with floating" $
      [fmt|{FooFloating 25.123:f}|] `shouldBe` "25.123000"
    it "works with string" $ do
      [fmt|{Foo:s}|] `shouldBe` "I'm a Foo"
      [fmt|{FooDefault:s}|] `shouldBe` "FooDefault"
    it "works with classify" $ do
      [fmt|{Foo}|] `shouldBe` "I'm a Foo"
      [fmt|{FooIntegral 100}|] `shouldBe` "100"
      let fooDouble = FooFloating (100.123 :: Double) in [fmt|{fooDouble}|] `shouldBe` "100.123"
      let fooFloat = FooFloating (100.123 :: Float) in [fmt|{fooFloat}|] `shouldBe` "100.123"
      [fmt|{FooDefault}|] `shouldBe` "FooDefault"
  describe "Special syntax " $ do
    it "[] is correct expression" $ do
      [fmt|{[] @Char}|] `shouldBe` ""
    it "() is correct expression" $ do
      [fmt|{const "STRING" ()}|] `shouldBe` "STRING"

  describe "instances" $ do
    describe "default" $ do
      it "bytestring" $ do
        let x = "hello" :: Data.ByteString.ByteString in [fmt|{x}|] `shouldBe` "hello"
      it "bytestring lazy" $ do
        let x = "hello" :: Data.ByteString.Lazy.ByteString in [fmt|{x}|] `shouldBe` "hello"
      it "bytestring char 8 lazy" $ do
        let x = "hello" :: Data.ByteString.Lazy.Char8.ByteString in [fmt|{x}|] `shouldBe` "hello"
      it "bytestring char 8" $ do
        let x = "hello" :: Data.ByteString.Char8.ByteString in [fmt|{x}|] `shouldBe` "hello"
      it "text" $ do
        let x = "hello" :: Data.Text.Text in [fmt|{x}|] `shouldBe` "hello"
      it "lazy text" $ do
        let x = "hello" :: Data.Text.Lazy.Text in [fmt|{x}|] `shouldBe` "hello"
      it "DiffTime" $ do
        let x = 3 :: Data.Time.DiffTime in [fmt|{x}|] `shouldBe` "3.0"
      it "NominalDiffTime" $ do
        let x = 3 :: Data.Time.NominalDiffTime in [fmt|{x}|] `shouldBe` "3.0"
      it "Ratio" $ do
        let x = 3 :: Data.Ratio.Ratio Int in [fmt|{x}|] `shouldBe` "3.0"
      it "Int" $ do
        let x = 3 :: Int in [fmt|{x}|] `shouldBe` "3"
      it "Char" $ do
        let x = 'a' in [fmt|{x}|] `shouldBe` "a"
    describe "forced" $ do
      it "bytestring" $ do
        let x = "hello" :: Data.ByteString.ByteString in [fmt|{x:s}|] `shouldBe` "hello"
      it "bytestring lazy" $ do
        let x = "hello" :: Data.ByteString.Lazy.ByteString in [fmt|{x:s}|] `shouldBe` "hello"
      it "bytestring char 8 lazy" $ do
        let x = "hello" :: Data.ByteString.Lazy.Char8.ByteString in [fmt|{x:s}|] `shouldBe` "hello"
      it "bytestring char 8" $ do
        let x = "hello" :: Data.ByteString.Char8.ByteString in [fmt|{x:s}|] `shouldBe` "hello"
      it "text" $ do
        let x = "hello" :: Data.Text.Text in [fmt|{x:s}|] `shouldBe` "hello"
      it "lazy text" $ do
        let x = "hello" :: Data.Text.Text in [fmt|{x:s}|] `shouldBe` "hello"
      it "DiffTime" $ do
        let x = 3 :: Data.Time.DiffTime in [fmt|{x:.2f}|] `shouldBe` "3.00"
      it "NominalDiffTime" $ do
        let x = 3 :: Data.Time.NominalDiffTime in [fmt|{x:.2f}|] `shouldBe` "3.00"
      it "Ratio" $ do
        let x = 3 :: Data.Ratio.Ratio Int in [fmt|{x:.2f}|] `shouldBe` "3.00"
      describe "int" $ do
        it "decimal" $ do
          let x = 3 :: Int in [fmt|{x:d}|] `shouldBe` "3"
        it "fractional" $ do
          let x = 3 :: Int in [fmt|{x:.2f}|] `shouldBe` "3.00"
      describe "Char" $ do
        it "string" $ do
          let x = 'a' in [fmt|{x:s}|] `shouldBe` "a"
        it "int" $ do
          let x = 'a' in [fmt|{x:d}|] `shouldBe` "97"

  describe "syntax" $ do
    describe "name" $ do
      it "qualified" $ do
        [fmt|{List.sort [1,2,3]:s}|] `shouldBe` "[1,2,3]"
    describe "literals" $ do
      it "list" $ do
        [fmt|{[1,2,3]:s}|] `shouldBe` "[1,2,3]"
      describe "arith seq" $ do
        it "from" $ [fmt|{take 3 [1..]:s}|] `shouldBe` "[1,2,3]"
        it "fromthen" $ [fmt|{take 3 [1, 3..]:s}|] `shouldBe` "[1,3,5]"
        it "fromto" $ [fmt|{[1..3]:s}|] `shouldBe` "[1,2,3]"
        it "fromthento" $ [fmt|{[1,3..5]:s}|] `shouldBe` "[1,3,5]"

    it "lambda" $ do
      [fmt|{(\x -> 2 * x) 10}|] `shouldBe` "20"
    it "section" $ do
      [fmt|{(100/) 10:.0f}|] `shouldBe` "10"
      [fmt|{(/2) 10:.0f}|] `shouldBe` "5"
    it "tuples" $ do
      [fmt|{fst (1, 2)}|] `shouldBe` "1"

  -- Disabled because it does not build with GHC < 8.10
  -- xit "tuples section" $ do
  -- [fmt|{fst ((,2) 1)}|] `shouldBe` "1"

  describe "multiline trimming" $ do
    it "works with overloading" $ do
      [fmtTrim|hello|] `shouldBe` ("hello" :: Data.Text.Text)
    it "overloading in overloading" $ do
      let foo = [fmtTrim|hello {10}|]
      -- foo `shouldBe` "hello 10"
      [fmtTrim|biz {foo} goodbye|] `shouldBe` ("biz hello 10 goodbye" :: Data.Text.Text)
    it "do not fail on trailing ignore line return" $ do
      [fmtTrim|
      hello\

      |]
        `shouldBe` "hello\n"
    it "do not take too much indent in account" $ do
      [fmtTrim|
      hello
      - a
        - b
      - c
      |]
        `shouldBe` "hello\n- a\n  - b\n- c\n"
    it "works with empty lines" $ do
      [fmtTrim|
      hello


      |]
        `shouldBe` "hello\n\n\n"
    it "works with empty last lines" $ do
      [fmtTrim|
      hello


|]
        `shouldBe` "hello\n\n\n"
    it "works" $ do
      [fmtTrim|
                  hello
                  - a
                   - b

                  - c
      |]
        `shouldBe` "hello\n- a\n - b\n\n- c\n"
    it "works with replacement" $ do
      [fmtTrim|
                  hello
                  - a
                   - {pi:.2}|]
        `shouldBe` "hello\n- a\n - 3.14"
    it "Do not ignore not indented lines" $ do
      [fmtTrim|  hello
- a
 - {pi:.2}|]
        `shouldBe` "  hello\n- a\n - 3.14"

    it "works with multiline" $ do
      [fmtTrim|
                  hello
                  - a
                   - {
                      2 + 2
                   :d}|]
        `shouldBe` "hello\n- a\n - 4"

    it "Do not touch single lines" $ do
      [fmtTrim|  hello|] `shouldBe` "  hello"
    it "trim when there is a linebreak" $ do
      -- https://github.com/guibou/PyF/issues/141
      [fmtTrim|
          Cannot convert formula 2.0 * exponent(unit=s, value=1.0) which has unit dimensionless to\
           unit dimensionless for they have different dimensions|]
          `shouldBe` "Cannot convert formula 2.0 * exponent(unit=s, value=1.0) which has unit dimensionless to unit dimensionless for they have different dimensions"
  describe "raw" $ do
    it "does not escape anything" $
      [raw|hello
  - a \n {\
  - b }
  |]
        `shouldBe` "hello\n  - a \\n {\\\n  - b }\n  "
  describe "str" $ do
    it "basic escaping but no indentation neither formatting" $
      [str|hello
  - a \n {\
  - b {pi}
  |]
        `shouldBe` "hello\n  - a \n {  - b {pi}\n  "
  describe "strTrim" $ do
    it "basic escaping neither formatting" $
      [strTrim|
  - a \b {
  - b {pi}
  |]
        `shouldBe` "- a \b {\n- b {pi}\n"

  describe "handle ::" $ do
    it "works in simple context" $ do
      [fmt|{-10 :: Int:d}|] `shouldBe` "-10"
    it "works in a padding = context" $ do
      [fmt|{-10 :: Int::=10d}|] `shouldBe` "-:::::::10"
    it "works in a padding < context" $ do
      [fmt|{-10 :: Int::<10d}|] `shouldBe` "-10:::::::"
    it "works in a padding > context" $ do
      [fmt|{-10 :: Int::>10d}|] `shouldBe` ":::::::-10"
    it "works in a padding ^ context" $ do
      [fmt|{-10 :: Int::^10d}|] `shouldBe` ":::-10::::"

  describe "variables" $ do
    it "local" $ do
      let var = "Guillaume" :: String
      [fmt|{var}|] `shouldBe` "Guillaume"
    it "global" $ do
      [fmt|{globalName}|] `shouldBe` "Valérian"
    it "multiple expressions with variables" $ do
      let padding = 10
      let precision = 3
      [fmt|{globalName:{padding}} and pi = {pi:.{precision}}|] `shouldBe` "Valérian   and pi = 3.142"
    it "an expression with multiples variables" $ do
      let padding = 10
      let precision = 3
      [fmt|pi = {pi:{padding}.{precision}}|] `shouldBe` "pi =      3.142"
    it "an expression with module.variable" $ do
      [fmt|{Text.intercalate " " ["a" :: Text.Text, "b" :: Text.Text]}|] `shouldBe` "a b"