AERN-Basics-2011.1: tools/BenchCsvToGnuplot.hs
module Main where
import System.Environment
import System.Exit
import System.Directory
import qualified Data.Map as Map
import qualified Data.Set as Set
import qualified Data.List as List
main =
do
args <- getArgs
inFileNames <- checkArgs args
recordLists <- mapM readCSV inFileNames
let benchRecords = concat $ map snd recordLists
let outputData = processData $ map separateImprecision benchRecords
let outFileNames = map makeOutFileName $ map fst outputData
mapM_ writeOutputFile $ zip outFileNames outputData
writeFile "plotAll.gnuplot" $ makeGnuplot inFileNames outFileNames
checkArgs [] = usageAndQuit
checkArgs inFileNames =
do
results <- mapM doesFileExist inFileNames
case and results of
True -> return inFileNames
False -> usageAndQuit
usageAndQuit =
do
progName <- getProgName
putStrLn $ "usage: " ++ progName ++ " <file1>.csv [<file2>.csv ...]"
exitFailure
slash2dash '/' = '-'
slash2dash c = c
writeOutputFile (outFileName, (name, benchRecords)) =
do
putStrLn outFileName
writeCSV outFileName (["Imprecision", "Mean"], benchRecords)
makeOutFileName name =
(map space2underscore name) ++ ".csv"
space2underscore ' ' = '_'
space2underscore c = c
separateImprecision record =
Map.insert "Name" name $
Map.insert "Imprecision" imprecision $
record
where
name = map slash2dash nameSlashes
imprecision = tail slashImprecision
(nameSlashes, slashImprecision) = splitAt slashIndex nameSlashImprecision
slashIndex = last $ List.elemIndices '/' nameSlashImprecision
nameSlashImprecision = lookupKey record "Name"
processData benchRecords =
map extractName names
where
names = Set.toList $ Set.fromList $ map (\r -> lookupKey r "Name") benchRecords
extractName name =
(name,
filter (\r -> lookupKey r "Name" == name) benchRecords)
makeGnuplot inFileNames outFileNames =
unlines $
[
"# AUTO GENERATED FROM " ++ (List.intercalate ", " inFileNames)
,"set term postscript eps enhanced"
,"set output \"plotAll.eps\""
,"set title 'computation time based on result imprecision'"
,"set key right bottom"
,"set key box linestyle 1"
,"set key spacing 1.5"
,"set logscale x"
,"set logscale y"
,"set xlabel 'imprecision'"
,"set ylabel 'time'"
,"set datafile separator \",\""
]
++
[makePlotLine outFileNames]
where
makePlotLine outFileNames =
"plot" ++ (List.intercalate "," (map makePlotImport outFileNames))
makePlotImport outFileName =
" \"" ++ outFileName ++ "\" using 1:2"
type Record = Map.Map String String
type Sheet = ([String], [Record])
lookupKey :: Record -> String -> String
lookupKey record key =
case Map.lookup key record of
Nothing -> "" -- error $ "key " ++ show key ++ " not found in record " ++ show record
Just value -> value
writeCSV :: FilePath -> Sheet -> IO ()
writeCSV filePath (keys, records) =
do
writeFile filePath $ unlines $ map formatLine $ (keys : dataLists)
where
dataLists =
map makeDataItems records
makeDataItems record =
[lookupKey record key | key <- keys ]
formatLine items =
separateWith "," $ map show items -- must not contain a comma
separateWith sep [] = ""
separateWith sep [a] = a
separateWith sep (h:t) = h ++ sep ++ (separateWith sep t)
readCSV :: FilePath -> IO Sheet
readCSV filePath =
do
contents <- readFile filePath
return $ processContents contents
where
processContents contents =
(header, recordMaps)
where
hdrRecords@(header : records) = parseCSV contents
recordMaps =
map snd $
indexRecordsByKeysAndHeader [] hdrRecords
indexRecordsByKeysAndHeader keys (header : records) =
map getKeysAndMap records
where
getKeysAndMap record =
(getKeys record, getMap record)
getKeys record =
map getKey keyIndices
where
getKey keyIx = record !! keyIx
keyIndices =
map keyIndex keys
keyIndex key =
case List.elemIndex key header of
Nothing -> error $ "key " ++ show key ++ " not found in the header " ++ show header
Just ix -> ix
getMap record =
Map.fromList $ zip header record
-- | parse records and their fields from the contents of a CSV file
parseCSV :: String -> [[String]]
parseCSV contents =
records
where
records =
map parseLine $ lines contents
parseLine line =
state1 0 [] "" line
where
-- expecting new field or end of line; initial state
state1 pos revPrevItems revPrevOutput [] =
reverse $ reverse revPrevOutput : revPrevItems
state1 pos revPrevItems revPrevOutput "\x0D" = -- DOS end of line
reverse $ reverse revPrevOutput : revPrevItems
state1 pos revPrevItems revPrevOutput (',' : cs) =
state1 (pos + 1) (reverse revPrevOutput : revPrevItems) "" cs
state1 pos revPrevItems revPrevOutput ('"' : cs) =
state3 (pos + 1) revPrevItems revPrevOutput cs
state1 pos revPrevItems revPrevOutput (c : cs) =
state2 (pos + 1) revPrevItems (c : revPrevOutput) cs
-- reading a field with no double quotes
state2 pos revPrevItems revPrevOutput [] =
reverse $ reverse revPrevOutput : revPrevItems
state2 pos revPrevItems revPrevOutput "\x0D" = -- DOS end of line
reverse $ reverse revPrevOutput : revPrevItems
state2 pos revPrevItems revPrevOutput (',' : cs) =
state1 (pos + 1) (reverse revPrevOutput : revPrevItems) "" cs
state2 pos revPrevItems revPrevOutput (c : cs) =
state2 (pos + 1) revPrevItems (c : revPrevOutput) cs
-- reading a field in double quotes
state3 pos revPrevItems revPrevOutput [] =
parseerror pos
state3 pos revPrevItems revPrevOutput ('"' : cs) =
state4 (pos + 1) revPrevItems revPrevOutput cs
state3 pos revPrevItems revPrevOutput (c : cs) =
state3 (pos + 1) revPrevItems (c : revPrevOutput) cs
-- reading a field in double quotes and just found a double quote
-- that could be the closing one or an inner one
state4 pos revPrevItems revPrevOutput [] =
reverse $ reverse revPrevOutput : revPrevItems
state4 pos revPrevItems revPrevOutput "\x0D" = -- DOS end of line
reverse $ reverse revPrevOutput : revPrevItems
state4 pos revPrevItems revPrevOutput (',' : cs) =
state1 (pos + 1) (reverse revPrevOutput : revPrevItems) "" cs
state4 pos revPrevItems revPrevOutput (c : cs) =
state3 (pos + 1) revPrevItems (c : revPrevOutput) cs
parseerror pos =
error $
"parse error in CVS file at pos:\n"
++ take pos line ++ "\n"
++ replicate pos ' ' ++ drop pos line