packages feed

descriptive 0.0.2 → 0.1.0

raw patch · 10 files changed

+413/−133 lines, 10 filesdep +HUnitdep +descriptivedep +hspecdep ~basePVP ok

version bump matches the API change (PVP)

Dependencies added: HUnit, descriptive, hspec

Dependency ranges changed: base

API changes (from Hackage documentation)

- Descriptive: instance Monoid a => Monoid (Either (Description d) a)
+ Descriptive: Continued :: e -> Result e a
+ Descriptive: Failed :: e -> Result e a
+ Descriptive: Succeeded :: a -> Result e a
+ Descriptive: data Result e a
+ Descriptive: instance (Eq e, Eq a) => Eq (Result e a)
+ Descriptive: instance (Ord e, Ord a) => Ord (Result e a)
+ Descriptive: instance (Show e, Show a) => Show (Result e a)
+ Descriptive: instance Bifunctor Result
+ Descriptive: instance Eq Bound
+ Descriptive: instance Eq a => Eq (Description a)
+ Descriptive: instance Monoid a => Monoid (Result (Description d) a)
+ Descriptive.Form: instance Eq Form
+ Descriptive.Formlet: instance Eq Formlet
+ Descriptive.Formlet: instance Eq FormletState
+ Descriptive.JSON: instance Eq Doc
+ Descriptive.Options: instance Eq Option
- Descriptive: Consumer :: (s -> (Description d, s)) -> (s -> (Either (Description d) a, s)) -> Consumer s d a
+ Descriptive: Consumer :: (s -> (Description d, s)) -> (s -> (Result (Description d) a, s)) -> Consumer s d a
- Descriptive: consume :: Consumer s d a -> s -> Either (Description d) a
+ Descriptive: consume :: Consumer s d a -> s -> Result (Description d) a
- Descriptive: consumer :: (s -> (Description d, s)) -> (s -> (Either (Description d) a, s)) -> Consumer s d a
+ Descriptive: consumer :: (s -> (Description d, s)) -> (s -> (Result (Description d) a, s)) -> Consumer s d a
- Descriptive: consumerParse :: Consumer s d a -> s -> (Either (Description d) a, s)
+ Descriptive: consumerParse :: Consumer s d a -> s -> (Result (Description d) a, s)
- Descriptive: runConsumer :: Consumer s d a -> s -> (Either (Description d) a, s)
+ Descriptive: runConsumer :: Consumer s d a -> s -> (Result (Description d) a, s)
- Descriptive: wrap :: (s -> (t -> (Description d, t)) -> (Description d, s)) -> (s -> (t -> (Description d, t)) -> (t -> (Either (Description d) a, t)) -> (Either (Description d) b, s)) -> Consumer t d a -> Consumer s d b
+ Descriptive: wrap :: (s -> (t -> (Description d, t)) -> (Description d, s)) -> (s -> (t -> (Description d, t)) -> (t -> (Result (Description d) a, t)) -> (Result (Description d) b, s)) -> Consumer t d a -> Consumer s d b

Files

