{-# LANGUAGE PatternGuards, RecordWildCards #-}

module Development.Shake.Internal.Profile(ProfileEntry(..), ProfileTrace(..), writeProfile) where

import General.Template
import Data.Tuple.Extra
import Data.Function
import Data.List
import System.FilePath
import Numeric.Extra
import General.Extra
import Development.Shake.Internal.Paths
import System.Time.Extra
import qualified Data.ByteString.Lazy.Char8 as LBS


data ProfileEntry = ProfileEntry
    {ProfileEntry -> String
prfName :: String, ProfileEntry -> Int
prfBuilt :: Int, ProfileEntry -> Int
prfChanged :: Int, ProfileEntry -> [Int]
prfDepends :: [Int], ProfileEntry -> Double
prfExecution :: Double, ProfileEntry -> [ProfileTrace]
prfTraces :: [ProfileTrace]}
data ProfileTrace = ProfileTrace
    {ProfileTrace -> String
prfCommand :: String, ProfileTrace -> Double
prfStart :: Double, ProfileTrace -> Double
prfStop :: Double}
prfTime :: ProfileTrace -> Double
prfTime ProfileTrace{..} = Double
prfStop Double -> Double -> Double
forall a. Num a => a -> a -> a
- Double
prfStart


-- | Generates an report given some build system profiling data.
writeProfile :: FilePath -> [ProfileEntry] -> IO ()
writeProfile :: String -> [ProfileEntry] -> IO ()
writeProfile out :: String
out xs :: [ProfileEntry]
xs
    | String -> String
takeExtension String
out String -> String -> Bool
forall a. Eq a => a -> a -> Bool
== ".js" = String -> String -> IO ()
writeFile String
out (String -> IO ()) -> String -> IO ()
forall a b. (a -> b) -> a -> b
$ "var shake = \n" String -> String -> String
forall a. [a] -> [a] -> [a]
++ [ProfileEntry] -> String
generateJSON [ProfileEntry]
xs
    | String -> String
takeExtension String
out String -> String -> Bool
forall a. Eq a => a -> a -> Bool
== ".json" = String -> String -> IO ()
writeFile String
out (String -> IO ()) -> String -> IO ()
forall a b. (a -> b) -> a -> b
$ [ProfileEntry] -> String
generateJSON [ProfileEntry]
xs
    | String -> String
takeExtension String
out String -> String -> Bool
forall a. Eq a => a -> a -> Bool
== ".trace" = String -> String -> IO ()
writeFile String
out (String -> IO ()) -> String -> IO ()
forall a b. (a -> b) -> a -> b
$ [ProfileEntry] -> String
generateTrace [ProfileEntry]
xs
    | String
out String -> String -> Bool
forall a. Eq a => a -> a -> Bool
== "-" = String -> IO ()
putStr (String -> IO ()) -> String -> IO ()
forall a b. (a -> b) -> a -> b
$ [String] -> String
unlines ([String] -> String) -> [String] -> String
forall a b. (a -> b) -> a -> b
$ [ProfileEntry] -> [String]
generateSummary [ProfileEntry]
xs
    -- NOTE: On my laptop writing 1.5Mb of profile report takes 0.6s.
    --       This is fundamentals of my laptop, not a Haskell profiling issue.
    --       Verified with similar "type foo > bar" commands taking similar time.
    | Bool
otherwise = String -> ByteString -> IO ()
LBS.writeFile String
out (ByteString -> IO ()) -> IO ByteString -> IO ()
forall (m :: * -> *) a b. Monad m => (a -> m b) -> m a -> m b
=<< [ProfileEntry] -> IO ByteString
generateHTML [ProfileEntry]
xs


generateSummary :: [ProfileEntry] -> [String]
generateSummary :: [ProfileEntry] -> [String]
generateSummary xs :: [ProfileEntry]
xs =
    ["* This database has tracked " String -> String -> String
forall a. [a] -> [a] -> [a]
++ Int -> String
forall a. Show a => a -> String
show ([Int] -> Int
forall (t :: * -> *) a. (Foldable t, Ord a) => t a -> a
maximum (0 Int -> [Int] -> [Int]
forall a. a -> [a] -> [a]
: (ProfileEntry -> Int) -> [ProfileEntry] -> [Int]
forall a b. (a -> b) -> [a] -> [b]
map ProfileEntry -> Int
prfChanged [ProfileEntry]
xs) Int -> Int -> Int
forall a. Num a => a -> a -> a
+ 1) String -> String -> String
forall a. [a] -> [a] -> [a]
++ " runs."
    ,let f :: [a] -> String
f = Int -> String
forall a. Show a => a -> String
show (Int -> String) -> ([a] -> Int) -> [a] -> String
forall b c a. (b -> c) -> (a -> b) -> a -> c
. [a] -> Int
forall (t :: * -> *) a. Foldable t => t a -> Int
length in "* There are " String -> String -> String
forall a. [a] -> [a] -> [a]
++ [ProfileEntry] -> String
forall a. [a] -> String
f [ProfileEntry]
xs String -> String -> String
forall a. [a] -> [a] -> [a]
++ " rules (" String -> String -> String
forall a. [a] -> [a] -> [a]
++ [ProfileEntry] -> String
forall a. [a] -> String
f [ProfileEntry]
ls String -> String -> String
forall a. [a] -> [a] -> [a]
++ " rebuilt in the last run)."
    ,let f :: [ProfileEntry] -> String
f = Int -> String
forall a. Show a => a -> String
show (Int -> String)
-> ([ProfileEntry] -> Int) -> [ProfileEntry] -> String
forall b c a. (b -> c) -> (a -> b) -> a -> c
. [Int] -> Int
forall (t :: * -> *) a. (Foldable t, Num a) => t a -> a
sum ([Int] -> Int)
-> ([ProfileEntry] -> [Int]) -> [ProfileEntry] -> Int
forall b c a. (b -> c) -> (a -> b) -> a -> c
. (ProfileEntry -> Int) -> [ProfileEntry] -> [Int]
forall a b. (a -> b) -> [a] -> [b]
map ([ProfileTrace] -> Int
forall (t :: * -> *) a. Foldable t => t a -> Int
length ([ProfileTrace] -> Int)
-> (ProfileEntry -> [ProfileTrace]) -> ProfileEntry -> Int
forall b c a. (b -> c) -> (a -> b) -> a -> c
. ProfileEntry -> [ProfileTrace]
prfTraces) in "* Building required " String -> String -> String
forall a. [a] -> [a] -> [a]
++ [ProfileEntry] -> String
f [ProfileEntry]
xs String -> String -> String
forall a. [a] -> [a] -> [a]
++ " traced commands (" String -> String -> String
forall a. [a] -> [a] -> [a]
++ [ProfileEntry] -> String
f [ProfileEntry]
ls String -> String -> String
forall a. [a] -> [a] -> [a]
++ " in the last run)."
    ,"* The total (unparallelised) time is " String -> String -> String
forall a. [a] -> [a] -> [a]
++ Double -> String
showDuration ([Double] -> Double
forall (t :: * -> *) a. (Foldable t, Num a) => t a -> a
sum ([Double] -> Double) -> [Double] -> Double
forall a b. (a -> b) -> a -> b
$ (ProfileEntry -> Double) -> [ProfileEntry] -> [Double]
forall a b. (a -> b) -> [a] -> [b]
map ProfileEntry -> Double
prfExecution [ProfileEntry]
xs) String -> String -> String
forall a. [a] -> [a] -> [a]
++
        " of which " String -> String -> String
forall a. [a] -> [a] -> [a]
++ Double -> String
showDuration ([Double] -> Double
forall (t :: * -> *) a. (Foldable t, Num a) => t a -> a
sum ([Double] -> Double) -> [Double] -> Double
forall a b. (a -> b) -> a -> b
$ (ProfileTrace -> Double) -> [ProfileTrace] -> [Double]
forall a b. (a -> b) -> [a] -> [b]
map ProfileTrace -> Double
prfTime ([ProfileTrace] -> [Double]) -> [ProfileTrace] -> [Double]
forall a b. (a -> b) -> a -> b
$ (ProfileEntry -> [ProfileTrace])
-> [ProfileEntry] -> [ProfileTrace]
forall (t :: * -> *) a b. Foldable t => (a -> [b]) -> t a -> [b]
concatMap ProfileEntry -> [ProfileTrace]
prfTraces [ProfileEntry]
xs) String -> String -> String
forall a. [a] -> [a] -> [a]
++ " is traced commands."
    ,let f :: [(Double, String)] -> String
f xs :: [(Double, String)]
xs = if [(Double, String)] -> Bool
forall (t :: * -> *) a. Foldable t => t a -> Bool
null [(Double, String)]
xs then "0s" else (\(a :: Double
a,b :: String
b) -> Double -> String
showDuration Double
a String -> String -> String
forall a. [a] -> [a] -> [a]
++ " (" String -> String -> String
forall a. [a] -> [a] -> [a]
++ String
b String -> String -> String
forall a. [a] -> [a] -> [a]
++ ")") ((Double, String) -> String) -> (Double, String) -> String
forall a b. (a -> b) -> a -> b
$ ((Double, String) -> (Double, String) -> Ordering)
-> [(Double, String)] -> (Double, String)
forall a. (a -> a -> Ordering) -> [a] -> a
maximumBy' (Double -> Double -> Ordering
forall a. Ord a => a -> a -> Ordering
compare (Double -> Double -> Ordering)
-> ((Double, String) -> Double)
-> (Double, String)
-> (Double, String)
-> Ordering
forall b c a. (b -> b -> c) -> (a -> b) -> a -> a -> c
`on` (Double, String) -> Double
forall a b. (a, b) -> a
fst) [(Double, String)]
xs in
        "* The longest rule takes " String -> String -> String
forall a. [a] -> [a] -> [a]
++ [(Double, String)] -> String
f ((ProfileEntry -> (Double, String))
-> [ProfileEntry] -> [(Double, String)]
forall a b. (a -> b) -> [a] -> [b]
map (ProfileEntry -> Double
prfExecution (ProfileEntry -> Double)
-> (ProfileEntry -> String) -> ProfileEntry -> (Double, String)
forall a b c. (a -> b) -> (a -> c) -> a -> (b, c)
&&& ProfileEntry -> String
prfName) [ProfileEntry]
xs) String -> String -> String
forall a. [a] -> [a] -> [a]
++
        ", and the longest traced command takes " String -> String -> String
forall a. [a] -> [a] -> [a]
++ [(Double, String)] -> String
f ((ProfileTrace -> (Double, String))
-> [ProfileTrace] -> [(Double, String)]
forall a b. (a -> b) -> [a] -> [b]
map (ProfileTrace -> Double
prfTime (ProfileTrace -> Double)
-> (ProfileTrace -> String) -> ProfileTrace -> (Double, String)
forall a b c. (a -> b) -> (a -> c) -> a -> (b, c)
&&& ProfileTrace -> String
prfCommand) ([ProfileTrace] -> [(Double, String)])
-> [ProfileTrace] -> [(Double, String)]
forall a b. (a -> b) -> a -> b
$ (ProfileEntry -> [ProfileTrace])
-> [ProfileEntry] -> [ProfileTrace]
forall (t :: * -> *) a b. Foldable t => (a -> [b]) -> t a -> [b]
concatMap ProfileEntry -> [ProfileTrace]
prfTraces [ProfileEntry]
xs) String -> String -> String
forall a. [a] -> [a] -> [a]
++ "."
    ,let sumLast :: Double
sumLast = [Double] -> Double
forall (t :: * -> *) a. (Foldable t, Num a) => t a -> a
sum ([Double] -> Double) -> [Double] -> Double
forall a b. (a -> b) -> a -> b
$ (ProfileTrace -> Double) -> [ProfileTrace] -> [Double]
forall a b. (a -> b) -> [a] -> [b]
map ProfileTrace -> Double
prfTime ([ProfileTrace] -> [Double]) -> [ProfileTrace] -> [Double]
forall a b. (a -> b) -> a -> b
$ (ProfileEntry -> [ProfileTrace])
-> [ProfileEntry] -> [ProfileTrace]
forall (t :: * -> *) a b. Foldable t => (a -> [b]) -> t a -> [b]
concatMap ProfileEntry -> [ProfileTrace]
prfTraces [ProfileEntry]
ls
         maxStop :: Double
maxStop = [Double] -> Double
forall (t :: * -> *) a. (Foldable t, Ord a) => t a -> a
maximum ([Double] -> Double) -> [Double] -> Double
forall a b. (a -> b) -> a -> b
$ 0 Double -> [Double] -> [Double]
forall a. a -> [a] -> [a]
: (ProfileTrace -> Double) -> [ProfileTrace] -> [Double]
forall a b. (a -> b) -> [a] -> [b]
map ProfileTrace -> Double
prfStop ((ProfileEntry -> [ProfileTrace])
-> [ProfileEntry] -> [ProfileTrace]
forall (t :: * -> *) a b. Foldable t => (a -> [b]) -> t a -> [b]
concatMap ProfileEntry -> [ProfileTrace]
prfTraces [ProfileEntry]
ls) in
        "* Last run gave an average parallelism of " String -> String -> String
forall a. [a] -> [a] -> [a]
++ Int -> Double -> String
forall a. RealFloat a => Int -> a -> String
showDP 2 (if Double
maxStop Double -> Double -> Bool
forall a. Eq a => a -> a -> Bool
== 0 then 0 else Double
sumLast Double -> Double -> Double
forall a. Fractional a => a -> a -> a
/ Double
maxStop) String -> String -> String
forall a. [a] -> [a] -> [a]
++
        " times over " String -> String -> String
forall a. [a] -> [a] -> [a]
++ Double -> String
showDuration Double
maxStop String -> String -> String
forall a. [a] -> [a] -> [a]
++ "."
    ]
    where ls :: [ProfileEntry]
