sv-core-0.2: Encode and decode separated values (CSV, PSV, ...)

Data.Sv.Decode.Core

Description

This module contains data structures, combinators, and primitives for decoding a CSV into a list of your Haskell datatype.

A Decode can be built using the primitives in this file. Decode is an Applicative and an Alt, allowing for composition of these values with <*> and <!>

The primitive Decodes in this file which use ByteString expect UTF-8 encoding. The Decode type has an instance of Profunctor, so you can lmap or alterInput to reencode on the way in.

This module is intended to be imported qualified like so

import qualified Data.Sv.Decode.Core as D

Synopsis

# The types

newtype Decode e s a Source #

A Decode e s a is for decoding some fields from a CSV row into our type a.

The second type parameter (s) is the input string type (usually ByteString or Text). The first type parameter (e) is the type of strings which occur in errors. Under most circumstances you want these type paraters to coincide, but they don't have to. They are two separate type parameters instead of one so that Decode can have a Profunctor instance.

There are primitive Decodes, and combinators for composing or otherwise manipulating them. In particular, Decode is an Applicative functor and an Alt from the semigroupoids package, also known as a SemiAlternative.

Decode is not a Monad, but we can perform monad-like operations on it with >>== or bindDecode

Constructors

 Decode FieldsunwrapDecode :: Compose (DecodeState s) (Compose (Writer (Last Bool)) (DecodeValidation e)) a
