packages feed

digestive-functors 0.8.1.1 → 0.8.2.0

raw patch · 7 files changed

+181/−57 lines, 7 filesdep ~HUnitdep ~QuickCheckPVP: major bump suggested

API removals or changes: PVP suggests a major version bump

Dependency ranges changed: HUnit, QuickCheck

API changes (from Hackage documentation)

+ Text.Digestive.Form: choiceMultiple :: (Eq a, Monad m, Monoid v) => [(a, v)] -> Formlet v m [a]
+ Text.Digestive.Form: choiceMultiple' :: (Monad m, Monoid v) => [(a, v)] -> Maybe [Int] -> Form v m [a]
+ Text.Digestive.Form: choiceWithMultiple :: (Eq a, Monad m, Monoid v) => [(Text, (a, v))] -> Formlet v m [a]
+ Text.Digestive.Form: choiceWithMultiple' :: (Monad m, Monoid v) => [(Text, (a, v))] -> Maybe [Int] -> Form v m [a]
+ Text.Digestive.Form: groupedChoiceMultiple :: (Eq a, Monad m, Monoid v) => [(Text, [(a, v)])] -> Formlet v m [a]
+ Text.Digestive.Form: groupedChoiceMultiple' :: (Monad m, Monoid v) => [(Text, [(a, v)])] -> Maybe [Int] -> Form v m [a]
+ Text.Digestive.Form: groupedChoiceWithMultiple :: (Eq a, Monad m, Monoid v) => [(Text, [(Text, (a, v))])] -> Formlet v m [a]
+ Text.Digestive.Form: groupedChoiceWithMultiple' :: (Monad m, Monoid v) => [(Text, [(Text, (a, v))])] -> Maybe [Int] -> Form v m [a]
- Text.Digestive.Form.Internal.Field: [Choice] :: [(Text, [(Text, (a, v))])] -> Int -> Field v (a, Int)
+ Text.Digestive.Form.Internal.Field: [Choice] :: [(Text, [(Text, (a, v))])] -> [Int] -> Field v [(a, Int)]

Files