ls = (ProfileEntry -> Bool) -> [ProfileEntry] -> [ProfileEntry]
forall a. (a -> Bool) -> [a] -> [a]
filter (Int -> Int -> Bool
forall a. Eq a => a -> a -> Bool
(==) 0 (Int -> Bool) -> (ProfileEntry -> Int) -> ProfileEntry -> Bool
forall b c a. (b -> c) -> (a -> b) -> a -> c
. ProfileEntry -> Int
prfBuilt) [ProfileEntry]
xs


generateHTML :: [ProfileEntry] -> IO LBS.ByteString
generateHTML :: [ProfileEntry] -> IO ByteString
generateHTML xs :: [ProfileEntry]
xs = do
    ByteString
report <- String -> IO ByteString
readDataFileHTML "profile.html"
    let f :: String -> IO ByteString
f name :: String
name | String
name String -> String -> Bool
forall a. Eq a => a -> a -> Bool
== "profile-data.js" = ByteString -> IO ByteString
forall (m :: * -> *) a. Monad m => a -> m a
return (ByteString -> IO ByteString) -> ByteString -> IO ByteString
forall a b. (a -> b) -> a -> b
$ String -> ByteString
LBS.pack (String -> ByteString) -> String -> ByteString
forall a b. (a -> b) -> a -> b
$ "var profile =\n" String -> String -> String
forall a. [a] -> [a] -> [a]
++ [ProfileEntry] -> String
generateJSON [ProfileEntry]
xs
               | String