Instances
 Source # Instance detailsDefined in Data.Sv.Decode.Type Methodsdimap :: (a -> b) -> (c -> d) -> Decode e b c -> Decode e a d #lmap :: (a -> b) -> Decode e b c -> Decode e a c #rmap :: (b -> c) -> Decode e a b -> Decode e a c #(#.) :: Coercible c b => q b c -> Decode e a b -> Decode e a c #(.#) :: Coercible b a => Decode e b c -> q a b -> Decode e a c # Semigroupoid (Decode e :: * -> * -> *) Source # Instance detailsDefined in Data.Sv.Decode.Type Methodso :: Decode e j k1 -> Decode e i j -> Decode e i k1 # Functor (Decode e s) Source # Instance detailsDefined in Data.Sv.Decode.Type Methodsfmap :: (a -> b) -> Decode e s a -> Decode e s b #(<$) :: a -> Decode e s b -> Decode e s a # Applicative (Decode e s) Source # Instance detailsDefined in Data.Sv.Decode.Type Methodspure :: a -> Decode e s a #(<*>) :: Decode e s (a -> b) -> Decode e s a -> Decode e s b #liftA2 :: (a -> b -> c) -> Decode e s a -> Decode e s b -> Decode e s c #(*>) :: Decode e s a -> Decode e s b -> Decode e s b #(<*) :: Decode e s a -> Decode e s b -> Decode e s a # Apply (Decode e s) Source # Instance detailsDefined in Data.Sv.Decode.Type Methods(<.>) :: Decode e s (a -> b) -> Decode e s a -> Decode e s b #(.>) :: Decode e s a -> Decode e s b -> Decode e s b #(<.) :: Decode e s a -> Decode e s b -> Decode e s a #liftF2 :: (a -> b -> c) -> Decode e s a -> Decode e s b -> Decode e s c # Alt (Decode e s) Source # Instance detailsDefined in Data.Sv.Decode.Type Methods() :: Decode e s a -> Decode e s a -> Decode e s a #some :: Applicative (Decode e s) => Decode e s a -> Decode e s [a] #many :: Applicative (Decode e s) => Decode e s a -> Decode e s [a] # type Decode' s = Decode s s Source # Decode' is Decode with the input and error types the same. You usually want them to be the same, and most primitives are set up this way. DecodeValidation is the error-accumulating Applicative underlying Decode data DecodeError e Source # DecodeError is a value indicating what went wrong during a parse or decode. Its constructor indictates the type of error which occured, and there is usually an associated string with more finely-grained details. Constructors  UnexpectedEndOfRow I was looking for another field, but I am at the end of the row ExpectedEndOfRow (Vector e) I should be at the end of the row, but I found extra fields UnknownCategoricalValue e [[e]] This decoder was built using the categorical primitive for categorical data MissingColumn e Looked for a column with this name, but could not find it MissingHeader There should have been a header but there was nothing BadConfig e sv is misconfigured BadParse e The parser failed, meaning decoding proper didn't even begin BadDecode e Some other kind of decoding failure occured Instances  Source # Instance detailsDefined in Data.Sv.Decode.Type Methodsfmap :: (a -> b) -> DecodeError a -> DecodeError b #(<$) :: a -> DecodeError b -> DecodeError a # Eq e => Eq (DecodeError e) Source # Instance detailsDefined in Data.Sv.Decode.Type Methods(==) :: DecodeError e -> DecodeError e -> Bool #(/=) :: DecodeError e -> DecodeError e -> Bool # Ord e => Ord (DecodeError e) Source # Instance detailsDefined in Data.Sv.Decode.Type Methodscompare :: DecodeError e -> DecodeError e -> Ordering #(<) :: DecodeError e -> DecodeError e -> Bool #(<=) :: DecodeError e -> DecodeError e -> Bool #(>) :: DecodeError e -> DecodeError e -> Bool #(>=) :: DecodeError e -> DecodeError e -> Bool #max :: DecodeError e -> DecodeError e -> DecodeError e #min :: DecodeError e -> DecodeError e -> DecodeError e # Show e => Show (DecodeError e) Source # Instance detailsDefined in Data.Sv.Decode.Type MethodsshowsPrec :: Int -> DecodeError e -> ShowS #show :: DecodeError e -> String #showList :: [DecodeError e] -> ShowS # Source # Instance detailsDefined in Data.Sv.Decode.Type Associated Typestype Rep (DecodeError e) :: * -> * # Methodsfrom :: DecodeError e -> Rep (DecodeError e) x #to :: Rep (DecodeError e) x -> DecodeError e # NFData e => NFData (DecodeError e) Source # Instance detailsDefined in Data.Sv.Decode.Type Methodsrnf :: DecodeError e -> () # type Rep (DecodeError e) Source # Instance detailsDefined in Data.Sv.Decode.Type type Rep (DecodeError e) = D1 (MetaData "DecodeError" "Data.Sv.Decode.Type" "sv-core-0.2-IGJLcu8XQWi8HRyINTsX4I" False) (((C1 (MetaCons "UnexpectedEndOfRow" PrefixI False) (U1 :: * -> *) :+: C1 (MetaCons "ExpectedEndOfRow" PrefixI False) (S1 (MetaSel (Nothing :: Maybe Symbol) NoSourceUnpackedness NoSourceStrictness DecidedLazy) (Rec0 (Vector e)))) :+: (C1 (MetaCons "UnknownCategoricalValue" PrefixI False) (S1 (MetaSel (Nothing :: Maybe Symbol) NoSourceUnpackedness NoSourceStrictness DecidedLazy) (Rec0 e) :*: S1 (MetaSel (Nothing :: Maybe Symbol) NoSourceUnpackedness NoSourceStrictness DecidedLazy) (Rec0 [[e]])) :+: C1 (MetaCons "MissingColumn" PrefixI False) (S1 (MetaSel (Nothing :: Maybe Symbol) NoSourceUnpackedness NoSourceStrictness DecidedLazy) (Rec0 e)))) :+: ((C1 (MetaCons "MissingHeader" PrefixI False) (U1 :: * -> *) :+: C1 (MetaCons "BadConfig" PrefixI False) (S1 (MetaSel (Nothing :: Maybe Symbol) NoSourceUnpackedness NoSourceStrictness DecidedLazy) (Rec0 e))) :+: (C1 (MetaCons "BadParse" PrefixI False) (S1 (MetaSel (Nothing :: Maybe Symbol) NoSourceUnpackedness NoSourceStrictness DecidedLazy) (Rec0 e)) :+: C1 (MetaCons "BadDecode" PrefixI False) (S1 (MetaSel (Nothing :: Maybe Symbol) NoSourceUnpackedness NoSourceStrictness DecidedLazy) (Rec0 e)))))

