    -- |
    -- Module     : Data.Ini.Config.Raw
    -- Copyright  : (c) Getty Ritter, 2017
    -- License    : BSD
    -- Maintainer : Getty Ritter <config-ini@infinitenegativeutility.com>
    -- Stability  : experimental
    --
    -- __Warning!__ This module is subject to change in the future, and therefore should
    -- not be relied upon to have a consistent API.
    module Data.Ini.Config.Raw
      ( -- * INI types
        RawIni (..),
        IniSection (..),
        IniValue (..),
        BlankLine (..),
        NormalizedText (..),
        normalize,

        -- * serializing and deserializing
        parseRawIni,
        printRawIni,

        -- * inspection
        lookupInSection,
        lookupSection,
        lookupValue,
      )
    where

    import Control.Monad (void)
    import qualified Data.Foldable as F
    import Data.Monoid ((<>))
    import Data.Sequence (Seq)
    import qualified Data.Sequence as Seq
    import Data.Text (Text)
    import qualified Data.Text as T
    import qualified Data.Text.Lazy as LazyText
    import qualified Data.Text.Lazy.Builder as Builder
    import Data.Void (Void)
    import Text.Megaparsec
    import Text.Megaparsec.Char

    type Parser = Parsec Void Text

    -- | The 'NormalizedText' type is an abstract representation of text
    -- which has had leading and trailing whitespace removed and been
    -- normalized to lower-case, but from which we can still extract the
    -- original, non-normalized version. This acts like the normalized
    -- text for the purposes of 'Eq' and 'Ord' operations, so
    --
    -- @
    --   'normalize' "  x  " == 'normalize' \"X\"
    -- @
    --
    -- This type is used to store section and key names in the
    data NormalizedText = NormalizedText
      { NormalizedText -> Text
    actualText :: Text,
        NormalizedText -> Text
    normalizedText :: Text
      }
      deriving (Int -> NormalizedText -> ShowS
    [NormalizedText] -> ShowS
    NormalizedText -> String
    (Int -> NormalizedText -> ShowS)
    -> (NormalizedText -> String)
    -> ([NormalizedText] -> ShowS)
    -> Show NormalizedText
    forall a.
    (Int -> a -> ShowS) -> (a -> String) -> ([a] -> ShowS) -> Show a
    $cshowsPrec :: Int -> NormalizedText -> ShowS
    showsPrec :: Int -> NormalizedText -> ShowS
    $cshow :: NormalizedText -> String
    show :: NormalizedText -> String
    $cshowList :: [NormalizedText] -> ShowS
    showList :: [NormalizedText] -> ShowS
    Show)

    -- | The constructor function to build a 'NormalizedText' value. You
    -- probably shouldn't be using this module directly, but if for some
    -- reason you are using it, then you should be using this function to
    -- create 'NormalizedText' values.
    normalize :: Text -> NormalizedText
    normalize :: Text -> NormalizedText
    normalize Text
    t = Text -> Text -> NormalizedText
    NormalizedText Text
    t (Text -> Text
    T.toLower (Text -> Text
    T.strip Text
    t))

    instance Eq NormalizedText where
      NormalizedText Text
    _ Text
    x == :: NormalizedText -> NormalizedText -> Bool
    == NormalizedText Text
    _ Text
    y =
        Text
    x Text -> Text -> Bool
    forall a. Eq a => a -> a -> Bool
    == Text
    y

    instance Ord NormalizedText where
      NormalizedText Text
    _ Text
    x compare :: NormalizedText -> NormalizedText -> Ordering
    `compare` NormalizedText Text
    _ Text
    y =
        Text
    x Text -> Text -> Ordering
    forall a. Ord a => a -> a -> Ordering
    `compare` Text
    y

    -- | An 'Ini' value is a mapping from section names to
    --   'IniSection' values. The section names in this mapping are
    --   normalized to lower-case and stripped of whitespace. This
    --   sequence retains the ordering of the original source file.
    newtype RawIni = RawIni
      { RawIni -> Seq (NormalizedText, IniSection)
    fromRawIni :: Seq (NormalizedText, IniSection)
      }
      deriving (RawIni -> RawIni -> Bool
    (RawIni -> RawIni -> Bool)
    -> (RawIni -> RawIni -> Bool) -> Eq RawIni
    forall a. (a -> a -> Bool) -> (a -> a -> Bool) -> Eq a
    $c== :: RawIni -> RawIni -> Bool
    == :: RawIni -> RawIni -> Bool
    $c/= :: RawIni -> RawIni -> Bool
    /= :: RawIni -> RawIni -> Bool
    Eq, Int -> RawIni -> ShowS
    [RawIni] -> ShowS
    RawIni -> String
    (Int -> RawIni -> ShowS)
    -> (RawIni -> String) -> ([RawIni] -> ShowS) -> Show RawIni
    forall a.
    (Int -> a -> ShowS) -> (a -> String) -> ([a] -> ShowS) -> Show a
    $cshowsPrec :: Int -> RawIni -> ShowS
    showsPrec :: Int -> RawIni -> ShowS
    $cshow :: RawIni -> String
    show :: RawIni -> String
    $cshowList :: [RawIni] -> ShowS
    showList :: [RawIni] -> ShowS
    Show)

    -- | An 'IniSection' consists of a name, a mapping of key-value pairs,
    --   and metadata about where the section starts and ends in the
    --   file. The section names found in 'isName' are __not__ normalized
    --   to lower-case or stripped of whitespace, and thus should appear
    --   exactly as they appear in the original source file.
    data IniSection = IniSection
      { -- | The name of the section, as it appears in the
        -- original INI source
        IniSection -> Text
    isName :: Text,
        -- | The key-value mapping within that section. Key
        -- names here are normalized to lower-case and
        -- stripped of whitespace. This sequence retains
        -- the ordering of the original source file.
        IniSection -> Seq (NormalizedText, IniValue)
    isVals :: Seq (NormalizedText, IniValue),
        -- | The line on which the section begins. This
        -- field is ignored when serializing, and is only
        -- used for error messages produced when parsing
        -- and deserializing an INI structure.
        IniSection -> Int
    isStartLine :: Int,
        -- | The line on which the section ends. This field
        -- is ignored when serializing, and is only used
        -- for error messages produced when parsing and
        -- deserializing an INI structure.
        IniSection -> Int
    isEndLine :: Int,
        -- | The blank lines and comments that appear prior
        -- to the section head declaration, retained for
        -- pretty-printing identical INI files.
        IniSection -> Seq BlankLine
    isComments :: Seq BlankLine
      }
      deriving (IniSection -> IniSection -> Bool
    (IniSection -> IniSection -> Bool)
    -> (IniSection -> IniSection -> Bool) -> Eq IniSection
    forall a. (a -> a -> Bool) -> (a -> a -> Bool) -> Eq a
    $c== :: IniSection -> IniSection -> Bool
    == :: IniSection -> IniSection -> Bool
    $c/= :: IniSection -> IniSection -> Bool
    /= :: IniSection -> IniSection -> Bool
    Eq, Int -> IniSection -> ShowS
    [IniSection] -> ShowS
    IniSection -> String
    (Int -> IniSection -> ShowS)
    -> (IniSection -> String)
    -> ([IniSection] -> ShowS)
    -> Show IniSection
    forall a.
    (Int -> a -> ShowS) -> (a -> String) -> ([a] -> ShowS) -> Show a
    $cshowsPrec :: Int -> IniSection -> ShowS
    showsPrec :: Int -> IniSection -> ShowS
    $cshow :: IniSection -> String
    show :: IniSection -> String
    $cshowList :: [IniSection] -> ShowS
    showList :: [IniSection] -> ShowS
    Show)

    -- | An 'IniValue' represents a key-value mapping, and also stores the
    --   line number where it appears. The key names and values found in
    --   'vName' and 'vValue' respectively are _not_ normalized to
    --   lower-case or stripped of whitespace, and thus should appear
    --   exactly as they appear in the original source file.
    data IniValue = IniValue
      { -- | The line on which the key/value mapping
        -- appears. This field is ignored when
        -- serializing, and is only used for error
        -- messages produced when parsing and
        -- deserializing an INI structure.
        IniValue -> Int
    vLineNo :: Int,
        -- | The name of the key, as it appears in the INI source.
        IniValue -> Text
    vName :: Text,
        -- | The value of the key
        IniValue -> Text
    vValue :: Text,
        IniValue -> Seq BlankLine
    vComments :: Seq BlankLine,
        -- | Right now, this will never show up in a parsed INI file, but
        --   it's used when emitting a default INI file: it causes the
        --   key-value line to include a leading comment as well.
        IniValue -> Bool
    vCommentedOut :: Bool,
        IniValue -> Char
    vDelimiter :: Char
      }
      deriving (IniValue -> IniValue -> Bool
    (IniValue -> IniValue -> Bool)
    -> (IniValue -> IniValue -> Bool) -> Eq IniValue
    forall a. (a -> a -> Bool) -> (a -> a -> Bool) -> Eq a
    $c== :: IniValue -> IniValue -> Bool
    == :: IniValue -> IniValue -> Bool
    $c/= :: IniValue -> IniValue -> Bool
    /= :: IniValue -> IniValue -> Bool
    Eq, Int -> IniValue -> ShowS
    [IniValue] -> ShowS
    IniValue -> String
    (Int -> IniValue -> ShowS)
    -> (IniValue -> String) -> ([IniValue] -> ShowS) -> Show IniValue
    forall a.
    (Int -> a -> ShowS) -> (a -> String) -> ([a] -> ShowS) -> Show a
    $cshowsPrec :: Int -> IniValue -> ShowS
    showsPrec :: Int -> IniValue -> ShowS
    $cshow :: IniValue -> String
    show :: IniValue -> String
    $cshowList :: [IniValue] -> ShowS
    showList :: [IniValue] -> ShowS
    Show)

    -- | We want to keep track of the whitespace/comments in between KV
    --   lines, so this allows us to track those lines in a reproducible
    --   way.
    data BlankLine
      = CommentLine Char Text
      | BlankLine
      deriving (BlankLine -> BlankLine -> Bool
    (BlankLine -> BlankLine -> Bool)
    -> (BlankLine -> BlankLine -> Bool) -> Eq BlankLine
    forall a. (a -> a -> Bool) -> (a -> a -> Bool) -> Eq a
    $c== :: BlankLine -> BlankLine -> Bool
    == :: BlankLine -> BlankLine -> Bool
    $c/= :: BlankLine -> BlankLine -> Bool
    /= :: BlankLine -> BlankLine -> Bool
    Eq, Int -> BlankLine -> ShowS
    [BlankLine] -> ShowS
    BlankLine -> String
    (Int -> BlankLine -> ShowS)
    -> (BlankLine -> String)
    -> ([BlankLine] -> ShowS)
    -> Show BlankLine
    forall a.
    (Int -> a -> ShowS) -> (a -> String) -> ([a] -> ShowS) -> Show a
    $cshowsPrec :: Int -> BlankLine -> ShowS
    showsPrec :: Int -> BlankLine -> ShowS
    $cshow :: BlankLine -> String
    show :: BlankLine -> String
    $cshowList :: [BlankLine] -> ShowS
    showList :: [BlankLine] -> ShowS
    Show)

    -- | Parse a 'Text' value into an 'Ini' value, retaining a maximal
    -- amount of structure as needed to reconstruct the original INI file.
    parseRawIni :: Text -> Either String RawIni
    parseRawIni :: Text -> Either String RawIni
    parseRawIni Text
    t = case Parsec Void Text RawIni
    -> String -> Text -> Either (ParseErrorBundle Text Void) RawIni
    forall e s a.
    Parsec e s a -> String -> s -> Either (ParseErrorBundle s e) a
    runParser Parsec Void Text RawIni
    pIni String
    "ini file" Text
    t of
      Left ParseErrorBundle Text Void
    err -> String -> Either String RawIni
    forall a b. a -> Either a b
    Left (ParseErrorBundle Text Void -> String
    forall s e.
    (VisualStream s, TraversableStream s, ShowErrorComponent e) =>
    ParseErrorBundle s e -> String
    errorBundlePretty ParseErrorBundle Text Void
    err)
      Right RawIni
    v -> RawIni -> Either String RawIni
    forall a b. b -> Either a b
    Right RawIni
    v

    pIni :: Parser RawIni
    pIni :: Parsec Void Text RawIni
    pIni = do
      leading <- Parser (Seq BlankLine)
    sBlanks
      pSections leading Seq.empty

    sBlanks :: Parser (Seq BlankLine)
    sBlanks :: Parser (Seq BlankLine)
    sBlanks = [BlankLine] -> Seq BlankLine
    forall a. [a] -> Seq a
    Seq.fromList ([BlankLine] -> Seq BlankLine)
    -> ParsecT Void Text Identity [BlankLine] -> Parser (Seq BlankLine)
    forall (f :: * -> *) a b. Functor f => (a -> b) -> f a -> f b
    <$> ParsecT Void Text Identity BlankLine
    -> ParsecT Void Text Identity [BlankLine]
    forall (m :: * -> *) a. MonadPlus m => m a -> m [a]
    many ((BlankLine
    BlankLine BlankLine
    -> ParsecT Void Text Identity ()
    -> ParsecT Void Text Identity BlankLine
    forall a b.
    a -> ParsecT Void Text Identity b -> ParsecT Void Text Identity a
    forall (f :: * -> *) a b. Functor f => a -> f b -> f a
    <$ ParsecT Void Text Identity (Tokens Text)
    -> ParsecT Void Text Identity ()
    forall (f :: * -> *) a. Functor f => f a -> f ()
    void ParsecT Void Text Identity (Tokens Text)
    forall e s (m :: * -> *).
    (MonadParsec e s m, Token s ~ Char) =>
    m (Tokens s)
    eol) ParsecT Void Text Identity BlankLine
    -> ParsecT Void Text Identity BlankLine
    -> ParsecT Void Text Identity BlankLine
    forall a.
    ParsecT Void Text Identity a
    -> ParsecT Void Text Identity a -> ParsecT Void Text Identity a
    forall (f :: * -> *) a. Alternative f => f a -> f a -> f a
    <|> ParsecT Void Text Identity BlankLine
    sComment)

    sComment :: Parser BlankLine
    sComment :: ParsecT Void Text Identity BlankLine
    sComment = do
      c <- [Token Text] -> ParsecT Void Text Identity (Token Text)
    forall (f :: * -> *) e s (m :: * -> *).
    (Foldable f, MonadParsec e s m) =>
    f (Token s) -> m (Token s)
    oneOf [Char
    ';', Char
    '#']
      txt <- T.pack `fmap` manyTill anySingle eol
      return (CommentLine c txt)

    pSections :: Seq BlankLine -> Seq (NormalizedText, IniSection) -> Parser RawIni
    pSections :: Seq BlankLine
    -> Seq (NormalizedText, IniSection) -> Parsec Void Text RawIni
    pSections Seq BlankLine
    leading Seq (NormalizedText, IniSection)
    prevs =
      Seq BlankLine
    -> Seq (NormalizedText, IniSection) -> Parsec Void Text RawIni
    pSection Seq BlankLine
    leading Seq (NormalizedText, IniSection)
    prevs Parsec Void Text RawIni
    -> Parsec Void Text RawIni -> Parsec Void Text RawIni
    forall a.
    ParsecT Void Text Identity a
    -> ParsecT Void Text Identity a -> ParsecT Void Text Identity a
    forall (f :: * -> *) a. Alternative f => f a -> f a -> f a
    <|> (Seq (NormalizedText, IniSection) -> RawIni
    RawIni Seq (NormalizedText, IniSection)
    prevs RawIni -> ParsecT Void Text Identity () -> Parsec Void Text RawIni
    forall a b.
    a -> ParsecT Void Text Identity b -> ParsecT Void Text Identity a
    forall (f :: * -> *) a b. Functor f => a -> f b -> f a
    <$ ParsecT Void Text Identity () -> ParsecT Void Text Identity ()
    forall (f :: * -> *) a. Functor f => f a -> f ()
    void ParsecT Void Text Identity ()
    forall e s (m :: * -> *). MonadParsec e s m => m ()
    eof)

    pSection :: Seq BlankLine -> Seq (NormalizedText, IniSection) -> Parser RawIni
    pSection :: Seq BlankLine
    -> Seq (NormalizedText, IniSection) -> Parsec Void Text RawIni
    pSection Seq BlankLine
    leading Seq (NormalizedText, IniSection)
    prevs = do
      start <- Parser Int
    getCurrentLine
      void (char '[')
      name <- T.pack `fmap` some (noneOf ['[', ']'])
      void (char ']')
      void eol
      comments <- sBlanks
      pPairs (T.strip name) start leading prevs comments Seq.empty

    pPairs ::
      Text ->
      Int ->
      Seq BlankLine ->
      Seq (NormalizedText, IniSection) ->
      Seq BlankLine ->
      Seq (NormalizedText, IniValue) ->
      Parser RawIni
    pPairs :: Text
    -> Int
    -> Seq BlankLine
    -> Seq (NormalizedText, IniSection)
    -> Seq BlankLine
    -> Seq (NormalizedText, IniValue)
    -> Parsec Void Text RawIni
    pPairs Text
    name Int
    start Seq BlankLine
    leading Seq (NormalizedText, IniSection)
    prevs Seq BlankLine
    comments Seq (NormalizedText, IniValue)
    pairs = Parsec Void Text RawIni
    newPair Parsec Void Text RawIni
    -> Parsec Void Text RawIni -> Parsec Void Text RawIni
    forall a.
    ParsecT Void Text Identity a
    -> ParsecT Void Text Identity a -> ParsecT Void Text Identity a
    forall (f :: * -> *) a. Alternative f => f a -> f a -> f a
    <|> Parsec Void Text RawIni
    finishedSection
      where
        newPair :: Parsec Void Text RawIni
    newPair = do
          (n, pair) <- Seq BlankLine -> Parser (NormalizedText, IniValue)
    pPair Seq BlankLine
    comments
          rs <- sBlanks
          pPairs name start leading prevs rs (pairs Seq.|> (n, pair))
        finishedSection :: Parsec Void Text RawIni
    finishedSection = do
          end <- Parser Int
    getCurrentLine
          let newSection =
                IniSection
                  { isName :: Text
    isName = Text
    name,
                    isVals :: Seq (NormalizedText, IniValue)
    isVals = Seq (NormalizedText, IniValue)
    pairs,
                    isStartLine :: Int
    isStartLine = Int
    start,
                    isEndLine :: Int
    isEndLine = Int
    end,
                    isComments :: Seq BlankLine
    isComments = Seq BlankLine
    leading
                  }
          pSections comments (prevs Seq.|> (normalize name, newSection))

    pPair :: Seq BlankLine -> Parser (NormalizedText, IniValue)
    pPair :: Seq BlankLine -> Parser (NormalizedText, IniValue)
    pPair Seq BlankLine
    leading = do
      pos <- Parser Int
    getCurrentLine
      key <- T.pack `fmap` some (noneOf ['[', ']', '=', ':'])
      delim <- oneOf [':', '=']
      val <- T.pack `fmap` manyTill anySingle eol
      return
        ( normalize key,
          IniValue
            { vLineNo = pos,
              vName = key,
              vValue = val,
              vComments = leading,
              vCommentedOut = False,
              vDelimiter = delim
            }
        )

    getCurrentLine :: Parser Int
    getCurrentLine :: Parser Int
    getCurrentLine = (Int -> Int
    forall a b. (Integral a, Num b) => a -> b
    fromIntegral (Int -> Int) -> (SourcePos -> Int) -> SourcePos -> Int
    forall b c a. (b -> c) -> (a -> b) -> a -> c
    . Pos -> Int
    unPos (Pos -> Int) -> (SourcePos -> Pos) -> SourcePos -> Int
    forall b c a. (b -> c) -> (a -> b) -> a -> c
    . SourcePos -> Pos
    sourceLine) (SourcePos -> Int)
    -> ParsecT Void Text Identity SourcePos -> Parser Int
    forall a b.
    (a -> b)
    -> ParsecT Void Text Identity a -> ParsecT Void Text Identity b
    forall (f :: * -> *) a b. Functor f => (a -> b) -> f a -> f b
    `fmap` ParsecT Void Text Identity SourcePos
    forall s e (m :: * -> *).
    (TraversableStream s, MonadParsec e s m) =>
    m SourcePos
    getSourcePos

    -- | Serialize an INI file to text, complete with any comments which
    -- appear in the INI structure, and retaining the aesthetic details
    -- which are present in the INI file.
    printRawIni :: RawIni -> Text
    printRawIni :: RawIni -> Text
    printRawIni = LazyText -> Text
    LazyText.toStrict (LazyText -> Text) -> (RawIni -> LazyText) -> RawIni -> Text
    forall b c a. (b -> c) -> (a -> b) -> a -> c
    . Builder -> LazyText
    Builder.toLazyText (Builder -> LazyText) -> (RawIni -> Builder) -> RawIni -> LazyText
    forall b c a. (b -> c) -> (a -> b) -> a -> c
    . ((NormalizedText, IniSection) -> Builder)
    -> Seq (NormalizedText, IniSection) -> Builder
    forall m a. Monoid m => (a -> m) -> Seq a -> m
    forall (t :: * -> *) m a.
    (Foldable t, Monoid m) =>
    (a -> m) -> t a -> m
    F.foldMap (NormalizedText, IniSection) -> Builder
    forall {a}. (a, IniSection) -> Builder
    build (Seq (NormalizedText, IniSection) -> Builder)
    -> (RawIni -> Seq (NormalizedText, IniSection))
    -> RawIni
    -> Builder
    forall b c a. (b -> c) -> (a -> b) -> a -> c
    . RawIni -> Seq (NormalizedText, IniSection)
    fromRawIni
      where
        build :: (a, IniSection) -> Builder
    build (a
    _, IniSection
    ini) =
          (BlankLine -> Builder) -> Seq BlankLine -> Builder
    forall m a. Monoid m => (a -> m) -> Seq a -> m
    forall (t :: * -> *) m a.
    (Foldable t, Monoid m) =>
    (a -> m) -> t a -> m
    F.foldMap BlankLine -> Builder
    buildComment (IniSection -> Seq BlankLine
    isComments IniSection
    ini)
            Builder -> Builder -> Builder
    forall a. Semigroup a => a -> a -> a
    <> Char -> Builder
    Builder.singleton Char
    '['
            Builder -> Builder -> Builder
    forall a. Semigroup a => a -> a -> a
    <> Text -> Builder
    Builder.fromText (IniSection -> Text
    isName IniSection
    ini)
            Builder -> Builder -> Builder
    forall a. Semigroup a => a -> a -> a
    <> String -> Builder
    Builder.fromString String
    "]\n"
            Builder -> Builder -> Builder
    forall a. Semigroup a => a -> a -> a
    <> ((NormalizedText, IniValue) -> Builder)
    -> Seq (NormalizedText, IniValue) -> Builder
    forall m a. Monoid m => (a -> m) -> Seq a -> m
    forall (t :: * -> *) m a.
    (Foldable t, Monoid m) =>
    (a -> m) -> t a -> m
    F.foldMap (NormalizedText, IniValue) -> Builder
    forall {a}. (a, IniValue) -> Builder
    buildKV (IniSection -> Seq (NormalizedText, IniValue)
    isVals IniSection
    ini)
        buildComment :: BlankLine -> Builder
    buildComment BlankLine
    BlankLine = Char -> Builder
    Builder.singleton Char
    '\n'
        buildComment (CommentLine Char
    c Text
    txt) =
          Char -> Builder
    Builder.singleton Char
    c Builder -> Builder -> Builder
    forall a. Semigroup a => a -> a -> a
    <> Text -> Builder
    Builder.fromText Text
    txt Builder -> Builder -> Builder
    forall a. Semigroup a => a -> a -> a
    <> Char -> Builder
    Builder.singleton Char
    '\n'
        buildKV :: (a, IniValue) -> Builder
    buildKV (a
    _, IniValue
    val) =
          (BlankLine -> Builder) -> Seq BlankLine -> Builder
    forall m a. Monoid m => (a -> m) -> Seq a -> m
    forall (t :: * -> *) m a.
    (Foldable t, Monoid m) =>
    (a -> m) -> t a -> m
    F.foldMap BlankLine -> Builder
    buildComment (IniValue -> Seq BlankLine
    vComments IniValue
    val)
            Builder -> Builder -> Builder
    forall a. Semigroup a => a -> a -> a
    <> (if IniValue -> Bool
    vCommentedOut IniValue
    val then String -> Builder
    Builder.fromString String
    "# " else Builder
    forall a. Monoid a => a
    mempty)
            Builder -> Builder -> Builder
    forall a. Semigroup a => a -> a -> a
    <> Text -> Builder
    Builder.fromText (IniValue -> Text
    vName IniValue
    val)
            Builder -> Builder -> Builder
    forall a. Semigroup a => a -> a -> a
    <> Char -> Builder
    Builder.singleton (IniValue -> Char
    vDelimiter IniValue
    val)
            Builder -> Builder -> Builder
    forall a. Semigroup a => a -> a -> a
    <> Text -> Builder
    Builder.fromText (IniValue -> Text
    vValue IniValue
    val)
            Builder -> Builder -> Builder
    forall a. Semigroup a => a -> a -> a
    <> Char -> Builder
    Builder.singleton Char
    '\n'

    -- | Look up an Ini value by section name and key. Returns the sequence
    -- of matches.
    lookupInSection ::
      -- | The section name. Will be normalized prior to
      -- comparison.
      Text ->
      -- | The key. Will be normalized prior to comparison.
      Text ->
      -- | The Ini to search.
      RawIni ->
      Seq.Seq Text
    lookupInSection :: Text -> Text -> RawIni -> Seq Text
    lookupInSection Text
    sec Text
    opt RawIni
    ini =
      IniValue -> Text
    vValue (IniValue -> Text) -> Seq IniValue -> Seq Text
    forall (f :: * -> *) a b. Functor f => (a -> b) -> f a -> f b
    <$> Seq (Seq IniValue) -> Seq IniValue
    forall (t :: * -> *) (f :: * -> *) a.
    (Foldable t, Alternative f) =>
    t (f a) -> f a
    F.asum (Text -> IniSection -> Seq IniValue
    lookupValue Text
    opt (IniSection -> Seq IniValue)
    -> Seq IniSection -> Seq (Seq IniValue)
    forall (f :: * -> *) a b. Functor f => (a -> b) -> f a -> f b
    <$> Text -> RawIni -> Seq IniSection
    lookupSection Text
    sec RawIni
    ini)

    -- | Look up an Ini section by name. Returns a sequence of all matching
    -- section records.
    lookupSection ::
      -- | The section name. Will be normalized prior to
      -- comparison.
      Text ->
      -- | The Ini to search.
      RawIni ->
      Seq.Seq IniSection
    lookupSection :: Text -> RawIni -> Seq IniSection
    lookupSection Text
    name RawIni
    ini =
      (NormalizedText, IniSection) -> IniSection
    forall a b. (a, b) -> b
    snd ((NormalizedText, IniSection) -> IniSection)
    -> Seq (NormalizedText, IniSection) -> Seq IniSection
    forall (f :: * -> *) a b. Functor f => (a -> b) -> f a -> f b
    <$> ((NormalizedText, IniSection) -> Bool)
    -> Seq (NormalizedText, IniSection)
    -> Seq (NormalizedText, IniSection)
    forall a. (a -> Bool) -> Seq a -> Seq a
    Seq.filter ((NormalizedText -> NormalizedText -> Bool
    forall a. Eq a => a -> a -> Bool
    == Text -> NormalizedText
    normalize Text
    name) (NormalizedText -> Bool)
    -> ((NormalizedText, IniSection) -> NormalizedText)
    -> (NormalizedText, IniSection)
    -> Bool
    forall b c a. (b -> c) -> (a -> b) -> a -> c
    . (NormalizedText, IniSection) -> NormalizedText
    forall a b. (a, b) -> a
    fst) (RawIni -> Seq (NormalizedText, IniSection)
    fromRawIni RawIni
    ini)

    -- | Look up an Ini key's value in a given section by the key. Returns
    -- the sequence of matches.
    lookupValue ::
      -- | The key. Will be normalized prior to comparison.
      Text ->
      -- | The section to search.
      IniSection ->
      Seq.Seq IniValue
    lookupValue :: Text -> IniSection -> Seq IniValue
    lookupValue Text
    name IniSection
    section =
      (NormalizedText, IniValue) -> IniValue
    forall a b. (a, b) -> b
    snd ((NormalizedText, IniValue) -> IniValue)
    -> Seq (NormalizedText, IniValue) -> Seq IniValue
    forall (f :: * -> *) a b. Functor f => (a -> b) -> f a -> f b
    <$> ((NormalizedText, IniValue) -> Bool)
    -> Seq (NormalizedText, IniValue) -> Seq (NormalizedText, IniValue)
    forall a. (a -> Bool) -> Seq a -> Seq a
    Seq.filter ((NormalizedText -> NormalizedText -> Bool
    forall a. Eq a => a -> a -> Bool
    == Text -> NormalizedText
    normalize Text
    name) (NormalizedText -> Bool)
    -> ((NormalizedText, IniValue) -> NormalizedText)
    -> (NormalizedText, IniValue)
    -> Bool
    forall b c a. (b -> c) -> (a -> b) -> a -> c
    . (NormalizedText, IniValue) -> NormalizedText
    forall a b. (a, b) -> a
    fst) (IniSection -> Seq (NormalizedText, IniValue)
    isVals IniSection
    section)