packages feed

digestive-functors 0.5.0.4 → 0.6.0.0

raw patch · 8 files changed

+500/−200 lines, 8 filesdep ~test-frameworkdep ~test-framework-hunitPVP ok

version bump matches the API change (PVP)

Dependency ranges changed: test-framework, test-framework-hunit

API changes (from Hackage documentation)

+ Text.Digestive.Form: groupedChoice :: (Eq a, Monad m) => [(Text, [(a, v)])] -> Formlet v m a
+ Text.Digestive.Form: groupedChoice' :: Monad m => [(Text, [(a, v)])] -> Maybe Int -> Form v m a
+ Text.Digestive.Form: groupedChoiceWith :: (Eq a, Monad m) => [(Text, [(Text, (a, v))])] -> Formlet v m a
+ Text.Digestive.Form: groupedChoiceWith' :: Monad m => [(Text, [(Text, (a, v))])] -> Maybe Int -> Form v m a
+ Text.Digestive.Form: listOf :: Monad m => Formlet v m a -> Formlet v m [a]
+ Text.Digestive.Form.Internal: (.:) :: Monad m => Text -> Form v m a -> Form v m a
+ Text.Digestive.Form.Internal: App :: FormTree t v m (b -> a) -> FormTree t v m b -> FormTree t v m a
+ Text.Digestive.Form.Internal: List :: DefaultList (FormTree t v m a) -> FormTree t v m [Int] -> FormTree t v m [a]
+ Text.Digestive.Form.Internal: Map :: (b -> m (Result v a)) -> FormTree t v m b -> FormTree t v m a
+ Text.Digestive.Form.Internal: Monadic :: t (FormTree t v m a) -> FormTree t v m a
+ Text.Digestive.Form.Internal: Pure :: Field v a -> FormTree t v m a
+ Text.Digestive.Form.Internal: Ref :: Ref -> FormTree t v m a -> FormTree t v m a
+ Text.Digestive.Form.Internal: SomeForm :: (FormTree Identity v m a) -> SomeForm v m
+ Text.Digestive.Form.Internal: children :: FormTree Identity v m a -> [SomeForm v m]
+ Text.Digestive.Form.Internal: data FormTree t v m a
+ Text.Digestive.Form.Internal: data SomeForm v m
+ Text.Digestive.Form.Internal: debugFormPaths :: Monad m => FormTree Identity v m a -> [Path]
+ Text.Digestive.Form.Internal: eval :: Monad m => Method -> Env m -> FormTree Identity v m a -> m (Result [(Path, v)] a, [(Path, FormInput)])
+ Text.Digestive.Form.Internal: formMapView :: Monad m => (v -> w) -> FormTree Identity v m a -> FormTree Identity w m a
+ Text.Digestive.Form.Internal: getRef :: FormTree Identity v m a -> Maybe Ref
+ Text.Digestive.Form.Internal: instance (Monad m, Monoid v) => Applicative (FormTree t v m)
+ Text.Digestive.Form.Internal: instance Monad m => Functor (FormTree t v m)
+ Text.Digestive.Form.Internal: instance Show (FormTree Identity v m a)
+ Text.Digestive.Form.Internal: instance Show (SomeForm v m)
+ Text.Digestive.Form.Internal: lookupForm :: Path -> FormTree Identity v m a -> [SomeForm v m]
+ Text.Digestive.Form.Internal: lookupList :: Path -> FormTree Identity v m a -> SomeForm v m
+ Text.Digestive.Form.Internal: monadic :: m (Form v m a) -> Form v m a
+ Text.Digestive.Form.Internal: queryField :: Path -> FormTree Identity v m a -> (forall b. Field v b -> c) -> c
+ Text.Digestive.Form.Internal: toField :: FormTree Identity v m a -> Maybe (SomeField v)
+ Text.Digestive.Form.Internal: toFormTree :: Monad m => Form v m a -> m (FormTree Identity v m a)
+ Text.Digestive.Form.Internal: transform :: Monad m => (a -> m (Result v b)) -> FormTree t v m a -> FormTree t v m b
+ Text.Digestive.Form.Internal: type Form v m a = FormTree m v m a
+ Text.Digestive.Form.Internal: type Ref = Text
+ Text.Digestive.Form.Internal.Field: Bool :: Bool -> Field v Bool
+ Text.Digestive.Form.Internal.Field: Choice :: [(Text, [(Text, (a, v))])] -> Int -> Field v (a, Int)
+ Text.Digestive.Form.Internal.Field: File :: Field v (Maybe FilePath)
+ Text.Digestive.Form.Internal.Field: Singleton :: a -> Field v a
+ Text.Digestive.Form.Internal.Field: SomeField :: (Field v a) -> SomeField v
+ Text.Digestive.Form.Internal.Field: Text :: Text -> Field v Text
+ Text.Digestive.Form.Internal.Field: data Field v a
+ Text.Digestive.Form.Internal.Field: data SomeField v
+ Text.Digestive.Form.Internal.Field: evalField :: Method -> [FormInput] -> Field v a -> a
+ Text.Digestive.Form.Internal.Field: fieldMapView :: (v -> w) -> Field v a -> Field w a
+ Text.Digestive.Form.Internal.Field: instance Show (Field v a)
+ Text.Digestive.Form.List: DefaultList :: a -> [a] -> DefaultList a
+ Text.Digestive.Form.List: data DefaultList a
+ Text.Digestive.Form.List: defaultListIndex :: DefaultList a -> Int -> a
+ Text.Digestive.Form.List: indicesRef :: Text
+ Text.Digestive.Form.List: instance Foldable DefaultList
+ Text.Digestive.Form.List: instance Functor DefaultList
+ Text.Digestive.Form.List: instance Traversable DefaultList
+ Text.Digestive.Form.List: parseIndices :: Text -> [Int]
+ Text.Digestive.Form.List: unparseIndices :: [Int] -> Text
+ Text.Digestive.View: debugViewPaths :: View v -> [Path]
+ Text.Digestive.View: fieldInputChoiceGroup :: Text -> View v -> [(Text, [(Text, v, Bool)])]
+ Text.Digestive.View: listSubViews :: Text -> View v -> [View v]
+ Text.Digestive.View: makeListSubView :: Text -> Int -> View v -> View v
- Text.Digestive.Form: type Formlet m v a = Maybe a -> Form m v a
+ Text.Digestive.Form: type Formlet v m a = Maybe a -> Form v m a