name String -> String -> Bool
forall a. Eq a => a -> a -> Bool
== "version.js" = ByteString -> IO ByteString
forall (m :: * -> *) a. Monad m => a -> m a
return (ByteString -> IO ByteString) -> ByteString -> IO ByteString
forall a b. (a -> b) -> a -> b
$ String -> ByteString
LBS.pack (String -> ByteString) -> String -> ByteString
forall a b. (a -> b) -> a -> b
$ "var version = " String -> String -> String
forall a. [a] -> [a] -> [a]
++ String -> String
forall a. Show a => a -> String
show String
shakeVersionString
               | Bool
otherwise = String -> IO ByteString
readDataFileHTML String
name
    (String -> IO ByteString) -> ByteString -> IO ByteString
forall (m :: * -> *).
(Functor m, MonadIO m) =>
(String -> m ByteString) -> ByteString -> m ByteString
runTemplate String -> IO ByteString
f ByteString
report


generateTrace :: [ProfileEntry] -> String
generateTrace :: [ProfileEntry] -> String
generateTrace xs :: [ProfileEntry]
xs = [String] -> String
jsonListLines ([String] -> String) -> [String] -> String
forall a b. (a -> b) -> a -> b
$
    Integer -> [ProfileTrace] -> [String]
forall a. Show a => a -> [ProfileTrace] -> [String]
showEntries 0 [ProfileTrace
y{prfCommand :: String
prfCommand=ProfileEntry -> String
prfName ProfileEntry
x} | ProfileEntry
x <- [ProfileEntry]
xs, ProfileTrace
y <- ProfileEntry -> [ProfileTrace]
prfTraces ProfileEntry
x] [String] -> [String] -> [String]
forall a. [a] -> [a] -> [a]
++
    Integer -> [ProfileTrace] -> [String]
