spreadsheet 0.1.3.4 → 0.1.3.5
raw patch · 4 files changed
+193/−146 lines, 4 filesdep +spreadsheetdep −bytestringdep ~basedep ~explicit-exceptiondep ~transformers
Dependencies added: spreadsheet
Dependencies removed: bytestring
Dependency ranges changed: base, explicit-exception, transformers, utility-ht
Files
- README.md +69/−0
- example/CSVReplace.hs +101/−0
- spreadsheet.cabal +23/−39
- src/CSVReplace.hs +0/−107
+ README.md view
@@ -0,0 +1,69 @@+## Example: `csvreplace`++If you build the package with the Cabal flag `-fbuildExamples`+then the program `csvreplace` will be built.+It allows you to replace placeholders in a template file+according to the columns of a CSV file.+E.g. given a file `template.txt` with content++~~~~+Name: FIRSTNAME SURNAME+Born: BIRTH+~~~~++and `names.csv` with content++~~~~+"FIRSTNAME","SURNAME",BIRTH+"Georg","Cantor",1845+"Haskell","Curry",1900+"Ada","Lovelace",1815+~~~~++the call++~~~~+csvreplace template.txt <names.csv+~~~~++produces the output++~~~~+Name: Georg Cantor+Born: 1845+Name: Haskell Curry+Born: 1900+Name: Ada Lovelace+Born: 1815+~~~~++You may also generate one file per CSV row in the following manner:++~~~~+csvreplace --multifile=FIRSTNAME-SURNAME.txt template.txt <names.csv+~~~~+++### Character Encoding++For simple replacement of parts of the text+we would not need to decode the input texts+and thus we would not need to know the used encoding scheme.+Essentially, we would only require that both CSV and template file+employ the same character encoding.++However, it is not as simple as that.+We need to decode the structure of the CSV file.+In multi-file mode we also need to generate proper file names.+Both requirements force us to decode both CSV and template file.+For the de- and encoding we use the default locale encoding.++If you want essentially a byte-by-byte replacement+and you assert that all files are in the same encoding+where the commas and quotation marks are compatible with ASCII+then you can set the encoding locally+to a complete 8-bit encoding like `latin1` as in:++~~~~+LANG=de_DE csvreplace --multifile=FIRSTNAME-SURNAME.txt template.txt <names.csv+~~~~
+ example/CSVReplace.hs view
@@ -0,0 +1,101 @@+module Main where++import qualified Data.Spreadsheet as Sheet+import qualified Data.List.HT as ListHT++import System.Console.GetOpt (getOpt, ArgOrder(..), OptDescr(..), ArgDescr(..), usageInfo, )+import System.Environment (getArgs, getProgName, )+import qualified System.Exit as Exit+import qualified System.IO as IO++import qualified Control.Monad.Exception.Asynchronous as AExc+import qualified Control.Monad.Exception.Synchronous as Exc+import Control.Monad.Trans.Class (lift, )+import Control.Monad (when, )+import Control.Applicative ((<$>), )++import qualified Data.Foldable as Fold+import Data.Foldable (forM_, )+++data Flags =+ Flags {+ optMultiFile :: Maybe FilePath,+ optQuotation,+ optDelimiter :: Char+ }++defltFlags :: Flags+defltFlags = Flags {+ optMultiFile = Nothing,+ optQuotation = '"',+ optDelimiter = ','+ }++exitFailureMsg :: String -> IO a+exitFailureMsg msg = do+ IO.hPutStrLn IO.stderr msg+ Exit.exitFailure++options :: [OptDescr (Flags -> IO Flags)]+options =+ Option ['h'] ["help"]+ (NoArg (\_ -> do+ programName <- getProgName+ putStrLn $ flip usageInfo options $+ "Usage: " ++ programName ++ " [OPTIONS] TEMPLATE-FILE\n" +++ "The CSV file is read from standard input.\n"+ Exit.exitSuccess))+ "show options" :+ Option [] ["multifile"]+ (flip ReqArg "FILEPATTERN" $ \str flags ->+ return $ flags{optMultiFile = Just str})+ "generate one file per CSV row" :+ Option ['d'] ["delimiter"]+ (flip ReqArg "CHAR" $ \str flags -> do+ case str of+ [c] -> return $ flags{optDelimiter = c}+ _ -> exitFailureMsg $ "delimiter must be one character, which " ++ show str ++ " is not")+ "field delimiter character" :+ Option ['q'] ["quotation"]+ (flip ReqArg "CHAR" $ \str flags -> do+ case str of+ [c] -> return $ flags{optQuotation = c}+ _ -> exitFailureMsg $ "quotation mark must be one character, which " ++ show str ++ " is not")+ "quotation mark character" :+ []+++replaceRow :: String -> [String] -> [String] -> String+replaceRow template names row =+ ListHT.multiReplace (filter (not . null . fst) $ zip names row) template++replace :: String -> [String] -> Sheet.T -> String+replace template names = concatMap (replaceRow template names)+++main :: IO ()+main =+ Exc.resolveT (\e -> exitFailureMsg $ "Aborted: " ++ e) $ do+ argv <- lift getArgs+ let (opts, files, errors) = getOpt RequireOrder options argv+ when (not (null errors)) $ Exc.throwT $ concat $ errors+ flags <- lift $ foldr (=<<) (return defltFlags) opts+ case files of+ [templateName] -> do+ template <- lift $ readFile templateName+ sheet <-+ Sheet.fromString (optQuotation flags) (optDelimiter flags) <$>+ lift getContents+ case AExc.result sheet of+ [] -> Exc.throwT "empty CSV input"+ names:rows ->+ lift $+ case optMultiFile flags of+ Nothing -> putStr $ replace template names rows+ Just filePattern ->+ forM_ rows $ \row ->+ writeFile (replaceRow filePattern names row) $+ replaceRow template names row+ Fold.mapM_ Exc.throwT $ AExc.exception sheet+ _ -> Exc.throwT "I need exactly one template file."
spreadsheet.cabal view
@@ -1,5 +1,5 @@ Name: spreadsheet-Version: 0.1.3.4+Version: 0.1.3.5 License: BSD3 License-File: LICENSE Author: Henning Thielemann <haskell@henning-thielemann.de>@@ -10,58 +10,39 @@ Description: Read and write spreadsheets from and to files containing comma separated values (CSV) in a lazy way.- See also the- csv package <http://hackage.haskell.org/package/csv> and- <http://www.xoltar.org/languages/haskell.html>,- <http://www.xoltar.org/languages/haskell/CSV.hs>.- Both do not parse lazy. Reading from other source than plain 'String's could be easily added. . If you install this package by .- cabal install -fbuildExamples+ > cabal install -fbuildExamples .- then an example program is compiled and installed, too.+ then the example program @csvreplace@ is compiled and installed, too. This program fills a template text using data from a CSV file.- E.g. given a file @template.txt@ with content- .- > Name: FIRSTNAME SURNAME- > Born: BIRTH- .- and @names.csv@ with content- .- > "FIRSTNAME","SURNAME",BIRTH- > "Georg","Cantor",1845- > "Haskell","Curry",1900- > "Ada","Lovelace",1815- .- the call- .- > csvreplace template.txt <names.csv+ For similar (non-Haskell) programs see @cut@, @csvfix@, @csvtool@. .- produces the output+ Related packages: .- > Name: Georg Cantor- > Born: 1845- > Name: Haskell Curry- > Born: 1900- > Name: Ada Lovelace- > Born: 1815+ * @csv@: strict parser .- You may also generate one file per CSV row in the following manner:+ * <http://www.xoltar.org/languages/haskell.html>,+ <http://www.xoltar.org/languages/haskell/CSV.hs>: strict parser .- > csvreplace --multifile=FIRSTNAME-SURNAME.txt template.txt <names.csv+ * @lazy-csv@: lazy @String@ and @ByteString@ parser .- For similar (non-Haskell) programs see @cut@, @csvfix@, @csvtool@.-Tested-With: GHC==6.8.2, GHC==6.12.3-Cabal-Version: >=1.6+ * @cassava@: high-level CSV parser that treats rows as records,+ parses ByteStrings and is biased towards UTF-8 encoding.+Tested-With: GHC==7.4.2, GHC==7.8.4+Cabal-Version: >=1.8 Build-Type: Simple+Extra-Source-Files:+ README.md+ Source-Repository head Type: darcs Location: http://code.haskell.org/~thielema/spreadsheet/ Source-Repository this- Tag: 0.1.3.4+ Tag: 0.1.3.5 Type: darcs Location: http://code.haskell.org/~thielema/spreadsheet/ @@ -93,9 +74,12 @@ Executable csvreplace If flag(buildExamples) Build-Depends:- bytestring >=0.9 && <0.11+ spreadsheet,+ utility-ht,+ transformers,+ explicit-exception,+ base Else Buildable: False GHC-Options: -Wall- Hs-Source-Dirs: src- Main-Is: CSVReplace.hs+ Main-Is: example/CSVReplace.hs
− src/CSVReplace.hs
@@ -1,107 +0,0 @@-module Main where--import qualified Data.Spreadsheet as Sheet-import qualified Data.List.HT as ListHT--import System.Console.GetOpt (getOpt, ArgOrder(..), OptDescr(..), ArgDescr(..), usageInfo, )-import System.Environment (getArgs, getProgName, )-import qualified System.Exit as Exit-import qualified System.IO as IO--import qualified Data.ByteString.Char8 as B-import qualified Data.ByteString.Lazy.Char8 as BL--import qualified Control.Monad.Exception.Asynchronous as AExc-import qualified Control.Monad.Exception.Synchronous as Exc-import Control.Monad.Trans.Class (lift, )-import Control.Monad (when, )-import Data.Foldable (forM_, )---data Flags =- Flags {- optMultiFile :: Maybe FilePath,- optQuotation,- optDelimiter :: Char- }--defltFlags :: Flags-defltFlags = Flags {- optMultiFile = Nothing,- optQuotation = '"',- optDelimiter = ','- }--exitFailureMsg :: String -> IO a-exitFailureMsg msg = do- IO.hPutStrLn IO.stderr msg- Exit.exitFailure--options :: [OptDescr (Flags -> IO Flags)]-options =- Option ['h'] ["help"]- (NoArg (\_ -> do- programName <- getProgName- putStrLn $ flip usageInfo options $- "Usage: " ++ programName ++ " [OPTIONS] TEMPLATE-FILE\n" ++- "The CSV file is read from standard input.\n"- Exit.exitSuccess))- "show options" :- Option [] ["multifile"]- (flip ReqArg "FILEPATTERN" $ \str flags ->- return $ flags{optMultiFile = Just str})- "generate one file per CSV row" :- Option ['d'] ["delimiter"]- (flip ReqArg "CHAR" $ \str flags -> do- case str of- [c] -> return $ flags{optDelimiter = c}- _ -> exitFailureMsg $ "delimiter must be one character, which " ++ show str ++ " is not")- "field delimiter character" :- Option ['q'] ["quotation"]- (flip ReqArg "CHAR" $ \str flags -> do- case str of- [c] -> return $ flags{optQuotation = c}- _ -> exitFailureMsg $ "quotation mark must be one character, which " ++ show str ++ " is not")- "quotation mark character" :- []---replaceRow :: String -> [String] -> [String] -> String-replaceRow template names row =- ListHT.multiReplace (filter (not . null . fst) $ zip names row) template--replaceRowBS :: String -> [String] -> [String] -> BL.ByteString-replaceRowBS template names row =- BL.pack $ replaceRow template names row--replace :: String -> [String] -> Sheet.T -> BL.ByteString-replace template names =- BL.concat . map (replaceRowBS template names)---main :: IO ()-main =- Exc.resolveT (\e -> exitFailureMsg $ "Aborted: " ++ e) $ do- argv <- lift getArgs- let (opts, files, errors) = getOpt RequireOrder options argv- when (not (null errors)) $ Exc.throwT $ concat $ errors- flags <- lift $ foldr (=<<) (return defltFlags) opts- case files of- [templateName] -> do- template <- fmap B.unpack $ lift $ B.readFile templateName- sheet <-- fmap (Sheet.fromString (optQuotation flags) (optDelimiter flags)- . BL.unpack) $- lift BL.getContents- case AExc.result sheet of- [] -> Exc.throwT "empty CSV input"- (names:rows) ->- case optMultiFile flags of- Nothing -> do- lift $ BL.putStr $ replace template names rows- forM_ (AExc.exception sheet) $ Exc.throwT- Just filePattern ->- lift $ forM_ rows $ \row ->- BL.writeFile (replaceRow filePattern names row) $- replaceRowBS template names row- _ -> Exc.throwT "I need exactly one template file."