Files

digestive-functors.cabal view
@@ -1,5 +1,5 @@ Name:     digestive-functors-Version:  0.5.0.4+Version:  0.6.0.0 Synopsis: A practical formlet library  Description:@@ -45,18 +45,17 @@   Hs-source-dirs: src   Ghc-options:    -Wall -fwarn-tabs -  Exposed-modules:     +  Exposed-modules:     Text.Digestive     Text.Digestive.Form     Text.Digestive.Form.Encoding+    Text.Digestive.Form.List     Text.Digestive.Ref     Text.Digestive.Types     Text.Digestive.Util     Text.Digestive.View--  Other-modules:-    Text.Digestive.Field     Text.Digestive.Form.Internal+    Text.Digestive.Form.Internal.Field    Build-depends:     base       >= 4       && < 5,@@ -73,8 +72,8 @@    Build-depends:     HUnit                >= 1.2 && < 1.3,-    test-framework       >= 0.4 && < 0.7,-    test-framework-hunit >= 0.2 && < 0.3,+    test-framework       >= 0.4 && < 0.9,+    test-framework-hunit >= 0.2 && < 0.4,     -- Copied from regular dependencies:     base       >= 4       && < 5,     bytestring >= 0.9     && < 0.11,
− src/Text/Digestive/Field.hs
@@ -1,89 +0,0 @@----------------------------------------------------------------------------------{-# LANGUAGE BangPatterns              #-}-{-# LANGUAGE ExistentialQuantification #-}-{-# LANGUAGE GADTs                     #-}-{-# LANGUAGE OverloadedStrings         #-}-module Text.Digestive.Field-    ( Field (..)-    , SomeField (..)-    , evalField-    , fieldMapView-    ) where------------------------------------------------------------------------------------import           Control.Arrow        (second)-import           Data.Maybe           (fromMaybe, listToMaybe)-import           Data.Text            (Text)------------------------------------------------------------------------------------import           Text.Digestive.Types-------------------------------------------------------------------------------------- | A single input field. This usually maps to a single HTML @<input>@ element.-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, (a, v))] -> Int -> Field v (a, Int)-    Bool      :: Bool -> Field v Bool-    File      :: Field v (Maybe FilePath)------------------------------------------------------------------------------------instance Show (Field v a) where-    show (Singleton _) = "Singleton _"-    show (Text t)      = "Text " ++ show t-    show (Choice _ _)  = "Choice _ _"-    show (Bool b)      = "Bool " ++ show b-    show (File)        = "File"------------------------------------------------------------------------------------data SomeField v = forall a. SomeField (Field v a)------------------------------------------------------------------------------------evalField :: Method       -- ^ Get/Post-          -> [FormInput]  -- ^ Given input-          -> Field v a    -- ^ Field-          -> a            -- ^ Result-evalField _    _                 (Singleton x) = x-evalField _    (TextInput x : _) (Text _)      = x-evalField _    _                 (Text x)      = x-evalField _    (TextInput x : _) (Choice ls y) =-    fromMaybe (fst (snd (ls !! y)), y) $ 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) = (fst (snd (ls !! x)), x)-evalField Get  _                 (Bool x)      = x-evalField Post (TextInput x : _) (Bool _)      = x == "on"-evalField Post _                 (Bool _)      = False-evalField Post (FileInput x : _) File          = Just x-evalField _    _                 File          = Nothing------------------------------------------------------------------------------------fieldMapView :: (v -> w) -> Field v a -> Field w a-fieldMapView _ (Singleton x)   = Singleton x-fieldMapView _ (Text x)        = Text x-fieldMapView f (Choice xs i)   = Choice (map (second (second f)) xs) i-fieldMapView _ (Bool x)        = Bool x-fieldMapView _ File            = File------------------------------------------------------------------------------------lookupIdx :: Eq k => k -> [(k, v)] -> Maybe (v, Int)-lookupIdx key = go 0-  where-    go _  []        = Nothing-    go !i ((k, v) : xs)-        | key == k  = Just (v, i)-        | otherwise = go (i + 1) xs
src/Text/Digestive/Form.hs view
@@ -17,6 +17,10 @@     , choice'     , choiceWith     , choiceWith'+    , groupedChoice+    , groupedChoice'+    , groupedChoiceWith+    , groupedChoiceWith'     , bool     , file @@ -33,32 +37,36 @@        -- * Lifting forms     , monadic++      -- * Dynamic list forms+    , listOf     ) where   ---------------------------------------------------------------------------------import           Control.Monad                (liftM)-import           Data.List                    (findIndex)-import           Data.Maybe                   (fromMaybe)-import           Data.Text                    (Text)-import qualified Data.Text                    as T+import           Control.Monad                      (liftM)+import           Data.List                          (findIndex)+import           Data.Maybe                         (fromMaybe)+import           Data.Text                          (Text)+import qualified Data.Text                          as T   ---------------------------------------------------------------------------------import           Text.Digestive.Field import           Text.Digestive.Form.Internal+import           Text.Digestive.Form.Internal.Field+import           Text.Digestive.Form.List import           Text.Digestive.Ref import           Text.Digestive.Types import           Text.Digestive.Util   ---------------------------------------------------------------------------------type Formlet m v a = Maybe a -> Form m v a+type Formlet v m a = Maybe a -> Form v m a   -------------------------------------------------------------------------------- text :: Formlet v m Text-text def = Pure Nothing $ Text $ fromMaybe "" def+text def = Pure $ Text $ fromMaybe "" def   --------------------------------------------------------------------------------@@ -97,19 +105,68 @@ -------------------------------------------------------------------------------- -- | A version of 'choiceWith' for when you have no good 'Eq' instance. choiceWith' :: Monad m => [(Text, (a, v))] -> Maybe Int -> Form v m a-choiceWith' items def = fmap fst $ Pure Nothing $ Choice items def'+choiceWith' items def = fmap fst $ Pure $ Choice [("", items)] def'   where     def' = fromMaybe 0 def   --------------------------------------------------------------------------------+groupedChoice :: (Eq a, Monad m) => [(Text, [(a, v)])] -> Formlet v m a+groupedChoice items def =+    groupedChoiceWith (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.+groupedChoice' :: Monad m => [(Text, [(a, v)])] -> Maybe Int -> Form v m a+groupedChoice' items def =+    groupedChoiceWith' (mkGroupedRefs items makeRefs) def+++mkGroupedRefs :: [(Text, [a])]+              -> [Text]+              -> [(Text, [(Text, a)])]+mkGroupedRefs [] _ = []+mkGroupedRefs (g:gs) is = cur : mkGroupedRefs gs b+  where+    (a,b) = splitAt (length $ snd g) is+    cur = (fst g, zip a (snd g))+++--------------------------------------------------------------------------------+-- | 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...+groupedChoiceWith :: (Eq a, Monad m)+                  => [(Text, [(Text, (a, v))])]+                  -> Formlet v m a+groupedChoiceWith items def = groupedChoiceWith' items def'+  where+    def' = def >>= (\d -> findIndex ((== d) . fst . snd) $+                            concat $ map snd items)+++--------------------------------------------------------------------------------+-- | Low-level support for grouped choice.+groupedChoiceWith' :: Monad m+                   => [(Text, [(Text, (a, v))])]+                   -> Maybe Int+                   -> Form v m a+groupedChoiceWith' items def = fmap fst $ Pure $ Choice items def'+  where+    def' = fromMaybe 0 def+++-------------------------------------------------------------------------------- bool :: Formlet v m Bool-bool = Pure Nothing . Bool . fromMaybe False+bool = Pure . Bool . fromMaybe False   -------------------------------------------------------------------------------- file :: Form v m (Maybe FilePath)-file = Pure Nothing File+file = Pure File   --------------------------------------------------------------------------------@@ -185,3 +242,22 @@ -------------------------------------------------------------------------------- readTransform :: (Monad m, Read a) => v -> String -> m (Result v a) readTransform err = return . maybe (Error err) return . readMaybe+++--------------------------------------------------------------------------------+listOf :: Monad m+       => Formlet v m a+       -> Formlet v m [a]+listOf single def =+    List (fmap single defList) (indicesRef .: listIndices ixs)+  where+    ixs = case def of+        Nothing -> [0]+        Just xs -> [0 .. length xs - 1]++    defList = DefaultList Nothing $ maybe [] (map Just) def+++--------------------------------------------------------------------------------+listIndices :: Monad m => [Int] -> Form v m [Int]+listIndices = fmap parseIndices . text . Just . unparseIndices
src/Text/Digestive/Form/Encoding.hs view
@@ -8,15 +8,15 @@   ---------------------------------------------------------------------------------import           Control.Monad                (liftM)-import           Control.Monad.Identity       (Identity)-import           Data.Maybe                   (mapMaybe)-import           Data.Monoid                  (Monoid(..), mconcat)+import           Control.Monad                      (liftM)+import           Control.Monad.Identity             (Identity)+import           Data.Maybe                         (mapMaybe)+import           Data.Monoid                        (Monoid (..), mconcat)   ---------------------------------------------------------------------------------import           Text.Digestive.Field import           Text.Digestive.Form.Internal+import           Text.Digestive.Form.Internal.Field   --------------------------------------------------------------------------------
src/Text/Digestive/Form/Internal.hs view
@@ -18,28 +18,35 @@     , (.:)     , getRef     , lookupForm+    , lookupList     , toField     , queryField     , eval     , formMapView++      -- * Debugging+    , debugFormPaths     ) where   ---------------------------------------------------------------------------------import           Control.Applicative    (Applicative(..))-import           Control.Monad          (liftM, liftM2, (>=>))-import           Control.Monad.Identity (Identity(..))-import           Data.Maybe             (maybeToList)-import           Data.Monoid            (Monoid)+import           Control.Applicative                (Applicative (..))+import           Control.Monad                      (liftM, liftM2,+                                                     mapAndUnzipM, (>=>))+import           Control.Monad.Identity             (Identity (..))+import           Data.Monoid                        (Monoid)+import           Data.Traversable                   (mapM, sequenceA)+import           Prelude                            hiding (mapM)   ---------------------------------------------------------------------------------import           Data.Text              (Text)-import qualified Data.Text              as T+import           Data.Text                          (Text)+import qualified Data.Text                          as T   ---------------------------------------------------------------------------------import           Text.Digestive.Field+import           Text.Digestive.Form.Internal.Field+import           Text.Digestive.Form.List import           Text.Digestive.Types  @@ -63,17 +70,25 @@  -------------------------------------------------------------------------------- data FormTree t v m a where-    Pure    :: Ref -> Field v a -> FormTree t v m a-    App     :: Ref-            -> FormTree t v m (b -> a)+    -- Setting refs+    Ref     :: Ref -> FormTree t v m a -> FormTree t v m a++    -- Applicative interface+    Pure    :: Field v a -> FormTree t v m a+    App     :: FormTree t v m (b -> a)             -> FormTree t v m b             -> FormTree t v m a +    -- Modifications     Map     :: (b -> m (Result v a)) -> FormTree t v m b -> FormTree t v m a-     Monadic :: t (FormTree t v m a) -> FormTree t v m a +    -- Dynamic lists+    List    :: DefaultList (FormTree t v m a)  -- Not the optimal structure+            -> FormTree t v m [Int]+            -> FormTree t v m [a] + -------------------------------------------------------------------------------- instance Monad m => Functor (FormTree t v m) where     fmap = transform . (return .) . (return .)@@ -81,8 +96,8 @@  -------------------------------------------------------------------------------- instance (Monad m, Monoid v) => Applicative (FormTree t v m) where-    pure x  = Pure Nothing (Singleton x)-    x <*> y = App Nothing x y+    pure x  = Pure (Singleton x)+    x <*> y = App x y   --------------------------------------------------------------------------------@@ -100,20 +115,25 @@   ---------------------------------------------------------------------------------type Ref = Maybe Text+type Ref = Text   -------------------------------------------------------------------------------- showForm :: FormTree Identity v m a -> [String] showForm form = case form of-    (Pure r x)  -> ["Pure (" ++ show r ++ ") (" ++ show x ++ ")"]-    (App r x y) -> concat-        [ ["App (" ++ show r ++ ")"]+    (Ref r x) -> ("Ref " ++ show r) : map indent (showForm x)+    (Pure x)  -> ["Pure (" ++ show x ++ ")"]+    (App x y) -> concat+        [ ["App"]         , map indent (showForm x)         , map indent (showForm y)         ]     (Map _ x)   -> "Map _" : map indent (showForm x)     (Monadic x) -> "Monadic" : map indent (showForm $ runIdentity x)+    (List _ is) -> concat+        [ ["List <defaults>"]  -- TODO show defaults+        , map indent (showForm is)+        ]   where     indent = ("  " ++) @@ -132,64 +152,97 @@  -------------------------------------------------------------------------------- toFormTree :: Monad m => Form v m a -> m (FormTree Identity v m a)-toFormTree (Pure r x)  = return $ Pure r x-toFormTree (App r x y) = liftM2 (App r) (toFormTree x) (toFormTree y)+toFormTree (Ref r x)   = liftM (Ref r) (toFormTree x)+toFormTree (Pure x)    = return $ Pure x+toFormTree (App x y)   = liftM2 App (toFormTree x) (toFormTree y) toFormTree (Map f x)   = liftM (Map f) (toFormTree x) toFormTree (Monadic x) = x >>= toFormTree >>= return . Monadic . Identity+toFormTree (List d is) = liftM2 List (mapM toFormTree d) (toFormTree is)   -------------------------------------------------------------------------------- children :: FormTree Identity v m a -> [SomeForm v m]-children (Pure _ _)    = []-children (App _ x y)   = [SomeForm x, SomeForm y]-children (Map _ x)     = children x-children (Monadic x)   = children $ runIdentity x+children (Ref _ x )  = children x+children (Pure _)    = []+children (App x y)   = [SomeForm x, SomeForm y]+children (Map _ x)   = children x+children (Monadic x) = children $ runIdentity x+children (List _ is) = [SomeForm is]   ---------------------------------------------------------------------------------setRef :: Monad t => Ref -> FormTree t v m a -> FormTree t v m a-setRef r (Pure _ x)  = Pure r x-setRef r (App _ x y) = App r x y-setRef r (Map f x)   = Map f (setRef r x)-setRef r (Monadic x) = Monadic $ liftM (setRef r) x+pushRef :: Monad t => Ref -> FormTree t v m a -> FormTree t v m a+pushRef = Ref   -------------------------------------------------------------------------------- -- | Operator to set a name for a subform. (.:) :: Monad m => Text -> Form v m a -> Form v m a-(.:) = setRef . Just+(.:) = pushRef infixr 5 .:   ---------------------------------------------------------------------------------getRef :: FormTree Identity v m a -> Ref-getRef (Pure r _)  = r-getRef (App r _ _) = r-getRef (Map _ x)   = getRef x-getRef (Monadic x) = getRef $ runIdentity x+popRef :: FormTree Identity v m a -> (Maybe Ref, FormTree Identity v m a)+popRef form = case form of+    (Ref r x)   -> (Just r, x)+    (Pure _)    -> (Nothing, form)+    (App _ _)   -> (Nothing, form)+    (Map f x)   -> let (r, form') = popRef x in (r, Map f form')+    (Monadic x) -> popRef $ runIdentity x+    (List _ _)  -> (Nothing, form)   --------------------------------------------------------------------------------+getRef :: FormTree Identity v m a -> Maybe Ref+getRef = fst . popRef+++-------------------------------------------------------------------------------- lookupForm :: Path -> FormTree Identity v m a -> [SomeForm v m] lookupForm path = go path . SomeForm   where+    -- Note how we use `popRef` to strip the ref away. This is really important.     go []       form            = [form]-    go (r : rs) (SomeForm form) = case getRef form of-        Just r'-            -- Note how we use `setRef Nothing` to strip the ref away. This is-            -- really important.-            | r == r' && null rs -> [SomeForm $ setRef Nothing form]+    go (r : rs) (SomeForm form) = case popRef form of+        (Just r', stripped)+            | r == r' && null rs -> [SomeForm stripped]             | r == r'            -> children form >>= go rs             | otherwise          -> []-        Nothing                  -> children form >>= go (r : rs)+        (Nothing, _)             -> children form >>= go (r : rs)   --------------------------------------------------------------------------------+-- | Always returns a List+lookupList :: Path -> FormTree Identity v m a -> SomeForm v m+lookupList path form = case candidates of+    (SomeForm f : _) -> SomeForm f+    []               -> error $ "Text.Digestive.Form.Internal: " +++        T.unpack (fromPath path) ++ ": expected List, but got another form"+  where+    candidates =+        [ x+        | SomeForm f <- lookupForm path form+        , x          <- getList f+        ]++    getList :: forall a v m. FormTree Identity v m a -> [SomeForm v m]+    getList (Ref _ _)   = []+    getList (Pure _)    = []+    getList (App x y)   = getList x ++ getList y+    getList (Map _ x)   = getList x+    getList (Monadic x) = getList $ runIdentity x+    getList (List d is) = [SomeForm (List d is)]+++-------------------------------------------------------------------------------- toField :: FormTree Identity v m a -> Maybe (SomeField v)-toField (Pure _ x)  = Just (SomeField x)+toField (Ref _ x)   = toField x+toField (Pure x)    = Just (SomeField x)+toField (App _ _)   = Nothing toField (Map _ x)   = toField x toField (Monadic x) = toField (runIdentity x)-toField _           = Nothing+toField (List _ _)  = Nothing   --------------------------------------------------------------------------------@@ -220,41 +273,47 @@ eval' :: Monad m => Path -> Method -> Env m -> FormTree Identity v m a       -> m (Result [(Path, v)] a, [(Path, FormInput)]) -eval' context method env form = case form of--    Pure Nothing (Singleton x) -> return (pure x, [])--    Pure Nothing f ->-        error $ "No ref specified for field " ++ show f+eval' path method env form = case form of+    Ref r x -> eval' (path ++ [r]) method env x -    Pure (Just _) field -> do+    Pure field -> do         val <- env path         let x = evalField method val field         return $ (pure x, [(path, v) | v <- val]) -    App _ x y -> do+    App x y -> do         (x', inp1) <- eval' path method env x         (y', inp2) <- eval' path method env y         return (x' <*> y', inp1 ++ inp2)      Map f x -> do-        (x', inp) <- eval' context method env x+        (x', inp) <- eval' path method env x         x''       <- bindResult (return x') (f >=> return . ann path)         return (x'', inp) -    Monadic x -> eval' context method env $ runIdentity x+    Monadic x -> eval' path method env $ runIdentity x -  where-    path = context ++ maybeToList (getRef form)+    List defs fis -> do+        (ris, inp1) <- eval' path method env fis+        case ris of+            Error errs -> return (Error errs, inp1)+            Success is -> do+                (results, inps) <- mapAndUnzipM+                    -- TODO fix head defs+                    (\i -> eval' (path ++ [T.pack $ show i])+                        method env $ defs `defaultListIndex` i) is+                return (sequenceA results, inp1 ++ concat inps)   -------------------------------------------------------------------------------- formMapView :: Monad m             => (v -> w) -> FormTree Identity v m a -> FormTree Identity w m a-formMapView f (Pure r x)  = Pure r $ (fieldMapView f) x-formMapView f (App r x y) = App r (formMapView f x) (formMapView f y)+formMapView f (Ref r x)   = Ref r $ formMapView f x+formMapView f (Pure x)    = Pure $ fieldMapView f x+formMapView f (App x y)   = App (formMapView f x) (formMapView f y) formMapView f (Map g x)   = Map (g >=> return . resultMapError f) (formMapView f x) formMapView f (Monadic x) = formMapView f $ runIdentity x+formMapView f (List d is) = List (fmap (formMapView f) d) (formMapView f is)   --------------------------------------------------------------------------------@@ -266,3 +325,16 @@     case x of         Error errs  -> return $ Error errs         Success x'  -> f x'+++--------------------------------------------------------------------------------+-- | Debugging purposes+debugFormPaths :: Monad m => FormTree Identity v m a -> [Path]+debugFormPaths (Pure _)    = [[]]+debugFormPaths (App x y)   = debugFormPaths x ++ debugFormPaths y+debugFormPaths (Map _ x)   = debugFormPaths x+debugFormPaths (Monadic x) = debugFormPaths $ runIdentity x+debugFormPaths (List d is) =+    debugFormPaths is +++    (map ("0" :) $ debugFormPaths $ d `defaultListIndex` 0)+debugFormPaths (Ref r x)   = map (r :) $ debugFormPaths x
+ src/Text/Digestive/Form/Internal/Field.hs view
@@ -0,0 +1,93 @@+--------------------------------------------------------------------------------+{-# LANGUAGE BangPatterns              #-}+{-# LANGUAGE ExistentialQuantification #-}+{-# LANGUAGE GADTs                     #-}+{-# LANGUAGE OverloadedStrings         #-}+module Text.Digestive.Form.Internal.Field+    ( Field (..)+    , SomeField (..)+    , evalField+    , fieldMapView+    ) where+++--------------------------------------------------------------------------------+import           Control.Arrow        (second)+import           Data.Maybe           (fromMaybe, listToMaybe)+import           Data.Text            (Text)+++--------------------------------------------------------------------------------+import           Text.Digestive.Types+++--------------------------------------------------------------------------------+-- | A single input field. This usually maps to a single HTML @<input>@ element.+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)+    Bool      :: Bool -> Field v Bool+    File      :: Field v (Maybe FilePath)+++--------------------------------------------------------------------------------+instance Show (Field v a) where+    show (Singleton _) = "Singleton _"+    show (Text t)      = "Text " ++ show t+    show (Choice _ _)  = "Choice _ _"+    show (Bool b)      = "Bool " ++ show b+    show (File)        = "File"+++--------------------------------------------------------------------------------+data SomeField v = forall a. SomeField (Field v a)+++--------------------------------------------------------------------------------+evalField :: Method       -- ^ Get/Post+          -> [FormInput]  -- ^ Given input+          -> Field v a    -- ^ Field+          -> a            -- ^ Result+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+        -- 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)+evalField Get  _                 (Bool x)      = x+evalField Post (TextInput x : _) (Bool _)      = x == "on"+evalField Post _                 (Bool _)      = False+evalField Post (FileInput x : _) File          = Just x+evalField _    _                 File          = Nothing+++--------------------------------------------------------------------------------+fieldMapView :: (v -> w) -> Field v a -> Field w a+fieldMapView _ (Singleton x)   = Singleton x+fieldMapView _ (Text x)        = Text x+fieldMapView f (Choice xs i)   = Choice (map (second func) xs) i+  where func = map (second (second f))+fieldMapView _ (Bool x)        = Bool x+fieldMapView _ File            = File+++--------------------------------------------------------------------------------+lookupIdx :: Eq k => k -> [(k, v)] -> Maybe (v, Int)+lookupIdx key = go 0+  where+    go _  []        = Nothing+    go !i ((k, v) : xs)+        | key == k  = Just (v, i)+        | otherwise = go (i + 1) xs
+ src/Text/Digestive/Form/List.hs view
@@ -0,0 +1,68 @@+--------------------------------------------------------------------------------+{-# LANGUAGE OverloadedStrings #-}+module Text.Digestive.Form.List+    ( indicesRef+    , parseIndices+    , unparseIndices+    , DefaultList (..)+    , defaultListIndex+    ) where+++--------------------------------------------------------------------------------+import           Control.Applicative ((<$>), (<*>))+import           Data.Foldable       (Foldable (..))+import           Data.Maybe          (mapMaybe)+import           Data.Text           (Text)+import qualified Data.Text           as T+import           Data.Traversable    (Traversable (..))+import           Prelude             hiding (foldr)+++--------------------------------------------------------------------------------+import           Text.Digestive.Util+++--------------------------------------------------------------------------------+indicesRef :: Text+indicesRef = "indices"+++--------------------------------------------------------------------------------+parseIndices :: Text -> [Int]+parseIndices = mapMaybe (readMaybe . T.unpack) . T.split (== ',')+++--------------------------------------------------------------------------------+unparseIndices :: [Int] -> Text+unparseIndices = T.intercalate "," . map (T.pack . show)+++--------------------------------------------------------------------------------+-- | A list which, when indexed on non-existant positions, returns a default+-- value.+data DefaultList a = DefaultList a [a]+++--------------------------------------------------------------------------------+instance Functor DefaultList where+    fmap f (DefaultList x xs) = DefaultList (f x) (map f xs)+++--------------------------------------------------------------------------------+instance Foldable DefaultList where+    foldr f z (DefaultList x xs) = f x (foldr f z xs)+++--------------------------------------------------------------------------------+instance Traversable DefaultList where+    traverse f (DefaultList x xs) = DefaultList <$> f x <*> traverse f xs+++--------------------------------------------------------------------------------+defaultListIndex :: DefaultList a -> Int -> a+defaultListIndex (DefaultList x xs) i+    | i < 0     = x+    | otherwise = case drop i xs of+        (y : _) -> y+        []      -> x
src/Text/Digestive/View.hs view
@@ -25,27 +25,36 @@       -- ** Input     , fieldInputText     , fieldInputChoice+    , fieldInputChoiceGroup     , fieldInputBool     , fieldInputFile +      -- ** List subview+    , listSubViews+    , makeListSubView+       -- ** Errors     , errors     , childErrors++      -- * Debugging+    , debugViewPaths     ) where   ---------------------------------------------------------------------------------import           Control.Arrow                (second)-import           Control.Monad.Identity       (Identity)-import           Data.List                    (isPrefixOf)-import           Data.Text                    (Text)-import qualified Data.Text                    as T+import           Control.Arrow                      (second)+import           Control.Monad.Identity             (Identity)+import           Data.List                          (isPrefixOf)+import           Data.Text                          (Text)+import qualified Data.Text                          as T   ---------------------------------------------------------------------------------import           Text.Digestive.Field import           Text.Digestive.Form.Encoding import           Text.Digestive.Form.Internal+import           Text.Digestive.Form.Internal.Field+import           Text.Digestive.Form.List import           Text.Digestive.Types  @@ -94,16 +103,28 @@ -------------------------------------------------------------------------------- subView :: Text -> View v -> View v subView ref (View name ctx form input errs method) =-    View name (ctx ++ path) form input errs method+    case lookupForm path form of+        []               ->+            View name (ctx ++ path) notFound (strip input) (strip errs) method+        (SomeForm f : _) ->+            View name (ctx ++ path) f (strip input) (strip errs) method   where-    path = toPath ref+    path     = toPath ref+    lpath    = length path +    strip :: [(Path, a)] -> [(Path, a)]+    strip xs = [(drop lpath p, x) | (p, x) <- xs, path `isPrefixOf` p] +    notFound :: FormTree Identity v Identity a+    notFound = error $ "Text.Digestive.View.subView: " +++        "No such subView: " ++ T.unpack ref++ -------------------------------------------------------------------------------- -- | Returns all immediate subviews of a view subViews :: View v -> [View v]-subViews view@(View _ ctx form _ _ _) =-    [subView r view | f <- lookupForm ctx form, r <- go f]+subViews view@(View _ _ form _ _ _) =+    [subView r view | r <- go (SomeForm form)]   where     go (SomeForm f) = case getRef f of         Nothing -> [r | c <- children f, r <- go c]@@ -113,7 +134,7 @@ -------------------------------------------------------------------------------- -- | Determine an absolute 'Path' for a field in the form absolutePath :: Text -> View v -> Path-absolutePath ref view@(View name _ _ _ _ _) = name : viewPath ref view+absolutePath ref (View name ctx _ _ _ _) = name : (ctx ++ toPath ref)   --------------------------------------------------------------------------------@@ -124,13 +145,6 @@   ----------------------------------------------------------------------------------- | Internal version of 'absolutePath' which does not take the form name into--- account-viewPath :: Text -> View v -> Path-viewPath ref (View _ ctx _ _ _ _) = ctx ++ toPath ref----------------------------------------------------------------------------------- viewEncType :: View v -> FormEncType viewEncType (View _ _ form _ _ _) = formTreeEncType form @@ -142,10 +156,10 @@  -------------------------------------------------------------------------------- fieldInputText :: forall v. Text -> View v -> Text-fieldInputText ref view@(View _ _ form input _ method) =+fieldInputText ref (View _ _ form input _ method) =     queryField path form eval'   where-    path       = viewPath ref view+    path       = toPath ref     givenInput = lookupInput path input      eval' :: Field v b -> Text@@ -158,27 +172,57 @@ -------------------------------------------------------------------------------- -- | Returns a list of (identifier, view, selected?) fieldInputChoice :: forall v. Text -> View v -> [(Text, v, Bool)]-fieldInputChoice ref view@(View _ _ form input _ method) =+fieldInputChoice ref (View _ _ form input _ method) =     queryField path form eval'   where-    path       = viewPath ref view+    path       = toPath ref     givenInput = lookupInput path input      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)) $ zip [0 ..] xs+            in map (\(i, (k, (_, v))) -> (k, v, i == 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+                      -> View v+                      -> [(Text, [(Text, v, Bool)])]+fieldInputChoiceGroup ref (View _ _ form input _ method) =+    queryField path form eval'+  where+    path       = toPath ref+    givenInput = lookupInput path input++    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)+            in merge idx xs [0..]+        f           -> error $ T.unpack ref ++ ": expected (Choice _ _), " +++            "but got: (" ++ show f ++ ")"++merge :: Int+      -> [(Text, [(Text, (a, v))])]+      -> [Int]+      -> [(Text, [(Text, v, Bool)])]+merge _ [] _ = []+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))++-------------------------------------------------------------------------------- fieldInputBool :: forall v. Text -> View v -> Bool-fieldInputBool ref view@(View _ _ form input _ method) =+fieldInputBool ref (View _ _ form input _ method) =     queryField path form eval'   where-    path       = viewPath ref view+    path       = toPath ref     givenInput = lookupInput path input      eval' :: Field v b -> Bool@@ -190,10 +234,10 @@  -------------------------------------------------------------------------------- fieldInputFile :: forall v. Text -> View v -> Maybe FilePath-fieldInputFile ref view@(View _ _ form input _ method) =+fieldInputFile ref (View _ _ form input _ method) =     queryField path form eval'   where-    path       = viewPath ref view+    path       = toPath ref     givenInput = lookupInput path input      eval' :: Field v b -> Maybe FilePath@@ -204,12 +248,49 @@   --------------------------------------------------------------------------------+listSubViews :: forall v. Text -> View v -> [View v]+listSubViews ref view =+    map (\i -> makeListSubView ref i view) indices+  where+    path        = toPath ref+    indicesPath = path ++ toPath indicesRef+    indices     = parseIndices $ fieldInputText (fromPath indicesPath) view+++--------------------------------------------------------------------------------+-- | Creates a sub view+makeListSubView :: Text+                -- ^ ref+                -> Int+                -- ^ index to use for the subview+                -> View v+                -- ^ list view+                -> View v+makeListSubView ref ind view@(View _ _ form _ _ _) =+    case subView (fromPath $ path ++ [T.pack $ show ind]) view of+        View name ctx _ input errs method ->+            case lookupList path form of+                -- TODO don't use head+                (SomeForm (List defs _)) ->+                    View name ctx (defs `defaultListIndex` ind)+                        input errs method+                _                                -> error $+                    T.unpack ref ++ ": expected List, but got another form"+  where+    path        = toPath ref+++-------------------------------------------------------------------------------- errors :: Text -> View v -> [v]-errors ref view = map snd $ filter ((== viewPath ref view) . fst) $-    viewErrors view+errors ref = map snd . filter ((== toPath ref) . fst) . viewErrors   -------------------------------------------------------------------------------- childErrors :: Text -> View v -> [v]-childErrors ref view = map snd $-    filter ((viewPath ref view `isPrefixOf`) . fst) $ viewErrors view+childErrors ref = map snd .+    filter ((toPath ref `isPrefixOf`) . fst) . viewErrors+++--------------------------------------------------------------------------------+debugViewPaths :: View v -> [Path]+debugViewPaths (View _ _ form _ _ _) = debugFormPaths form