forall a. Show a => a -> [ProfileTrace] -> [String]
showEntries 1 ((ProfileEntry -> [ProfileTrace])
-> [ProfileEntry] -> [ProfileTrace]
forall (t :: * -> *) a b. Foldable t => (a -> [b]) -> t a -> [b]
concatMap ProfileEntry -> [ProfileTrace]
prfTraces [ProfileEntry]
xs)
    where
        showEntries :: a -> [ProfileTrace] -> [String]
showEntries pid :: a
pid xs :: [ProfileTrace]
xs = ((Int, ProfileTrace) -> String)
-> [(Int, ProfileTrace)] -> [String]
forall a b. (a -> b) -> [a] -> [b]
map (a -> (Int, ProfileTrace) -> String
forall a a. (Show a, Show a) => a -> (a, ProfileTrace) -> String
showEntry a
pid) ([(Int, ProfileTrace)] -> [String])
-> [(Int, ProfileTrace)] -> [String]
forall a b. (a -> b) -> a -> b
$ ([ProfileTrace], [(Int, ProfileTrace)]) -> [(Int, ProfileTrace)]
forall a b. (a, b) -> b
snd (([ProfileTrace], [(Int, ProfileTrace)]) -> [(Int, ProfileTrace)])
-> ([ProfileTrace], [(Int, ProfileTrace)]) -> [(Int, ProfileTrace)]
forall a b. (a -> b) -> a -> b
$ ([ProfileTrace]
 -> ProfileTrace -> ([ProfileTrace], (Int, ProfileTrace)))
