delimiter-separated (empty) → 0.1.0.0
raw patch · 9 files changed
+508/−0 lines, 9 filesdep +basedep +uhc-utildep +uulibsetup-changed
Dependencies added: base, uhc-util, uulib
Files
- LICENSE +28/−0
- README.md +2/−0
- Setup.hs +11/−0
- delimiter-separated.cabal +30/−0
- src/Text/DelimiterSeparated.hs +404/−0
- test/data.csv +3/−0
- test/data.tsv +3/−0
- test/mk +3/−0
- test/test.hs +24/−0
+ LICENSE view
@@ -0,0 +1,28 @@+Copyright (c) 2015, atzedijkstra+All rights reserved.++Redistribution and use in source and binary forms, with or without+modification, are permitted provided that the following conditions are met:++* Redistributions of source code must retain the above copyright notice, this+ list of conditions and the following disclaimer.++* Redistributions in binary form must reproduce the above copyright notice,+ this list of conditions and the following disclaimer in the documentation+ and/or other materials provided with the distribution.++* Neither the name of delimiter-separated nor the names of its+ contributors may be used to endorse or promote products derived from+ this software without specific prior written permission.++THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"+AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE+IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE+DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE+FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL+DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR+SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER+CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,+OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.+
+ README.md view
@@ -0,0 +1,2 @@+# delimiter-separated+Haskell library for dealing with tab and/or comma (or other) separated files
+ Setup.hs view
@@ -0,0 +1,11 @@+import Distribution.Simple+main = defaultMain++{-+import Distribution.Simple (defaultMainWithHooks)+import Distribution.Simple.UUAGC (uuagcLibUserHook)+import UU.UUAGC (uuagc)++main :: IO ()+main = defaultMainWithHooks (uuagcLibUserHook uuagc)+-}
+ delimiter-separated.cabal view
@@ -0,0 +1,30 @@+Name: delimiter-separated+Version: 0.1.0.0+Copyright: Utrecht University, Department of Information and Computing Sciences, Software Technology group+Description: Delimeter separated file handling+Synopsis: Library for dealing with tab and/or comma (or other) separated files+Homepage: https://github.com/atzedijkstra/delimiter-separated+Bug-Reports: https://github.com/atzedijkstra/delimiter-separated/issues+License: BSD3+License-file: LICENSE+Author: atze@uu.nl+Maintainer: atze@uu.nl+Category: Development+Build-Type: Simple+Cabal-Version: >= 1.8+Extra-Source-Files: +data-files: LICENSE, README.md, test/mk, test/test.hs, test/data.csv, test/data.tsv++Source-Repository head+ Type: git+ Location: https://github.com/atzedijkstra/delimiter-separated++Library+ Hs-Source-Dirs: src+ Extensions: RankNTypes, FlexibleContexts, TypeSynonymInstances, FlexibleInstances+ Exposed-Modules: Text.DelimiterSeparated+ Other-Modules: + Build-Depends: base >= 4 && < 5,+ uhc-util >= 0.1.1.0,+ uulib >= 0.9+
+ src/Text/DelimiterSeparated.hs view
@@ -0,0 +1,404 @@+-------------------------------------------------------------------------------------------+-- Input/output of delimiter separated strings+-------------------------------------------------------------------------------------------++{-|+Module : Text.DelimiterSeparated+Description : Library for manipulating delimiter separated records.+Copyright : (c) Atze Dijkstra, 2015+License : BSD3+Maintainer : atze@uu.nl+Stability : experimental+Portability : POSIX++The library provides parsing/unparsing of 'Records' as well as interpreting those records to a datatype of your choice,+via 'toRecords' and 'fromRecords' using class 'DelimSepRecord', where each individual field can be interpreted via class 'DelimSepField'.++The following example demonstrates the basic parsing/unparsing:++> module Main where+> +> import Text.DelimiterSeparated+> import System.IO+> import Control.Monad+> +> main = do+> txt <- readFile "data.csv"+> putStrLn txt+> case recordsFromDelimiterSeparated csv txt of+> Left es -> forM_ es putStrLn+> Right recs -> do+> putStrLn $ show recs+> writeFile "data-out-csv.csv" $ recordsToDelimiterSeparated csv recs+> writeFile "data-out-csv.tsv" $ recordsToDelimiterSeparated tsv recs+> +> txt <- readFile "data.tsv"+> -- putStrLn txt+> case recordsFromDelimiterSeparated tsv txt of+> Left es -> forM_ es putStrLn+> Right recs -> do+> putStrLn $ show recs+> writeFile "data-out-tsv.csv" $ recordsToDelimiterSeparated csv recs++-}++module Text.DelimiterSeparated+ ( -- * Types+ Field(..)+ , Record+ , Records+ , DelimiterStyle(..)+ , csv, tsv+ + -- * Construction+ , emptyField+ + -- * Encoding, decoding+ , recordsToDelimiterSeparated+ , recordsFromDelimiterSeparated+ + -- * Checks, fixes+ , Check(..)+ , checkAll+ , recordsCheck+ + , Fix(..)+ , recordsFix+ + -- * Construction+ , recordsFromHeaderStr+ , recordsAddHeaderStr+ + -- * Manipulation+ , recordsPartitionRows+ , recordsPartitionColsBasedOnHeader+ , recordsSpan+ , recordsSplitHeader+ + -- * Conversion+ , recordsToStrings+ , recordsFromStrings+ + -- * Overloaded conversion+ , DelimSepField(..)+ , DelimSepRecord(..)+ + , toRecords+ , toRecordsWithHeader+ , toRecordsWithHeaderStr+ + , fromRecords+ )+ where++-------------------------------------------------------------------------------------------+import UU.Parsing+import UU.Parsing.CharParser+import UHC.Util.ParseUtils+import UHC.Util.Utils+import Data.List+-------------------------------------------------------------------------------------------++-------------------------------------------------------------------------------------------+-- Types+-------------------------------------------------------------------------------------------++-- | Field+data Field+ = Field+ { fldStr :: String -- base case+ }+ deriving (Eq)++-- | Empty field+emptyField :: Field+emptyField = Field ""++instance Show Field where+ show = showField tsv++-- | Show field, depending on delimiter style+showField :: DelimiterStyle -> Field -> String+showField (CSV {}) (Field s) = "\"" ++ s ++ "\""+showField (TSV {}) (Field s) = s++-- | Record is sequence of fields (representation may change in future)+type Record = [Field]++-- | Records is sequence of records (representation may change in future)+type Records = [Record]++-- | Style of delimitation+data DelimiterStyle+ = CSV+ { styleFieldDelimChars :: [Char] -- field delimiters, first one used when unparsing+ , styleRecordDelimChars :: [Char] -- record delimiters, first one used when unparsing+ }+ | TSV++-- | Predefined delimiter style for comma field separated, newline/return record separated+csv :: DelimiterStyle+csv = CSV "," "\n\r"++-- | Predefined delimiter style for tab field separated, newline/return record separated+tsv :: DelimiterStyle+tsv = TSV++-------------------------------------------------------------------------------------------+-- Parsing, encoding, decoding+-------------------------------------------------------------------------------------------++type P p = PlainParser Char p++-- | Parsing of records, given a delimiterstyle+pRecords :: DelimiterStyle -> P Records+pRecords style+ = concat <$> pList1Sep_ng pNL pRecordsNonEmpty -- <* (pNL `opt` '?')+ where pField = case style of+ TSV -> Field <$> pList_ng (pExcept (minBound, maxBound, '?') "\t\n\r")+ CSV {styleFieldDelimChars=fs, styleRecordDelimChars=ls} ->+ Field <$>+ ( pDQ *> pList_ng pQChar <* pDQ+ <|> pList_ng pChar+ )+ where pQChar+ = pExcept (minBound, maxBound, '?') "\""+ <|> pDQ <* pDQ+ pChar+ = pExcept (minBound, maxBound, '?') ("\"" ++ fs ++ ls)+ pRecord = pList1Sep_ng pSep pField+ pRecordsNonEmpty = chk <$> pRecord+ where chk [] = []+ chk [Field ""] = []+ chk fs = [fs]++ pNL = case style of+ TSV -> pAnySym "\n\r"+ CSV {styleRecordDelimChars=ls} -> pAnySym ls+ + pDQ = pSym '"'+ pSep = case style of+ TSV -> pAnySym "\t"+ CSV {styleFieldDelimChars=fs} -> pAnySym fs++-- | Encode internal representation in external delimiter separated format+recordsToDelimiterSeparated :: DelimiterStyle -> Records -> String+recordsToDelimiterSeparated style recs+ = (mk ls $ map (mk fs . map (showField style)) recs) ++ "\n"+ where (fs,ls) = case style of+ TSV -> ('\t', '\n')+ CSV {styleFieldDelimChars=(fs:_), styleRecordDelimChars=(ls:_)} -> (fs,ls)+ mk sep = concat . intersperse [sep]++-- | Decode internal representation from external delimiter separated format, possible failing with error messages+recordsFromDelimiterSeparated :: DelimiterStyle -> String -> Either [String] Records+recordsFromDelimiterSeparated style str+ | null errs = Right res+ | otherwise = Left $ map show errs+ where (res,errs) = parseToResMsgs (pRecords style) str++-------------------------------------------------------------------------------------------+-- Construction+-------------------------------------------------------------------------------------------++-- | Convert a String representation of a header to actual record+recordsFromHeaderStr :: String -> Records+recordsFromHeaderStr s = recordsFromStrings [words s]++-- | Add a header described by string holding whitespaced separated labels+recordsAddHeaderStr :: String -> Records -> Records+recordsAddHeaderStr s = (recordsFromHeaderStr s ++)++-------------------------------------------------------------------------------------------+-- Manipulation+-------------------------------------------------------------------------------------------++-- | Lift a predicate to a Record+liftRecPred :: ([String] -> Bool) -> (Record -> Bool)+liftRecPred pred = pred . map fldStr++-- | Do something with records, taking into account header+recordsDoHeader+ :: Bool -- ^ first rec is header?+ -> (Records -> Records -> res) -- ^ do it, given header (if any) and records+ -> Records+ -> res+recordsDoHeader fstIsHdr mk recs+ | fstIsHdr = mk [hd] tl+ | otherwise = mk [] recs+ where (hd:tl) = recs++-- | Partition record rows.+-- Fst of tuple holds the possible header, if indicated it is present.+-- Snd of tuple holds records failing the predicate.+-- Assumes >0 records+recordsPartitionRows :: Bool -> ([String] -> Bool) -> Records -> (Records, Records)+recordsPartitionRows fstIsHdr pred recs+ = recordsDoHeader fstIsHdr mk recs+ where mk hd rs = (hd++y,n)+ where (y,n) = partition (liftRecPred pred) rs++-- | Partition record columns, fst of tuple holds the obligatory header upon wich partitioning is done+-- Assumes header and records all have same nr of fields+recordsPartitionColsBasedOnHeader :: (String -> Bool) -> Records -> (Records, Records)+recordsPartitionColsBasedOnHeader pred hr@(hdr:_)+ = let spl = split hdr in unzip $ map spl hr+ where split (h:t) | pred (fldStr h) = let tspl = split t in \(rh:rt) -> let (r1,r2) = tspl rt in (rh:r1, r2)+ | otherwise = let tspl = split t in \(rh:rt) -> let (r1,r2) = tspl rt in ( r1, rh:r2)+ split [] = \[] -> ( [], [])++-- | Partition records, fst of tuple holds the possible header, if indicated it is present.+-- Assumes >0 records+recordsSpan :: Bool -> ([String] -> Bool) -> Records -> (Records, Records)+recordsSpan fstIsHdr pred recs+ = recordsDoHeader fstIsHdr mk recs+ where mk hd rs = (hd++y,n)+ where (y,n) = span (liftRecPred pred) rs++-- | Split of header, assuming there is one+recordsSplitHeader :: Records -> (Record, Records)+recordsSplitHeader (h:t) = (h, t)+++-------------------------------------------------------------------------------------------+-- Conversion+-------------------------------------------------------------------------------------------++-- | Get all fields as strings+recordsToStrings :: Records -> [[String]]+recordsToStrings = map (map fldStr)++-- | Lift strings as Records+recordsFromStrings :: [[String]] -> Records+recordsFromStrings = map (map Field)++-------------------------------------------------------------------------------------------+-- Check(s) & fixes+-------------------------------------------------------------------------------------------++-- | Which checks are to be done by 'recordsCheck'+data Check+ = Check_DupHdrNms -- ^ check for duplicate header names+ | Check_EqualSizedRecs -- ^ check for equal sized records (ignoring possible header)+ | Check_AtLeast1Rec -- ^ check for at least 1 record (ignoring possible header)+ | Check_EqualSizedHdrRecs -- ^ check for equal sized header and records+ | Check_NoRecsLargerThanHdr -- ^ check for records not larger than header+ | Check_NoRecsSmallerThanHdr -- ^ check for records not smaller than header+ deriving (Eq,Enum,Bounded)++-- | All checks+checkAll :: [Check]+checkAll = [minBound .. maxBound]++-- | Check records, possibly yielding errors+recordsCheck :: Bool -> [Check] -> Records -> Maybe String+recordsCheck fstIsHdr chks recs+ --- | null recs = Just $ "no records nor header"++ | (Check_AtLeast1Rec `elem` chks || checksHdrSizeAndRecs) && + not (has1Rec recs)+ = Just $ "not at least 1 record" ++ (if fstIsHdr then " and header" else "")++ | Check_EqualSizedRecs `elem` chks && + length tlSzs > 1+ = Just $ "records have varying sizes: " ++ show tlSzs++ | checksHdrSizeAndRecs && fstIsHdr &&+ not (null cmp_tlSzs)+ = Just $ "header size=" ++ show hdLen ++ " and records sizes=" ++ show cmp_tlSzs ++ " differ"++ | otherwise = Nothing++ where has1Rec (_:_:_) | fstIsHdr = True+ has1Rec (_:_ ) | fstIsHdr = False+ | otherwise = True+ has1Rec _ = False+ ~(~[rhd],rtl) = recordsDoHeader fstIsHdr (,) recs+ dupnms = concat $ map head $ filter (\l -> length l > 1) $ groupSortOn id $ map fldStr rhd+ tlNrAndLen = zipWith (\i r -> (i, length r)) [1::Int ..] rtl+ hdLen = length rhd+ tlSzs@(~(hd_tlSzs@(~(hd_tlSz,_)):_))+ = [ (l, map fst nl) | nl@((_,l):_) <- groupSortOn snd tlNrAndLen ]+ checksGT = Check_NoRecsLargerThanHdr `elem` chks+ checksLT = Check_NoRecsSmallerThanHdr `elem` chks+ checksHdrSizeAndRecs = checksGT || checksLT+ cmpSz | checksGT && checksLT = (/=)+ | checksGT = (>)+ | checksLT = (<)+ | otherwise = \_ _ -> False+ cmp_tlSzs = filter (\(sz,_) -> cmpSz sz hdLen) tlSzs++-- | Which fixes are to be done by 'recordsCheck'+data Fix+ = Fix_Pad -- ^ pad+ | Fix_PadToHdrLen -- ^ in combi with pad, pad to header len+ deriving (Eq,Enum,Bounded)++-- | Fix sizes of records by padding to max size+recordsFix :: Bool -> [Fix] -> Records -> Records+recordsFix fstIsHdr fxs recs+ = map fix recs+ where ~(~[rhd],rtl) = recordsDoHeader fstIsHdr (,) recs+ maxl | Fix_PadToHdrLen `elem` fxs && fstIsHdr = length rhd+ | otherwise = maximum $ map length rtl+ fix | Fix_Pad `elem` fxs = \r -> r ++ take (maxl - length r) p+ | otherwise = id+ where p = repeat emptyField++-------------------------------------------------------------------------------------------+-- Additional conversion/interpretation of field, i.e. show/read (why not use it like that?)+-------------------------------------------------------------------------------------------++-- | Conversion to/from Field, i.e. kinda show/read+class DelimSepField x where+ toDelimSepField :: x -> Field+ fromDelimSepField :: Field -> x++instance DelimSepField Field where+ toDelimSepField = id+ fromDelimSepField = id++instance {-# OVERLAPPABLE #-} DelimSepField x => DelimSepField [x] where+ toDelimSepField = toDelimSepField . unwords . map (fromDelimSepField . toDelimSepField)+ fromDelimSepField = map (fromDelimSepField . toDelimSepField) . words . fromDelimSepField++instance DelimSepField String where+ toDelimSepField = Field+ fromDelimSepField = fldStr++instance DelimSepField Integer where+ toDelimSepField = Field . show+ fromDelimSepField (Field x) = read x++instance DelimSepField Int where+ toDelimSepField = Field . show+ fromDelimSepField (Field x) = read x++instance DelimSepField Double where+ toDelimSepField = Field . show+ fromDelimSepField (Field x) = read x++-- | Conversion to/from Record+class DelimSepRecord x where+ toDelimSepRecord :: x -> Record+ fromDelimSepRecord :: Record -> x++instance {-# OVERLAPPABLE #-} DelimSepRecord Record where+ toDelimSepRecord = id+ fromDelimSepRecord = id++-- | Convert to records+toRecords :: DelimSepRecord x => [x] -> Records+toRecords = map toDelimSepRecord++-- | Convert to records, with a header described by string holding whitespaced separated labels+toRecordsWithHeader :: DelimSepRecord x => [Record] -> [x] -> Records+toRecordsWithHeader h = (h++) . toRecords++-- | Convert to records, with a header described by string holding whitespaced separated labels+toRecordsWithHeaderStr :: DelimSepRecord x => String -> [x] -> Records+toRecordsWithHeaderStr s = toRecordsWithHeader (recordsFromHeaderStr s)++-- | Convert from records+fromRecords :: DelimSepRecord x => Records -> [x]+fromRecords = map fromDelimSepRecord
+ test/data.csv view
@@ -0,0 +1,3 @@+/Users/sk/Music/iTunes/iTunes%20Music/Unknown%20Artist/Unknown%20Album/bummbumm.wav.mp3,0.03200000151991844,0.19200000166893005,0.9995909654314382,1.1448329230877248,1.2714721054530917,1.2927777950383028,1.2258539469919074,1.1275260497327948,1.007627284617382,0.8307895437172607,0.6963983522352151,0.6038522898665477,0.678277532511434,0.7829649869777328,3.019877011746812,3.6721289260958424E-4,9438.807662644498,-2.447088900865536,4.100193553539682,-0.12943993495929387,-6.90922812653433,-13.69428137030968,-16.283890170623266,-17.687202641965037,-18.27143813252461,-18.798327477661285,-19.160884562437893,-17.029512009859257,-17.21379450993305,-15.913488567673545,-15.645628338658279,-15.72830189645247,-15.172482398670333,-16.751137186808432,-17.27609905292102,-18.129456660727165,-19.898483803478577,-19.28359581490902,-20.188461066548225,-22.167085506958017,-23.54305927156909,-21.682182052480297,-22.153204790166512,-24.50021501266152,-23.334023569377504,-23.447214006292278,-26.578179434691254,-26.078093008351296,-28.165330414236333,-28.425195787464286,-28.294948662424982,-29.356971966031182,-31.862794824902593,-31.840246726994543,-34.02344292400379,-36.438152658158785,-39.388729121022585,4.592778742826233,-0.31367607962311617,1.6169361392277137,0.7541350976164396,1.612616595297597,0.6450404351749938,0.634337005415671,0.022760598948171867,0.3218182829449279,-0.20349345012470121,0.07679334369738107,-0.10771340255616313,-0.12116588702235398,8.42302919684374,559.9434664045244,0.02197556215395824+/Users/sk/Music/iTunes/iTunes%20Music/Unknown%20Artist/Unknown%20Album/bummbumm.wav.mp3,0.2240000069141388,0.17599999904632568,0.9746707219498666,1.1671316438307684,1.3217604253949975,1.3828134645667698,1.2989620627858258,1.1563957348822362,0.9634028904047387,0.7673071183828495,0.5922354279255881,0.47882394077814583,0.5900238647057049,0.7476595489716499,2.989648106174724,5.386914912802615E-4,-151.09917630417988,9.040418955722165,12.395741959638872,6.008055291943099,-6.839326872211888,-11.204253199081952,-13.119510468139424,-14.375215790385868,-16.576616220553976,-15.323830297118384,-13.77417497632024,-12.848404743424814,-13.952865456744265,-13.966724904298731,-14.555360175243893,-15.501099463044596,-16.009909151050763,-18.836763574779763,-18.19246489227611,-18.467274533418163,-19.540353998361542,-21.04046760741786,-21.188974332815135,-26.889314345456267,-29.90088232887947,-28.901550500650238,-29.686160668903067,-31.728787280339365,-33.32795663091535,-35.37592513485504,-37.10127227873265,-38.474977149586664,-40.63697155978034,-40.547451533398494,-40.68477781761101,-41.23272850385577,-42.82473910265789,-43.717349059141235,-46.502886211806384,-51.553083760413045,-60.26314334291782,9.177974659608198,-0.6244261856181933,1.6427387060867982,1.0651933986383453,1.8280220826563138,0.6031410422576562,1.365597990730021,0.34270934233824013,0.8851693746488324,-0.3176470132766348,0.3812310911561604,-0.07083169283783974,0.1360119733629103,0.0,189.7818034175284,1.1536715598368582E-4+/Users/sk/Music/iTunes/iTunes%20Music/Unknown%20Artist/Unknown%20Album/bummbumm.wav.mp3,0.4000000059604645,0.17599999904632568,1.0778043854808024,1.0996704580888912,1.1487601586562055,1.1411909160115201,1.0963204656317105,1.078084115118316,1.0454150811852605,0.9502169045231982,0.8070326833748533,0.7079010249241344,0.794816496851268,0.9289501193855842,2.9116156361552967,3.05210818747569E-4,-151.36600053200883,4.666259800504124,9.971417036411308,4.7735836392492885,-7.096358064223474,-9.723187042441586,-11.597554162187746,-11.421930965342126,-10.568355253918915,-9.947068106146446,-10.895314766510497,-11.37826882145405,-10.221172256327332,-9.77027137935714,-7.9837351046751985,-9.555067817109219,-11.231954691121311,-11.795521621444255,-11.110885401005955,-13.44970387792948,-14.674790094399008,-15.660669966092824,-15.779094112887847,-20.911414567281177,-23.51697494799904,-24.335406373422074,-24.823170797285805,-28.26341142931034,-32.69296823091563,-35.25031522743438,-36.74496610944522,-38.471303942147834,-39.61459693086177,-40.85385502609932,-42.02048510162983,-43.446151263585286,-43.87175287534318,-46.32075547618171,-49.40872977361136,-53.05420616345488,-58.2618264908827,9.77390580532916,-2.391068841981746,1.2629106043314486,1.0243722740997256,1.439629736552944,0.39717447322803223,1.1286081918079884,0.08450535243464721,0.7164428107635228,0.0021199767634070693,0.33667722867924005,0.0369097979127717,0.09114738100836475,0.0,319.2953702272283,0.0011021323970666528
+ test/data.tsv view
@@ -0,0 +1,3 @@+/Users/sk/Music/iTunes/iTunes%20Music/Unknown%20Artist/Unknown%20Album/bummbumm.wav.mp3 0.03200000151991844 0.19200000166893005 0.9995909654314382 1.1448329230877248 1.2714721054530917 1.2927777950383028 1.2258539469919074 1.1275260497327948 1.007627284617382 0.8307895437172607 0.6963983522352151 0.6038522898665477 0.678277532511434 0.7829649869777328 3.019877011746812 3.6721289260958424E-4 9438.807662644498 -2.447088900865536 4.100193553539682 -0.12943993495929387 -6.90922812653433 -13.69428137030968 -16.283890170623266 -17.687202641965037 -18.27143813252461 -18.798327477661285 -19.160884562437893 -17.029512009859257 -17.21379450993305 -15.913488567673545 -15.645628338658279 -15.72830189645247 -15.172482398670333 -16.751137186808432 -17.27609905292102 -18.129456660727165 -19.898483803478577 -19.28359581490902 -20.188461066548225 -22.167085506958017 -23.54305927156909 -21.682182052480297 -22.153204790166512 -24.50021501266152 -23.334023569377504 -23.447214006292278 -26.578179434691254 -26.078093008351296 -28.165330414236333 -28.425195787464286 -28.294948662424982 -29.356971966031182 -31.862794824902593 -31.840246726994543 -34.02344292400379 -36.438152658158785 -39.388729121022585 4.592778742826233 -0.31367607962311617 1.6169361392277137 0.7541350976164396 1.612616595297597 0.6450404351749938 0.634337005415671 0.022760598948171867 0.3218182829449279 -0.20349345012470121 0.07679334369738107 -0.10771340255616313 -0.12116588702235398 8.42302919684374 559.9434664045244 0.02197556215395824+/Users/sk/Music/iTunes/iTunes%20Music/Unknown%20Artist/Unknown%20Album/bummbumm.wav.mp3 0.2240000069141388 0.17599999904632568 0.9746707219498666 1.1671316438307684 1.3217604253949975 1.3828134645667698 1.2989620627858258 1.1563957348822362 0.9634028904047387 0.7673071183828495 0.5922354279255881 0.47882394077814583 0.5900238647057049 0.7476595489716499 2.989648106174724 5.386914912802615E-4 -151.09917630417988 9.040418955722165 12.395741959638872 6.008055291943099 -6.839326872211888 -11.204253199081952 -13.119510468139424 -14.375215790385868 -16.576616220553976 -15.323830297118384 -13.77417497632024 -12.848404743424814 -13.952865456744265 -13.966724904298731 -14.555360175243893 -15.501099463044596 -16.009909151050763 -18.836763574779763 -18.19246489227611 -18.467274533418163 -19.540353998361542 -21.04046760741786 -21.188974332815135 -26.889314345456267 -29.90088232887947 -28.901550500650238 -29.686160668903067 -31.728787280339365 -33.32795663091535 -35.37592513485504 -37.10127227873265 -38.474977149586664 -40.63697155978034 -40.547451533398494 -40.68477781761101 -41.23272850385577 -42.82473910265789 -43.717349059141235 -46.502886211806384 -51.553083760413045 -60.26314334291782 9.177974659608198 -0.6244261856181933 1.6427387060867982 1.0651933986383453 1.8280220826563138 0.6031410422576562 1.365597990730021 0.34270934233824013 0.8851693746488324 -0.3176470132766348 0.3812310911561604 -0.07083169283783974 0.1360119733629103 0.0 189.7818034175284 1.1536715598368582E-4+/Users/sk/Music/iTunes/iTunes%20Music/Unknown%20Artist/Unknown%20Album/bummbumm.wav.mp3 0.4000000059604645 0.17599999904632568 1.0778043854808024 1.0996704580888912 1.1487601586562055 1.1411909160115201 1.0963204656317105 1.078084115118316 1.0454150811852605 0.9502169045231982 0.8070326833748533 0.7079010249241344 0.794816496851268 0.9289501193855842 2.9116156361552967 3.05210818747569E-4 -151.36600053200883 4.666259800504124 9.971417036411308 4.7735836392492885 -7.096358064223474 -9.723187042441586 -11.597554162187746 -11.421930965342126 -10.568355253918915 -9.947068106146446 -10.895314766510497 -11.37826882145405 -10.221172256327332 -9.77027137935714 -7.9837351046751985 -9.555067817109219 -11.231954691121311 -11.795521621444255 -11.110885401005955 -13.44970387792948 -14.674790094399008 -15.660669966092824 -15.779094112887847 -20.911414567281177 -23.51697494799904 -24.335406373422074 -24.823170797285805 -28.26341142931034 -32.69296823091563 -35.25031522743438 -36.74496610944522 -38.471303942147834 -39.61459693086177 -40.85385502609932 -42.02048510162983 -43.446151263585286 -43.87175287534318 -46.32075547618171 -49.40872977361136 -53.05420616345488 -58.2618264908827 9.77390580532916 -2.391068841981746 1.2629106043314486 1.0243722740997256 1.439629736552944 0.39717447322803223 1.1286081918079884 0.08450535243464721 0.7164428107635228 0.0021199767634070693 0.33667722867924005 0.0369097979127717 0.09114738100836475 0.0 319.2953702272283 0.0011021323970666528
+ test/mk view
@@ -0,0 +1,3 @@+#!/bin/sh++ghc -i../src --make test.hs -XRankNTypes -XFlexibleContexts -XTypeSynonymInstances -XFlexibleInstances
+ test/test.hs view
@@ -0,0 +1,24 @@+module Main where++import Text.DelimiterSeparated+import System.IO+import Control.Monad++main = do+ txt <- readFile "data.csv"+ putStrLn txt+ case recordsFromDelimiterSeparated csv txt of+ Left es -> forM_ es putStrLn+ Right recs -> do+ putStrLn $ show recs+ writeFile "data-out-csv.csv" $ recordsToDelimiterSeparated csv recs+ writeFile "data-out-csv.tsv" $ recordsToDelimiterSeparated tsv recs++ txt <- readFile "data.tsv"+ -- putStrLn txt+ case recordsFromDelimiterSeparated tsv txt of+ Left es -> forM_ es putStrLn+ Right recs -> do+ putStrLn $ show recs+ writeFile "data-out-tsv.csv" $ recordsToDelimiterSeparated csv recs+