newtype DecodeErrors e Source #

DecodeErrors is a Semigroup full of DecodeError. It is used as the error side of a DecodeValidation. When multiple errors occur, they will be collected.

Constructors

 DecodeErrors (NonEmpty (DecodeError e))
Instances
 Source # Instance detailsDefined in Data.Sv.Decode.Type Methodsfmap :: (a -> b) -> DecodeErrors a -> DecodeErrors b #(<\$) :: a -> DecodeErrors b -> DecodeErrors a # Eq e => Eq (DecodeErrors e) Source # Instance detailsDefined in Data.Sv.Decode.Type Methods(==) :: DecodeErrors e -> DecodeErrors e -> Bool #(/=) :: DecodeErrors e -> DecodeErrors e -> Bool # Ord e => Ord (DecodeErrors e) Source # Instance detailsDefined in Data.Sv.Decode.Type Methodscompare :: DecodeErrors e -> DecodeErrors e -> Ordering #(<) :: DecodeErrors e -> DecodeErrors e -> Bool #(<=) :: DecodeErrors e -> DecodeErrors e -> Bool #(>) :: DecodeErrors e -> DecodeErrors e -> Bool #(>=) :: DecodeErrors e -> DecodeErrors e -> Bool #max :: DecodeErrors e -> DecodeErrors e -> DecodeErrors e #min :: DecodeErrors e -> DecodeErrors e -> DecodeErrors e # Show e => Show (DecodeErrors e) Source # Instance detailsDefined in Data.Sv.Decode.Type MethodsshowsPrec :: Int -> DecodeErrors e -> ShowS #show :: DecodeErrors e -> String #showList :: [DecodeErrors e] -> ShowS # Source # Instance detailsDefined in Data.Sv.Decode.Type Associated Typestype Rep (DecodeErrors e) :: * -> * # Methodsfrom :: DecodeErrors e -> Rep (DecodeErrors e) x #to :: Rep (DecodeErrors e) x -> DecodeErrors e # Source # Instance detailsDefined in Data.Sv.Decode.Type Methods(<>) :: DecodeErrors e -> DecodeErrors e -> DecodeErrors e #stimes :: Integral b => b -> DecodeErrors e -> DecodeErrors e # NFData e => NFData (DecodeErrors e) Source # Instance detailsDefined in Data.Sv.Decode.Type Methodsrnf :: DecodeErrors e -> () # type Rep (DecodeErrors e) Source # Instance detailsDefined in Data.Sv.Decode.Type type Rep (DecodeErrors e) = D1 (MetaData "DecodeErrors" "Data.Sv.Decode.Type" "sv-core-0.2-IGJLcu8XQWi8HRyINTsX4I" True) (C1 (MetaCons "DecodeErrors" PrefixI False) (S1 (MetaSel (Nothing :: Maybe Symbol) NoSourceUnpackedness NoSourceStrictness DecidedLazy) (Rec0 (NonEmpty (DecodeError e)))))

# Running Decodes

decode :: Traversable f => Decode' ByteString a -> f (Vector ByteString) -> DecodeValidation ByteString (f a) Source #

Decodes a sv into a list of its values using the provided Decode

# Convenience constructors and functions

decodeMay :: DecodeError e -> (s -> Maybe a) -> Decode e s a Source #

Build a Decode, given a function that returns Maybe.

Return the given error if the function returns Nothing.

decodeEither :: (s -> Either (DecodeError e) a) -> Decode e s a Source #

Build a Decode, given a function that returns Either.

decodeEither' :: (e -> DecodeError e') -> (s -> Either e a) -> Decode e' s a Source #

Build a Decode, given a function that returns Either, and a function to build the error.

mapErrors :: (e -> x) -> Decode e s a -> Decode x s a Source #

Map over the errors of a Decode

To map over the other two parameters, use the Profunctor instance.

alterInput :: (e -> x) -> (t -> s) -> Decode e s a -> Decode x t a Source #

This transforms a Decode' s a into a Decode' t a. It needs functions in both directions because the errors can include fragments of the input.

