content: -- |
  -- Module      : Gauge.CSV
  -- Copyright   : (c) 2017 Vincent Hanquez
  --
  -- a very simple CSV printer
  -- 
  -- import qualified for best result
  --
  module Gauge.CSV
      ( Row(..)
      , outputRow
      , Field
      , float
      , integral
      , string
      , write
      ) where
  
  import Data.List (intercalate)
  
  -- | a CSV Field (numerical or string)
  --
  -- The content inside is properly escaped
  newtype Field = Field { unField :: String }
      deriving (Show, Eq)
  
  -- | A Row of fields
  newtype Row = Row [Field]
      deriving (Show, Eq)
  
  -- | Create a field from Double
  float :: Double -> Field
  float d = Field $ show d
  
  -- | Create a field for numerical integral
  integral :: Integral a => a -> Field
  integral i = Field $ show (toInteger i)
  
  -- | Create a field from String
  string :: String -> Field
  string s =
      -- potentially a random string need to be escape,
      -- first find out how it need to escaped, then
      -- escape the data properly.
      case needEscape NoEscape s of
          NoEscape       -> Field s
          Escape         -> Field ('"' : (s ++ "\""))
          EscapeDoubling -> Field ('"' : doubleQuotes s)
    where
      needEscape EscapeDoubling _      = EscapeDoubling
      needEscape e              []     = e
      needEscape e              (x:xs)
          | x == '"'                   = EscapeDoubling
          | x `elem` toEscape          = needEscape (max e Escape) xs
          | otherwise                  = needEscape e              xs
  
      toEscape = ",\r\n"
  
      doubleQuotes [] = ['"']
      doubleQuotes (x:xs)
          | x == '"'  = '"':'"':doubleQuotes xs
          | otherwise = x : doubleQuotes xs
  
  -- | Output a row to a String
  outputRow :: Row -> String
  outputRow (Row fields) = intercalate "," $ map unField fields
  
  -- | 3 Possible modes of escaping:
  -- * none
  -- * normal quotes escapes
  -- * content need doubling because of double quote in content
  data Escaping = NoEscape | Escape | EscapeDoubling
      deriving (Show, Eq, Ord)
  
  write :: Maybe FilePath
        -> Row
        -> IO ()
  write Nothing _   = return ()
  write (Just fp) r = appendFile fp (outputRow r ++ "\r\n")
  