-> [ProfileTrace]
-> [ProfileTrace]
-> ([ProfileTrace], [(Int, ProfileTrace)])
forall (t :: * -> *) a b c.
Traversable t =>
(a -> b -> (a, c)) -> a -> t b -> (a, t c)
mapAccumL [ProfileTrace]
-> ProfileTrace -> ([ProfileTrace], (Int, ProfileTrace))
alloc [] ([ProfileTrace] -> ([ProfileTrace], [(Int, ProfileTrace)]))
-> [ProfileTrace] -> ([ProfileTrace], [(Int, ProfileTrace)])
forall a b. (a -> b) -> a -> b
$ (ProfileTrace -> ProfileTrace -> Ordering)
-> [ProfileTrace] -> [ProfileTrace]
forall a. (a -> a -> Ordering) -> [a] -> [a]
sortBy (Double -> Double -> Ordering
forall a. Ord a => a -> a -> Ordering
compare (Double -> Double -> Ordering)
-> (ProfileTrace -> Double)
-> ProfileTrace
-> ProfileTrace
-> Ordering
forall b c a. (b -> b -> c) -> (a -> b) -> a -> a -> c
`on` ProfileTrace -> Double
prfStart) [ProfileTrace]
xs
        alloc :: [ProfileTrace]
-> ProfileTrace -> ([ProfileTrace], (Int, ProfileTrace))
alloc as :: [ProfileTrace]
as r :: ProfileTrace
r | (a1 :: [ProfileTrace]
a1,an :: ProfileTrace
an:a2 :: [ProfileTrace]
a2) <- (ProfileTrace -> Bool)
-> [ProfileTrace] -> ([ProfileTrace], [ProfileTrace])
forall a. (a -> Bool) -> [a] -> ([a], [a])
break (\a :: ProfileTrace
a -> ProfileTrace -> Double
prfStop ProfileTrace
a Double -> Double -> Bool
forall a. Ord a => a -> a -> Bool
<= ProfileTrace -> Double
prfStart ProfileTrace
r) [ProfileTrace]
as = ([ProfileTrace]
a1[ProfileTrace] -> [ProfileTrace] -> [ProfileTrace]
forall a. [a] -> [a] -> [a]
++ProfileTrace
rProfileTrace -> [ProfileTrace] -> [ProfileTrace]
forall a. a -> [a] -> [a]
:[ProfileTrace]
a2, ([ProfileTrace] -> Int
forall (t :: * -> *) a. Foldable t => t a -> Int
length [ProfileTrace]
a1,ProfileTrace
r))
                   | Bool
otherwise = ([ProfileTrace]
as[ProfileTrace] -> [ProfileTrace] -> [ProfileTrace]
forall a. [a] -> [a] -> [a]
++[ProfileTrace
r], ([ProfileTrace] -> Int
forall (t :: * -> *) a. Foldable t => t a -> Int
length [ProfileTrace]
as,ProfileTrace
r))
        showEntry :: a -> (a, ProfileTrace) -> String
