kontrakcja-templates-0.1: src/Text/StringTemplates/TextTemplates.hs
-- | module for reading dictionaries of templates from csv files.
-- may be used for reading of csv containing translation templates
module Text.StringTemplates.TextTemplates (getTextTemplates) where
import Data.Char (isSpace, isControl)
import Data.List (isSuffixOf)
import System.IO
import Data.Map (Map)
import qualified Data.Map as M
import Text.ParserCombinators.Parsec (parse)
import Data.CSV (csvFile)
import Text.StringTemplates.Utils
type Schema = [String]
-- | Searches recursively a directory for .csv files and
-- parses them with the following format:
--
-- @
-- \"WHATEVER\", \"column_name1\", \"column_name2\", ...
-- \"name\", \"value1\", \"value2\", ...
-- \"name2\", \"value3\", \"value4\", ...
-- ...
-- @
--
-- and returns a list of (name, value) pairs for the chosen column_name
-- (across all files) (e.g. for \"column_name1\" from example,
-- it would return [(\"name\", \"value1\"), (\"name2\", \"value3\")])
-- if the value is missing for column_name (but it's present in other columns),
-- tries values from other columns (in order)
getTextTemplates :: FilePath -- ^ path of a directory to search for .csv files
-> IO (Map String [(String, String)])
getTextTemplates path = do
paths <- directoryFilesRecursive path
M.unionsWith (++) `fmap` mapM getTextTemplatesFromFile paths
-- Parses a .csv file with the following format:
-- (line1): WHATEVER, column_name1, column_name2, ...
-- (lineN): name, value1, value2, ...
-- and returns a map from column name to a list of (name, value) pairs
getTextTemplatesFromFile :: FilePath -> IO (Map String [(String, String)])
getTextTemplatesFromFile path | not $ ".csv" `isSuffixOf` path = return $ M.empty
| otherwise = do
csv <- basicCSVParser path
case csv of
[] -> error $ "CSV parsing error in" ++ path ++ "\nEmpty file"
schemaRow:rows ->
case schemaRow of
[] -> error $ "CSV parsing error in" ++ path ++ "\nEmpty schema/first line"
_:schema -> return $ parseTextTemplates schema rows
-- Takes a schema (list of column names) and a list of rows
-- (row looks like: [name, value1, value2...]
-- and returns a map from column name to a list of (name, value) pairs
parseTextTemplates :: Schema -> [[String]] -> Map String [(String, String)]
parseTextTemplates schema = M.unionsWith (++) . map aux
where aux :: [String] -> Map String [(String, String)]
aux [] = error "parseTextTemplates: row cannot be empty"
aux (name:fields) =
M.fromList $ zip schema $ map makeList $ filter goodField $
zip (repeat name) $ map fixField fields
fixField = replace '\n' ' '
goodField (_, f) = any notSpaceOrControl f || (length f >= 6)
notSpaceOrControl c = not $ isSpace c || isControl c
replace a b = map $ \x -> if x == a then b else x
makeList x = [x]
-- Parses csv from filepath (using utf-8)
basicCSVParser :: FilePath -> IO [[String]]
basicCSVParser path =
withFile path ReadMode $ \h -> do
hSetEncoding h utf8
content <- hGetContents h
case parse csvFile path content of
Right csv -> return csv
Left s -> error $ "CSV parse error in " ++ path ++ ": " ++ show s