CHANGELOG view
@@ -1,3 +1,7 @@+- 0.8.2.0+    * Update `QuickCheck` version bound+    * Updated CHoice type to allow for multiple choices+ - 0.8.1.1     * Bump `time` dependency to allow up to `time-1.6` 
digestive-functors.cabal view
@@ -1,5 +1,5 @@ Name:     digestive-functors-Version:  0.8.1.1+Version:  0.8.2.0 Synopsis: A practical formlet library  Description:@@ -88,8 +88,8 @@       Text.Digestive.Types.QTests    Build-depends:-    HUnit                      >= 1.2 && < 1.4,-    QuickCheck                 >= 2.5 && < 2.9,+    HUnit                      >= 1.2 && < 1.6,+    QuickCheck                 >= 2.5 && < 2.10,     test-framework             >= 0.4 && < 0.9,     test-framework-hunit       >= 0.3 && < 0.4,     test-framework-quickcheck2 >= 0.3 && < 0.4,
src/Text/Digestive/Form.hs view
@@ -21,10 +21,18 @@     , choice'     , choiceWith     , choiceWith'+    , choiceMultiple+    , choiceMultiple'+    , choiceWithMultiple+    , choiceWithMultiple'     , groupedChoice     , groupedChoice'     , groupedChoiceWith     , groupedChoiceWith'+    , groupedChoiceMultiple+    , groupedChoiceMultiple'+    , groupedChoiceWithMultiple+    , groupedChoiceWithMultiple'     , bool     , file     , fileMultiple@@ -65,7 +73,7 @@ import           Control.Applicative import           Control.Monad                      (liftM, liftM2) import           Data.List                          (findIndex)-import           Data.Maybe                         (fromMaybe, listToMaybe)+import           Data.Maybe                         (fromMaybe, listToMaybe, catMaybes) import           Data.Monoid                        (Monoid) import           Data.Text                          (Text) import qualified Data.Text                          as T@@ -108,7 +116,7 @@   ----------------------------------------------------------------------------------- | Returns a 'Formlet' for a value restricted to+-- | Returns a 'Formlet' for a value restricted to a single value from -- the provided list of value-message tuples choice :: (Eq a, Monad m, Monoid v) => [(a, v)] -> Formlet v m a choice items def = choiceWith (zip makeRefs items) def@@ -137,13 +145,55 @@ -- | A version of 'choiceWith' for when there is no good 'Eq' instance. choiceWith'     :: (Monad m, Monoid v) => [(Text, (a, v))] -> Maybe Int -> Form v m a-choiceWith' items def = fmap fst $ Pure $ Choice [("", items)] def'+choiceWith' []    _   = error "choice expects a list with at least one item in it"+choiceWith' items def = fromMaybe defaultItem . listToMaybe . map fst <$> (Pure $ Choice [("", items)] def')   where-    def' = fromMaybe 0 def+    defaultItem = fst $ snd $ head items+    def' = case def of+      Just x  -> [x]+      Nothing -> [0]   ----------------------------------------------------------------------------------- | Returns a 'Formlet' for named groups of choices.+-- | Returns a 'Formlet' for a value restricted to multiple values from+-- the provided list of value-message tuples.  Intended for use with the+-- @multiple@ attribute for select elements.  Allows for an empty result.+choiceMultiple :: (Eq a, Monad m, Monoid v) => [(a, v)] -> Formlet v m [a]+choiceMultiple items def = choiceWithMultiple (zip makeRefs items) def+++--------------------------------------------------------------------------------+-- | Sometimes there is no good 'Eq' instance for 'choice'. In this case, you+-- can use this function, which takes an index in the list as default.+choiceMultiple' :: (Monad m, Monoid v) => [(a, v)] -> Maybe [Int] -> Form v m [a]+choiceMultiple' items def = choiceWithMultiple' (zip makeRefs items) def+++--------------------------------------------------------------------------------+-- | Allows you to assign your own values: these values will be used in the+-- resulting HTML instead of the default @[0 ..]@. This fixes some race+-- conditions that might otherwise appear, e.g. if new choice items are added to+-- some database while a user views and submits the form...+choiceWithMultiple+    :: (Eq a, Monad m, Monoid v) => [(Text, (a, v))] -> Formlet v m [a]+choiceWithMultiple items def = choiceWithMultiple' items def'+  where+    def' = def >>= Just . catMaybes . map (\d -> findIndex ((== d) . fst . snd) items)+++--------------------------------------------------------------------------------+-- | A version of 'choiceWithMultiple' for when there is no good 'Eq' instance.+choiceWithMultiple'+    :: (Monad m, Monoid v) => [(Text, (a, v))] -> Maybe [Int] -> Form v m [a]+choiceWithMultiple' items def = map fst <$> (Pure $ Choice [("", items)] def')+  where+    def' = case def of+      Just x  -> x+      Nothing -> []+++--------------------------------------------------------------------------------+-- | Returns a 'Formlet' for a single value from named groups of choices. groupedChoice     :: (Eq a, Monad m, Monoid v) => [(Text, [(a, v)])] -> Formlet v m a groupedChoice items def =@@ -190,10 +240,60 @@                    => [(Text, [(Text, (a, v))])]                    -> Maybe Int                    -> Form v m a-groupedChoiceWith' items def = fmap fst $ Pure $ Choice items def'+groupedChoiceWith' items def =+  case concatMap snd items of+    [] -> error "groupedChoice expects a list with at least one item in it"+    _  -> head . map fst <$> (Pure $ Choice items def')   where-    def' = fromMaybe 0 def+    def' = case def of+      Just x  -> [x]+      Nothing -> [0] ++--------------------------------------------------------------------------------+-- | Returns a 'Formlet' for multiple values from named groups of choices.+-- Intended for use with the @multiple@ attribute for select elements that+-- have optgroups.  Allows for an empty result.+groupedChoiceMultiple+    :: (Eq a, Monad m, Monoid v) => [(Text, [(a, v)])] -> Formlet v m [a]+groupedChoiceMultiple items def =+    groupedChoiceWithMultiple (mkGroupedRefs items makeRefs) def+++--------------------------------------------------------------------------------+-- | Sometimes there is no good 'Eq' instance for 'choice'. In this case, you+-- can use this function, which takes an index in the list as default.+groupedChoiceMultiple'+    :: (Monad m, Monoid v) => [(Text, [(a, v)])] -> Maybe [Int] -> Form v m [a]+groupedChoiceMultiple' items def =+    groupedChoiceWithMultiple' (mkGroupedRefs items makeRefs) def+++--------------------------------------------------------------------------------+-- | Allows you to assign your own values: these values will be used in the+-- resulting HTML instead of the default @[0 ..]@. This fixes some race+-- conditions that might otherwise appear, e.g. if new choice items are added to+-- some database while a user views and submits the form...+groupedChoiceWithMultiple :: (Eq a, Monad m, Monoid v)+                  => [(Text, [(Text, (a, v))])]+                  -> Formlet v m [a]+groupedChoiceWithMultiple items def = groupedChoiceWithMultiple' items def'+  where+    def' = def >>= Just . catMaybes . map (\d -> findIndex ((== d) . fst . snd) $+                            concat $ map snd items)+++--------------------------------------------------------------------------------+-- | Low-level support for grouped choice.+groupedChoiceWithMultiple' :: (Monad m, Monoid v)+                   => [(Text, [(Text, (a, v))])]+                   -> Maybe [Int]+                   -> Form v m [a]+groupedChoiceWithMultiple' items def = map fst <$> (Pure $ Choice items def')+  where+    def' = case def of+      Just x  -> x+      Nothing -> []  -------------------------------------------------------------------------------- -- | Returns a 'Formlet' for binary choices
src/Text/Digestive/Form/Internal/Field.hs view
@@ -14,7 +14,7 @@  -------------------------------------------------------------------------------- import           Control.Arrow        (second)-import           Data.Maybe           (fromMaybe, listToMaybe, mapMaybe)+import           Data.Maybe           (listToMaybe, mapMaybe, catMaybes) import           Data.Text            (Text)  @@ -27,10 +27,10 @@ data Field v a where     Singleton :: a -> Field v a     Text      :: Text -> Field v Text-    -- A list of identifier, value, view. Then we have the default index in-    -- the list. The return value has the actual value as well as the index in-    -- the list.-    Choice    :: [(Text, [(Text, (a, v))])] -> Int -> Field v (a, Int)+    -- A list of (group name, [(identifier, (value, view))]).+    -- Then we have the default index in the list.+    -- The return value has the actual value as well as the index in the list.+    Choice    :: [(Text, [(Text, (a, v))])] -> [Int] -> Field v [(a, Int)]     Bool      :: Bool -> Field v Bool     File      :: Field v [FilePath] @@ -59,17 +59,20 @@ evalField _    _                 (Singleton x) = x evalField _    (TextInput x : _) (Text _)      = x evalField _    _                 (Text x)      = x-evalField _    (TextInput x : _) (Choice ls' y) =-  let ls = concat (map snd ls') in-    fromMaybe (fst (snd (ls !! y)), y) $ do+evalField _ ts@(TextInput _ : _) (Choice ls _) =+  let ls' = concat (map snd ls) in+    catMaybes $+      map (\(TextInput x) -> do         -- Expects input in the form of "foo.bar.2". This is not needed for         -- <select> fields, but we need it for labels for radio buttons.-        t      <- listToMaybe $ reverse $ toPath x-        (c, i) <- lookupIdx t ls-        return (fst c, i)-evalField _    _                 (Choice ls' x) =-  let ls = concat (map snd ls') in-    (fst (snd (ls !! x)), x)+        t <- listToMaybe . reverse $ toPath x+        (c, i) <- lookupIdx t ls'+        return (fst c, i)) ts+evalField Get  _                 (Choice ls x) =+  -- this populates the default values when displaying a view+  let ls' = concat (map snd ls) in+    map (\i -> (fst $ snd $ ls' !! i, i)) x+evalField Post _                 (Choice _  _) = [] evalField Get  _                 (Bool x)      = x evalField Post (TextInput x : _) (Bool _)      = x == "on" evalField Post _                 (Bool _)      = False
src/Text/Digestive/View.hs view
@@ -205,13 +205,12 @@     eval' :: Field v b -> [(Text, v, Bool)]     eval' field = case field of         Choice xs didx ->-            let idx = snd $ evalField method givenInput (Choice xs didx)-            in map (\(i, (k, (_, v))) -> (k, v, i == idx)) $+            let idx = map snd $ evalField method givenInput (Choice xs didx)+            in map (\(i, (k, (_, v))) -> (k, v, i `elem` idx)) $                  zip [0 ..] $ concat $ map snd xs         f           -> error $ T.unpack ref ++ ": expected (Choice _ _), " ++             "but got: (" ++ show f ++ ")" - -------------------------------------------------------------------------------- -- | Returns a list of (groupName, [(identifier, view, selected?)]) fieldInputChoiceGroup :: forall v. Text@@ -226,12 +225,12 @@     eval' :: Field v b -> [(Text, [(Text, v, Bool)])]     eval' field = case field of         Choice xs didx ->-            let idx = snd $ evalField method givenInput (Choice xs didx)+            let idx = map snd $ evalField method givenInput (Choice xs didx)             in merge idx xs [0..]         f           -> error $ T.unpack ref ++ ": expected (Choice _ _), " ++             "but got: (" ++ show f ++ ")" -merge :: Int+merge :: [Int]       -> [(Text, [(Text, (a, v))])]       -> [Int]       -> [(Text, [(Text, v, Bool)])]@@ -239,7 +238,7 @@ merge idx (g:gs) is = cur : merge idx gs b   where     (a,b) = splitAt (length $ snd g) is-    cur = (fst g, map (\(i, (k, (_, v))) -> (k, v, i == idx)) $ zip a (snd g))+    cur = (fst g, map (\(i, (k, (_, v))) -> (k, v, i `elem` idx)) $ zip a (snd g))  -------------------------------------------------------------------------------- -- | Returns True/False based on the field referred to by the given
tests/Text/Digestive/Tests/Fixtures.hs view
@@ -59,21 +59,32 @@   ---------------------------------------------------------------------------------data Type = Water | Fire | Leaf+data Type = Water | Fire | Leaf | Rock     deriving (Eq, Show)   --------------------------------------------------------------------------------+typeChoices :: [(Type, Text)]+typeChoices = [(Water, "Water"), (Fire, "Fire"), (Leaf, "Leaf"), (Rock, "Rock")]+++-------------------------------------------------------------------------------- typeForm :: Monad m => Form Text m Type-typeForm = choice [(Water, "Water"), (Fire, "Fire"), (Leaf, "Leaf")] Nothing+typeForm = choice typeChoices Nothing   --------------------------------------------------------------------------------+weaknessForm :: Monad m => Form Text m [Type]+weaknessForm = choiceMultiple typeChoices Nothing+++-------------------------------------------------------------------------------- data Pokemon = Pokemon-    { pokemonName  :: Text-    , pokemonLevel :: Maybe Int-    , pokemonType  :: Type-    , pokemonRare  :: Bool+    { pokemonName     :: Text+    , pokemonLevel    :: Maybe Int+    , pokemonType     :: Type+    , pokemonWeakness :: [Type]+    , pokemonRare     :: Bool     } deriving (Eq, Show)  @@ -93,10 +104,11 @@ -------------------------------------------------------------------------------- pokemonForm :: Form Text TrainerM Pokemon pokemonForm = Pokemon-    <$> "name"  .: validate isPokemon (text Nothing)-    <*> "level" .: levelForm-    <*> "type"  .: typeForm-    <*> "rare"  .: bool Nothing+    <$> "name"     .: validate isPokemon (text Nothing)+    <*> "level"    .: levelForm+    <*> "type"     .: typeForm+    <*> "weakness" .: weaknessForm+    <*> "rare"     .: bool Nothing   where     definitelyNoPokemon = ["dog", "cat"]     isPokemon name@@ -176,9 +188,9 @@  -------------------------------------------------------------------------------- canCatch :: Catch -> Bool-canCatch (Catch (Pokemon _ _ _ False) _)      = True-canCatch (Catch (Pokemon _ _ _ True)  Ultra)  = True-canCatch (Catch (Pokemon _ _ _ True)  Master) = True+canCatch (Catch (Pokemon _ _ _ _ False) _)      = True+canCatch (Catch (Pokemon _ _ _ _ True)  Ultra)  = True+canCatch (Catch (Pokemon _ _ _ _ True)  Master) = True canCatch _                                    = False  
tests/Text/Digestive/View/Tests.hs view
@@ -33,18 +33,22 @@ tests :: Test tests = testGroup "Text.Digestive.View.Tests"     [ testCase "Simple postForm" $ (@=?)-        (Just (Pokemon "charmander" (Just 5) Fire False)) $+        (Just (Pokemon "charmander" (Just 5) Fire [Water, Rock] False)) $         snd $ runTrainerM $ postForm "f" pokemonForm $ testEnv-            [ ("f.name",  "charmander")-            , ("f.level", "5")-            , ("f.type",  "type.1")+            [ ("f.name",     "charmander")+            , ("f.level",    "5")+            , ("f.type",     "type.1")+            , ("f.weakness", "weakness.0")+            , ("f.weakness", "weakness.3")             ]      , testCase "optional unspecified" $ (@=?)-        (Just (Pokemon "magmar" Nothing Fire False)) $+        (Just (Pokemon "magmar" Nothing Fire [Water, Rock] False)) $         snd $ runTrainerM $ postForm "f" pokemonForm $ testEnv-            [ ("f.name",  "magmar")-            , ("f.type",  "type.1")+            [ ("f.name",     "magmar")+            , ("f.type",     "type.1")+            , ("f.weakness", "weakness.0")+            , ("f.weakness", "weakness.3")             ]      , testCase "stringRead float" $ (@=?)@@ -91,12 +95,14 @@             postForm "f" pokemonForm $ testEnv [("f.type",  "type.2")]      , testCase "Nested postForm" $ (@=?)-        (Just (Catch (Pokemon "charmander" (Just 5) Fire False) Ultra)) $+        (Just (Catch (Pokemon "charmander" (Just 5) Fire [Water, Rock] False) Ultra)) $         snd $ runTrainerM $ postForm "f" catchForm $ testEnv-            [ ("f.pokemon.name",  "charmander")-            , ("f.pokemon.level", "5")-            , ("f.pokemon.type",  "type.1")-            , ("f.ball",          "ball.2")+            [ ("f.pokemon.name",     "charmander")+            , ("f.pokemon.level",    "5")+            , ("f.pokemon.type",     "type.1")+            , ("f.pokemon.weakness", "weakness.0")+            , ("f.pokemon.weakness", "weakness.3")+            , ("f.ball",             "ball.2")             ]      , testCase "subView errors" $ (@=?)@@ -118,11 +124,11 @@                 ]      , testCase "subViews length" $ (@=?)-        4 $+        5 $         length $ subViews $ runTrainerM $ getForm "f" pokemonForm      , testCase "subViews after subView length" $ (@=?)-        4 $+        5 $         length $ subViews $ subView "pokemon" $             runTrainerM $ getForm "f" catchForm