showEntry pid :: a
pid (tid :: a
tid, ProfileTrace{..}) = [(String, String)] -> String
forall a. Show a => [(a, String)] -> String
jsonObject
            [("args","{}"), ("ph",String -> String
forall a. Show a => a -> String
show "X"), ("cat",String -> String
forall a. Show a => a -> String
show "target")
            ,("name",String -> String
forall a. Show a => a -> String
show String
prfCommand), ("tid",a -> String
forall a. Show a => a -> String
show a
tid), ("pid",a -> String
forall a. Show a => a -> String
show a
pid)
            ,("ts",Double -> String
forall a. Show a => a -> String
show (Double -> String) -> Double -> String
forall a b. (a -> b) -> a -> b
$ 1000000Double -> Double -> Double
forall a. Num a => a -> a -> a
*Double
prfStart), ("dur",Double -> String
forall a. Show a => a -> String
show (Double -> String) -> Double -> String
forall a b. (a -> b) -> a -> b
$ 1000000Double -> Double -> Double
forall a. Num a => a -> a -> a
*(Double
prfStopDouble -> Double -> Double
forall a. Num a => a -> a -> a
-Double
prfStart))]


generateJSON :: [ProfileEntry] -> String
generateJSON :: [ProfileEntry] -> String
generateJSON = [String] -> String
jsonListLines ([String] -> String)
-> ([ProfileEntry] -> [String]) -> [ProfileEntry] -> String
forall b c a. (b -> c) -> (a -> b) -> a -> c
. (ProfileEntry -> String) -> [ProfileEntry] -> [String]
forall a b. (a -> b) -> [a] -> [b]
map ProfileEntry -> String
showEntry
    where
        showEntry :: ProfileEntry -> String
showEntry ProfileEntry{..} = [(String, String)] -> String
forall a. Show a => [(a, String)] -> String
jsonObject ([(String, String)] -> String) -> [(String, String)] -> String
forall a b. (a -> b) -> a -> b
$
            [("name", String -> String
forall a. Show a => a -> String
show String
prfName)
            ,("built", Int -> String
forall a. Show a => a -> String
show Int
prfBuilt)
            ,("changed", Int -> String
forall a. Show a => a -> String
show Int
prfChanged)
            ,("depends", [Int] -> String
forall a. Show a => a -> String
show [Int]
prfDepends)
            ,("execution", Int -> Double -> String
forall a. RealFloat a => Int -> a -> String
showDP 4 Double
prfExecution)] [(String, String)] -> [(String, String)] -> [(String, String)]
forall a. [a] -> [a] -> [a]
++
            [("traces", [String] -> String
jsonList ([String] -> String) -> [String] -> String
forall a b. (a -> b) -> a -> b
$ (ProfileTrace -> String) -> [ProfileTrace] -> [String]
forall a b. (a -> b) -> [a] -> [b]
map ProfileTrace -> String
showTrace [ProfileTrace]
prfTraces) | Bool -> Bool
not (Bool -> Bool) -> Bool -> Bool
forall a b. (a -> b) -> a -> b
$ [ProfileTrace] -> Bool
forall (t :: * -> *) a. Foldable t => t a -> Bool
null [ProfileTrace]
prfTraces]
        showTrace :: ProfileTrace -> String
showTrace ProfileTrace{..} = [(String, String)] -> String
forall a. Show a => [(a, String)] -> String
jsonObject
            [("command",String -> String
forall a. Show a => a -> String
show String
prfCommand), ("start",Double -> String
forall a. Show a => a -> String
show Double
prfStart), ("stop",Double -> String
forall a. Show a => a -> String
show Double
prfStop)]

jsonListLines :: [String] -> String
jsonListLines xs :: [String]
xs = "[" String -> String -> String
forall a. [a] -> [a] -> [a]
++ String -> [String] -> String
forall a. [a] -> [[a]] -> [a]
intercalate "\n," [String]
xs String -> String -> String
forall a. [a] -> [a] -> [a]
++ "\n]"
jsonList :: [String] -> String
jsonList xs :: [String]
xs = "[" String -> String -> String
forall a. [a] -> [a] -> [a]
++ String -> [String] -> String
forall a. [a] -> [[a]] -> [a]
intercalate "," [String]
xs String -> String -> String
forall a. [a] -> [a] -> [a]
++ "]"
jsonObject :: [(a, String)] -> String
jsonObject xs :: [(a, String)]
xs = "{" String -> String -> String
forall a. [a] -> [a] -> [a]
++ String -> [String] -> String
forall a. [a] -> [[a]] -> [a]
intercalate "," [a -> String
forall a. Show a => a -> String
show a
a String -> String -> String
forall a. [a] -> [a] -> [a]
++ ":" String -> String -> String
forall a. [a] -> [a] -> [a]
++ String
b | (a :: a
a,b :: String
b) <- [(a, String)]
xs] String -> String -> String
forall a. [a] -> [a] -> [a]
++ "}"