{-|
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
showList :: [NormalizedText] -> ShowS
$cshowList :: [NormalizedText] -> ShowS
show :: NormalizedText -> String
$cshow :: NormalizedText -> String
showsPrec :: Int -> NormalizedText -> ShowS
$cshowsPrec :: Int -> 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
/= :: RawIni -> RawIni -> Bool
$c/= :: RawIni -> RawIni -> Bool
== :: RawIni -> RawIni -> Bool
$c== :: 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
showList :: [RawIni] -> ShowS
$cshowList :: [RawIni] -> ShowS
show :: RawIni -> String
$cshow :: RawIni -> String
showsPrec :: Int -> RawIni -> ShowS
$cshowsPrec :: Int -> 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
  { IniSection -> Text
isName      :: Text
                   -- ^ The name of the section, as it appears in the
                   -- original INI source
  , IniSection -> Seq (NormalizedText, IniValue)
isVals      :: Seq (NormalizedText, IniValue)
                   -- ^ 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 -> Int
isStartLine :: Int
                   -- ^ 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
isEndLine   :: 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 -> Seq BlankLine
isComments  :: Seq BlankLine
                   -- ^ The blank lines and comments that appear prior
                   -- to the section head declaration, retained for
                   -- pretty-printing identical INI files.
  } deriving (IniSection -> IniSection -> Bool
(IniSection -> IniSection -> Bool)
-> (IniSection -> IniSection -> Bool) -> Eq IniSection
forall a. (a -> a -> Bool) -> (a -> a -> Bool) -> Eq a
/= :: IniSection -> IniSection -> Bool
$c/= :: IniSection -> IniSection -> Bool
== :: IniSection -> IniSection -> Bool
$c== :: 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
showList :: [IniSection] -> ShowS
$cshowList :: [IniSection] -> ShowS
show :: IniSection -> String
$cshow :: IniSection -> String
showsPrec :: Int -> IniSection -> ShowS
$cshowsPrec :: Int -> 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
  { IniValue -> Int
vLineNo       :: Int
                     -- ^ 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 -> Text
vName         :: Text
                     -- ^ The name of the key, as it appears in the INI source.
  , IniValue -> Text
vValue        :: Text
                     -- ^ The value of the key
  , IniValue -> Seq BlankLine
vComments     :: Seq BlankLine
  , IniValue -> Bool
vCommentedOut :: Bool
    -- ^ 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 -> 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
/= :: IniValue -> IniValue -> Bool
$c/= :: IniValue -> IniValue -> Bool
== :: IniValue -> IniValue -> Bool
$c== :: 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
showList :: [IniValue] -> ShowS
$cshowList :: [IniValue] -> ShowS
show :: IniValue -> String
$cshow :: IniValue -> String
showsPrec :: Int -> IniValue -> ShowS
$cshowsPrec :: Int -> 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
/= :: BlankLine -> BlankLine -> Bool
$c/= :: BlankLine -> BlankLine -> Bool
== :: BlankLine -> BlankLine -> Bool
$c== :: 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
showList :: [BlankLine] -> ShowS
$cshowList :: [BlankLine] -> ShowS
show :: BlankLine -> String
$cshow :: BlankLine -> String
showsPrec :: Int -> BlankLine -> ShowS
$cshowsPrec :: Int -> 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
  Seq BlankLine
leading <- Parser (Seq BlankLine)
sBlanks
  Seq BlankLine
-> Seq (NormalizedText, IniSection) -> Parsec Void Text RawIni
pSections Seq BlankLine
leading Seq (NormalizedText, IniSection)
forall a. Seq a
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 (f :: * -> *) a b. Functor f => a -> f b -> f a
<$ ParsecT Void Text Identity Text -> ParsecT Void Text Identity ()
forall (f :: * -> *) a. Functor f => f a -> f ()
void ParsecT Void Text Identity 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 (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
  Char
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 String
[Token Text]
";#"
  Text
txt <- String -> Text
T.pack (String -> Text)
-> ParsecT Void Text Identity String
-> ParsecT Void Text Identity Text
forall (f :: * -> *) a b. Functor f => (a -> b) -> f a -> f b
`fmap` ParsecT Void Text Identity Char
-> ParsecT Void Text Identity Text
-> ParsecT Void Text Identity String
forall (m :: * -> *) a end. MonadPlus m => m a -> m end -> m [a]
manyTill ParsecT Void Text Identity Char
forall e s (m :: * -> *). MonadParsec e s m => m (Token s)
anySingle ParsecT Void Text Identity Text
forall e s (m :: * -> *).
(MonadParsec e s m, Token s ~ Char) =>
m (Tokens s)
eol
  BlankLine -> ParsecT Void Text Identity BlankLine
forall (m :: * -> *) a. Monad m => a -> m a
return (Char -> Text -> BlankLine
CommentLine Char
c Text
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 (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 (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
  Int
start <- Parser Int
getCurrentLine
  ParsecT Void Text Identity Char -> ParsecT Void Text Identity ()
forall (f :: * -> *) a. Functor f => f a -> f ()
void (Token Text -> ParsecT Void Text Identity (Token Text)
forall e s (m :: * -> *).
(MonadParsec e s m, Token s ~ Char) =>
Token s -> m (Token s)
char Char
Token Text
'[')
  Text
name <- String -> Text
T.pack (String -> Text)
-> ParsecT Void Text Identity String
-> ParsecT Void Text Identity Text
forall (f :: * -> *) a b. Functor f => (a -> b) -> f a -> f b
`fmap` ParsecT Void Text Identity Char
-> ParsecT Void Text Identity String
forall (m :: * -> *) a. MonadPlus m => m a -> m [a]
some ([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)
noneOf String
[Token Text]
"[]")
  ParsecT Void Text Identity Char -> ParsecT Void Text Identity ()
forall (f :: * -> *) a. Functor f => f a -> f ()
void (Token Text -> ParsecT Void Text Identity (Token Text)
forall e s (m :: * -> *).
(MonadParsec e s m, Token s ~ Char) =>
Token s -> m (Token s)
char Char
Token Text
']')
  ParsecT Void Text Identity Text -> ParsecT Void Text Identity ()
forall (f :: * -> *) a. Functor f => f a -> f ()
void ParsecT Void Text Identity Text
forall e s (m :: * -> *).
(MonadParsec e s m, Token s ~ Char) =>
m (Tokens s)
eol
  Seq BlankLine
comments <- Parser (Seq BlankLine)
sBlanks
  Text
-> Int
-> Seq BlankLine
-> Seq (NormalizedText, IniSection)
-> Seq BlankLine
-> Seq (NormalizedText, IniValue)
-> Parsec Void Text RawIni
pPairs (Text -> Text
T.strip Text
name) Int
start Seq BlankLine
leading Seq (NormalizedText, IniSection)
prevs Seq BlankLine
comments Seq (NormalizedText, IniValue)
forall a. Seq a
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 (f :: * -> *) a. Alternative f => f a -> f a -> f a
<|> Parsec Void Text RawIni
finishedSection
  where
    newPair :: Parsec Void Text RawIni
newPair = do
      (NormalizedText
n, IniValue
pair) <- Seq BlankLine -> Parser (NormalizedText, IniValue)
pPair Seq BlankLine
comments
      Seq BlankLine
rs <- Parser (Seq BlankLine)
sBlanks
      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
rs (Seq (NormalizedText, IniValue)
pairs Seq (NormalizedText, IniValue)
-> (NormalizedText, IniValue) -> Seq (NormalizedText, IniValue)
forall a. Seq a -> a -> Seq a
Seq.|> (NormalizedText
n, IniValue
pair))
    finishedSection :: Parsec Void Text RawIni
finishedSection = do
      Int
end <- Parser Int
getCurrentLine
      let newSection :: IniSection
newSection = IniSection :: Text
-> Seq (NormalizedText, IniValue)
-> Int
-> Int
-> Seq BlankLine
-> IniSection
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
            }
      Seq BlankLine
-> Seq (NormalizedText, IniSection) -> Parsec Void Text RawIni
pSections Seq BlankLine
comments (Seq (NormalizedText, IniSection)
prevs Seq (NormalizedText, IniSection)
-> (NormalizedText, IniSection) -> Seq (NormalizedText, IniSection)
forall a. Seq a -> a -> Seq a
Seq.|> (Text -> NormalizedText
normalize Text
name, IniSection
newSection))

pPair :: Seq BlankLine -> Parser (NormalizedText, IniValue)
pPair :: Seq BlankLine -> Parser (NormalizedText, IniValue)
pPair Seq BlankLine
leading = do
  Int
pos <- Parser Int
getCurrentLine
  Text
key <- String -> Text
T.pack (String -> Text)
-> ParsecT Void Text Identity String
-> ParsecT Void Text Identity Text
forall (f :: * -> *) a b. Functor f => (a -> b) -> f a -> f b
`fmap` ParsecT Void Text Identity Char
-> ParsecT Void Text Identity String
forall (m :: * -> *) a. MonadPlus m => m a -> m [a]
some ([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)
noneOf String
[Token Text]
"[]=:")
  Char
delim <- [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 String
[Token Text]
":="
  Text
val <- String -> Text
T.pack (String -> Text)
-> ParsecT Void Text Identity String
-> ParsecT Void Text Identity Text
forall (f :: * -> *) a b. Functor f => (a -> b) -> f a -> f b
`fmap` ParsecT Void Text Identity Char
-> ParsecT Void Text Identity Text
-> ParsecT Void Text Identity String
forall (m :: * -> *) a end. MonadPlus m => m a -> m end -> m [a]
manyTill ParsecT Void Text Identity Char
forall e s (m :: * -> *). MonadParsec e s m => m (Token s)
anySingle ParsecT Void Text Identity Text
forall e s (m :: * -> *).
(MonadParsec e s m, Token s ~ Char) =>
m (Tokens s)
eol
  (NormalizedText, IniValue) -> Parser (NormalizedText, IniValue)
forall (m :: * -> *) a. Monad m => a -> m a
return ( Text -> NormalizedText
normalize Text
key
         , IniValue :: Int -> Text -> Text -> Seq BlankLine -> Bool -> Char -> IniValue
IniValue
             { vLineNo :: Int
vLineNo       = Int
pos
             , vName :: Text
vName         = Text
key
             , vValue :: Text
vValue        = Text
val
             , vComments :: Seq BlankLine
vComments     = Seq BlankLine
leading
             , vCommentedOut :: Bool
vCommentedOut = Bool
False
             , vDelimiter :: Char
vDelimiter    = Char
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 (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 = Text -> Text
LazyText.toStrict (Text -> Text) -> (RawIni -> Text) -> RawIni -> Text
forall b c a. (b -> c) -> (a -> b) -> a -> c
. Builder -> Text
Builder.toLazyText (Builder -> Text) -> (RawIni -> Builder) -> RawIni -> Text
forall b c a. (b -> c) -> (a -> b) -> a -> c
. ((NormalizedText, IniSection) -> Builder)
-> Seq (NormalizedText, IniSection) -> Builder
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 (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 (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 (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 :: Text
                -- ^ The section name. Will be normalized prior to
                -- comparison.
                -> Text
                -- ^ The key. Will be normalized prior to comparison.
                -> RawIni
                -- ^ The Ini to search.
                -> 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 :: Text
              -- ^ The section name. Will be normalized prior to
              -- comparison.
              -> RawIni
              -- ^ The Ini to search.
              -> 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) (Seq (NormalizedText, IniSection)
 -> Seq (NormalizedText, IniSection))
-> Seq (NormalizedText, IniSection)
-> Seq (NormalizedText, IniSection)
forall a b. (a -> b) -> a -> b
$ 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 :: Text
            -- ^ The key. Will be normalized prior to comparison.
            -> IniSection
            -- ^ The section to search.
            -> 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)