mime-directory 0.1 → 0.5
raw patch · 3 files changed
+220/−85 lines, 3 filesdep +base64-stringdep +containersPVP ok
version bump matches the API change (PVP)
Dependencies added: base64-string, containers
API changes (from Hackage documentation)
- Codec.MIME.ContentType.Text.Directory: data Rfc2425Types
- Codec.MIME.ContentType.Text.Directory: instance PrintValue Rfc2425Types
- Codec.MIME.ContentType.Text.Directory: instance Show Rfc2425Types
- Codec.MIME.ContentType.Text.Directory: pa_bool :: ValueParser u
- Codec.MIME.ContentType.Text.Directory: param_value :: Parameter -> ByteString
+ Codec.MIME.ContentType.Text.Directory: class PrintValue a
+ Codec.MIME.ContentType.Text.Directory: data Rfc2425Value
+ Codec.MIME.ContentType.Text.Directory: decodeValue :: [Parameter] -> ByteString -> ByteString
+ Codec.MIME.ContentType.Text.Directory: encodeValue :: [Parameter] -> ByteString -> ByteString
+ Codec.MIME.ContentType.Text.Directory: escape :: ByteString -> ByteString -> ByteString
+ Codec.MIME.ContentType.Text.Directory: fromList :: [Property u] -> Directory u
+ Codec.MIME.ContentType.Text.Directory: groupByBeginEnd :: [Property u] -> [[Property u]]
+ Codec.MIME.ContentType.Text.Directory: instance PrintValue Rfc2425Value
+ Codec.MIME.ContentType.Text.Directory: instance Show Rfc2425Value
+ Codec.MIME.ContentType.Text.Directory: lookupParameter :: ByteString -> [Parameter] -> Maybe [ByteString]
+ Codec.MIME.ContentType.Text.Directory: pa_boolean :: ValueParser u
+ Codec.MIME.ContentType.Text.Directory: param_values :: Parameter -> [ByteString]
+ Codec.MIME.ContentType.Text.Directory: parseDirectory' :: ValueParser u -> ByteString -> [Property u]
+ Codec.MIME.ContentType.Text.Directory: printDirectory' :: (PrintValue u) => [Property u] -> ByteString
+ Codec.MIME.ContentType.Text.Directory: printValue :: (PrintValue a) => a -> ByteString
+ Data.ByteString.Lazy.Char8.Caseless: concat :: [ByteString] -> ByteString
+ Data.ByteString.Lazy.Char8.Caseless: data ByteString
+ Data.ByteString.Lazy.Char8.Caseless: instance Data ByteString
+ Data.ByteString.Lazy.Char8.Caseless: instance Eq ByteString
+ Data.ByteString.Lazy.Char8.Caseless: instance IsString ByteString
+ Data.ByteString.Lazy.Char8.Caseless: instance Monoid ByteString
+ Data.ByteString.Lazy.Char8.Caseless: instance Ord ByteString
+ Data.ByteString.Lazy.Char8.Caseless: instance Show ByteString
+ Data.ByteString.Lazy.Char8.Caseless: instance Typeable ByteString
+ Data.ByteString.Lazy.Char8.Caseless: intercalate :: ByteString -> [ByteString] -> ByteString
+ Data.ByteString.Lazy.Char8.Caseless: pack :: String -> ByteString
+ Data.ByteString.Lazy.Char8.Caseless: sensitize :: ByteString -> ByteString
+ Data.ByteString.Lazy.Char8.Caseless: unpack :: ByteString -> String
+ Data.ByteString.Lazy.Char8.Caseless: unsensitize :: ByteString -> ByteString
- Codec.MIME.ContentType.Text.Directory: Param :: ByteString -> ByteString -> Parameter
+ Codec.MIME.ContentType.Text.Directory: Param :: ByteString -> [ByteString] -> Parameter
- Codec.MIME.ContentType.Text.Directory: printDirectory :: (PrintValue u) => [Property u] -> ByteString
+ Codec.MIME.ContentType.Text.Directory: printDirectory :: (PrintValue u) => Directory u -> ByteString
- Codec.MIME.ContentType.Text.Directory: type Directory u = [Property u]
+ Codec.MIME.ContentType.Text.Directory: type Directory u = [Map Type [Property u]]
Files
- Codec/MIME/ContentType/Text/Directory.hs +177/−80
- Data/ByteString/Lazy/Char8/Caseless.hs +36/−0
- mime-directory.cabal +7/−5
Codec/MIME/ContentType/Text/Directory.hs view
@@ -1,25 +1,45 @@+-- | Library for parsing and generating the text/directory mime content type.+-- This library implements all the required mechanisms in RFC 2425, which other+-- libraries may use to implement parsing and generating specific profiles,+-- such as vCard. module Codec.MIME.ContentType.Text.Directory- ( Directory, Property(..), Type(..), Parameter(..), Value(..)- , Rfc2425Types- , ValueParser+ ( -- * Types+ Directory, Property(..)+ , Type(..), Parameter(..)+ , Value(..), Rfc2425Value, PrintValue(..), ValueParser , nakedType, (@@)- , parseDirectory+ , lookupParameter+ -- * Encoding\/decoding values+ , decodeValue, encodeValue+ , escape+ -- * Parsing+ , parseDirectory, parseDirectory', fromList, groupByBeginEnd+ -- ** Value Parsers , pa_URI, pa_text, pa_date, pa_time, pa_dateTime- , pa_integer, pa_bool, pa_float, pa_textList+ , pa_integer, pa_boolean, pa_float, pa_textList+ -- ** Value parser combinators , many- , printDirectory- , printProperty) where+ -- * Printing+ , printDirectory, printDirectory', printProperty) where import Data.Time import System.Locale import Data.Char (toLower) import Data.Maybe (fromJust) import Text.Regex.PCRE.ByteString.Lazy+import qualified Codec.Binary.Base64.String as Base64 import qualified Data.ByteString.Lazy.Char8 as B+import qualified Data.ByteString.Lazy.Char8.Caseless as I+import qualified Data.Map as Map+import Control.Monad (liftM) import System.IO.Unsafe -type Directory u = [Property u]+-- | A directory is a list of groups of semantically related entities. These+-- entities are grouped together in RFC 2425 using @BEGIN ... END@ pairs.+-- Within a group properties are further grouped together by the property+-- types.+type Directory u = [Map.Map Type [Property u]] data Property u = Prop { prop_type :: Type@@ -28,33 +48,35 @@ deriving Show data Type = Type- { type_group :: Maybe B.ByteString- , type_name :: B.ByteString }- deriving Show--instance Eq Type where- x == y = let f = B.map toLower . type_name- in f x == f y+ { type_group :: Maybe I.ByteString+ , type_name :: I.ByteString }+ deriving (Eq, Ord, Show) -- | Make a property type without any grouping.-nakedType :: B.ByteString -> Type+nakedType :: I.ByteString -> Type nakedType name = Type { type_group = Nothing, type_name = name } -- | Check whether the given property is an instance of the given type.-(@@) :: Property u -> B.ByteString -> Bool+(@@) :: Property u -> I.ByteString -> Bool prop @@ name = prop_type prop == nakedType name -instance Ord Type where- compare x y = let f = B.map toLower . type_name- in compare (f x) (f y)- data Parameter = Param- { param_name :: B.ByteString- , param_value :: B.ByteString }+ { param_name :: I.ByteString+ , param_values :: [B.ByteString] } deriving Show +-- | Find the parameter values for a given parameter name.+lookupParameter :: I.ByteString -> [Parameter] -> Maybe [B.ByteString]+lookupParameter pname [] = Nothing+lookupParameter pname (p:ps)+ | param_name p == pname = Just (param_values p)+ | otherwise = lookupParameter pname ps+ type URI = B.ByteString +-- | This is sufficient to represent values whose specification is defined in+-- RFC 2425. Values with other specifications can be represented via the+-- 'IANAValue' constructor. data Value u = URI URI | Text B.ByteString | Date Day@@ -66,14 +88,14 @@ -- Decode a list of values as a list of properties, since rfc2425 -- considers them to be semantically equivalent. -- | List (Value u)- | IANAValue u -- an IANA defined type not part of rfc2425+ | IANAValue u -- ^ An IANA defined type not part of rfc2425 deriving (Eq, Show) -- | Instantiate Value with this phantom type to indicate that property types -- should be none other than those defined in rfc2425.-data Rfc2425Types+data Rfc2425Value -instance Show Rfc2425Types where+instance Show Rfc2425Value where show _ = undefined -- | The type of parsers for property values, for instance to read an integer@@ -103,59 +125,116 @@ p :: B.ByteString -- ^ Text of the regular expression. -> P B.ByteString -- ^ The matching part of the input.-p pat = P $ \s -> unsafePerformIO $ do- Right r <- compile compUngreedy execAnchored pat- Right result <- regexec r s- return $ case result of- Just (_, match, s', _) -> (match, s')- Nothing -> error $ "Parse error: " ++ show (B.unpack s)+p pat =+ let Right r = unsafePerformIO $ compile compBlank execAnchored pat+ in P $ \s -> unsafePerformIO $ do+ Right result <- regexec r s+ return $ case result of+ Just (_, match, s', _) -> (match, s')+ Nothing -> error $ "Parse error: "+ ++ take 50 (show (B.unpack s)) ++ " ..." capture :: B.ByteString -- ^ Text of the regular expression containing capturing groups. -> P [B.ByteString] -- ^ The captured subparts of the input.-capture pat = P $ \s -> unsafePerformIO $ do- Right r <- compile compUngreedy execAnchored pat- Right result <- regexec r s- return $ case result of- Just (_, _, s', captures) -> (captures, s')- Nothing -> error $ "Parse error: " ++ show (B.unpack s)+capture pat =+ let Right r = unsafePerformIO $ compile compBlank execAnchored pat+ in P $ \s -> unsafePerformIO $ do+ Right result <- regexec r s+ return $ case result of+ Just (_, _, s', captures) -> (captures, s')+ Nothing -> error $ "Parse error: "+ ++ take 50 (show (B.unpack s)) ++ " ..." +-- | Parse one character in the string.+nextChar :: P Char+nextChar = P $ \s -> (B.head s, B.tail s)++-- | Produces a map where properties are grouped together using their type as key. parseDirectory :: ValueParser u- -- ^ Given a Property Type and a list of parameters,- -- parse a string representation into a Value.+ -- ^ Given a Property Type and a list of parameters,+ -- parse a string representation into a Value.+ -> B.ByteString+ -> Directory u+parseDirectory valparse = fromList . parseDirectory' valparse++-- | An alternative version of 'parseDirectory' that produces a list+-- of properties rather than a mapping from property types to+-- properties. Note that here properties in the list are in the same+-- order as in the input string.+parseDirectory' :: ValueParser u -> B.ByteString- -> Directory u-parseDirectory valparse = concatMap (fst . unP (pa_property valparse)) . unfoldLines+ -> [Property u]+parseDirectory' valparse = concatMap (fst . unP (pa_property valparse)) . unfoldLines --- | Pa_ a string representation into a property. Note that the--- return type here is actually a list of properties, because we--- desugar properties whose values are lists into a list of--- properties, one for each element of the value list.+-- | Group properties into blocks delimited by @begin..end@ pairs.+groupByBeginEnd :: [Property u] -> [[Property u]]+groupByBeginEnd [] = []+groupByBeginEnd xs = tail $ foldr f [[]] xs+ where f p (ps:pss) | p @@ "begin" =+ [] : (p:ps) : pss+ f p (ps:pss) = (p:ps):pss++-- | Build a directory from a list of properties.+fromList :: [Property u] -> Directory u+fromList = map (Map.fromListWith (\x y -> x ++ y) . map (\p -> (prop_type p, [p])))+ . groupByBeginEnd++-- | Parse a string representation into a property. Note that the return type+-- here is actually a list of properties, because we desugar properties whose+-- values are lists into a list of properties, one for each element of the+-- value list. pa_property :: ValueParser u- -- ^ Given a Property Type and a list of parameters,- -- parse a string representation into a (list of) Value.+ -- ^ Given a Property Type and a list of parameters, parse a+ -- string representation into a (list of) Value. -> P [Property u] pa_property valparse = do- [groupt, typt, sept] <- capture "(?:((?:[[:alnum:]]|-)+).)?((?:[[:alnum:]]|-)+)(:|;)"- params <- case B.unpack sept of+ [groupt, typt, sept] <-+ capture "(?U)(?:((?:[[:alnum:]]|-)+).)?((?:[[:alnum:]]|-)+)(:|;)"+ params <- case sept of ";" -> pa_parameterList ":" -> return [] rest <- p ".*$"- let group = if B.null groupt then Nothing else Just groupt- let typ = Type { type_group = group, type_name = typt }- prop v = Prop { prop_type = typ- , prop_parameters = params- , prop_value = v }- return $ map prop $ valparse (typ, params) rest+ let group = if B.null groupt then Nothing else Just (I.unsensitize groupt)+ let typ = Type { type_group = group, type_name = I.unsensitize typt }+ mkprop v = Prop { prop_type = typ+ , prop_parameters = params+ , prop_value = v }+ return $ map mkprop $ valparse (typ, params) (decodeValue params rest) pa_parameterList :: P [Parameter]-pa_parameterList = do- [name, val, qval, sep] <- capture "((?:[[:alnum:]]|-)+)=(?:([^;:,\"]*)|\"([^\"]*)\")(,|:)"- ps <- case sep of- "," -> pa_parameterList- ":" -> return []- let value = if B.null val then qval else val- return $ Param { param_name = name, param_value = value } : ps+pa_parameterList = aux where+ paramName = capture "((?:[[:alnum:]]|-)+)="+ paramValue = capture "(?:([^,;:\"]*)|\"([^\"]*)\")(,?)"+ paramValues = do+ [val,qval,sep] <- paramValue+ vs <- case sep of+ "," -> paramValues+ _ -> return []+ return $ if B.null qval then val:vs else qval:vs+ aux = do [name] <- paramName+ vs <- paramValues+ sep <- nextChar+ ps <- case sep of+ ';' -> aux+ ':' -> return []+ return $ Param { param_name = I.unsensitize name, param_values = vs } : ps +-- | Properties may indicate an encoding, so this decodes the value+-- if need be before parsing.+decodeValue = codec Base64.decode++-- | Properties may indicate an encoding, so this encodes the value if need be+-- after printing.+encodeValue = codec Base64.encode++codec :: (String -> String) -> [Parameter] -> B.ByteString -> B.ByteString+codec f params input =+ case lookupParameter "encoding" params of+ Nothing -> input+ Just ["b"] -> B.pack $ f $ B.unpack input+ Just ["B"] -> B.pack $ f $ B.unpack input+ _ -> error "Unknown encoding."+ -- A few canned parsers for value types defined in rfc2425 pa_URI :: ValueParser u@@ -182,10 +261,10 @@ pa_integer :: ValueParser u pa_integer _ = (:[]) . Integer . fst . fromJust . B.readInteger -pa_bool :: ValueParser u-pa_bool _ "TRUE" = [Boolean True]-pa_bool _ "FALSE" = [Boolean False]-pa_bool _ _ = error "Not a valid boolean."+pa_boolean :: ValueParser u+pa_boolean _ "TRUE" = [Boolean True]+pa_boolean _ "FALSE" = [Boolean False]+pa_boolean _ _ = error "Not a valid boolean." pa_float :: ValueParser u pa_float _ = (:[]) . Float . read . B.unpack@@ -195,8 +274,10 @@ pa_textList _ s = map (Text . B.pack . B.unpack) $ B.foldr f [B.empty] s where f ',' (xs:xss) = B.empty : xs : xss f '\\' ("":xs:xss) = B.cons ',' xs : xss- f '\\' (xs:xss) | Just ('n',_) <- B.uncons xs = B.cons '\n' xs : xss- f '\\' (xs:xss) | Just ('N',_) <- B.uncons xs = B.cons '\n' xs : xss+ f '\\' (xs:xss) | Just ('n',_) <- B.uncons xs =+ B.append "\r\n" xs : xss+ f '\\' (xs:xss) | Just ('N',_) <- B.uncons xs =+ B.append "\r\n" xs : xss f '\\' (xs:xss) | Just ('\\',_) <- B.uncons xs = B.cons '\\' xs : xss f x (xs:xss) = B.cons x xs : xss @@ -214,13 +295,23 @@ showBS :: Show a => a -> B.ByteString showBS = B.pack . show +-- | Escape any occurrence of the characters given as first argument with a+-- backslash. Newlines are always replaced by the two character sequence+-- @"\\n"@. The backslash character is always escaped.+escape :: B.ByteString -> B.ByteString -> B.ByteString+escape chars = B.foldr f "" where+ f '\r' xs | Just ('\n', xs') <- B.uncons xs = B.append "\\n" xs'+ | otherwise = error "CR not followed by LF."+ f x xs | x `B.elem` B.cons '\\' chars = B.cons '\\' (B.cons x xs)+ | otherwise = B.cons x xs+ -- Pretty printing of values class PrintValue a where printValue :: a -> B.ByteString instance PrintValue u => PrintValue (Value u) where printValue (URI v) = showBS v- printValue (Text v) = v+ printValue (Text v) = escape "," $ v printValue (Date v) = showBS v printValue (Time v) = showBS v printValue (DateTime v) = showBS v@@ -230,25 +321,31 @@ printValue (Float v) = showBS v printValue (IANAValue v) = printValue v -instance PrintValue Rfc2425Types where+instance PrintValue Rfc2425Value where printValue _ = error "No other types in RFC 2425." -printDirectory :: PrintValue u => [Property u] -> B.ByteString-printDirectory props = B.intercalate "\r\n" $ map printProperty props+printDirectory :: PrintValue u => Directory u -> B.ByteString+printDirectory = printDirectory' . concat . concat . map Map.elems +printDirectory' :: PrintValue u => [Property u] -> B.ByteString+printDirectory' props = B.intercalate "\r\n" $ map printProperty props+ printProperty :: PrintValue u => Property u -> B.ByteString printProperty prop =- if null (prop_parameters prop)+ if null params then B.concat [ printType (prop_type prop), ":"- , printValue (prop_value prop) ]+ , encodeValue params $ printValue $ prop_value prop ] else B.concat [ printType (prop_type prop), ";" , B.concat $ map printParameter $ prop_parameters prop, ":"- , printValue (prop_value prop) ]+ , printValue $ prop_value prop ]+ where params = prop_parameters prop printType :: Type -> B.ByteString-printType typ = case type_group typ of- Just group -> B.concat [group, ".", type_name typ]- Nothing -> type_name typ+printType typ =+ I.sensitize $ case type_group typ of+ Just group -> I.concat [group, ".", type_name typ]+ Nothing -> type_name typ printParameter :: Parameter -> B.ByteString-printParameter param = B.concat [param_name param, "=", param_value param]+printParameter param = B.concat [I.sensitize $ param_name param, "="+ , B.intercalate "," $ param_values param]
+ Data/ByteString/Lazy/Char8/Caseless.hs view
@@ -0,0 +1,36 @@+-- | A variant of ByteString where strings differing in the case of some of+-- its characters are identified.+module Data.ByteString.Lazy.Char8.Caseless+ (ByteString, sensitize, unsensitize, concat, intercalate, pack, unpack) where++import qualified Data.ByteString.Lazy.Char8 as B+import Data.Char (toLower)+import GHC.Exts (IsString)+import Data.Monoid (Monoid)+import Data.Generics.Basics (Data, Typeable)+import Prelude hiding (concat)++-- | Wrapper for case insensitive strings.+newtype ByteString = I B.ByteString+ deriving (Data, Eq, IsString, Ord, Show, Monoid, Typeable)++-- | Inject a bytestring into a case insensitive bytestring. Note that the case+-- of all letters in the ByteString is folded to lower case.+unsensitize :: B.ByteString -> ByteString+unsensitize = I . B.map toLower++-- | Project back to a regular bytestring.+sensitize :: ByteString -> B.ByteString+sensitize (I str) = str++concat :: [ByteString] -> ByteString+concat = I . B.concat . map sensitize++intercalate :: ByteString -> [ByteString] -> ByteString+intercalate sep = I . B.intercalate (sensitize sep) . map sensitize++pack :: String -> ByteString+pack = unsensitize . B.pack++unpack :: ByteString -> String+unpack = B.unpack . sensitize
mime-directory.cabal view
@@ -1,14 +1,14 @@ name: mime-directory-version: 0.1-author: Mathieu Boespflug-maintainer: Mathieu Boespflug <mboes@tweag.net>+version: 0.5+author: Mathieu Boespflug+maintainer: Mathieu Boespflug <mboes@tweag.net> homepage: http://code.haskell.org/~mboes/mime-directory.git synopsis: A library for parsing/printing the text/directory mime type. description: A library for parsing\/printing the text\/directory mime type. category: Text license: LGPL-license-file: LICENSE+license-file: LICENSE copyright: (c) 2008. Mathieu Boespflug <mboes@tweag.net> cabal-version: >= 1.2.0 build-type: Simple@@ -16,6 +16,8 @@ library build-depends: base >= 3, bytestring >= 0.9, regex-pcre >= 0.94,- old-locale, time >= 1.1+ old-locale, time >= 1.1, containers, base64-string extensions: OverloadedStrings, EmptyDataDecls, PatternGuards+ GeneralizedNewtypeDeriving, DeriveDataTypeable exposed-modules: Codec.MIME.ContentType.Text.Directory+ Data.ByteString.Lazy.Char8.Caseless