module TOML.Components where
import Control.Monad
import Data.Foldable
import Data.List
import Data.Maybe
import Data.Ord
import Data.Text (Text)
import qualified Data.Text as Text
import TOML.Value
data Component
= InitialEntry [(Text,Value)]
| TableEntry Path [(Text,Value)]
| ArrayEntry Path [(Text,Value)]
deriving (Read, Show)
type Path = [Text]
componentsToTable :: [Component] -> Either Path [(Text,Value)]
componentsToTable = flattenTableList . collapseComponents
collapseComponents :: [Component] -> [(Path,Value)]
collapseComponents [] = []
collapseComponents (InitialEntry kvs : xs) =
[ ([k],v) | (k,v) <- kvs ] ++ collapseComponents xs
collapseComponents (TableEntry k kvs : xs) =
(k, Table kvs) : collapseComponents xs
collapseComponents xs@(ArrayEntry k _ : _) =
case splitArrays k xs of
(kvss, xs') -> (k, List (map Table kvss)) : collapseComponents xs'
splitArrays :: Path -> [Component] -> ([[(Text,Value)]], [Component])
splitArrays k1 (ArrayEntry k2 kvs : xs)
| k1 == k2 =
case splitArrays k1 xs of
(kvss, xs2) -> (kvs:kvss, xs2)
splitArrays _ xs = ([],xs)
factorHeads :: Eq k => [([k],v)] -> [(k,[([k],v)])]
factorHeads xs = [ (h, [ (k, v) | (_:k,v) <- g ])
| let eq (x,_) (y,_) = take 1 x == take 1 y
, g@((h:_,_):_) <- groupBy eq xs
]
flattenTableList :: [(Path, Value)] -> Either Path [(Text, Value)]
flattenTableList = go [] . order
where
go path xs = sequenceA [ flattenGroup path x ys | (x,ys) <- factorHeads xs ]
flattenGroup :: Path -> Text -> [(Path,Value)] -> Either Path (Text,Value)
flattenGroup path k (([],Table t):kvs) =
flattenGroup path k (mergeInlineTable t kvs)
flattenGroup path k (([],v):rest)
| null rest = (k,v) <$ validateInlineTables (k:path) v
| otherwise = Left (reverse (k:path))
flattenGroup path k kvs =
do kvs' <- go (k:path) kvs
return (k, Table kvs')
mergeInlineTable :: [(Text,value)] -> [(Path,value)] -> [(Path,value)]
mergeInlineTable t kvs = order ([([i],j) | (i,j) <- t] ++ kvs)
order :: [(Path,value)] -> [(Path,value)]
order = sortBy (comparing fst)
validateInlineTables :: Path -> Value -> Either Path ()
validateInlineTables path (Table t) =
case findDuplicate (map fst t) of
Just k -> Left (reverse (k:path))
Nothing -> traverse_ (\(k,v) -> validateInlineTables (k:path) v) t
validateInlineTables path (List xs) =
zipWithM_ (\i x -> validateInlineTables (Text.pack (show i):path) x)
[0::Int ..] xs
validateInlineTables _ _ = Right ()
findDuplicate :: Ord a => [a] -> Maybe a
findDuplicate = listToMaybe . map head . filter (not . null . tail) . group . sort