+ CHANGELOG view
@@ -0,0 +1,3 @@+0.1.0:++* Change to Result type which supports Continued constructor.
README.md view
@@ -24,7 +24,7 @@  ``` haskell consumer :: (s -> (Description d,s))-         -> (s -> (Either (Description d) a,s))+         -> (s -> (Result (Description d) a,s))          -> Consumer s d a ``` @@ -37,8 +37,8 @@ To use a consumer or describe what it does, these are used:  ``` haskell-consume :: Consumer s d a -> s -> (Either (Description d) a,s)-describe :: Consumer s d a -> s -> (Description d,s)+consume :: Consumer s d a -> s -> Result (Description d) a+describe :: Consumer s d a -> s -> Description d ```  A description is like this:@@ -61,9 +61,9 @@ parser, a text parsing grammar, etc. For example:  ``` haskell-describeParser :: Consumer [Char] Text Demo -> Text-describeForm :: Consumer (Map Text Text) (Html ()) Login -> Html ()-describeArgs :: Consumer [Text] CmdArgs MyApp -> CmdArgs+describeParser :: Description Text -> Text+describeForm :: Description (Html ()) -> Html ()+describeArgs :: Description CmdArgs -> Text ```  See below for some examples of this library.@@ -78,11 +78,11 @@      (Sequence [Unit "a",Sequence [Unit "b",Sequence [Unit "c",Sequence []]]]) ,"") λ> consume (many (char 'k') <> string "abc") "kkkabc"-(Right "kkkabc","")+(Succeeded "kkkabc") λ> consume (many (char 'k') <> string "abc") "kkkab"-(Left (Unit "a character"),"")+(Failed (Unit "a character")) λ> consume (many (char 'k') <> string "abc") "kkkabj"-(Left (Unit "c"),"")+(Failed (Unit "c")) ```  ## Validating forms with named inputs@@ -97,32 +97,35 @@             input "username" <*>             input "password")            (M.fromList [("username","chrisdone"),("password","god")])-(Right ("chrisdone","god")+(Succeeded ("chrisdone","god") ,fromList [("password","god"),("username","chrisdone")]) ```  Conditions on two inputs:  ``` haskell-login = validate "confirmed password (entered the same twice)"-                 (\(x,y) ->-                    if x == y-                       then Just y-                       else Nothing)-                 ((,) <$>-                  input "password" <*>-                  input "password2")+login =+  validate "confirmed password (entered the same twice)"+           (\(x,y) ->+              if x == y+                 then Just y+                 else Nothing)+           ((,) <$>+            input "password" <*>+            input "password2") <|>+  input "token" ```  ``` haskell-λ> describe login mempty-(Wrap (Constraint "confirmed password (entered the same twice)")-      (And (Unit (Input "password"))-           (Unit (Input "password2")))-,fromList [])- λ> consume login (M.fromList [("password2","gob"),("password","gob")])-(Right "gob",fromList [("password","gob"),("password2","gob")])+Succeeded "gob"+λ> consume login (M.fromList [("password2","gob"),("password","go")])+Continued (And (Wrap (Constraint "confirmed password (entered the same twice)")+                     (And (Unit (Input "password"))+                          (Unit (Input "password2"))))+               (Unit (Input "token")))+λ> consume login (M.fromList [("password2","gob"),("password","go"),("token","woot")])+Succeeded "woot" ```  ## Validating forms with auto-generated input indexes@@ -132,22 +135,15 @@ ``` haskell λ> describe ((,) <$> indexed <*> indexed)             (FormletState mempty 0)-(And (Unit (Index 0))-     (Unit (Index 1))-,FormletState {formletMap = fromList []+And (Unit (Index 0))+    (Unit (Index 1))               ,formletIndex = 2}) λ> consume ((,) <$> indexed <*> indexed)            (FormletState (M.fromList [(0,"chrisdone"),(1,"god")]) 0)-(Right ("chrisdone","god")-,FormletState {formletMap =-                 fromList [(0,"chrisdone"),(1,"god")]-              ,formletIndex = 2})+Succeeded ("chrisdone","god") λ> consume ((,) <$> indexed <*> indexed)            (FormletState (M.fromList [(0,"chrisdone")]) 0)-(Left (Unit (Index 1))-,FormletState {formletMap =-                 fromList [(0,"chrisdone")]-              ,formletIndex = 2})+Failed (Unit (Index 1)) ```  ## Parsing command-line options@@ -165,17 +161,15 @@  ``` haskell λ> describe server []-(And (And (And (Unit (Constant "start"))+And (And (And (Unit (Constant "start"))                (Unit (AnyString "SERVER_NAME")))           (Unit (Flag "dev" "Enable dev mode?")))      (Unit (Arg "port" "Port to listen on"))-,[])-λ> consume server ["start","any","--port","1234","-fdev"]-(Right ("start","any",True,"1234"),[])+λ> consume server ["start","any","--port","1234","--dev"]+Succeeded ("start","any",True,"1234") λ> consume server ["start","any","--port","1234"]-(Right ("start","any",False,"1234"),[])-λ> consume server ["start","any"]-(Left (Unit (Arg "port" "Port to listen on")),[])+Succeeded ("start","any",False,"1234")+λ> ```  ## Self-documenting JSON parser@@ -219,7 +213,7 @@  ``` haskell λ> describe submission (toJSON ())-(Wrap (Struct "Submission")+Wrap (Struct "Submission")       (And (And (And (Wrap (Key "token")                            (Unit (Integer "Submission token; see the API docs")))                      (Wrap (Key "title")@@ -228,26 +222,17 @@                       (Unit (Text "Submission comment"))))            (Wrap (Key "subreddit")                  (Unit (Integer "The ID of the subreddit"))))-,Array (fromList [])) + λ> consume submission sample-(Right (Submission {submissionToken = 123+Succeeded (Submission {submissionToken = 123                    ,submissionTitle = "Some title"                    ,submissionComment = "This is good"                    ,submissionSubreddit = 234214})-,Object (fromList [("token",Number 123.0)-                  ,("subreddit",Number 234214.0)-                  ,("title",String "Some title")-                  ,("comment",String "This is good")]))- λ> consume submission badsample-(Left (Wrap (Struct "Submission")+Failed (Wrap (Struct "Submission")             (Wrap (Key "comment")                   (Unit (Text "Submission comment"))))-,Object (fromList [("token",Number 123.0)-                  ,("subreddit",Number 234214.0)-                  ,("title",String "Some title")-                  ,("comment",Number 123.0)])) ```  The bad sample yields an informative message that:@@ -266,7 +251,7 @@  ``` haskell parsed complV "<distrans-verb> a <noun> <prep> a <noun>" ==-Right (ComplVDisV (DistransitiveV "<distrans-verb>")+Succeeded (ComplVDisV (DistransitiveV "<distrans-verb>")                   (ComplNP (NPCoordUnmarked (UnmarkedNPCoord anoun Nothing)))                   (ComplPP (PP (Preposition "<prep>")                                (NPCoordUnmarked (UnmarkedNPCoord anoun Nothing)))))
descriptive.cabal view
@@ -1,5 +1,5 @@ name:                descriptive-version:             0.0.2+version:             0.1.0 synopsis:            Self-describing consumers/parsers; forms, cmd-line args, JSON, etc. description:         Self-describing consumers/parsers. See the README.md for more information. It is currently EXPERIMENTAL. stability:           Experimental@@ -12,7 +12,7 @@ category:            Parsing build-type:          Simple cabal-version:       >=1.8-extra-source-files:  README.md+extra-source-files:  README.md, CHANGELOG  library   hs-source-dirs:    src/@@ -25,3 +25,12 @@                      Descriptive.JSON   build-depends:     base >= 4 && <5,                      transformers, containers, text, mtl, aeson, bifunctors++test-suite test+    type: exitcode-stdio-1.0+    main-is: Main.hs+    hs-source-dirs: src/test+    build-depends: base,+                   descriptive, transformers, containers, text, mtl, aeson, bifunctors,+                   HUnit,+                   hspec
src/Descriptive.hs view
@@ -18,6 +18,7 @@   ,Description(..)   ,Bound(..)   ,Consumer(..)+  ,Result(..)   -- * Combinators   ,consumer   ,wrap@@ -25,7 +26,7 @@   where  import Control.Applicative-import Control.Arrow+import Data.Bifunctor import Data.Function import Data.Monoid @@ -35,7 +36,7 @@ -- | Run a consumer. consume :: Consumer s d a -- ^ The consumer to run.         -> s -- ^ Initial state.-        -> Either (Description d) a+        -> Result (Description d) a consume (Consumer _ m) = fst . m  -- | Describe a consumer.@@ -48,7 +49,7 @@ -- | Run a consumer. runConsumer :: Consumer s d a -- ^ The consumer to run.             -> s -- ^ Initial state.-            -> (Either (Description d) a,s)+            -> (Result (Description d) a,s) runConsumer (Consumer _ m) = m  -- | Describe a consumer.@@ -70,7 +71,7 @@   | Sequence [Description a]   | Wrap a (Description a)   | None-  deriving (Show)+  deriving (Show,Eq)  instance Monoid (Description d) where   mempty = None@@ -80,26 +81,46 @@ data Bound   = NaturalBound !Integer   | UnlimitedBound-  deriving (Show)+  deriving (Show,Eq)  -- | A consumer. data Consumer s d a =   Consumer {consumerDesc :: s -> (Description d,s)-           ,consumerParse :: s -> (Either (Description d) a,s)}+           ,consumerParse :: s -> (Result (Description d) a,s)} +-- | Some result.+data Result e a+  = Failed e    -- ^ The whole process failed.+  | Succeeded a -- ^ The whole process succeeded.+  | Continued e -- ^ There were errors but we continued to collect all the errors.+  deriving (Show,Eq,Ord)++instance Bifunctor Result where+  second f r =+    case r of+      Succeeded a -> Succeeded (f a)+      Failed e -> Failed e+      Continued e -> Continued e+  first f r =+    case r of+      Succeeded a -> Succeeded a+      Failed e -> Failed (f e)+      Continued e -> Continued (f e)+ instance Functor (Consumer s d) where   fmap f (Consumer d p) =     Consumer d              (\s ->                 case p s of-                  (Left e,s') -> (Left e,s')-                  (Right a,s') ->-                    (Right (f a),s'))+                  (Failed e,s') -> (Failed e,s')+                  (Continued e,s') -> (Continued e,s')+                  (Succeeded a,s') ->+                    (Succeeded (f a),s'))  instance Applicative (Consumer s d) where   pure a =     consumer (\s -> (mempty,s))-             (\s -> (Right a,s))+             (\s -> (Succeeded a,s))   Consumer d pf <*> Consumer d' p' =     consumer (\s ->                 let !(e,s') = d s@@ -109,17 +130,28 @@                 let !(mf,s') = pf s                     !(ma,s'') = p' s'                 in case mf of-                     Left e -> (Left e,s')-                     Right f ->+                     Failed e -> (Failed e,s')+                     Continued e ->                        case ma of-                         Left e -> (Left e,s'')-                         Right a ->-                           (Right (f a),s''))+                         Failed e' ->+                           (Failed e',s'')+                         Continued e' ->+                           (Continued (e <> e'),s'')+                         Succeeded _ ->+                           (Continued e,s'')+                     Succeeded f ->+                       case ma of+                         Continued e ->+                           (Continued e,s'')+                         Failed e ->+                           (Failed e,s'')+                         Succeeded a ->+                           (Succeeded (f a),s''))  instance Alternative (Consumer s d) where   empty =     Consumer (\s -> (mempty,s))-             (\s -> (Left mempty,s))+             (\s -> (Failed mempty,s))   a <|> b =     Consumer (\s ->                 let !(d1,s') = consumerDesc a s@@ -127,13 +159,24 @@                 in (Or d1 d2,s''))              (\s ->                 case consumerParse a s of-                  (Left e1,_) ->+                  (Continued e1,s') ->+                    case consumerParse b s' of+                      (Failed e2,s'') ->+                        (Failed e2,s'')+                      (Continued e2,s'') ->+                        (Continued (e1 <> e2),s'')+                      (Succeeded a',s'') ->+                        (Succeeded a',s'')+                  (Failed e1,_) ->                     case consumerParse b s of-                      (Left e2,s') ->-                        (Left (Or e1 e2),s')-                      (Right a2,s') ->-                        (Right a2,s')-                  (Right a1,s') -> (Right a1,s'))+                      (Failed e2,s') ->+                        (Failed (Or e1 e2),s')+                      (Continued e2,s'') ->+                        (Continued e2,s'')+                      (Succeeded a2,s') ->+                        (Succeeded a2,s')+                  (Succeeded a1,s') ->+                    (Succeeded a1,s'))   some = sequenceHelper 1   many = sequenceHelper 0 @@ -145,30 +188,49 @@        (\s _ r ->           fix (\go !i s' as ->                  case r s' of-                   (Right a,s'') ->+                   (Succeeded a,s'') ->                      go (i + 1)                         s''                         (a : as)-                   (Left e,s'')+                   (Continued e,s'') ->+                     fix (\continue e' s''' ->+                            case r s''' of+                              (Continued e'',s'''') ->+                                continue (e' <> e'') s''''+                              (Succeeded{},s'''') ->+                                continue e' s''''+                              (Failed e'',s'''')+                                | i >= minb ->+                                  (Continued e',s''')+                                | otherwise ->+                                  (Failed (redescribe e''),s''''))+                         e+                         s''+                   (Failed e,s'')                      | i >= minb ->-                       (Right (reverse as),s')+                       (Succeeded (reverse as),s')                      | otherwise ->-                       (Left (redescribe e),s''))+                       (Failed (redescribe e),s''))               0               s               [])-  where redescribe =-          Bounded minb UnlimitedBound+  where redescribe = Bounded minb UnlimitedBound -instance (Monoid a) => Monoid (Either (Description d) a) where-  mempty = Right mempty+instance (Monoid a) => Monoid (Result (Description d) a) where+  mempty = Succeeded mempty   mappend x y =     case x of-      Left e -> Left e-      Right a ->+      Failed e -> Failed e+      Continued e ->         case y of-          Left e -> Left e-          Right b -> Right (a <> b)+          Failed e' -> Failed e'+          Continued e' -> Continued (e <> e')+          Succeeded _ -> Continued e+      Succeeded a ->+        case y of+          Failed e -> Failed e+          Continued e -> Continued e+          Succeeded b -> Succeeded (a <> b)  instance (Monoid a) => Monoid (Consumer s d a) where   mempty = Consumer (\s -> (mempty,s)) (\s -> (mempty,s))@@ -179,14 +241,14 @@  -- | Make a consumer. consumer :: (s -> (Description d,s)) -- ^ Produce description based on the state.-         -> (s -> (Either (Description d) a,s)) -- ^ Parse the state and maybe transform it if desired.+         -> (s -> (Result (Description d) a,s)) -- ^ Parse the state and maybe transform it if desired.          -> Consumer s d a consumer d p =   Consumer d p  -- | Wrap a consumer with another consumer. wrap :: (s -> (t -> (Description d,t)) -> (Description d,s)) -- ^ Transformer the description.-     -> (s -> (t -> (Description d,t)) -> (t -> (Either (Description d) a,t)) -> (Either (Description d) b,s)) -- ^ Transform the parser. Can re-run the parser if desired.+     -> (s -> (t -> (Description d,t)) -> (t -> (Result (Description d) a,t)) -> (Result (Description d) b,s)) -- ^ Transform the parser. Can re-run the parser if desired.      -> Consumer t d a -- ^ The consumer to transform.      -> Consumer s d b -- ^ A new consumer with a potentially new state type. wrap redescribe reparse (Consumer d p) =
src/Descriptive/Char.hs view
@@ -17,8 +17,8 @@   consumer (d,)            (\s ->               case s of-                (c':cs') -> (Right c',cs')-                [] -> (Left d,s))+                (c':cs') -> (Succeeded c',cs')+                [] -> (Failed d,s))   where d = Unit "a character"  -- | A character consumer.@@ -28,10 +28,11 @@         (d,))        (\s _ p ->           case p s of-            (Left e,s') -> (Left e,s')-            (Right c',s')-              | c' == c -> (Right c,s')-              | otherwise -> (Left d,s'))+            (Failed e,s') -> (Failed e,s')+            (Continued e,s') -> (Continued e,s')+            (Succeeded c',s')+              | c' == c -> (Succeeded c,s')+              | otherwise -> (Failed d,s'))        anyChar   where d = Unit (T.singleton c) 
src/Descriptive/Form.hs view
@@ -23,7 +23,7 @@ data Form   = Input !Text   | Constraint !Text-  deriving (Show)+  deriving (Show,Eq)  -- | Consume any input value. input :: Text -> Consumer (Map Text Text) Form Text@@ -31,8 +31,8 @@   consumer (d,)            (\s ->               (case M.lookup name s of-                 Nothing -> Left d-                 Just a -> Right a+                 Nothing -> Continued d+                 Just a -> Succeeded a               ,s))   where d = Unit (Input name) @@ -45,10 +45,12 @@   wrap (\s d -> redescribe (d s))        (\s d p ->           case p s of-            (Left e,s') -> (Left e,s')-            (Right a,s') ->+            (Failed e,s') -> (Failed e,s')+            (Continued e,s') -> (Continued (wrapper e),s')+            (Succeeded a,s') ->               case check a of                 Nothing ->-                  (Left (fst (redescribe (d s))),s')-                Just a' -> (Right a',s'))-  where redescribe = first (Wrap (Constraint d'))+                  (Continued (fst (redescribe (d s))),s')+                Just a' -> (Succeeded a',s'))+  where redescribe = first wrapper+        wrapper = Wrap (Constraint d')
src/Descriptive/Formlet.hs view
@@ -21,13 +21,13 @@ data Formlet   = Index !Integer   | Constrained !Text-  deriving (Show)+  deriving (Show,Eq)  -- | State used when running a formlet. data FormletState =   FormletState {formletMap :: (Map Integer Text)                ,formletIndex :: !Integer}-  deriving (Show)+  deriving (Show,Eq)  -- | Consume any character. indexed :: Consumer FormletState Formlet Text@@ -35,8 +35,8 @@   consumer (\(nextIndex -> (i,s)) -> (d i,s))            (\(nextIndex -> (i,s)) ->               case M.lookup i (formletMap s) of-                Nothing -> (Left (d i),s)-                Just a -> (Right a,s))+                Nothing -> (Failed (d i),s)+                Just a -> (Succeeded a,s))   where d = Unit . Index         nextIndex s =           (formletIndex s,s {formletIndex = formletIndex s + 1})
src/Descriptive/JSON.hs view
@@ -32,7 +32,7 @@   | Text !Text   | Struct !Text   | Key !Text-  deriving (Show)+  deriving (Show,Eq)  -- | Consume an object. obj :: Text -- ^ Description of what the object is.@@ -42,11 +42,12 @@   wrap (\v d -> (Wrap doc (fst (d mempty)),v))        (\v _ p ->           case fromJSON v of-            Error{} -> (Left (Unit doc),v)+            Error{} -> (Failed (Unit doc),v)             Success o ->               (case p o of-                 (Left e,_) -> Left (Wrap doc e)-                 (Right a,_) -> Right a+                 (Failed e,_) -> Failed (Wrap doc e)+                 (Continued e,_) -> Failed (Wrap doc e)+                 (Succeeded a,_) -> Succeeded a               ,toJSON o))   where doc = Struct desc @@ -62,7 +63,7 @@        (\o _ p ->           case parseMaybe (const (o .: k))                           () of-            Nothing -> (Left (Unit doc),o)+            Nothing -> (Failed (Unit doc),o)             Just (v :: Value) ->               first (bimap (Wrap doc) id)                     (second (const o)@@ -76,8 +77,8 @@   consumer (d,)            (\s ->               case fromJSON s of-                Error{} -> (Left d,s)-                Success a -> (Right a,s))+                Error{} -> (Failed d,s)+                Success a -> (Succeeded a,s))   where d = Unit (Text doc)  -- | Consume an integer.@@ -87,6 +88,6 @@   consumer (d,)            (\s ->               case fromJSON s of-                Error{} -> (Left d,s)-                Success a -> (Right a,s))+                Error{} -> (Failed d,s)+                Success a -> (Succeeded a,s))   where d = Unit (Integer doc)
src/Descriptive/Options.hs view
@@ -33,7 +33,7 @@   | Flag !Text !Text   | Arg !Text !Text   | Prefix !Text !Text-  deriving (Show)+  deriving (Show,Eq)  -- | Consume one argument from the argument list. anyString :: Text -> Consumer [Text] Option Text@@ -41,8 +41,8 @@   consumer (d,)            (\s ->               case s of-                [] -> (Left d,s)-                (x:s') -> (Right x,s'))+                [] -> (Failed d,s)+                (x:s') -> (Succeeded x,s'))   where d = Unit (AnyString help)  -- | Consume one argument from the argument list.@@ -52,8 +52,8 @@            (\s ->               case s of                 (x:s') | x == x' ->-                  (Right x,s')-                _ -> (Left d,s))+                  (Succeeded x,s')+                _ -> (Failed d,s))   where d = Unit (Constant x')  -- | Find a short boolean flag.@@ -61,7 +61,7 @@ flag name help =   consumer (d,)            (\s ->-              (Right (elem ("--" <> name) s),filter (/= "--" <> name) s))+              (Succeeded (elem ("--" <> name) s),filter (/= "--" <> name) s))   where d = Unit (Flag name help)  -- | Find an argument prefixed by -X.@@ -70,8 +70,8 @@   consumer (d,)            (\s ->               case find (T.isPrefixOf ("-" <> pref)) s of-                Nothing -> (Left d,s)-                Just a -> (Right (T.drop (T.length pref + 1) a), delete a s))+                Nothing -> (Failed d,s)+                Just a -> (Succeeded (T.drop (T.length pref + 1) a), delete a s))   where d = Unit (Prefix pref help)  -- | Find a named argument.@@ -82,12 +82,12 @@               let indexedArgs =                     zip [0 :: Integer ..] s               in case find ((== "--" <> name) . snd) indexedArgs of-                   Nothing -> (Left d,s)+                   Nothing -> (Failed d,s)                    Just (i,_) ->                      case lookup (i + 1) indexedArgs of-                       Nothing -> (Left d,s)+                       Nothing -> (Failed d,s)                        Just text ->-                         (Right text+                         (Succeeded text                          ,map snd (filter (\(j,_) -> j /= i && j /= i + 1) indexedArgs)))   where d = Unit (Arg name help) 
+ src/test/Main.hs view
@@ -0,0 +1,217 @@+{-# LANGUAGE FlexibleContexts #-}+{-# LANGUAGE ExtendedDefaultRules #-}+{-# LANGUAGE OverloadedStrings #-}+{-# LANGUAGE NoMonomorphismRestriction #-}++-- | Test suite for ACE.++module Main where++import           Control.Applicative+import           Data.Aeson (Value(..),toJSON,object,(.=))+import qualified Data.Map.Strict as M+import           Data.Monoid+import           Data.Text (Text)+import           Descriptive+import qualified Descriptive.Char as Char+import qualified Descriptive.Form as Form+import qualified Descriptive.Formlet as Formlet+import qualified Descriptive.JSON as JSON+import qualified Descriptive.Options as Options+import           Test.HUnit+import           Test.Hspec (Spec,it,hspec)+import qualified Test.Hspec as Hspec++-- | Test suite entry point, returns exit failure if any test fails.+main :: IO ()+main = hspec spec++-- | Test suite.+spec :: Spec+spec = do+  Hspec.describe "Descriptive.Char" characters+  Hspec.describe "Descriptive.Form" form+  Hspec.describe "Descriptive.Formlet" formlet+  Hspec.describe "Descriptive.JSON" json+  Hspec.describe "Descriptive.Options" options++--------------------------------------------------------------------------------+-- Character parsing tests++characters :: Spec+characters =+  do it "describe"+        (describe (many (Char.char 'k') <>+                   Char.string "abc")+                  mempty ==+         And (Bounded 0 UnlimitedBound (Unit "k"))+             (Sequence [Unit "a"+                       ,Sequence [Unit "b",Sequence [Unit "c",Sequence []]]]))+     it "consume"+        (consume (many (Char.char 'k') <>+                  Char.string "abc")+                 "kkkabc" ==+         (Succeeded "kkkabc"))+     it "fail generic"+        (consume (many (Char.char 'k') <>+                  Char.string "abc")+                 "kkkab" ==+         (Failed (Unit "a character")))+     it "fail specific"+        (consume (many (Char.char 'k') <>+                  Char.string "abc")+                 "kkkabj" ==+         (Failed (Unit "c")))++--------------------------------------------------------------------------------+-- Form tests++form :: Spec+form =+  do it "basic describe login"+        (describe ((,) <$>+                   Form.input "username" <*>+                   Form.input "password")+                  mempty ==+         (And (Unit (Form.Input "username"))+              (Unit (Form.Input "password"))))+     it "basic describe login"+        (consume ((,) <$>+                  Form.input "username" <*>+                  Form.input "password")+                 (M.fromList [("username","chrisdone"),("password","god")]) ==+         Succeeded ("chrisdone","god"))+     it "succeeding login"+        (consume login (M.fromList [("password2","gob"),("password","gob")]) ==+         Succeeded "gob")+     it "continuing login"+        (consume login (M.fromList [("password2","gob"),("password","go")]) ==+         Continued (And (Wrap (Form.Constraint "confirmed password (entered the same twice)")+                              (And (Unit (Form.Input "password"))+                                   (Unit (Form.Input "password2"))))+                        (Unit (Form.Input "token"))))+     it "succeeding disjunction"+        (consume login+                 (M.fromList+                    [("password2","gob"),("password","go"),("token","woot")]) ==+         Succeeded "woot")+  where login =+          Form.validate+            "confirmed password (entered the same twice)"+            (\(x,y) ->+               if x == y+                  then Just y+                  else Nothing)+            ((,) <$>+             Form.input "password" <*>+             Form.input "password2") <|>+          Form.input "token"++--------------------------------------------------------------------------------+-- Formlet tests++formlet :: Spec+formlet =+  do it "basic formlet"+        (describe ((,) <$> Formlet.indexed <*> Formlet.indexed)+                  (Formlet.FormletState mempty 0) ==+         And (Unit (Formlet.Index 0))+             (Unit (Formlet.Index 1)))+     it "succeeding formlet"+        (consume ((,) <$> Formlet.indexed <*> Formlet.indexed)+                 (Formlet.FormletState (M.fromList [(0,"chrisdone"),(1,"god")])+                                       0) ==+         Succeeded ("chrisdone","god"))+     it "succeeding formlet"+        (consume ((,) <$> Formlet.indexed <*> Formlet.indexed)+                 (Formlet.FormletState (M.fromList [(0,"chrisdone")])+                                       0) ==+         Failed (Unit (Formlet.Index 1)))++--------------------------------------------------------------------------------+-- Options tests++options :: Spec+options =+  do it "describe options"+        (describe server [] ==+         And (And (And (Unit (Options.Constant "start"))+                       (Unit (Options.AnyString "SERVER_NAME")))+                  (Unit (Options.Flag "dev" "Enable dev mode?")))+             (Unit (Options.Arg "port" "Port to listen on")))+     it "succeeding options"+        (consume server ["start","any","--port","1234","--dev"] ==+         Succeeded ("start","any",True,"1234"))+     it "succeeding omitting port options"+        (consume server ["start","any","--port","1234"] ==+         Succeeded ("start","any",False,"1234"))+     it "failing options"+        (consume server ["start","any"] ==+         Failed (Unit (Options.Arg "port" "Port to listen on")))+  where server =+          ((,,,) <$>+           Options.constant "start" <*>+           Options.anyString "SERVER_NAME" <*>+           Options.flag "dev" "Enable dev mode?" <*>+           Options.arg "port" "Port to listen on")++--------------------------------------------------------------------------------+-- JSON tests++-- | Submit a URL to reddit.+data Submission =+  Submission {submissionToken :: !Integer+             ,submissionTitle :: !Text+             ,submissionComment :: !Text+             ,submissionSubreddit :: !Integer}+  deriving (Show,Eq)++submission :: Consumer Value JSON.Doc Submission+submission =+  JSON.obj "Submission"+           (Submission+             <$> JSON.key "token" (JSON.integer "Submission token; see the API docs")+             <*> JSON.key "title" (JSON.string "Submission title")+             <*> JSON.key "comment" (JSON.string "Submission comment")+             <*> JSON.key "subreddit" (JSON.integer "The ID of the subreddit"))++sample :: Value+sample =+  toJSON (object+            ["token" .= 123+            ,"title" .= "Some title"+            ,"comment" .= "This is good"+            ,"subreddit" .= 234214])++badsample :: Value+badsample =+  toJSON (object+            ["token" .= 123+            ,"title" .= "Some title"+            ,"comment" .= 123+            ,"subreddit" .= 234214])++json :: Spec+json =+  do it "describe JSON"+        (describe submission (toJSON ()) ==+         Wrap (JSON.Struct "Submission")+              (And (And (And (Wrap (JSON.Key "token")+                                   (Unit (JSON.Integer "Submission token; see the API docs")))+                             (Wrap (JSON.Key "title")+                                   (Unit (JSON.Text "Submission title"))))+                        (Wrap (JSON.Key "comment")+                              (Unit (JSON.Text "Submission comment"))))+                   (Wrap (JSON.Key "subreddit")+                         (Unit (JSON.Integer "The ID of the subreddit")))))+     it "succeeding json"+        (consume submission sample ==+         Succeeded (Submission {submissionToken = 123+                               ,submissionTitle = "Some title"+                               ,submissionComment = "This is good"+                               ,submissionSubreddit = 234214}))+     it "failing json"+        (consume submission badsample ==+         Failed (Wrap (JSON.Struct "Submission")+                      (Wrap (JSON.Key "comment")+                            (Unit (JSON.Text "Submission comment")))))