goal-core-0.20: Goal/Core/Project.hs
{-# LANGUAGE OverloadedStrings #-}
-- | This module provides functions for incorporating Goal into a
-- data-processing project. In particular, this module provides tools for
-- managing CSV files, and connecting them with gnuplot scripts for plotting.
-- CSV management is powered by @cassava@.
module Goal.Core.Project
(
-- * CSV
goalImport
, goalImportNamed
, goalExport
, goalExportLines
, goalExportNamed
, goalExportNamedLines
-- ** CSV Instances
, goalCSVParser
, goalCSVNamer
, goalCSVOrder
-- * Util
, runGnuplot
, runGnuplotWithVariables
) where
--- Imports ---
-- Unqualified --
import System.Process
import System.Directory
import Data.Csv
import Data.Char
import GHC.Generics
-- Qualified --
import qualified Data.Vector as V
import qualified Data.ByteString.Lazy as BS
import qualified Data.ByteString as BSI
--- Experiments ---
--- Import/Export ---
-- | Runs @gnuplot@ on the given @.gpi@, passing it a @load_path@ variable to
-- help it find Goal-generated csvs.
runGnuplot
:: FilePath -- ^ Gnuplot loadpath
-> String -- ^ Gnuplot script
-> IO ()
runGnuplot ldpth gpipth = do
let cmd = concat [ "gnuplot ", " -e \"load_path='", ldpth, "'\" ",gpipth,".gpi" ]
putStrLn $ "Running Command: " ++ cmd
callCommand cmd
-- | Runs @gnuplot@ on the given @.gpi@, passing it a @load_path@ variable to
-- help it find Goal-generated csvs, and a list of variables.
runGnuplotWithVariables
:: FilePath -- ^ Gnuplot loadpath
-> String -- ^ Gnuplot script
-> [(String,String)] -- ^ Arguments
-> IO ()
runGnuplotWithVariables ldpth gpipth args = do
let cmd = concat $ [ "gnuplot ", " -e \"load_path='", ldpth, "'" ]
++ (mapArgs <$> args) ++ [ "\" ",gpipth,".gpi" ]
putStrLn $ "Running Command: " ++ cmd
callCommand cmd
where mapArgs (nm,val) = concat ["; ",nm,"='",val,"'"]
-- | Load the given CSV file. The @.csv@ extension is automatically added.
goalImport
:: FromRecord r
=> FilePath
-> IO (Either String [r]) -- ^ CSVs
goalImport flpth = do
bstrm <- decode NoHeader <$> BS.readFile (flpth ++ ".csv")
case bstrm of
Right as -> return . Right $ V.toList as
Left str -> return $ Left str
-- | Load the given CSV file with headers. The @.csv@ extension is automatically added.
goalImportNamed
:: FromNamedRecord r
=> FilePath
-> IO (Either String [r]) -- ^ CSVs
goalImportNamed flpth = do
bstrm <- decodeByName <$> BS.readFile (flpth ++ ".csv")
case bstrm of
Right as -> return . Right . V.toList $ snd as
Left str -> return $ Left str
filePather :: FilePath -> FilePath -> IO FilePath
filePather ldpth flnm = do
createDirectoryIfMissing True ldpth
return $ concat [ldpth,"/",flnm,".csv"]
-- | Export the given CSVs to a file in the given directory. The @.csv@
-- extension is automatically added to the file name.
goalExport
:: ToRecord r
=> FilePath -- load_path
-> String -- File Name
-> [r] -- ^ CSVs
-> IO ()
goalExport ldpth flnm csvs = do
flpth <- filePather ldpth flnm
BS.writeFile flpth $ encode csvs
-- | Export the given list of CSVs to a file in the given directory, seperating
-- each set of CSVs by a single line. This causes gnuplot to the read CSV as a
-- collection of line segments. The @.csv@ extension is automatically added to
-- the file name.
goalExportLines
:: ToRecord r
=> FilePath
-> FilePath
-> [[r]] -- ^ CSVss
-> IO ()
goalExportLines ldpth flnm csvss = do
flpth <- filePather ldpth flnm
BS.writeFile flpth . BS.tail . BS.tail . BS.concat $ BS.append "\r\n" . encode <$> csvss
-- | Export the named CSVs to a file in the given directory, adding a header to
-- the @.csv@ file.
goalExportNamed
:: (ToNamedRecord r, DefaultOrdered r)
=> FilePath
-> FilePath
-> [r] -- ^ CSVs
-> IO ()
goalExportNamed ldpth flnm csvs = do
flpth <- filePather ldpth flnm
BS.writeFile flpth $ encodeDefaultOrderedByName csvs
-- | Export the given list of named CSVs to a file, breaking it into a set of
-- line segments (with headers).
goalExportNamedLines
:: (ToNamedRecord r, DefaultOrdered r)
=> FilePath
-> FilePath
-> [[r]] -- ^ CSVss
-> IO ()
goalExportNamedLines ldpth flnm csvss = do
flpth <- filePather ldpth flnm
BS.writeFile flpth . BS.concat $ BS.append "\r\n" . encodeDefaultOrderedByName <$> csvss
--- Util ---
deCamelCaseLoop :: String -> String
deCamelCaseLoop "" = ""
deCamelCaseLoop (c:wrds) =
let (wrd,wrds') = span isLower wrds
in (c:wrd) ++ ' ' : deCamelCaseLoop wrds'
deCamelCase :: String -> String
deCamelCase (c:wrds) = init $ deCamelCaseLoop (toUpper c : wrds)
deCamelCase "" = error "How is deCamelCase being run on an empty string?"
deCamelCaseCSV :: Options
deCamelCaseCSV = defaultOptions { fieldLabelModifier = deCamelCase }
-- | A generic @.csv@ parser which reorganizes a header name in camel case into
-- "human readable" text. Useful for instantiating 'FromNamedRecord'.
goalCSVParser :: (Generic a, GFromNamedRecord (Rep a)) => NamedRecord -> Parser a
goalCSVParser = genericParseNamedRecord deCamelCaseCSV
-- | A generic @.csv@ namer which reorganizes a header name in camel case into
-- "human readable" text. Useful for instantiating 'ToNamedRecord'.
goalCSVNamer
:: (Generic a, GToRecord (Rep a) (BSI.ByteString, BSI.ByteString)) => a -> NamedRecord
goalCSVNamer = genericToNamedRecord deCamelCaseCSV
-- | A generic @.csv@ order which reorganizes a header name in camel case into
-- "human readable" text. Useful for instantiating 'DefaultOrdered'.
goalCSVOrder :: (Generic a, GToNamedRecordHeader (Rep a)) => a -> Header
goalCSVOrder = genericHeaderOrder deCamelCaseCSV