packages feed

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 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+