packages feed

rowrecord (empty) → 0.1

raw patch · 5 files changed

+264/−0 lines, 5 filesdep +basedep +containersdep +template-haskellsetup-changed

Dependencies added: base, containers, template-haskell

Files

+ LICENSE view
@@ -0,0 +1,26 @@+Copyright (c) Keegan McAllister 2010++All rights reserved.++Redistribution and use in source and binary forms, with or without+modification, are permitted provided that the following conditions+are met:+1. Redistributions of source code must retain the above copyright+   notice, this list of conditions and the following disclaimer.+2. 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.+3. Neither the name of the author nor the names of his 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 AUTHORS 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.hs view
@@ -0,0 +1,4 @@+#! /usr/bin/runhaskell++import Distribution.Simple+main = defaultMain
+ Text/RowRecord.hs view
@@ -0,0 +1,131 @@+{-# LANGUAGE+    ViewPatterns+  , TypeSynonymInstances #-}++-- | Convert lists of strings to records.++module Text.RowRecord+  ( -- * Tables+    Column+  , Row+  , Table+  , fromStrings++    -- * Parsing fields+  , Result  (..)+  , RowError(..)+  , Field   (..)+  , require+  , safeRead+  , getField++    -- * Parsing tables+  , ParseRow(..)+  , parseTable+  ) where++import Control.Applicative+import Control.Monad++import qualified Data.Map as M++-- | Identifies a column.+type Column = String++-- | A row of @'String'@ data.+type Row   = M.Map Column String++-- | A table.+type Table = [Row]++-- | Convert a list of @'String'@ rows into a @'Table'@.+-- Uses the first row as column names.+fromStrings :: [[String]] -> Result Table+fromStrings []     = Failure $ MissingField "header"+fromStrings (h:xs) = Success $ map (M.fromList . zip h) xs++-- | A parse result.+data Result a+  = Success a+  | Failure RowError+  deriving (Show)++-- | Possible errors from parsing a row.+data RowError+  = MissingField Column+  | NoParse      Column String+  deriving (Show)++instance Functor Result where+  fmap f (Success v) = Success $ f v+  fmap _ (Failure e) = Failure e++instance Monad Result where+   return = Success+   Success x >>= f = f x+   Failure e >>= _ = Failure e++instance Applicative Result where+  pure = return+  (<*>) = ap++-- | Class of field types which can be decoded from @'String'@.+--+-- The input can be @'Nothing'@ to represent a missing field.+-- The instance @Field a => Field (Maybe a)@ models optional fields.+--+-- If your record contains custom types, you must create a @'Field'@+-- instance for each.  If you have base types but need different +-- parsing behavior, you can use a @newtype@ wrapper.+class Field a where+  decode :: Maybe String -> Result a++-- | @'read'@ in @'Maybe'@.+safeRead :: (Read a) => String -> Maybe a+safeRead (reads -> [(v,"")]) = Just v+safeRead _ = Nothing++-- | Implement @'decode'@ for a required field.+require :: (String -> Maybe a) -> Maybe String -> Result a+require _ Nothing = Failure $ MissingField ""+require f (Just (f -> Just v)) = Success v+require _ (Just xs) = Failure $ NoParse "" xs++instance Field Bool    where decode = require safeRead+instance Field Int     where decode = require safeRead+instance Field Integer where decode = require safeRead+instance Field Float   where decode = require safeRead+instance Field Double  where decode = require safeRead+instance Field String  where decode = require Just++instance Field Char where+  decode = require f where+    f [x] = Just x+    f _   = Nothing++instance (Field a) => Field (Maybe a) where+  decode Nothing       = Success Nothing+  decode (Just "")     = Success Nothing+  decode (decode -> r) = Just <$> r++-- | Decode a field by column name.+--+-- Called from TH-generated code, but may be+-- useful independently.+getField :: (Field a) => Column -> Row -> Result a+getField k m = case decode $ M.lookup k m of+  v@(Success _)  -> v+  Failure e -> Failure $ case e of+    MissingField _ -> MissingField k+    (NoParse _ s)  -> NoParse k s++-- | Class of types which can be parsed from a @'Row'@.+-- These types are typically single-constructor records.+--+-- Instances may be generated using @Text.RowRecord.TH@.+class ParseRow a where+  parseRow :: Row -> Result a++-- | Parse a whole table.+parseTable :: (ParseRow a) => Table -> Result [a]+parseTable = mapM parseRow
+ Text/RowRecord/TH.hs view
@@ -0,0 +1,68 @@+{-# LANGUAGE+    TemplateHaskell+  , ViewPatterns+  , FlexibleInstances #-}++-- | Generate instances for converting lists of strings to records.++module Text.RowRecord.TH+  ( rowRecords+  ) where++import Text.RowRecord++import Control.Applicative++import Language.Haskell.TH+import Language.Haskell.TH.Syntax++-- Turn a record label name into a column name.+-- Drop everything through the first '_'.+getColumn :: Name -> Column+getColumn = f . nameBase where+  f (break (=='_') -> (_, ('_':xs))) = xs+  f n = error ("RowRecord: bad label: " ++ n)++-- | Generate a @'ParseRow'@ instance for each of the named types.+--+-- Each type must have exactly one constructor, in record style.+--+-- Column names are derived from the record field names by dropping the first+-- @'_'@-separated component.  This allows for a prefix to disambiguate record+-- labels between types.+--+-- For example, with+--+-- > data Foo = Foo+-- >   { f_bar :: String+-- >   , f_baz :: Int }+-- > $(rowRecords [''Foo])+--+-- we can parse files of the form+-- +-- > bar,baz+-- > abc,3+-- > def,5+--+-- assuming an appropriate CSV parser.++rowRecords :: [Name] -> Q [Dec]+rowRecords ns = concat <$> mapM rowRecord ns++rowRecord :: Name -> Q [Dec]+rowRecord n = do+  i <- reify n+  case i of+    TyConI (DataD    _ _ _ [c] _) -> fromCon n c+    TyConI (NewtypeD _ _ _ c   _) -> fromCon n c+    _ -> error ("RowRecord: not a data type: " ++ show n)++fromCon :: Name -> Con -> Q [Dec]+fromCon ty (RecC ctor fields) =+  let gets m = [ [| getField $(lift $ getColumn n) $(varE m) |]+               | (n,_,_) <- fields ]+      splat a b = [| $a <*> $b |]+      bdy = [| \m -> $(foldl1 splat ([| pure $(conE ctor) |] : gets 'm)) |]+  in  [d| instance ParseRow $(conT ty) where { parseRow = $bdy } |]++fromCon _ c = error ("RowRecord: not a record: " ++ show c)
+ rowrecord.cabal view
@@ -0,0 +1,35 @@+name:                rowrecord+version:             0.1+license:             BSD3+license-file:        LICENSE+synopsis:            Build records from lists of strings, as from CSV files.+category:            Data, Text+author:              Keegan McAllister <mcallister.keegan@gmail.com>+maintainer:          Keegan McAllister <mcallister.keegan@gmail.com>+build-type:          Simple+cabal-version:       >=1.2+description:+  Given rows of @String@ data with column headings, this library will create+  values of user-defined record types.  Records can contain mandatory or+  optional fields of any type, subject to a class constraint.+  Heading names and and record construction code are derived using+  Template Haskell.+  .+  One use case for this library is parsing records from a CSV file.  A+  parser from CSV to @[[String]]@ is not included, but there are several+  suitable packages on Hackage.+  .+  The emphasis of this library is on simplicity of use rather than performance.+  It is likely to be suitable for a hundred thousand rows, but not many+  millions.  A future version may support packed input formats like+  @ByteString@ or @Text@.  Suggestions and patches are welcome.++library+  exposed-modules:+      Text.RowRecord+    , Text.RowRecord.TH+  ghc-options:       -Wall+  build-depends:+      base             >= 3 && < 5+    , template-haskell >= 2.4+    , containers       >= 0.3