    -- | Parse format strings provided by --format, with awareness of
    -- hledger's report item fields. The formats are used by
    -- report-specific renderers like renderBalanceReportItem.

    {-# LANGUAGE FlexibleContexts, OverloadedStrings, TypeFamilies, PackageImports #-}

    module Hledger.Data.StringFormat (
              parseStringFormat
            , defaultStringFormatStyle
            , StringFormat(..)
            , StringFormatComponent(..)
            , ReportItemField(..)
            , tests_StringFormat
            ) where

    import Prelude ()
    import "base-compat-batteries" Prelude.Compat
    import Numeric
    import Data.Char (isPrint)
    import Data.Maybe
    -- import qualified Data.Text as T
    import Text.Megaparsec
    import Text.Megaparsec.Char

    import Hledger.Utils.Parse
    import Hledger.Utils.String (formatString)
    import Hledger.Utils.Test

    -- | A format specification/template to use when rendering a report line item as text.
    --
    -- A format is a sequence of components; each is either a literal
    -- string, or a hledger report item field with specified width and
    -- justification whose value will be interpolated at render time.
    --
    -- A component's value may be a multi-line string (or a
    -- multi-commodity amount), in which case the final string will be
    -- either single-line or a top or bottom-aligned multi-line string
    -- depending on the StringFormat variant used.
    --
    -- Currently this is only used in the balance command's single-column
    -- mode, which provides a limited StringFormat renderer.
    --
    data StringFormat =
        OneLine [StringFormatComponent]       -- ^ multi-line values will be rendered on one line, comma-separated
      | TopAligned [StringFormatComponent]    -- ^ values will be top-aligned (and bottom-padded to the same height)
      | BottomAligned [StringFormatComponent] -- ^ values will be bottom-aligned (and top-padded)
      deriving (Int -> StringFormat -> ShowS
    [StringFormat] -> ShowS
    StringFormat -> String
    (Int -> StringFormat -> ShowS)
    -> (StringFormat -> String)
    -> ([StringFormat] -> ShowS)
    -> Show StringFormat
    forall a.
    (Int -> a -> ShowS) -> (a -> String) -> ([a] -> ShowS) -> Show a
    showList :: [StringFormat] -> ShowS
    $cshowList :: [StringFormat] -> ShowS
    show :: StringFormat -> String
    $cshow :: StringFormat -> String
    showsPrec :: Int -> StringFormat -> ShowS
    $cshowsPrec :: Int -> StringFormat -> ShowS
    Show, StringFormat -> StringFormat -> Bool
    (StringFormat -> StringFormat -> Bool)
    -> (StringFormat -> StringFormat -> Bool) -> Eq StringFormat
    forall a. (a -> a -> Bool) -> (a -> a -> Bool) -> Eq a
    /= :: StringFormat -> StringFormat -> Bool
    $c/= :: StringFormat -> StringFormat -> Bool
    == :: StringFormat -> StringFormat -> Bool
    $c== :: StringFormat -> StringFormat -> Bool
    Eq)

    data StringFormatComponent =
        FormatLiteral String        -- ^ Literal text to be rendered as-is
      | FormatField Bool
                    (Maybe Int)
                    (Maybe Int)
                    ReportItemField -- ^ A data field to be formatted and interpolated. Parameters:
                                    --
                                    -- - Left justify ? Right justified if false
                                    -- - Minimum width ? Will be space-padded if narrower than this
                                    -- - Maximum width ? Will be clipped if wider than this
                                    -- - Which of the standard hledger report item fields to interpolate
      deriving (Int -> StringFormatComponent -> ShowS
    [StringFormatComponent] -> ShowS
    StringFormatComponent -> String
    (Int -> StringFormatComponent -> ShowS)
    -> (StringFormatComponent -> String)
    -> ([StringFormatComponent] -> ShowS)
    -> Show StringFormatComponent
    forall a.
    (Int -> a -> ShowS) -> (a -> String) -> ([a] -> ShowS) -> Show a
    showList :: [StringFormatComponent] -> ShowS
    $cshowList :: [StringFormatComponent] -> ShowS
    show :: StringFormatComponent -> String
    $cshow :: StringFormatComponent -> String
    showsPrec :: Int -> StringFormatComponent -> ShowS
    $cshowsPrec :: Int -> StringFormatComponent -> ShowS
    Show, StringFormatComponent -> StringFormatComponent -> Bool
    (StringFormatComponent -> StringFormatComponent -> Bool)
    -> (StringFormatComponent -> StringFormatComponent -> Bool)
    -> Eq StringFormatComponent
    forall a. (a -> a -> Bool) -> (a -> a -> Bool) -> Eq a
    /= :: StringFormatComponent -> StringFormatComponent -> Bool
    $c/= :: StringFormatComponent -> StringFormatComponent -> Bool
    == :: StringFormatComponent -> StringFormatComponent -> Bool
    $c== :: StringFormatComponent -> StringFormatComponent -> Bool
    Eq)

    -- | An id identifying which report item field to interpolate.  These
    -- are drawn from several hledger report types, so are not all
    -- applicable for a given report.
    data ReportItemField =
        AccountField      -- ^ A posting or balance report item's account name
      | DefaultDateField  -- ^ A posting or register or entry report item's date
      | DescriptionField  -- ^ A posting or register or entry report item's description
      | TotalField        -- ^ A balance or posting report item's balance or running total.
                          --   Always rendered right-justified.
      | DepthSpacerField  -- ^ A balance report item's indent level (which may be different from the account name depth).
                          --   Rendered as this number of spaces, multiplied by the minimum width spec if any.
      | FieldNo Int       -- ^ A report item's nth field. May be unimplemented.
        deriving (Int -> ReportItemField -> ShowS
    [ReportItemField] -> ShowS
    ReportItemField -> String
    (Int -> ReportItemField -> ShowS)
    -> (ReportItemField -> String)
    -> ([ReportItemField] -> ShowS)
    -> Show ReportItemField
    forall a.
    (Int -> a -> ShowS) -> (a -> String) -> ([a] -> ShowS) -> Show a
    showList :: [ReportItemField] -> ShowS
    $cshowList :: [ReportItemField] -> ShowS
    show :: ReportItemField -> String
    $cshow :: ReportItemField -> String
    showsPrec :: Int -> ReportItemField -> ShowS
    $cshowsPrec :: Int -> ReportItemField -> ShowS
    Show, ReportItemField -> ReportItemField -> Bool
    (ReportItemField -> ReportItemField -> Bool)
    -> (ReportItemField -> ReportItemField -> Bool)
    -> Eq ReportItemField
    forall a. (a -> a -> Bool) -> (a -> a -> Bool) -> Eq a
    /= :: ReportItemField -> ReportItemField -> Bool
    $c/= :: ReportItemField -> ReportItemField -> Bool
    == :: ReportItemField -> ReportItemField -> Bool
    $c== :: ReportItemField -> ReportItemField -> Bool
    Eq)

    ----------------------------------------------------------------------

    -- renderStringFormat :: StringFormat -> Map String String -> String
    -- renderStringFormat fmt params =

    ----------------------------------------------------------------------

    -- | Parse a string format specification, or return a parse error.
    parseStringFormat :: String -> Either String StringFormat
    parseStringFormat :: String -> Either String StringFormat
    parseStringFormat input :: String
    input = case (Parsec CustomErr String StringFormat
    -> String
    -> String
    -> Either (ParseErrorBundle String CustomErr) StringFormat
    forall e s a.
    Parsec e s a -> String -> s -> Either (ParseErrorBundle s e) a
    runParser (Parsec CustomErr String StringFormat
    stringformatp Parsec CustomErr String StringFormat
    -> ParsecT CustomErr String Identity ()
    -> Parsec CustomErr String StringFormat
    forall (f :: * -> *) a b. Applicative f => f a -> f b -> f a
    <* ParsecT CustomErr String Identity ()
    forall e s (m :: * -> *). MonadParsec e s m => m ()
    eof) "(unknown)") String
    input of
        Left y :: ParseErrorBundle String CustomErr
    y -> String -> Either String StringFormat
    forall a b. a -> Either a b
    Left (String -> Either String StringFormat)
    -> String -> Either String StringFormat
    forall a b. (a -> b) -> a -> b
    $ ParseErrorBundle String CustomErr -> String
    forall a. Show a => a -> String
    show ParseErrorBundle String CustomErr
    y
        Right x :: StringFormat
    x -> StringFormat -> Either String StringFormat
    forall a b. b -> Either a b
    Right StringFormat
    x

    defaultStringFormatStyle :: [StringFormatComponent] -> StringFormat
    defaultStringFormatStyle = [StringFormatComponent] -> StringFormat
    BottomAligned

    stringformatp :: SimpleStringParser StringFormat
    stringformatp :: Parsec CustomErr String StringFormat
    stringformatp = do
      Maybe Char
    alignspec <- ParsecT CustomErr String Identity Char
    -> ParsecT CustomErr String Identity (Maybe Char)
    forall (f :: * -> *) a. Alternative f => f a -> f (Maybe a)
    optional (ParsecT CustomErr String Identity Char
    -> ParsecT CustomErr String Identity Char
    forall e s (m :: * -> *) a. MonadParsec e s m => m a -> m a
    try (ParsecT CustomErr String Identity Char
     -> ParsecT CustomErr String Identity Char)
    -> ParsecT CustomErr String Identity Char
    -> ParsecT CustomErr String Identity Char
    forall a b. (a -> b) -> a -> b
    $ Token String -> ParsecT CustomErr String Identity (Token String)
    forall e s (m :: * -> *).
    (MonadParsec e s m, Token s ~ Char) =>
    Token s -> m (Token s)
    char Token String
    '%' ParsecT CustomErr String Identity Char
    -> ParsecT CustomErr String Identity Char
    -> ParsecT CustomErr String Identity Char
    forall (m :: * -> *) a b. Monad m => m a -> m b -> m b
    >> [Token String] -> ParsecT CustomErr String Identity (Token String)
    forall (f :: * -> *) e s (m :: * -> *).
    (Foldable f, MonadParsec e s m) =>
    f (Token s) -> m (Token s)
    oneOf ("^_,"::String))
      let constructor :: [StringFormatComponent] -> StringFormat
    constructor =
            case Maybe Char
    alignspec of
              Just '^' -> [StringFormatComponent] -> StringFormat
    TopAligned
              Just '_' -> [StringFormatComponent] -> StringFormat
    BottomAligned
              Just ',' -> [StringFormatComponent] -> StringFormat
    OneLine
              _        -> [StringFormatComponent] -> StringFormat
    defaultStringFormatStyle
      [StringFormatComponent] -> StringFormat
    constructor ([StringFormatComponent] -> StringFormat)
    -> ParsecT CustomErr String Identity [StringFormatComponent]
    -> Parsec CustomErr String StringFormat
    forall (f :: * -> *) a b. Functor f => (a -> b) -> f a -> f b
    <$> ParsecT CustomErr String Identity StringFormatComponent
    -> ParsecT CustomErr String Identity [StringFormatComponent]
    forall (m :: * -> *) a. MonadPlus m => m a -> m [a]
    many ParsecT CustomErr String Identity StringFormatComponent
    componentp

    componentp :: SimpleStringParser StringFormatComponent
    componentp :: ParsecT CustomErr String Identity StringFormatComponent
    componentp = ParsecT CustomErr String Identity StringFormatComponent
    formatliteralp ParsecT CustomErr String Identity StringFormatComponent
    -> ParsecT CustomErr String Identity StringFormatComponent
    -> ParsecT CustomErr String Identity StringFormatComponent
    forall (f :: * -> *) a. Alternative f => f a -> f a -> f a
    <|> ParsecT CustomErr String Identity StringFormatComponent
    formatfieldp

    formatliteralp :: SimpleStringParser StringFormatComponent
    formatliteralp :: ParsecT CustomErr String Identity StringFormatComponent
    formatliteralp = do
        String
    s <- ParsecT CustomErr String Identity Char
    -> ParsecT CustomErr String Identity String
    forall (m :: * -> *) a. MonadPlus m => m a -> m [a]
    some ParsecT CustomErr String Identity Char
    c
        StringFormatComponent
    -> ParsecT CustomErr String Identity StringFormatComponent
    forall (m :: * -> *) a. Monad m => a -> m a
    return (StringFormatComponent
     -> ParsecT CustomErr String Identity StringFormatComponent)
    -> StringFormatComponent
    -> ParsecT CustomErr String Identity StringFormatComponent
    forall a b. (a -> b) -> a -> b
    $ String -> StringFormatComponent
    FormatLiteral String
    s
        where
          isPrintableButNotPercentage :: Char -> Bool
    isPrintableButNotPercentage x :: Char
    x = Char -> Bool
    isPrint Char
    x Bool -> Bool -> Bool
    && Char
    x Char -> Char -> Bool
    forall a. Eq a => a -> a -> Bool
    /= '%'
          c :: ParsecT CustomErr String Identity Char
    c =     ((Token String -> Bool)
    -> ParsecT CustomErr String Identity (Token String)
    forall e s (m :: * -> *).
    MonadParsec e s m =>
    (Token s -> Bool) -> m (Token s)
    satisfy Char -> Bool
    Token String -> Bool
    isPrintableButNotPercentage ParsecT CustomErr String Identity Char
    -> String -> ParsecT CustomErr String Identity Char
    forall e s (m :: * -> *) a.
    MonadParsec e s m =>
    m a -> String -> m a
    <?> "printable character")
              ParsecT CustomErr String Identity Char
    -> ParsecT CustomErr String Identity Char
    -> ParsecT CustomErr String Identity Char
    forall (f :: * -> *) a. Alternative f => f a -> f a -> f a
    <|> ParsecT CustomErr String Identity Char
    -> ParsecT CustomErr String Identity Char
    forall e s (m :: * -> *) a. MonadParsec e s m => m a -> m a
    try (Tokens String -> ParsecT CustomErr String Identity (Tokens String)
    forall e s (m :: * -> *).
    MonadParsec e s m =>
    Tokens s -> m (Tokens s)
    string "%%" ParsecT CustomErr String Identity (Tokens String)
    -> ParsecT CustomErr String Identity Char
    -> ParsecT CustomErr String Identity Char
    forall (m :: * -> *) a b. Monad m => m a -> m b -> m b
    >> Char -> ParsecT CustomErr String Identity Char
    forall (m :: * -> *) a. Monad m => a -> m a
    return '%')

    formatfieldp :: SimpleStringParser StringFormatComponent
    formatfieldp :: ParsecT CustomErr String Identity StringFormatComponent
    formatfieldp = do
        Token String -> ParsecT CustomErr String Identity (Token String)
    forall e s (m :: * -> *).
    (MonadParsec e s m, Token s ~ Char) =>
    Token s -> m (Token s)
    char Token String
    '%'
        Maybe Char
    leftJustified <- ParsecT CustomErr String Identity Char
    -> ParsecT CustomErr String Identity (Maybe Char)
    forall (f :: * -> *) a. Alternative f => f a -> f (Maybe a)
    optional (Token String -> ParsecT CustomErr String Identity (Token String)
    forall e s (m :: * -> *).
    (MonadParsec e s m, Token s ~ Char) =>
    Token s -> m (Token s)
    char Token String
    '-')
        Maybe String
    minWidth <- ParsecT CustomErr String Identity String
    -> ParsecT CustomErr String Identity (Maybe String)
    forall (f :: * -> *) a. Alternative f => f a -> f (Maybe a)
    optional (ParsecT CustomErr String Identity Char
    -> ParsecT CustomErr String Identity String
    forall (m :: * -> *) a. MonadPlus m => m a -> m [a]
    some (ParsecT CustomErr String Identity Char
     -> ParsecT CustomErr String Identity String)
    -> ParsecT CustomErr String Identity Char
    -> ParsecT CustomErr String Identity String
    forall a b. (a -> b) -> a -> b
    $ ParsecT CustomErr String Identity Char
    forall e s (m :: * -> *).
    (MonadParsec e s m, Token s ~ Char) =>
    m (Token s)
    digitChar)
        Maybe String
    maxWidth <- ParsecT CustomErr String Identity String
    -> ParsecT CustomErr String Identity (Maybe String)
    forall (f :: * -> *) a. Alternative f => f a -> f (Maybe a)
    optional (do Token String -> ParsecT CustomErr String Identity (Token String)
    forall e s (m :: * -> *).
    (MonadParsec e s m, Token s ~ Char) =>
    Token s -> m (Token s)
    char Token String
    '.'; ParsecT CustomErr String Identity Char
    -> ParsecT CustomErr String Identity String
    forall (m :: * -> *) a. MonadPlus m => m a -> m [a]
    some (ParsecT CustomErr String Identity Char
     -> ParsecT CustomErr String Identity String)
    -> ParsecT CustomErr String Identity Char
    -> ParsecT CustomErr String Identity String
    forall a b. (a -> b) -> a -> b
    $ ParsecT CustomErr String Identity Char
    forall e s (m :: * -> *).
    (MonadParsec e s m, Token s ~ Char) =>
    m (Token s)
    digitChar) -- TODO: Can this be (char '1') *> (some digitChar)
        Token String -> ParsecT CustomErr String Identity (Token String)
    forall e s (m :: * -> *).
    (MonadParsec e s m, Token s ~ Char) =>
    Token s -> m (Token s)
    char Token String
    '('
        ReportItemField
    f <- SimpleStringParser ReportItemField
    fieldp
        Token String -> ParsecT CustomErr String Identity (Token String)
    forall e s (m :: * -> *).
    (MonadParsec e s m, Token s ~ Char) =>
    Token s -> m (Token s)
    char Token String
    ')'
        StringFormatComponent
    -> ParsecT CustomErr String Identity StringFormatComponent
    forall (m :: * -> *) a. Monad m => a -> m a
    return (StringFormatComponent
     -> ParsecT CustomErr String Identity StringFormatComponent)
    -> StringFormatComponent
    -> ParsecT CustomErr String Identity StringFormatComponent
    forall a b. (a -> b) -> a -> b
    $ Bool
    -> Maybe Int
    -> Maybe Int
    -> ReportItemField
    -> StringFormatComponent
    FormatField (Maybe Char -> Bool
    forall a. Maybe a -> Bool
    isJust Maybe Char
    leftJustified) (Maybe String -> Maybe Int
    forall a. (Eq a, Num a) => Maybe String -> Maybe a
    parseDec Maybe String
    minWidth) (Maybe String -> Maybe Int
    forall a. (Eq a, Num a) => Maybe String -> Maybe a
    parseDec Maybe String
    maxWidth) ReportItemField
    f
        where
          parseDec :: Maybe String -> Maybe a
    parseDec s :: Maybe String
    s = case Maybe String
    s of
            Just text :: String
    text -> a -> Maybe a
    forall a. a -> Maybe a
    Just a
    m where ((m :: a
    m,_):_) = ReadS a
    forall a. (Eq a, Num a) => ReadS a
    readDec String
    text
            _ -> Maybe a
    forall a. Maybe a
    Nothing

    fieldp :: SimpleStringParser ReportItemField
    fieldp :: SimpleStringParser ReportItemField
    fieldp = do
            SimpleStringParser ReportItemField
    -> SimpleStringParser ReportItemField
    forall e s (m :: * -> *) a. MonadParsec e s m => m a -> m a
    try (Tokens String -> ParsecT CustomErr String Identity (Tokens String)
    forall e s (m :: * -> *).
    MonadParsec e s m =>
    Tokens s -> m (Tokens s)
    string "account" ParsecT CustomErr String Identity String
    -> SimpleStringParser ReportItemField
    -> SimpleStringParser ReportItemField
    forall (m :: * -> *) a b. Monad m => m a -> m b -> m b
    >> ReportItemField -> SimpleStringParser ReportItemField
    forall (m :: * -> *) a. Monad m => a -> m a
    return ReportItemField
    AccountField)
        SimpleStringParser ReportItemField
    -> SimpleStringParser ReportItemField
    -> SimpleStringParser ReportItemField
    forall (f :: * -> *) a. Alternative f => f a -> f a -> f a
    <|> SimpleStringParser ReportItemField
    -> SimpleStringParser ReportItemField
    forall e s (m :: * -> *) a. MonadParsec e s m => m a -> m a
    try (Tokens String -> ParsecT CustomErr String Identity (Tokens String)
    forall e s (m :: * -> *).
    MonadParsec e s m =>
    Tokens s -> m (Tokens s)
    string "depth_spacer" ParsecT CustomErr String Identity String
    -> SimpleStringParser ReportItemField
    -> SimpleStringParser ReportItemField
    forall (m :: * -> *) a b. Monad m => m a -> m b -> m b
    >> ReportItemField -> SimpleStringParser ReportItemField
    forall (m :: * -> *) a. Monad m => a -> m a
    return ReportItemField
    DepthSpacerField)
        SimpleStringParser ReportItemField
    -> SimpleStringParser ReportItemField
    -> SimpleStringParser ReportItemField
    forall (f :: * -> *) a. Alternative f => f a -> f a -> f a
    <|> SimpleStringParser ReportItemField
    -> SimpleStringParser ReportItemField
    forall e s (m :: * -> *) a. MonadParsec e s m => m a -> m a
    try (Tokens String -> ParsecT CustomErr String Identity (Tokens String)
    forall e s (m :: * -> *).
    MonadParsec e s m =>
    Tokens s -> m (Tokens s)
    string "date" ParsecT CustomErr String Identity String
    -> SimpleStringParser ReportItemField
    -> SimpleStringParser ReportItemField
    forall (m :: * -> *) a b. Monad m => m a -> m b -> m b
    >> ReportItemField -> SimpleStringParser ReportItemField
    forall (m :: * -> *) a. Monad m => a -> m a
    return ReportItemField
    DescriptionField)
        SimpleStringParser ReportItemField
    -> SimpleStringParser ReportItemField
    -> SimpleStringParser ReportItemField
    forall (f :: * -> *) a. Alternative f => f a -> f a -> f a
    <|> SimpleStringParser ReportItemField
    -> SimpleStringParser ReportItemField
    forall e s (m :: * -> *) a. MonadParsec e s m => m a -> m a
    try (Tokens String -> ParsecT CustomErr String Identity (Tokens String)
    forall e s (m :: * -> *).
    MonadParsec e s m =>
    Tokens s -> m (Tokens s)
    string "description" ParsecT CustomErr String Identity String
    -> SimpleStringParser ReportItemField
    -> SimpleStringParser ReportItemField
    forall (m :: * -> *) a b. Monad m => m a -> m b -> m b
    >> ReportItemField -> SimpleStringParser ReportItemField
    forall (m :: * -> *) a. Monad m => a -> m a
    return ReportItemField
    DescriptionField)
        SimpleStringParser ReportItemField
    -> SimpleStringParser ReportItemField
    -> SimpleStringParser ReportItemField
    forall (f :: * -> *) a. Alternative f => f a -> f a -> f a
    <|> SimpleStringParser ReportItemField
    -> SimpleStringParser ReportItemField
    forall e s (m :: * -> *) a. MonadParsec e s m => m a -> m a
    try (Tokens String -> ParsecT CustomErr String Identity (Tokens String)
    forall e s (m :: * -> *).
    MonadParsec e s m =>
    Tokens s -> m (Tokens s)
    string "total" ParsecT CustomErr String Identity String
    -> SimpleStringParser ReportItemField
    -> SimpleStringParser ReportItemField
    forall (m :: * -> *) a b. Monad m => m a -> m b -> m b
    >> ReportItemField -> SimpleStringParser ReportItemField
    forall (m :: * -> *) a. Monad m => a -> m a
    return ReportItemField
    TotalField)
        SimpleStringParser ReportItemField
    -> SimpleStringParser ReportItemField
    -> SimpleStringParser ReportItemField
    forall (f :: * -> *) a. Alternative f => f a -> f a -> f a
    <|> SimpleStringParser ReportItemField
    -> SimpleStringParser ReportItemField
    forall e s (m :: * -> *) a. MonadParsec e s m => m a -> m a
    try ((Int -> ReportItemField
    FieldNo (Int -> ReportItemField)
    -> (String -> Int) -> String -> ReportItemField
    forall b c a. (b -> c) -> (a -> b) -> a -> c
    . String -> Int
    forall a. Read a => String -> a
    read) (String -> ReportItemField)
    -> ParsecT CustomErr String Identity String
    -> SimpleStringParser ReportItemField
    forall (f :: * -> *) a b. Functor f => (a -> b) -> f a -> f b
    <$> ParsecT CustomErr String Identity Char
    -> ParsecT CustomErr String Identity String
    forall (m :: * -> *) a. MonadPlus m => m a -> m [a]
    some ParsecT CustomErr String Identity Char
    forall e s (m :: * -> *).
    (MonadParsec e s m, Token s ~ Char) =>
    m (Token s)
    digitChar)

    ----------------------------------------------------------------------

    formatStringTester :: StringFormatComponent -> String -> String -> Assertion
    formatStringTester fs :: StringFormatComponent
    fs value :: String
    value expected :: String
    expected = String
    actual String -> String -> Assertion
    forall a. (Eq a, Show a, HasCallStack) => a -> a -> Assertion
    @?= String
    expected
      where
        actual :: String
    actual = case StringFormatComponent
    fs of
          FormatLiteral l :: String
    l                   -> Bool -> Maybe Int -> Maybe Int -> ShowS
    formatString Bool
    False Maybe Int
    forall a. Maybe a
    Nothing Maybe Int
    forall a. Maybe a
    Nothing String
    l
          FormatField leftJustify :: Bool
    leftJustify min :: Maybe Int
    min max :: Maybe Int
    max _ -> Bool -> Maybe Int -> Maybe Int -> ShowS
    formatString Bool
    leftJustify Maybe Int
    min Maybe Int
    max String
    value

    tests_StringFormat :: TestTree
    tests_StringFormat = String -> [TestTree] -> TestTree
    tests "StringFormat" [

       String -> Assertion -> TestTree
    test "formatStringHelper" (Assertion -> TestTree) -> Assertion -> TestTree
    forall a b. (a -> b) -> a -> b
    $ do
          StringFormatComponent -> String -> String -> Assertion
    formatStringTester (String -> StringFormatComponent
    FormatLiteral " ")                                     ""            " "
          StringFormatComponent -> String -> String -> Assertion
    formatStringTester (Bool
    -> Maybe Int
    -> Maybe Int
    -> ReportItemField
    -> StringFormatComponent
    FormatField Bool
    False Maybe Int
    forall a. Maybe a
    Nothing Maybe Int
    forall a. Maybe a
    Nothing ReportItemField
    DescriptionField)    "description" "description"
          StringFormatComponent -> String -> String -> Assertion
    formatStringTester (Bool
    -> Maybe Int
    -> Maybe Int
    -> ReportItemField
    -> StringFormatComponent
    FormatField Bool
    False (Int -> Maybe Int
    forall a. a -> Maybe a
    Just 20) Maybe Int
    forall a. Maybe a
    Nothing ReportItemField
    DescriptionField)  "description" "         description"
          StringFormatComponent -> String -> String -> Assertion
    formatStringTester (Bool
    -> Maybe Int
    -> Maybe Int
    -> ReportItemField
    -> StringFormatComponent
    FormatField Bool
    False Maybe Int
    forall a. Maybe a
    Nothing (Int -> Maybe Int
    forall a. a -> Maybe a
    Just 20) ReportItemField
    DescriptionField)  "description" "description"
          StringFormatComponent -> String -> String -> Assertion
    formatStringTester (Bool
    -> Maybe Int
    -> Maybe Int
    -> ReportItemField
    -> StringFormatComponent
    FormatField Bool
    True Maybe Int
    forall a. Maybe a
    Nothing (Int -> Maybe Int
    forall a. a -> Maybe a
    Just 20) ReportItemField
    DescriptionField)   "description" "description"
          StringFormatComponent -> String -> String -> Assertion
    formatStringTester (Bool
    -> Maybe Int
    -> Maybe Int
    -> ReportItemField
    -> StringFormatComponent
    FormatField Bool
    True (Int -> Maybe Int
    forall a. a -> Maybe a
    Just 20) Maybe Int
    forall a. Maybe a
    Nothing ReportItemField
    DescriptionField)   "description" "description         "
          StringFormatComponent -> String -> String -> Assertion
    formatStringTester (Bool
    -> Maybe Int
    -> Maybe Int
    -> ReportItemField
    -> StringFormatComponent
    FormatField Bool
    True (Int -> Maybe Int
    forall a. a -> Maybe a
    Just 20) (Int -> Maybe Int
    forall a. a -> Maybe a
    Just 20) ReportItemField
    DescriptionField) "description" "description         "
          StringFormatComponent -> String -> String -> Assertion
    formatStringTester (Bool
    -> Maybe Int
    -> Maybe Int
    -> ReportItemField
    -> StringFormatComponent
    FormatField Bool
    True Maybe Int
    forall a. Maybe a
    Nothing (Int -> Maybe Int
    forall a. a -> Maybe a
    Just 3) ReportItemField
    DescriptionField)    "description" "des"

      ,let s :: String
    s gives :: String -> StringFormat -> TestTree
    `gives` expected :: StringFormat
    expected = String -> Assertion -> TestTree
    test String
    s (Assertion -> TestTree) -> Assertion -> TestTree
    forall a b. (a -> b) -> a -> b
    $ String -> Either String StringFormat
    parseStringFormat String
    s Either String StringFormat
    -> Either String StringFormat -> Assertion
    forall a. (Eq a, Show a, HasCallStack) => a -> a -> Assertion
    @?= StringFormat -> Either String StringFormat
    forall a b. b -> Either a b
    Right StringFormat
    expected
       in String -> [TestTree] -> TestTree
    tests "parseStringFormat" [
          ""                           String -> StringFormat -> TestTree
    `gives` ([StringFormatComponent] -> StringFormat
    defaultStringFormatStyle [])
        , "D"                          String -> StringFormat -> TestTree
    `gives` ([StringFormatComponent] -> StringFormat
    defaultStringFormatStyle [String -> StringFormatComponent
    FormatLiteral "D"])
        , "%(date)"                    String -> StringFormat -> TestTree
    `gives` ([StringFormatComponent] -> StringFormat
    defaultStringFormatStyle [Bool
    -> Maybe Int
    -> Maybe Int
    -> ReportItemField
    -> StringFormatComponent
    FormatField Bool
    False Maybe Int
    forall a. Maybe a
    Nothing Maybe Int
    forall a. Maybe a
    Nothing ReportItemField
    DescriptionField])
        , "%(total)"                   String -> StringFormat -> TestTree
    `gives` ([StringFormatComponent] -> StringFormat
    defaultStringFormatStyle [Bool
    -> Maybe Int
    -> Maybe Int
    -> ReportItemField
    -> StringFormatComponent
    FormatField Bool
    False Maybe Int
    forall a. Maybe a
    Nothing Maybe Int
    forall a. Maybe a
    Nothing ReportItemField
    TotalField])
        -- TODO
        -- , "^%(total)"                  `gives` (TopAligned [FormatField False Nothing Nothing TotalField])
        -- , "_%(total)"                  `gives` (BottomAligned [FormatField False Nothing Nothing TotalField])
        -- , ",%(total)"                  `gives` (OneLine [FormatField False Nothing Nothing TotalField])
        , "Hello %(date)!"             String -> StringFormat -> TestTree
    `gives` ([StringFormatComponent] -> StringFormat
    defaultStringFormatStyle [String -> StringFormatComponent
    FormatLiteral "Hello ", Bool
    -> Maybe Int
    -> Maybe Int
    -> ReportItemField
    -> StringFormatComponent
    FormatField Bool
    False Maybe Int
    forall a. Maybe a
    Nothing Maybe Int
    forall a. Maybe a
    Nothing ReportItemField
    DescriptionField, String -> StringFormatComponent
    FormatLiteral "!"])
        , "%-(date)"                   String -> StringFormat -> TestTree
    `gives` ([StringFormatComponent] -> StringFormat
    defaultStringFormatStyle [Bool
    -> Maybe Int
    -> Maybe Int
    -> ReportItemField
    -> StringFormatComponent
    FormatField Bool
    True Maybe Int
    forall a. Maybe a
    Nothing Maybe Int
    forall a. Maybe a
    Nothing ReportItemField
    DescriptionField])
        , "%20(date)"                  String -> StringFormat -> TestTree
    `gives` ([StringFormatComponent] -> StringFormat
    defaultStringFormatStyle [Bool
    -> Maybe Int
    -> Maybe Int
    -> ReportItemField
    -> StringFormatComponent
    FormatField Bool
    False (Int -> Maybe Int
    forall a. a -> Maybe a
    Just 20) Maybe Int
    forall a. Maybe a
    Nothing ReportItemField
    DescriptionField])
        , "%.10(date)"                 String -> StringFormat -> TestTree
    `gives` ([StringFormatComponent] -> StringFormat
    defaultStringFormatStyle [Bool
    -> Maybe Int
    -> Maybe Int
    -> ReportItemField
    -> StringFormatComponent
    FormatField Bool
    False Maybe Int
    forall a. Maybe a
    Nothing (Int -> Maybe Int
    forall a. a -> Maybe a
    Just 10) ReportItemField
    DescriptionField])
        , "%20.10(date)"               String -> StringFormat -> TestTree
    `gives` ([StringFormatComponent] -> StringFormat
    defaultStringFormatStyle [Bool
    -> Maybe Int
    -> Maybe Int
    -> ReportItemField
    -> StringFormatComponent
    FormatField Bool
    False (Int -> Maybe Int
    forall a. a -> Maybe a
    Just 20) (Int -> Maybe Int
    forall a. a -> Maybe a
    Just 10) ReportItemField
    DescriptionField])
        , "%20(account) %.10(total)"   String -> StringFormat -> TestTree
    `gives` ([StringFormatComponent] -> StringFormat
    defaultStringFormatStyle [Bool
    -> Maybe Int
    -> Maybe Int
    -> ReportItemField
    -> StringFormatComponent
    FormatField Bool
    False (Int -> Maybe Int
    forall a. a -> Maybe a
    Just 20) Maybe Int
    forall a. Maybe a
    Nothing ReportItemField
    AccountField
                                                                         ,String -> StringFormatComponent
    FormatLiteral " "
                                                                         ,Bool
    -> Maybe Int
    -> Maybe Int
    -> ReportItemField
    -> StringFormatComponent
    FormatField Bool
    False Maybe Int
    forall a. Maybe a
    Nothing (Int -> Maybe Int
    forall a. a -> Maybe a
    Just 10) ReportItemField
    TotalField
                                                                         ])
        , String -> Assertion -> TestTree
    test "newline not parsed" (Assertion -> TestTree) -> Assertion -> TestTree
    forall a b. (a -> b) -> a -> b
    $ Either String StringFormat -> Assertion
    forall b a. (HasCallStack, Eq b, Show b) => Either a b -> Assertion
    assertLeft (Either String StringFormat -> Assertion)
    -> Either String StringFormat -> Assertion
    forall a b. (a -> b) -> a -> b
    $ String -> Either String StringFormat
    parseStringFormat "\n"
        ]
     ]