spreadsheet (empty) → 0.1
raw patch · 6 files changed
+366/−0 lines, 6 filesdep +basedep +explicit-exceptiondep +transformerssetup-changed
Dependencies added: base, explicit-exception, transformers, utility-ht
Files
- LICENSE +31/−0
- Setup.lhs +3/−0
- spreadsheet.cabal +53/−0
- src/Data/Spreadsheet.hs +129/−0
- src/Data/Spreadsheet/CharSource.hs +40/−0
- src/Data/Spreadsheet/Parser.hs +110/−0
+ LICENSE view
@@ -0,0 +1,31 @@+Copyright (c) 2009, Henning Thielemann++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.++ * The names of contributors may not 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+OWNER 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.
+ Setup.lhs view
@@ -0,0 +1,3 @@+#! /usr/bin/env runhaskell+> import Distribution.Simple+> main = defaultMain
+ spreadsheet.cabal view
@@ -0,0 +1,53 @@+Name: spreadsheet+Version: 0.1+License: BSD3+License-File: LICENSE+Author: Henning Thielemann <haskell@henning-thielemann.de>+Maintainer: Henning Thielemann <haskell@henning-thielemann.de>+Homepage: http://www.haskell.org/haskellwiki/Spreadsheet+Category: Data, Text+Synopsis: Read and write spreadsheets from and to CSV files in a lazy way+Description:+ Read and write spreadsheets from and to CSV files in a lazy way.+ See also the csv package on Hackage 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.+Tested-With: GHC==6.8.2+Cabal-Version: >=1.6+Build-Type: Simple+Source-Repository head+ type: darcs+ location: http://code.haskell.org/~thielema/spreadsheet/++Source-Repository this+ type: darcs+ location: http://code.haskell.org/~thielema/spreadsheet/+ tag: 0.1++Flag splitBase+ description: Choose the new smaller, split-up base package.++Flag buildTests+ description: Build test executables+ default: False++Library+ Build-Depends:+ utility-ht >=0.0.2 && <0.1,+ transformers >=0.0 && <0.1,+ explicit-exception >=0.1 && <0.2+ If flag(splitBase)+ Build-Depends: base >= 2+ Else+ Build-Depends: base >= 1.0 && < 2++ GHC-Options: -Wall+ Extensions: GeneralizedNewtypeDeriving+ Hs-Source-Dirs: src+ Exposed-Modules:+ Data.Spreadsheet+ Other-Modules:+ Data.Spreadsheet.Parser+ Data.Spreadsheet.CharSource
+ src/Data/Spreadsheet.hs view
@@ -0,0 +1,129 @@+module Data.Spreadsheet (+ T,+ -- * parsing+ fromString,+ fromStringSimple,+ -- * formatting+ toString,+ toStringSimple,+ ) where++import Data.List.HT (chop, switchR, )+import Data.List (intersperse, )+import Data.Maybe.HT (toMaybe, )++import qualified Data.Spreadsheet.Parser as Parser+import Control.Monad.Trans.State (runState, )+import Control.Monad (liftM, mplus, )++import qualified Control.Monad.Exception.Asynchronous as Async+import qualified Data.Spreadsheet.CharSource as CharSource+++{- |+A spreadsheet is a list of lines,+each line consists of cells,+and each cell is a string.+Ideally, spreadsheets read from a CSV file+have lines with the same number of cells per line.+However, we cannot assert this,+and thus we parse the lines as they come in.+-}+type T = [[String]]++parseChar :: CharSource.C source =>+ Char -> Parser.Fallible source Char+parseChar qm =+ Parser.eitherOr+ (Parser.satisfy (qm/=))+ (Parser.string [qm,qm] >> return qm)++parseQuoted :: CharSource.C source =>+ Char -> Parser.PartialFallible source String+parseQuoted qm =+ Parser.between "missing closing quote"+ (Parser.char qm) (Parser.char qm)+ (liftM Async.pure $ Parser.many (parseChar qm))++parseUnquoted :: CharSource.C source =>+ Char -> Char -> Parser.Straight source String+parseUnquoted qm sep =+ Parser.many+ (Parser.satisfy (not . flip elem [qm,sep,'\r','\n']))++parseCell :: CharSource.C source =>+ Char -> Char -> Parser.Partial source String+parseCell qm sep =+ Parser.deflt (liftM Async.pure $ parseUnquoted qm sep) (parseQuoted qm)++parseLine :: CharSource.C source =>+ Char -> Char -> Parser.Partial source [String]+parseLine qm sep =+ Parser.sepByIncomplete (Parser.char sep) (CharSource.fallible $ parseCell qm sep)++parseLineEnd :: CharSource.C source =>+ Parser.Fallible source ()+parseLineEnd =+ (Parser.char '\r' >> (Parser.char '\n' `Parser.eitherOr` return ()))+ `Parser.eitherOr`+ Parser.char '\n'++parseLineWithEnd :: CharSource.C source =>+ Char -> Char -> Parser.Partial source [String]+parseLineWithEnd qm sep =+ Parser.terminated "line end expected" parseLineEnd $+ parseLine qm sep+++parseTable :: CharSource.C source =>+ Char -> Char -> Parser.Partial source [[String]]+parseTable qm sep =+ Parser.manyIncomplete $+{-+ CharSource.fallible $ parseLineWithEnd qm sep+-}+ CharSource.fallible CharSource.isEnd >>= \b ->+ if b then CharSource.stop else CharSource.fallible $ parseLineWithEnd qm sep++{- |+@fromString qm sep text@ parses @text@ into a spreadsheet,+using the quotation character @qm@ and the separator character @sep@.+-}+fromString :: Char -> Char -> String -> Async.Exceptional Parser.UserMessage T+fromString qm sep str =+ let (~(Async.Exceptional e table), rest) =+ runState (CharSource.runString (parseTable qm sep)) str+ in Async.Exceptional+ (mplus e (toMaybe (not (null rest)) "junk after table")) table+++toString :: Char -> Char -> T -> String+toString qm sep =+ unlines . map (concat . intersperse [sep] . map (quote qm))++quote :: Char -> String -> String+quote qm s = qm : foldr (\c cs -> c : if c==qm then qm:cs else cs) [qm] s+-- quote qm s = [qm] ++ replace [qm] [qm,qm] s ++ [qm]+++fromStringSimple :: Char -> Char -> String -> T+fromStringSimple qm sep =+ map (map (dequoteSimple qm) . chop (sep==)) . lines++toStringSimple :: Char -> Char -> T -> String+toStringSimple qm sep =+ unlines . map (concat . intersperse [sep] . map (\s -> [qm]++s++[qm]))++dequoteSimple :: Eq a => a -> [a] -> [a]+dequoteSimple _ [] = error "dequoteSimple: string is empty"+dequoteSimple qm (x:xs) =+ if x == qm+ then error "dequoteSimple: quotation mark missing at beginning"+ else+ switchR+ (error "dequoteSimple: string consists only of a single quotation mark")+ (\ys y ->+ if y == qm+ then ys+ else (error "dequoteSimple: string does not end with a quotation mark"))+ xs
+ src/Data/Spreadsheet/CharSource.hs view
@@ -0,0 +1,40 @@+module Data.Spreadsheet.CharSource where++import qualified Control.Monad.Trans.State as State+import Control.Monad.Trans.State (StateT(StateT), gets, runStateT, mapStateT, )+import Control.Monad.Identity (Identity(Identity), runIdentity, )++import Data.List.HT (viewL, )+import Data.Tuple.HT (forcePair, )++import qualified Prelude as P+import Prelude hiding (String)+++class (Monad (m Maybe), Monad (m Identity)) => C m where+ get :: m Maybe Char+ isEnd :: m Identity Bool+ stop :: m Maybe a+ fallible :: m Identity a -> m Maybe a+ {- |+ Try to run a parser.+ If it succeeds, return (Just value) and advance input position.+ If it fails, return Nothing and keep the input position.+ -}+ try :: m Maybe a -> m Identity (Maybe a)+++newtype String fail a = String {runString :: StateT P.String fail a}+ deriving (Monad)+++instance C String where+ get = String $ StateT viewL+ isEnd = String $ gets null+ stop = String $ StateT $ const Nothing+ fallible x = String $ mapStateT (Just . runIdentity) $ runString x+ try x = String $ StateT $ \s0 ->+ Identity $ forcePair $+ case runStateT (runString x) s0 of+ Nothing -> (Nothing, s0)+ Just (a,s1) -> (Just a, s1)
+ src/Data/Spreadsheet/Parser.hs view
@@ -0,0 +1,110 @@+module Data.Spreadsheet.Parser where++import qualified Data.Spreadsheet.CharSource as CharSource++import qualified Control.Monad.Exception.Asynchronous as Async++import Control.Monad.Identity (Identity, )+import Control.Monad (liftM, liftM2, )++import Data.Maybe (fromMaybe, )+++type T source a = source a++type Straight source a = source Identity a+type Fallible source a = source Maybe a+type Partial source a = source Identity (PossiblyIncomplete a)+type PartialFallible source a = source Maybe (PossiblyIncomplete a)++type PossiblyIncomplete a = Async.Exceptional UserMessage a++type UserMessage = String+++satisfy ::+ (CharSource.C source) =>+ (Char -> Bool) -> Fallible source Char+satisfy p =+ do c <- CharSource.get+ if p c+ then return c+ else CharSource.stop++char :: (CharSource.C source) =>+ Char -> Fallible source ()+char c = satisfy (c==) >> return ()++string :: (CharSource.C source) =>+ String -> Fallible source ()+string s = mapM_ char s++many :: (CharSource.C source) =>+ Fallible source a -> Straight source [a]+many p =+ let go =+ liftM (fromMaybe []) $+ CharSource.try+ (liftM2 (:) p (CharSource.fallible go))+ in go++appendIncomplete ::+ CharSource.C source =>+ PartialFallible source a ->+ Partial source [a] ->+ PartialFallible source [a]+appendIncomplete p ps =+ do ~(Async.Exceptional me x) <- p+ CharSource.fallible $ liftM (fmap (x:)) $+ maybe ps (\_ -> return (Async.Exceptional me [])) me++absorbException ::+ (CharSource.C source) =>+ PartialFallible source [a] ->+ Partial source [a]+absorbException =+ liftM (fromMaybe (Async.pure [])) .+ CharSource.try++manyIncomplete :: CharSource.C source =>+ PartialFallible source a -> Partial source [a]+manyIncomplete p =+ let go = absorbException (appendIncomplete p go)+ in go++sepByIncomplete :: CharSource.C source =>+ Fallible source sep -> PartialFallible source a -> Partial source [a]+sepByIncomplete sep p =+ absorbException $+ appendIncomplete p $+ manyIncomplete (sep >> p)++between :: (CharSource.C source) =>+ UserMessage ->+ Fallible source open -> Fallible source close ->+ Partial source a -> PartialFallible source a+between msg open close p =+ open >>+ CharSource.fallible (terminated msg close p)++terminated :: (CharSource.C source) =>+ UserMessage ->+ Fallible source close ->+ Partial source a -> Partial source a+terminated msg close p =+ do enclosed <- p+ term <- CharSource.try close+ return (enclosed `Async.maybeAbort`+ maybe (Just msg) (const Nothing) term)+++-- mplus+eitherOr :: (CharSource.C source) =>+ Fallible source a -> Fallible source a -> Fallible source a+eitherOr x y =+ CharSource.fallible (CharSource.try x) >>= maybe y return++deflt :: (CharSource.C source) =>+ Straight source a -> Fallible source a -> Straight source a+deflt x y =+ maybe x return =<< CharSource.try y