alterInput :: (s -> t) -> (t -> s) -> Decode' s a -> Decode' t a

# Primitive Decodes

## Name-based

column :: Ord s => s -> Decode' s a -> NameDecode' s a Source #

This is the primitive for building decoders that work with columns

Look for the column with the given name and run the given decoder on it

(.:) :: Ord s => s -> Decode' s a -> NameDecode' s a infixl 5 Source #

Infix alias for column

## Field-based

contents :: Decode e s s Source #

Get the contents of a field without doing any decoding. This never fails.

Get a field that's a single char. This will fail if there are mulitple characters in the field.

Get the contents of a field as a bytestring.

Alias for contents

Get the contents of a UTF-8 encoded field as Text

This will also work for ASCII text, as ASCII is a subset of UTF-8

Get the contents of a field as a lazy Text

Get the contents of a field as a lazy ByteString

Get the contents of a field as a String

Decode a UTF-8 ByteString field as an Int

Decode a UTF-8 ByteString field as an Integer

Decode a UTF-8 ByteString field as a Float

Decode a UTF-8 ByteString field as a Double

boolean :: (IsString s, Ord s) => Decode' s Bool Source #

Decode a field as a Bool

This aims to be tolerant to different forms a boolean might take.

boolean' :: Ord s => (String -> s) -> Decode' s Bool Source #

Decode a field as a Bool. This version lets you provide the fromString function that's right for you, since IsString on a ByteString will do the wrong thing in the case of many encodings such as UTF-16 or UTF-32.

This aims to be tolerant to different forms a boolean might take.

ignore :: Decode e s () Source #

Throw away the contents of a field. This is useful for skipping unneeded fields.

replace :: a -> Decode e s a Source #

Throw away the contents of a field, and return the given value.

exactly :: (Semigroup s, Eq s, IsString s) => s -> Decode' s s Source #

Decode exactly the given string, or else fail.

emptyField :: (Eq s, IsString s, Semigroup s) => Decode' s () Source #

Succeed only when the given field is the empty string.

The empty string surrounded in quotes or spaces is still the empty string.

## Row-based

row :: Decode e s (Vector s) Source #

Grab the whole row as a Vector

# Combinators

choice :: Decode e s a -> Decode e s a -> Decode e s a Source #

Choose the leftmost Decode that succeeds. Alias for <!>

element :: NonEmpty (Decode e s a) -> Decode e s a Source #

Choose the leftmost Decode that succeeds. Alias for asum1

optionalField :: Decode e s a -> Decode e s (Maybe a) Source #

Try the given Decode. If it fails, succeed without consuming anything.

This usually isn't what you want. ignoreFailure and orEmpty are more likely what you are after.

ignoreFailure :: Decode e s a -> Decode e s (Maybe a) Source #

Try the given Decode. If it fails, instead succeed with Nothing.

orEmpty :: (Eq s, IsString s, Semigroup s) => Decode' s a -> Decode' s (Maybe a) Source #

If the field is the empty string, succeed with Nothing. Otherwise try the given Decode.

either :: Decode e s a -> Decode e s b -> Decode e s (Either a b) Source #

Try the first, then try the second, and wrap the winner in an Either.

This is left-biased, meaning if they both succeed, left wins.

orElse :: Decode e s a -> a -> Decode e s a Source #

Try the given decoder, otherwise succeed with the given value.

orElseE :: Decode e s b -> a -> Decode e s (Either a b) Source #

Try the given decoder, or if it fails succeed with the given value, in an Either.

categorical :: (Ord s, Show a) => [(a, s)] -> Decode' s a Source #

Decode categorical data, given a list of the values and the strings which match them.

Usually this is used with sum types with nullary constructors.

data TrafficLight = Red | Amber | Green
categorical [(Red, "red"), (Amber, "amber"), (Green, "green")]

categorical' :: forall s a. (Ord s, Show a) => [(a, [s])] -> Decode' s a Source #

Decode categorical data, given a list of the values and lists of strings which match them.

This version allows for multiple strings to match each value, which is useful for when the categories are inconsistently labelled.

data TrafficLight = Red | Amber | Green
categorical' [(Red, ["red", "R"]), (Amber, ["amber", "orange", "A"]), (Green, ["green", "G"])]

For another example of its usage, see the source for boolean.

(>>==) :: Decode e s a -> (a -> DecodeValidation e b) -> Decode e s b infixl 1 Source #

This can be used to build a Decode whose value depends on the result of another Decode. This is especially useful since Decode is not a Monad.

If you need something like this but with more power, look at bindDecode

(==<<) :: (a -> DecodeValidation e b) -> Decode e s a -> Decode e s b infixr 1 Source #

flipped >>==

bindDecode :: Decode e s a -> (a -> Decode e s b) -> Decode e s b Source #

Bind through a Decode.

This bind does not agree with the Applicative instance because it does not accumulate multiple error values. This is a violation of the Monad laws, meaning Decode is not a Monad.

That is not to say that there is anything wrong with using this function. It can be quite useful.

Use the Readable instance to try to decode the given value.

Use the Readable instance to try to decode the given value, or fail with the given error message.

decodeReadWithMsg :: Readable a => (ByteString -> e) -> Decode e ByteString a Source #

Use the Readable instance to try to decode the given value, or use the value to build an error message.

# Building Decodes from parsers

Build a Decode from a Trifecta parser

Build a Decode from an Attoparsec parser

Build a Decode from a Parsec parser

# Working with errors

onError :: Decode e s a -> (DecodeErrors e -> Decode e s a) -> Decode e s a Source #

Run a Decode, and based on its errors build a new Decode.

Build a failing DecodeValidation

Fail with UnexpectedEndOfRow

Fail with ExpectedEndOfRow. This takes the rest of the row, so that it can be displayed to the user.

unknownCategoricalValue :: e -> [[e]] -> DecodeValidation e a Source #

Fail with UnknownCategoricalValue. It takes the unknown value and the list of good categorical values.

This mostly exists to be used by the categorical function.

badParse :: e -> DecodeValidation e a Source #

Fail with BadParse with the given message. This is for when the parse step fails, and decoding does not even begin.

badDecode :: e -> DecodeValidation e a Source #

Fail with BadDecode with the given message. This is something of a generic error for when decoding a field goes wrong.

Build a DecodeValidation from an Either

validateEitherWith :: (e -> DecodeError e') -> Either e a -> DecodeValidation e' a Source #

Build a DecodeValidation from an Either, given a function to build the error.

validateMaybe :: DecodeError e -> Maybe b -> DecodeValidation e b Source #

Build a DecodeValidation from a Maybe. You have to supply an error to use in the Nothing case

# Implementation details

runDecode :: Decode e s a -> Vector s -> Ind -> (DecodeValidation e a, Last Bool, Ind) Source #

Convenience to get the underlying function out of a Decode in a useful form

buildDecode :: (Vector s -> Ind -> (DecodeValidation e a, Last Bool, Ind)) -> Decode e s a Source #

Convenient constructor for Decode that handles all the newtype noise for you.

mkDecode :: (s -> DecodeValidation e a) -> Decode e s a Source #

Build a Decode from a function.

promote :: Decode' s a -> Vector s -> DecodeValidation s a Source #

Promotes a Decode to work on a whole Record at once. This does not need to be called by the user. Instead use decode.

promote' :: (s -> e) -> Decode e s a -> Vector s -> DecodeValidation e a Source #

Promotes a Decode to work on a whole Record at once. This does not need to be called by the user. Instead use decode.

This version lets the error string and input string type pararms differ, but needs a function to convert between them.

runNamed :: NameDecode e s a -> Map s Ind -> DecodeValidation e (Decode e s a) Source #

Convenience to get the underlying function out of a NameDecode in a useful form

anonymous :: Decode e s a -> NameDecode e s a Source #

Promote a Decode to a NameDecode that doesn't look for any names

makePositional :: Ord s => Vector s -> NameDecode e s a -> DecodeValidation e (Decode e s a) Source #

Given a header and a NameDecode, resolve header names to positions and return a Decode