digestive-functors 0.2.1.0 → 0.3.0.0
raw patch · 17 files changed
+669/−1090 lines, 17 filesdep +HUnitdep +test-frameworkdep +test-framework-hunitdep ~basePVP ok
version bump matches the API change (PVP)
Dependencies added: HUnit, test-framework, test-framework-hunit
Dependency ranges changed: base
API changes (from Hackage documentation)
- Text.Digestive.Cli: instance Monoid InputMap
- Text.Digestive.Cli: instance Monoid PromptView
- Text.Digestive.Cli: instance Show FieldItem
- Text.Digestive.Cli: instance Show InputMap
- Text.Digestive.Cli: instance Show PromptView
- Text.Digestive.Cli: prompt :: String -> Prompt String
- Text.Digestive.Cli: promptList :: String -> Prompt a -> Prompt [a]
- Text.Digestive.Cli: promptRead :: Read a => String -> String -> Prompt a
- Text.Digestive.Cli: runPrompt :: Prompt a -> IO (Either [String] a)
- Text.Digestive.Cli: type Prompt a = Form IO String String PromptView a
- Text.Digestive.Common: childErrors :: Monad m => ([e] -> v) -> Form m i e v ()
- Text.Digestive.Common: errors :: Monad m => ([e] -> v) -> Form m i e v ()
- Text.Digestive.Common: input :: (Monad m, Functor m) => (Bool -> Maybe i -> d -> s) -> (Maybe i -> Result e a) -> (FormId -> s -> v) -> d -> Form m i e v a
- Text.Digestive.Common: label :: Monad m => (FormId -> v) -> Form m i e v ()
- Text.Digestive.Forms: class FormInput i f | i -> f where getInputString = listToMaybe . getInputStrings getInputText = listToMaybe . getInputTexts getInputTexts = map pack . getInputStrings
- Text.Digestive.Forms: getInputFile :: FormInput i f => i -> Maybe f
- Text.Digestive.Forms: getInputString :: FormInput i f => i -> Maybe String
- Text.Digestive.Forms: getInputStrings :: FormInput i f => i -> [String]
- Text.Digestive.Forms: getInputText :: FormInput i f => i -> Maybe Text
- Text.Digestive.Forms: getInputTexts :: FormInput i f => i -> [Text]
- Text.Digestive.Forms: inputBool :: (Monad m, Functor m, FormInput i f) => (FormId -> Bool -> v) -> Bool -> Form m i e v Bool
- Text.Digestive.Forms: inputChoice :: (Monad m, Functor m, FormInput i f, Monoid v, Eq a) => (FormId -> String -> Bool -> a -> v) -> a -> [a] -> Form m i e v a
- Text.Digestive.Forms: inputChoices :: (Monad m, Functor m, FormInput i f, Monoid v, Eq a) => (FormId -> String -> Bool -> a -> v) -> [a] -> [a] -> Form m i e v [a]
- Text.Digestive.Forms: inputFile :: (Monad m, Functor m, FormInput i f) => (FormId -> v) -> Form m i e v (Maybe f)
- Text.Digestive.Forms: inputList :: (Monad m, Monoid v) => Formlet m i e v Int -> Formlet m i e v a -> Formlet m i e v [a]
- Text.Digestive.Forms: inputRead :: (Monad m, Functor m, FormInput i f, Read a, Show a) => (FormId -> Maybe String -> v) -> e -> Maybe a -> Form m i e v a
- Text.Digestive.Forms: inputString :: (Monad m, Functor m, FormInput i f) => (FormId -> Maybe String -> v) -> Maybe String -> Form m i e v String
- Text.Digestive.Forms: inputText :: (Monad m, Functor m, FormInput i f) => (FormId -> Maybe Text -> v) -> Maybe Text -> Form m i e v Text
- Text.Digestive.Forms.Html: FormHtml :: FormEncType -> (FormHtmlConfig -> a) -> FormHtml a
- Text.Digestive.Forms.Html: FormHtmlConfig :: [String] -> [String] -> [String] -> [String] -> [String] -> FormHtmlConfig
- Text.Digestive.Forms.Html: MultiPart :: FormEncType
- Text.Digestive.Forms.Html: UrlEncoded :: FormEncType
- Text.Digestive.Forms.Html: applyClasses :: (a -> String -> a) -> [FormHtmlConfig -> [String]] -> FormHtmlConfig -> a -> a
- Text.Digestive.Forms.Html: createFormHtml :: (FormHtmlConfig -> a) -> FormHtml a
- Text.Digestive.Forms.Html: createFormHtmlWith :: FormEncType -> (FormHtmlConfig -> a) -> FormHtml a
- Text.Digestive.Forms.Html: data FormEncType
- Text.Digestive.Forms.Html: data FormHtml a
- Text.Digestive.Forms.Html: data FormHtmlConfig
- Text.Digestive.Forms.Html: defaultHtmlConfig :: FormHtmlConfig
- Text.Digestive.Forms.Html: emptyHtmlConfig :: FormHtmlConfig
- Text.Digestive.Forms.Html: formEncType :: FormHtml a -> FormEncType
- Text.Digestive.Forms.Html: formHtml :: FormHtml a -> FormHtmlConfig -> a
- Text.Digestive.Forms.Html: htmlErrorClasses :: FormHtmlConfig -> [String]
- Text.Digestive.Forms.Html: htmlErrorListClasses :: FormHtmlConfig -> [String]
- Text.Digestive.Forms.Html: htmlInputClasses :: FormHtmlConfig -> [String]
- Text.Digestive.Forms.Html: htmlLabelClasses :: FormHtmlConfig -> [String]
- Text.Digestive.Forms.Html: htmlSubmitClasses :: FormHtmlConfig -> [String]
- Text.Digestive.Forms.Html: instance Eq FormEncType
- Text.Digestive.Forms.Html: instance Functor FormHtml
- Text.Digestive.Forms.Html: instance Monoid FormEncType
- Text.Digestive.Forms.Html: instance Monoid a => Monoid (FormHtml a)
- Text.Digestive.Forms.Html: instance Show FormEncType
- Text.Digestive.Forms.Html: instance Show FormHtmlConfig
- Text.Digestive.Forms.Html: mapViewHtml :: (Monad m, Functor m) => (v -> w) -> Form m i e (FormHtml v) a -> Form m i e (FormHtml w) a
- Text.Digestive.Forms.Html: renderFormHtml :: FormHtml a -> (a, FormEncType)
- Text.Digestive.Forms.Html: renderFormHtmlWith :: FormHtmlConfig -> FormHtml a -> (a, FormEncType)
- Text.Digestive.Forms.Html: viewHtml :: Monad m => a -> Form m i e (FormHtml a) ()
- Text.Digestive.Result: Error :: [(FormRange, e)] -> Result e ok
- Text.Digestive.Result: FormRange :: FormId -> FormId -> FormRange
- Text.Digestive.Result: Ok :: ok -> Result e ok
- Text.Digestive.Result: data FormId
- Text.Digestive.Result: data FormRange
- Text.Digestive.Result: data Result e ok
- Text.Digestive.Result: formIdList :: FormId -> [Integer]
- Text.Digestive.Result: getResult :: Result e ok -> Maybe ok
- Text.Digestive.Result: incrementFormId :: FormId -> FormId
- Text.Digestive.Result: instance (Eq e, Eq ok) => Eq (Result e ok)
- Text.Digestive.Result: instance (Show e, Show ok) => Show (Result e ok)
- Text.Digestive.Result: instance Applicative (Result e)
- Text.Digestive.Result: instance Eq FormId
- Text.Digestive.Result: instance Eq FormRange
- Text.Digestive.Result: instance Functor (Result e)
- Text.Digestive.Result: instance Monad (Result e)
- Text.Digestive.Result: instance Ord FormId
- Text.Digestive.Result: instance Show FormId
- Text.Digestive.Result: instance Show FormRange
- Text.Digestive.Result: isInRange :: FormId -> FormRange -> Bool
- Text.Digestive.Result: isSubRange :: FormRange -> FormRange -> Bool
- Text.Digestive.Result: mapId :: ([Integer] -> [Integer]) -> FormId -> FormId
- Text.Digestive.Result: retainChildErrors :: FormRange -> [(FormRange, e)] -> [e]
- Text.Digestive.Result: retainErrors :: FormRange -> [(FormRange, e)] -> [e]
- Text.Digestive.Result: unitRange :: FormId -> FormRange
- Text.Digestive.Result: zeroId :: String -> FormId
- Text.Digestive.Transform: Transformer :: (a -> m (Either [e] b)) -> Transformer m e a b
- Text.Digestive.Transform: instance Monad m => Arrow (Transformer m e)
- Text.Digestive.Transform: instance Monad m => Category (Transformer m e)
- Text.Digestive.Transform: newtype Transformer m e a b
- Text.Digestive.Transform: required :: Monad m => e -> Transformer m e (Maybe a) a
- Text.Digestive.Transform: transform :: Monad m => Form m i e v a -> Transformer m e a b -> Form m i e v b
- Text.Digestive.Transform: transformEither :: Monad m => (a -> Either e b) -> Transformer m e a b
- Text.Digestive.Transform: transformEitherM :: Monad m => (a -> m (Either e b)) -> Transformer m e a b
- Text.Digestive.Transform: transformFormlet :: Monad m => (b -> a) -> Formlet m i e v a -> Transformer m e a b -> Formlet m i e v b
- Text.Digestive.Transform: transformRead :: (Monad m, Read a) => e -> Transformer m e String a
- Text.Digestive.Transform: unTransformer :: Transformer m e a b -> a -> m (Either [e] b)
- Text.Digestive.Types: (++>) :: (Monad m, Monoid v) => Form m i e v () -> Form m i e v a -> Form m i e v a
- Text.Digestive.Types: (<++) :: (Monad m, Monoid v) => Form m i e v a -> Form m i e v () -> Form m i e v a
- Text.Digestive.Types: Environment :: (FormId -> m (Maybe i)) -> Environment m i
- Text.Digestive.Types: Form :: FormState m i (View e v, m (Result e a)) -> Form m i e v a
- Text.Digestive.Types: NoEnvironment :: Environment m i
- Text.Digestive.Types: View :: ([(FormRange, e)] -> v) -> View e v
- Text.Digestive.Types: bracketState :: Monad m => FormState m i a -> FormState m i a
- Text.Digestive.Types: data Environment m i
- Text.Digestive.Types: eitherForm :: Monad m => Form m i e v a -> String -> Environment m i -> m (Either v a)
- Text.Digestive.Types: fromList :: Monad m => [(FormId, i)] -> Environment m i
- Text.Digestive.Types: getFormId :: Monad m => FormState m i FormId
- Text.Digestive.Types: getFormInput :: Monad m => FormState m i (Maybe i)
- Text.Digestive.Types: getFormInput' :: Monad m => FormId -> FormState m i (Maybe i)
- Text.Digestive.Types: getFormRange :: Monad m => FormState m i FormRange
- Text.Digestive.Types: incState :: Monad m => FormState m i ()
- Text.Digestive.Types: instance (Monad m, Monoid v) => Applicative (Form m i e v)
- Text.Digestive.Types: instance Functor (View e)
- Text.Digestive.Types: instance Monad m => Functor (Form m i e v)
- Text.Digestive.Types: instance Monad m => Monoid (Environment m i)
- Text.Digestive.Types: instance Monoid v => Monoid (View e v)
- Text.Digestive.Types: isFormInput :: Monad m => FormState m i Bool
- Text.Digestive.Types: mapView :: (Monad m, Functor m) => (v -> w) -> Form m i e v a -> Form m i e w a
- Text.Digestive.Types: newtype Form m i e v a
- Text.Digestive.Types: newtype View e v
- Text.Digestive.Types: runForm :: Monad m => Form m i e v a -> String -> Environment m i -> m (View e v, m (Result e a))
- Text.Digestive.Types: runViewForm :: Monad m => Form m i e v a -> String -> Environment m i -> m (v, Maybe a)
- Text.Digestive.Types: type FormState m i a = ReaderT (Environment m i) (StateT FormRange m) a
- Text.Digestive.Types: type Formlet m i e v a = Maybe a -> Form m i e v a
- Text.Digestive.Types: unForm :: Form m i e v a -> FormState m i (View e v, m (Result e a))
- Text.Digestive.Types: unView :: View e v -> [(FormRange, e)] -> v
- Text.Digestive.Types: view :: Monad m => v -> Form m i e v ()
- Text.Digestive.Types: viewForm :: Monad m => Form m i e v a -> String -> m v
- Text.Digestive.Validate: check :: Monad m => e -> (a -> Bool) -> Validator m e a
- Text.Digestive.Validate: checkM :: Monad m => e -> (a -> m Bool) -> Validator m e a
- Text.Digestive.Validate: data Validator m e a
- Text.Digestive.Validate: instance Monad m => Monoid (Validator m e a)
- Text.Digestive.Validate: validate :: Monad m => Form m i e v a -> Validator m e a -> Form m i e v a
- Text.Digestive.Validate: validateMany :: Monad m => Form m i e v a -> [Validator m e a] -> Form m i e v a
+ Text.Digestive.Form: (.:) :: Text -> Form v m a -> Form v m a
+ Text.Digestive.Form: SomeForm :: (Form v m a) -> SomeForm v m
+ Text.Digestive.Form: bool :: Bool -> Form v m Bool
+ Text.Digestive.Form: check :: Monad m => v -> (a -> Bool) -> Form v m a -> Form v m a
+ Text.Digestive.Form: checkM :: Monad m => v -> (a -> m Bool) -> Form v m a -> Form v m a
+ Text.Digestive.Form: choice :: Eq a => [(a, v)] -> Maybe a -> Form v m a
+ Text.Digestive.Form: data Form v m a
+ Text.Digestive.Form: data SomeForm v m
+ Text.Digestive.Form: file :: Form v m (Maybe FilePath)
+ Text.Digestive.Form: string :: Monad m => Maybe String -> Form v m String
+ Text.Digestive.Form: stringRead :: (Monad m, Read a, Show a) => v -> Maybe a -> Form v m a
+ Text.Digestive.Form: text :: Maybe Text -> Form v m Text
+ Text.Digestive.Form: validate :: Monad m => (a -> Result v b) -> Form v m a -> Form v m b
+ Text.Digestive.Form: validateM :: Monad m => (a -> m (Result v b)) -> Form v m a -> Form v m b
+ Text.Digestive.Form.Encoding: MultiPart :: FormEncType
+ Text.Digestive.Form.Encoding: UrlEncoded :: FormEncType
+ Text.Digestive.Form.Encoding: data FormEncType
+ Text.Digestive.Form.Encoding: formEncType :: Form v m a -> FormEncType
+ Text.Digestive.Form.Encoding: instance Eq FormEncType
+ Text.Digestive.Form.Encoding: instance Monoid FormEncType
+ Text.Digestive.Form.Encoding: instance Show FormEncType
+ Text.Digestive.Types: Error :: v -> Result v a
+ Text.Digestive.Types: FileInput :: FilePath -> FormInput
+ Text.Digestive.Types: Get :: Method
+ Text.Digestive.Types: Post :: Method
+ Text.Digestive.Types: Success :: a -> Result v a
+ Text.Digestive.Types: TextInput :: Text -> FormInput
+ Text.Digestive.Types: data FormInput
+ Text.Digestive.Types: data Method
+ Text.Digestive.Types: data Result v a
+ Text.Digestive.Types: fromPath :: Path -> Text
+ Text.Digestive.Types: instance (Show v, Show a) => Show (Result v a)
+ Text.Digestive.Types: instance Eq Method
+ Text.Digestive.Types: instance Functor (Result v)
+ Text.Digestive.Types: instance Monad (Result v)
+ Text.Digestive.Types: instance Monoid v => Applicative (Result v)
+ Text.Digestive.Types: instance Ord Method
+ Text.Digestive.Types: instance Show FormInput
+ Text.Digestive.Types: instance Show Method
+ Text.Digestive.Types: resultMapError :: (v -> w) -> Result v a -> Result w a
+ Text.Digestive.Types: toPath :: Text -> Path
+ Text.Digestive.Types: type Env m = Path -> m [FormInput]
+ Text.Digestive.Types: type Path = [Text]
+ Text.Digestive.Util: readMaybe :: Read a => String -> Maybe a
+ Text.Digestive.View: View :: Text -> Path -> Form v m a -> [(Path, FormInput)] -> [(Path, v)] -> Method -> View v
+ Text.Digestive.View: absolutePath :: Text -> View v -> Path
+ Text.Digestive.View: childErrors :: Text -> View v -> [v]
+ Text.Digestive.View: data View v
+ Text.Digestive.View: errors :: Text -> View v -> [v]
+ Text.Digestive.View: fieldInputBool :: Text -> View v -> Bool
+ Text.Digestive.View: fieldInputChoice :: Text -> View v -> ([v], Int)
+ Text.Digestive.View: fieldInputFile :: Text -> View v -> Maybe FilePath
+ Text.Digestive.View: fieldInputText :: Text -> View v -> Text
+ Text.Digestive.View: getForm :: Monad m => Text -> Form v m a -> View v
+ Text.Digestive.View: instance Functor View
+ Text.Digestive.View: instance Show v => Show (View v)
+ Text.Digestive.View: postForm :: Monad m => Text -> Form v m a -> Env m -> m (View v, Maybe a)
+ Text.Digestive.View: subView :: Text -> View v -> View v
+ Text.Digestive.View: viewContext :: View v -> Path
+ Text.Digestive.View: viewEncType :: View v -> FormEncType
+ Text.Digestive.View: viewErrors :: View v -> [(Path, v)]
+ Text.Digestive.View: viewForm :: View v -> Form v m a
+ Text.Digestive.View: viewInput :: View v -> [(Path, FormInput)]
+ Text.Digestive.View: viewMethod :: View v -> Method
+ Text.Digestive.View: viewName :: View v -> Text
Files
- digestive-functors.cabal +64/−17
- src/Text/Digestive.hs +8/−8
- src/Text/Digestive/Cli.hs +0/−196
- src/Text/Digestive/Common.hs +0/−46
- src/Text/Digestive/Field.hs +59/−0
- src/Text/Digestive/Form.hs +92/−0
- src/Text/Digestive/Form/Encoding.hs +42/−0
- src/Text/Digestive/Form/Internal.hs +177/−0
- src/Text/Digestive/Forms.hs +0/−208
- src/Text/Digestive/Forms/Html.hs +0/−133
- src/Text/Digestive/Result.hs +0/−113
- src/Text/Digestive/Transform.hs +0/−91
- src/Text/Digestive/Types.hs +54/−216
- src/Text/Digestive/Util.hs +9/−0
- src/Text/Digestive/Validate.hs +0/−62
- src/Text/Digestive/View.hs +148/−0
- tests/TestSuite.hs +16/−0
digestive-functors.cabal view
@@ -1,39 +1,86 @@ Name: digestive-functors-Version: 0.2.1.0-Synopsis: A general way to consume input using applicative functors+Version: 0.3.0.0+Synopsis: A practical formlet library -Description: Digestive functors is a library to generate and process- HTML forms. You can find an introduction here:+Description:+ Digestive functors is a library inspired by formlets: - <http://github.com/jaspervdj/digestive-functors/blob/master/digestive-functors/README.lhs>+ . + <http://groups.inf.ed.ac.uk/links/formlets/>++ .++ It is intended to be an improvement of the Haskell formlets library, with as+ main advantages:++ .++ * better error handling, so a web page can display input errors right next+ to the corresponding fields;++ .++ * the ability to easily add @\<label\>@ elements;++ .++ * separation of the validation model and the HTML output.++ .++ Tutorial:+ <http://github.com/jaspervdj/digestive-functors/blob/master/examples/tutorial.lhs>+ Homepage: http://github.com/jaspervdj/digestive-functors License: BSD3 License-file: LICENSE-Author: Jasper Van der Jeugt-Maintainer: jaspervdj@gmail.com+Author: Jasper Van der Jeugt <m@jaspervdj.be>+Maintainer: Jasper Van der Jeugt <m@jaspervdj.be> Category: Web Build-type: Simple-Cabal-version: >= 1.6+Cabal-version: >= 1.8 Library Hs-source-dirs: src- Ghc-options: -Wall+ Ghc-options: -Wall -fwarn-tabs Exposed-modules: - Text.Digestive.Validate,- Text.Digestive.Common,+ Text.Digestive,+ Text.Digestive.Form,+ Text.Digestive.Form.Encoding, Text.Digestive.Types,- Text.Digestive.Transform,- Text.Digestive.Forms,- Text.Digestive.Forms.Html,- Text.Digestive.Result,- Text.Digestive.Cli,- Text.Digestive+ Text.Digestive.View,+ Text.Digestive.Util + Other-modules:+ Text.Digestive.Field,+ Text.Digestive.Form.Internal+ Build-depends: base >= 4 && < 5, bytestring >= 0.9, containers >= 0.3, mtl >= 1.1.0.0 && < 3, text >= 0.10++Test-suite digestive-functors-tests+ Type: exitcode-stdio-1.0+ Hs-source-dirs: src tests+ Main-is: TestSuite.hs+ Ghc-options: -Wall++ Build-depends:+ HUnit >= 1.2 && < 1.3,+ test-framework >= 0.4 && < 0.7,+ test-framework-hunit >= 0.2 && < 0.3,+ -- Copied from regular dependencies:+ base >= 4 && < 5,+ bytestring >= 0.9,+ containers >= 0.3,+ mtl >= 1.1.0.0 && < 3,+ text >= 0.10++Source-repository head+ Type: git+ Location: https://github.com/jaspervdj/digestive-functors
src/Text/Digestive.hs view
@@ -1,13 +1,13 @@--- | Module re-exporting all core definitions---+-- | Tutorial:+-- <http://github.com/jaspervdj/digestive-functors/blob/master/examples/tutorial.lhs> module Text.Digestive- ( module Text.Digestive.Result+ ( module Text.Digestive.Form+ , module Text.Digestive.Form.Encoding , module Text.Digestive.Types- , module Text.Digestive.Transform- , module Text.Digestive.Validate+ , module Text.Digestive.View ) where -import Text.Digestive.Result+import Text.Digestive.Form+import Text.Digestive.Form.Encoding import Text.Digestive.Types-import Text.Digestive.Transform-import Text.Digestive.Validate+import Text.Digestive.View
− src/Text/Digestive/Cli.hs
@@ -1,196 +0,0 @@--- | Proof-of-concept module: use digestive functors for a command line--- interface prompt----{-# LANGUAGE GeneralizedNewtypeDeriving #-}-module Text.Digestive.Cli- ( Prompt- , prompt- , promptList- , promptRead- , runPrompt- ) where--import Control.Applicative ((<$>))-import Data.Monoid (Monoid, mappend, mempty)--import Text.Digestive.Result-import Text.Digestive.Types-import Text.Digestive.Transform-import Text.Digestive.Forms (inputList)---- A representation of an element in the structure used to gather inputs--- from the user.----data FieldItem- -- A tangible item that the user is prompted for.- = FieldItemSingle FormId String [String]- -- A delimter marking the start of a series of prompts to be entered- -- multiple times.- | FieldItemMultiStart FormId String [String]- -- A delimiter marking the end of a multiple input prompt.- | FieldItemMultiEnd- deriving (Show)---- The structure of a prompt, built up as a View.----newtype PromptView = PromptView- { unPromptView :: [FieldItem]- } deriving (Show, Monoid)---- | Type for a prompt----type Prompt a = Form IO String String PromptView a---- An association list of FormIds and their inputs gathered by prompting the--- user.----newtype InputMap = InputMap- { unInputMap :: [(FormId, String)]- } deriving (Show, Monoid)---- Create an environment from an input map----inputMapEnvironment :: Monad m => InputMap -> Environment m String-inputMapEnvironment map' = Environment $ return . flip lookup (unInputMap map')---- | Generate a prompt field for a String----prompt :: String -- ^ Description- -> Prompt String -- ^ Resulting prompt-prompt descr = Form $ do- id' <- getFormId- inp <- getFormInput- range <- getFormRange- let v :: [(FormRange, String)] -> PromptView- v errs = PromptView [FieldItemSingle id' descr matching]- where- -- Only errors which apply specifically to this item- matching = retainErrors range errs- result = case inp of- Just x -> Ok x- Nothing -> Error [(range, "No input")]- return (View v, return result)---- | Convert a prompt for a single item into a prompt for multiple items----promptList :: String -- ^ Description of resulting multi-prompt- -> Prompt a -- ^ Prompt to convert- -> Prompt [a] -- ^ Resulting multiple input prompt-promptList descr prmpt = Form $ do- id' <- getFormId- (v, rs) <- unForm $ inputList numPrompt (const prmpt) Nothing- range <- getFormRange- -- The monoid for the view will look like this: [vstart, v, vend]- -- 'vstart' and 'vend' delimit the beginning and end of the inputs that- -- are converted into repeatable inputs. When we are prompting the user to- -- fill out the form, we use these delimiters to control the behavior of- -- the prompts. Anything between them will be treated as repeatable. The- -- 'vstart' item (FieldItemMultiStart) also contains the FormId of the- -- field used to count the number of entries, as well as an additional- -- description of of the multiple input itself (for example, 'Users', when- -- the contained items are used to enter in a single User.)- let vstart errs = PromptView [item]- where- item = FieldItemMultiStart id' descr $ retainErrors range errs- vend _ = PromptView [FieldItemMultiEnd]- return (View vstart `mappend` v `mappend` View vend, rs)- where- numPrompt _ = Form $ do- inp <- getFormInput- return (mempty, return (readN inp))- readN (Just x) = Ok (read x)- readN Nothing = Error []---- | Generate a prompt field for a value which can be read----promptRead :: Read a- => String -- ^ Error when the value can't be read- -> String -- ^ Description- -> Prompt a -- ^ Resulting prompt-promptRead error' descr = prompt descr `transform` transformRead error'---- Get a single line of text from the user----cliInput :: IO String-cliInput = putStr "> " >> getLine---- Get the input for a list of prompt items that have been defined.------ Notably, this supports nested 'mass input' forms (inputList/promptList)--- which are delimited by FieldItemMultiStart and FieldItemMultiEnd.--- FieldItemMultiSingle represents a tangible item to prompt for. If a--- FieldItemMultiStart is reached, we need to prompt for all of the items--- until the next FieldItemMultiEnd an arbitrary number of times.----inputForItems :: [FieldItem]- -- ^ Items to get input for- -> [(FormId, String)]- -- ^ Accumulated association list of inputs we've prompted for- -> (FormId -> FormId)- -- ^ A function to transform the FormId of this item. Used to- -- change the index of the item when prompting multiple times.- -> IO ([FieldItem], [(FormId, String)])- -- ^ A pair of the remaining items (empty if we are not- -- returning from a multiple input item) and accumulated inputs, -inputForItems [] accum _fid = return ([], accum)--inputForItems (FieldItemMultiEnd : rest) accum _fid = return (rest, accum)---- The simple case for a single item.-inputForItems (FieldItemSingle id' descr _errs : rest) accum fid = do- putStrLn descr- val <- cliInput- inputForItems rest ((fid id', val) : accum) fid---- The case for a multiple input prompt.-inputForItems (FieldItemMultiStart id' descr _errs : rest) accum fid = do- let id'' = fid id'- putStrLn $ "How many '" ++ descr ++ "' do you want to input?"- -- Leave this as a string, since we must put it into a hidden form field- -- for inputList, which must read it again. We only prompt for it here- -- instead of as a discrete form item because we want to know, right now,- -- how many the user wants to input.- nStr <- cliInput- -- Prompt for all of the delimited items, and put them at index i for this- -- multi-input item.- -- TODO use foldM- let f i = do putStrLn $ descr ++ " #" ++ show (i + 1) ++ ":"- inputForItems rest [] (modifyId id'' i)- delimited <- mapM f [0..(read nStr - 1)]- let rest' = fst $ last delimited- countfield = (id'', nStr)- inputForItems rest' ([countfield] ++ accum ++ concatMap snd delimited) fid---- Construct a function to transform the 'children' (delimited items) of a--- mass input item to the correct index.----modifyId :: FormId -> Integer -> FormId -> FormId-modifyId parent i = mapId (\x -> head x : i : formIdList parent)---- | Run a Prompt, sequentially prompting the user for each item.----runPrompt :: Prompt a- -- ^ The Prompt to run- -> IO (Either [String] a)- -- ^ A list of error strings, or the result.-runPrompt prmpt = do- prmptv <- viewForm prmpt "form"- inpmap <- InputMap . snd <$> inputForItems (unPromptView prmptv) [] id- eith <- eitherForm prmpt "form" (inputMapEnvironment inpmap)- return $ case eith of- Left v -> Left (fieldItemErrors `concatMap` unPromptView v)- Right x -> Right x---- Read the errors from a FieldItem, if any.----fieldItemErrors :: FieldItem -> [String]-fieldItemErrors (FieldItemSingle id' descr errs) =- descriptiveErrors id' descr errs-fieldItemErrors (FieldItemMultiStart id' descr errs) = -- TODO bad- descriptiveErrors id' descr errs-fieldItemErrors FieldItemMultiEnd = []--descriptiveErrors :: FormId -> String -> [String] -> [String]-descriptiveErrors id' descr errs = map str errs- where- str err = "(" ++ show id' ++ ") " ++ descr ++ ": " ++ err
− src/Text/Digestive/Common.hs
@@ -1,46 +0,0 @@--- | Functions to construct common forms----module Text.Digestive.Common- ( input- , label- , errors- , childErrors- ) where--import Text.Digestive.Types-import Text.Digestive.Result--input :: (Monad m, Functor m)- => (Bool -> Maybe i -> d -> s) -- ^ Get the viewed result- -> (Maybe i -> Result e a) -- ^ Get the returned result- -> (FormId -> s -> v) -- ^ View constructor- -> d -- ^ Default value- -> Form m i e v a -- ^ Resulting form-input toView toResult createView defaultInput = Form $ do- isInput <- isFormInput- inp <- getFormInput- id' <- getFormId- let view' = toView isInput inp defaultInput- result' = toResult inp- return (View (const $ createView id' view'), return result')--label :: Monad m- => (FormId -> v)- -> Form m i e v ()-label f = Form $ do- id' <- getFormId- return (View (const $ f id'), return (Ok ()))--errors :: Monad m- => ([e] -> v)- -> Form m i e v ()-errors f = Form $ do- range <- getFormRange- return (View (f . retainErrors range), return (Ok ()))--childErrors :: Monad m- => ([e] -> v)- -> Form m i e v ()-childErrors f = Form $ do- range <- getFormRange- return (View (f . retainChildErrors range), return (Ok ()))
+ src/Text/Digestive/Field.hs view
@@ -0,0 +1,59 @@+{-# LANGUAGE ExistentialQuantification, GADTs, OverloadedStrings #-}+module Text.Digestive.Field+ ( Field (..)+ , SomeField (..)+ , evalField+ , fieldMapView+ ) where++import Control.Arrow (second)+import Data.Maybe (fromMaybe, listToMaybe)++import Data.Text (Text)+import qualified Data.Text as T++import Text.Digestive.Types+import Text.Digestive.Util++-- | 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+ Choice :: Eq a => [(a, v)] -> Int -> Field v a+ 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 $ ls !! y) $ do+ -- Expects input in the form of @foo.bar.2@+ t <- listToMaybe $ reverse $ toPath x+ i <- readMaybe $ T.unpack t+ return $ fst $ ls !! i+evalField _ _ (Choice ls x) = fst $ ls !! 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 f) xs) i+fieldMapView _ (Bool x) = Bool x+fieldMapView _ File = File
+ src/Text/Digestive/Form.hs view
@@ -0,0 +1,92 @@+{-# LANGUAGE ExistentialQuantification, GADTs, OverloadedStrings, Rank2Types #-}+module Text.Digestive.Form+ ( Form+ , SomeForm (..)+ , (.:)++ -- * Forms+ , text+ , string+ , stringRead+ , choice+ , bool+ , file++ -- * Validation+ , check+ , checkM+ , validate+ , validateM+ ) where++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.Types+import Text.Digestive.Util++text :: Maybe Text -> Form v m Text+text def = Pure Nothing $ Text $ fromMaybe "" def++string :: Monad m => Maybe String -> Form v m String+string = fmap T.unpack . text . fmap T.pack++stringRead :: (Monad m, Read a, Show a) => v -> Maybe a -> Form v m a+stringRead err = transform readTransform . string . fmap show+ where+ readTransform = return . maybe (Error err) return . readMaybe++choice :: Eq a => [(a, v)] -> Maybe a -> Form v m a+choice items def = Pure Nothing $ Choice items $ fromMaybe 0 $+ maybe Nothing (\d -> findIndex ((== d) . fst) items) def++bool :: Bool -- ^ Default value+ -> Form v m Bool -- ^ Resulting form+bool = Pure Nothing . Bool++file :: Form v m (Maybe FilePath)+file = Pure Nothing File++-- | Validate the results of a form with a simple predicate+--+-- Example:+--+-- > check "Can't be empty" (not . null) (string Nothing)+check :: Monad m+ => v -- ^ Error message (if fail)+ -> (a -> Bool) -- ^ Validating predicate+ -> Form v m a -- ^ Form to validate+ -> Form v m a -- ^ Resulting form+check err = checkM err . (return .)++-- | Version of 'check' which allows monadic validations+checkM :: Monad m => v -> (a -> m Bool) -> Form v m a -> Form v m a+checkM err predicate form = validateM f form+ where+ f x = do+ r <- predicate x+ return $ if r then return x else Error err++-- | This is an extension of 'check' that can be used to apply transformations+-- that optionally fail+--+-- Example: taking the first character of an input string+--+-- > head' :: String -> Result String Char+-- > head' [] = Error "Is empty"+-- > head' (x : _) = Success x+-- >+-- > char :: Monad m => Form m String Char+-- > char = validate head' (string Nothing)+--+validate :: Monad m => (a -> Result v b) -> Form v m a -> Form v m b+validate = validateM . (return .)++-- | Version of 'validate' which allows monadic validations+validateM :: Monad m => (a -> m (Result v b)) -> Form v m a -> Form v m b+validateM = transform
+ src/Text/Digestive/Form/Encoding.hs view
@@ -0,0 +1,42 @@+{-# LANGUAGE GADTs #-}+module Text.Digestive.Form.Encoding+ ( FormEncType (..)+ , formEncType+ ) where++import Data.Maybe (mapMaybe)+import Data.Monoid (Monoid (..), mconcat)++import Text.Digestive.Field+import Text.Digestive.Form.Internal++data FormEncType+ = UrlEncoded+ | MultiPart+ deriving (Eq)++instance Show FormEncType where+ show UrlEncoded = "application/x-www-form-urlencoded"+ show MultiPart = "multipart/form-data"++-- Monoid instance for encoding types: prefer UrlEncoded, but fallback to+-- MultiPart when needed+instance Monoid FormEncType where+ mempty = UrlEncoded+ mappend UrlEncoded x = x+ mappend MultiPart _ = MultiPart++fieldEncType :: Field v a -> FormEncType+fieldEncType File = MultiPart+fieldEncType _ = UrlEncoded++formEncType :: Form v m a -> FormEncType+formEncType = mconcat . map fieldEncType' . fieldList+ where+ fieldEncType' (SomeField f) = fieldEncType f++fieldList :: Form v m a -> [SomeField v]+fieldList = mapMaybe toField' . fieldList' . SomeForm+ where+ fieldList' (SomeForm f) = SomeForm f : concatMap fieldList' (children f)+ toField' (SomeForm f) = toField f
+ src/Text/Digestive/Form/Internal.hs view
@@ -0,0 +1,177 @@+-- | This module mostly meant for internal usage, and might change between minor+-- releases.+{-# LANGUAGE ExistentialQuantification, GADTs, OverloadedStrings, Rank2Types #-}+module Text.Digestive.Form.Internal+ ( Form (..)+ , SomeForm (..)+ , transform+ , children+ , (.:)+ , lookupForm+ , toField+ , queryField+ , eval+ , formMapView+ ) where++import Control.Applicative (Applicative (..))+import Control.Monad ((>=>))+import Data.Maybe (maybeToList)+import Data.Monoid (Monoid)++import Data.Text (Text)+import qualified Data.Text as T++import Text.Digestive.Types+import Text.Digestive.Field++type Ref = Maybe Text++-- | Base type for a form.+--+-- The three type parameters are:+--+-- * @v@: the type for textual information, displayed to the user. For example,+-- error messages are of this type. @v@ stands for "view".+--+-- * @m@: the monad in which validators operate. The classical example is when+-- validating input requires access to a database, in which case this @m@+-- should be an instance of @MonadIO@.+--+-- * @a@: the type of the value returned by the form, used for its Applicative+-- instance.+--+data Form v m a where+ Pure :: Ref -> Field v a -> Form v m a+ App :: Ref -> Form v m (b -> a) -> Form v m b -> Form v m a++ Map :: (b -> m (Result v a)) -> Form v m b -> Form v m a++instance Monad m => Functor (Form v m) where+ fmap = transform . (return .) . (return .)++instance (Monad m, Monoid v) => Applicative (Form v m) where+ pure x = Pure Nothing (Singleton x)+ x <*> y = App Nothing x y++instance Show (Form v m a) where+ show = unlines . showForm++data SomeForm v m = forall a. SomeForm (Form v m a)++instance Show (SomeForm v m) where+ show (SomeForm f) = show f++showForm :: Form 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 ++ ")"]+ , map indent (showForm x)+ , map indent (showForm y)+ ]+ (Map _ x) -> "Map _" : map indent (showForm x)+ where+ indent = (" " ++)++transform :: Monad m => (a -> m (Result v b)) -> Form v m a -> Form v m b+transform f (Map g x) = flip Map x $ \y -> bindResult (g y) f+transform f x = Map f x++children :: Form v m a -> [SomeForm v m]+children (Pure _ _) = []+children (App _ x y) = [SomeForm x, SomeForm y]+children (Map _ x) = children x++setRef :: Ref -> Form v m a -> Form 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)++-- | Operator to set a name for a subform.+(.:) :: Text -> Form v m a -> Form v m a+(.:) = setRef . Just+infixr 5 .:++getRef :: Form v m a -> Ref+getRef (Pure r _) = r+getRef (App r _ _) = r+getRef (Map _ x) = getRef x++lookupForm :: Path -> Form v m a -> [SomeForm v m]+lookupForm path = go path . SomeForm+ where+ 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]+ | r == r' -> children form >>= go rs+ | otherwise -> []+ Nothing -> children form >>= go (r : rs)++toField :: Form v m a -> Maybe (SomeField v)+toField (Pure _ x) = Just (SomeField x)+toField (Map _ x) = toField x+toField _ = Nothing++queryField :: Path+ -> Form v m a+ -> (forall b. Field v b -> c)+ -> c+queryField path form f = case lookupForm path form of+ [] -> error $ ref ++ " does not exist"+ (SomeForm form' : _) -> case toField form' of+ Just (SomeField field) -> f field+ _ -> error $ ref ++ " is not a field"+ where+ ref = T.unpack $ fromPath path++ann :: Path -> Result v a -> Result [(Path, v)] a+ann _ (Success x) = Success x+ann path (Error x) = Error [(path, x)]++eval :: Monad m => Method -> Env m -> Form v m a+ -> m (Result [(Path, v)] a, [(Path, FormInput)])+eval = eval' []++eval' :: Monad m => Path -> Method -> Env m -> Form v m a+ -> m (Result [(Path, v)] a, [(Path, FormInput)])++eval' context method env form = case form of++ Pure Nothing _ ->+ error "No ref specified for field"++ Pure (Just _) field -> do+ val <- env path+ let x = evalField method val field+ return $ (pure x, [(path, v) | v <- val])++ 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'' <- bindResult (return x') (f >=> return . ann path)+ return (x'', inp)++ where+ path = context ++ maybeToList (getRef form)++formMapView :: Monad m => (v -> w) -> Form v m a -> Form 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 (Map g x) = Map (g >=> return . resultMapError f) (formMapView f x)++-- | Utility: bind for 'Result' inside another monad+bindResult :: Monad m+ => m (Result v a) -> (a -> m (Result v b)) -> m (Result v b)+bindResult mx f = do+ x <- mx+ case x of+ Error errs -> return $ Error errs+ Success x' -> f x'
− src/Text/Digestive/Forms.hs
@@ -1,208 +0,0 @@-{-# LANGUAGE MultiParamTypeClasses, FunctionalDependencies,- NoMonomorphismRestriction #-}-module Text.Digestive.Forms- ( FormInput (..)- , inputString- , inputText- , inputRead- , inputBool- , inputChoice- , inputChoices- , inputFile- , inputList- ) where--import Control.Applicative ((<$>))-import Control.Monad (liftM, mplus)-import Control.Monad.State (put, get)-import Control.Monad.Trans (lift)-import Data.Monoid (Monoid, mappend, mconcat)-import Data.Maybe (fromMaybe, listToMaybe, mapMaybe)--import Data.Text (Text)-import qualified Data.Text as T (pack, empty)--import Text.Digestive.Common-import Text.Digestive.Types-import Text.Digestive.Result-import Text.Digestive.Transform---- | Class which all backends should implement. @i@ is here the type that is--- used to represent a value uploaded by the client in the request----class FormInput i f | i -> f where- -- | Parse the input into a string. This is used for simple text fields- -- among other things- --- getInputString :: i -> Maybe String- getInputString = listToMaybe . getInputStrings-- -- | Should be implemented- --- getInputStrings :: i -> [String]-- -- | Parse the input value into 'Text'- --- getInputText :: i -> Maybe Text- getInputText = listToMaybe . getInputTexts-- -- | Can be overriden for efficiency concerns- --- getInputTexts :: i -> [Text]- getInputTexts = map T.pack . getInputStrings-- -- | Get a file descriptor for an uploaded file- --- getInputFile :: i -> Maybe f--inputString :: (Monad m, Functor m, FormInput i f)- => (FormId -> Maybe String -> v) -- ^ View constructor- -> Maybe String -- ^ Default value- -> Form m i e v String -- ^ Resulting form-inputString = input toView toResult- where- toView _ inp def = (getInputString =<< inp) `mplus` def- toResult = Ok . fromMaybe "" . (getInputString =<<)--inputText :: (Monad m, Functor m, FormInput i f)- => (FormId -> Maybe Text -> v) -- ^ View constructor- -> Maybe Text -- ^ Default value- -> Form m i e v Text -- ^ Resulting form-inputText = input toView toResult- where- toView _ inp def = (getInputText =<< inp) `mplus` def- toResult = Ok . fromMaybe T.empty . (getInputText =<<)--inputRead :: (Monad m, Functor m, FormInput i f, Read a, Show a)- => (FormId -> Maybe String -> v) -- ^ View constructor- -> e -- ^ Error when no read- -> Maybe a -- ^ Default input- -> Form m i e v a -- ^ Resulting form-inputRead cons' error' def = inputString cons' (fmap show def)- `transform` transformRead error'--inputBool :: (Monad m, Functor m, FormInput i f)- => (FormId -> Bool -> v) -- ^ View constructor- -> Bool -- ^ Default input- -> Form m i e v Bool -- ^ Resulting form-inputBool = input toView toResult- where- toView isInput inp def- | isInput = readBool (getInputString =<< inp)- | otherwise = def- toResult inp = Ok $ readBool (getInputString =<< inp)- readBool (Just x) = not $ null x- readBool _ = False--inputChoice :: (Monad m, Functor m, FormInput i f, Monoid v, Eq a)- => (FormId -> String -> Bool -> a -> v) -- ^ Choice constructor- -> a -- ^ Default option- -> [a] -- ^ Choices- -> Form m i e v a -- ^ Resulting form-inputChoice toView defaultInput choices = Form $ do- inputKey <- fromMaybe "" . (getInputString =<<) <$> getFormInput- id' <- getFormId- let -- Find the actual input, based on the key, or use the default input- inp = fromMaybe defaultInput $ lookup inputKey $ zip (ids id') choices- -- Apply the toView' function to all choices- view' = mconcat $ zipWith (toView' id' inp) (ids id') choices- return (View (const view'), return (Ok inp))- where- ids id' = map (((show id' ++ "-") ++) . show) [1 .. length choices]- toView' id' inp key x = toView id' key (inp == x) x---- An input element that allows multiple selections, such as--- checkboxes or multiple-select boxes.------ When multiple results are submitted, they should all have the same--- name attribute.-inputChoices :: (Monad m, Functor m, FormInput i f, Monoid v, Eq a)- => (FormId -> String -> Bool -> a -> v) -- ^ Choice constructor- -> [a] -- ^ Default choices- -> [a] -- ^ Choices- -> Form m i e v [a] -- ^ Resulting form-inputChoices toView defaults choices = Form $ do- inputKeys <- maybe [] getInputStrings <$> getFormInput- id' <- getFormId- formInput <- isFormInput- let -- Find the actual input, based on the key, or use the default input- inps = if formInput - then mapMaybe (\inputKey -> lookup inputKey $ zip (ids id') choices) inputKeys- else defaults- -- Apply the toView' function to all choices- view' = mconcat $ zipWith (toView' id' inps) (ids id') choices- return (View (const view'), return (Ok inps))- where- ids id' = map (((show id' ++ "-") ++) . show) [1 .. length choices]- toView' id' inps key x = toView id' key (x `elem` inps) x--inputFile :: (Monad m, Functor m, FormInput i f)- => (FormId -> v) -- ^ View constructor- -> Form m i e v (Maybe f) -- ^ Resulting form-inputFile viewCons = input toView toResult viewCons' ()- where- toView _ _ _ = ()- toResult inp = Ok $ getInputFile =<< inp- viewCons' id' () = viewCons id'--up :: Monad m => Int -> FormState m i ()-up n = do- FormRange s _ <- get- put $ unitRange $ mapId ((!!n) . iterate tail) s--down :: Monad m => Int -> FormState m i ()-down n = do- FormRange s _ <- get- put $ unitRange $ mapId ((!!n) . iterate (0:)) s---- | Converts a formlet representing a single item into a formlet representing a--- dynamically sized list of those items. It requires that the user specify a--- formlet to hold the length of the list. Typically this will be a hidden--- field that is automatically updated by client-side javascript.------ The field names must be generated as follows. Assume that if inputList had--- not been used, the field name would have been prefix-f5. In this case, the--- list length field name will be prefix-f5. The first item in the list will--- receive field names starting at prefix-f5.0.0. If each item is a composed--- form with two fields, those fields will have the names prefix-f5.0.0 and--- prefix-f5.0.1. The field names for the second item will be prefix-f5.1.0--- and prefix-f5.1.1.----inputList :: (Monad m, Monoid v)- => Formlet m i e v Int -- ^ A formlet for the list length- -> Formlet m i e v a -- ^ The formlet for a single list element- -> Formlet m i e v [a] -- ^ The dynamic list formlet-inputList countField single defaults = Form $ do- let defCount = maybe 1 length defaults- (countView, mcountRes) <- unForm $ countField (Just defCount)-- -- We need to evaluate the count- countRes <- lift $ lift mcountRes- let count = fromMaybe defCount (getResult countRes)-- -- Use the provided defaults, then loop Nothing- defaults' = map Just (fromMaybe [] defaults) ++ repeat Nothing-- -- Apply the single form to the defaults- forms = zipWith ($) (replicate count single) defaults'- down 2- list <- mapM (incAfter . unForm) forms- up 2-- return ( countView `mappend` (mconcat $ map fst list)- , liftM (combineResults [] []) . sequence $ map snd list- )- where- incAfter k = do- res <- k- up 1 >> incState >> down 1- return res-- combineResults es os [] =- case es of- [] -> Ok $ reverse os- _ -> Error es- combineResults es os (r:rs) =- case r of- Error es' -> combineResults (es ++ es') os rs- Ok o -> combineResults es (o:os) rs
− src/Text/Digestive/Forms/Html.hs
@@ -1,133 +0,0 @@--- | General functions for forms that are rendered to some sort of HTML----module Text.Digestive.Forms.Html- ( FormHtmlConfig (..)- , FormEncType (..)- , FormHtml (..)- , createFormHtml- , createFormHtmlWith- , viewHtml- , mapViewHtml- , applyClasses- , defaultHtmlConfig- , emptyHtmlConfig- , renderFormHtml- , renderFormHtmlWith- ) where--import Data.Monoid (Monoid (..))-import Data.List (intercalate)-import Control.Applicative ((<*>), pure)-import Control.Arrow ((&&&))--import Text.Digestive.Types (Form, mapView, view)---- | Settings for classes in generated HTML.----data FormHtmlConfig = FormHtmlConfig- { htmlInputClasses :: [String] -- ^ Classes applied to input elements- , htmlSubmitClasses :: [String] -- ^ Classes applied to submit buttons- , htmlLabelClasses :: [String] -- ^ Classes applied to labels- , htmlErrorClasses :: [String] -- ^ Classes applied to errors- , htmlErrorListClasses :: [String] -- ^ Classes for error lists- } deriving (Show)---- | Encoding type for the form----data FormEncType = UrlEncoded- | MultiPart- deriving (Eq)--instance Show FormEncType where- show UrlEncoded = "application/x-www-form-urlencoded"- show MultiPart = "multipart/form-data"---- Monoid instance for encoding types: prefer UrlEncoded, but fallback to--- MultiPart when needed-instance Monoid FormEncType where- mempty = UrlEncoded- mappend UrlEncoded x = x- mappend MultiPart _ = MultiPart---- | HTML describing a form----data FormHtml a = FormHtml- { formEncType :: FormEncType- , formHtml :: FormHtmlConfig -> a- }--instance Monoid a => Monoid (FormHtml a) where- mempty = FormHtml mempty $ const mempty- mappend (FormHtml x f) (FormHtml y g) =- FormHtml (x `mappend` y) $ f `mappend` g--instance Functor FormHtml where- fmap f (FormHtml e g) = FormHtml e (f . g)---- | Create form HTML with the default encoding type----createFormHtml :: (FormHtmlConfig -> a) -> FormHtml a-createFormHtml = FormHtml mempty---- | Create form HTML with a custom encoding type----createFormHtmlWith :: FormEncType -> (FormHtmlConfig -> a) -> FormHtml a-createFormHtmlWith = FormHtml---- | A shortcut for inserting HTML to the view, defined as a combination of--- 'view' and 'createFormHtml'----viewHtml :: Monad m => a -> Form m i e (FormHtml a) ()-viewHtml = view . createFormHtml . const---- | Lifted version of 'mapView'----mapViewHtml :: (Monad m, Functor m)- => (v -> w) -- ^ Map over the contained HTML- -> Form m i e (FormHtml v) a -- ^ Initial form- -> Form m i e (FormHtml w) a -- ^ Resulting form-mapViewHtml f = mapView $ \(FormHtml e h) -> FormHtml e (f . h)---- | Apply all classes to an HTML element. If no classes are found, nothing--- happens.----applyClasses :: (a -> String -> a) -- ^ Apply the class attribute- -> [FormHtmlConfig -> [String]] -- ^ Labels to apply- -> FormHtmlConfig -- ^ Label configuration- -> a -- ^ HTML element- -> a -- ^ Resulting element-applyClasses applyAttribute fs cfg element = case concat (fs <*> pure cfg) of- [] -> element -- No labels to apply- classes -> applyAttribute element $ intercalate " " classes---- | Default configuration----defaultHtmlConfig :: FormHtmlConfig-defaultHtmlConfig = FormHtmlConfig- { htmlInputClasses = ["digestive-input"]- , htmlSubmitClasses = ["digestive-submit"]- , htmlLabelClasses = ["digestive-label"]- , htmlErrorClasses = ["digestive-error"]- , htmlErrorListClasses = ["digestive-error-list"]- }---- | Empty configuration (no classes are set)----emptyHtmlConfig :: FormHtmlConfig-emptyHtmlConfig = FormHtmlConfig- { htmlInputClasses = []- , htmlSubmitClasses = []- , htmlLabelClasses = []- , htmlErrorClasses = []- , htmlErrorListClasses = []- }---- | Render FormHtml using the default configuration----renderFormHtml :: FormHtml a -> (a, FormEncType)-renderFormHtml = renderFormHtmlWith defaultHtmlConfig---- | Render FormHtml using a custom configuration----renderFormHtmlWith :: FormHtmlConfig -> FormHtml a -> (a, FormEncType)-renderFormHtmlWith cfg = ($ cfg) . formHtml &&& formEncType
− src/Text/Digestive/Result.hs
@@ -1,113 +0,0 @@--- | Module for the core result type, and related functions----module Text.Digestive.Result- ( Result (..)- , getResult- , FormId- , zeroId- , mapId- , formIdList- , FormRange (..)- , incrementFormId- , unitRange- , isInRange- , isSubRange- , retainErrors- , retainChildErrors- ) where--import Control.Applicative (Applicative (..))-import Data.List (intercalate)---- | Type for failing computations----data Result e ok = Error [(FormRange, e)]- | Ok ok- deriving (Show, Eq)--instance Functor (Result e) where- fmap _ (Error x) = Error x- fmap f (Ok x) = Ok (f x)--instance Monad (Result e) where- return = Ok- Error x >>= _ = Error x- Ok x >>= f = f x--instance Applicative (Result e) where- pure = Ok- Error x <*> Error y = Error $ x ++ y- Error x <*> Ok _ = Error x- Ok _ <*> Error y = Error y- Ok x <*> Ok y = Ok $ x y--getResult :: Result e ok -> Maybe ok-getResult (Error _) = Nothing-getResult (Ok r) = Just r---- | An ID used to identify forms----data FormId = FormId- { -- | Global prefix for the form- formPrefix :: String- , -- | Stack indicating field. Head is most specific to this item- formIdList :: [Integer]- } deriving (Eq, Ord)---- | The zero ID, i.e. the first ID that is usable----zeroId :: String -> FormId-zeroId p = FormId- { formPrefix = p- , formIdList = [0]- }--mapId :: ([Integer] -> [Integer]) -> FormId -> FormId-mapId f (FormId p is) = FormId p $ f is--instance Show FormId where- show (FormId p xs) =- p ++ "-fval[" ++ (intercalate "." $ reverse $ map show xs) ++ "]"--formId :: FormId -> Integer-formId = head . formIdList---- | A range of ID's to specify a group of forms----data FormRange = FormRange FormId FormId- deriving (Eq, Show)---- | Increment a form ID----incrementFormId :: FormId -> FormId-incrementFormId (FormId p (x:xs)) = FormId p $ (x + 1):xs-incrementFormId (FormId _ []) = error "Bad FormId list"--unitRange :: FormId -> FormRange-unitRange i = FormRange i $ incrementFormId i---- | Check if a 'FormId' is contained in a 'FormRange'----isInRange :: FormId -- ^ Id to check for- -> FormRange -- ^ Range- -> Bool -- ^ If the range contains the id-isInRange a (FormRange b c) = formId a >= formId b && formId a < formId c---- | Check if a 'FormRange' is contained in another 'FormRange'----isSubRange :: FormRange -- ^ Sub-range- -> FormRange -- ^ Larger range- -> Bool -- ^ If the sub-range is contained in the larger range-isSubRange (FormRange a b) (FormRange c d) = formId a >= formId c- && formId b <= formId d---- | Select the errors for a certain range----retainErrors :: FormRange -> [(FormRange, e)] -> [e]-retainErrors range = map snd . filter ((== range) . fst)---- | Select the errors originating from this form or from any of the children of--- this form----retainChildErrors :: FormRange -> [(FormRange, e)] -> [e]-retainChildErrors range = map snd . filter ((`isSubRange` range) . fst)
− src/Text/Digestive/Transform.hs
@@ -1,91 +0,0 @@--- | Optionally failing transformers for forms----module Text.Digestive.Transform- ( Transformer (..)- , transform- , transformFormlet- , transformEither- , transformEitherM- , transformRead- , required- ) where--import Prelude hiding ((.), id)--import Control.Monad ((<=<))-import Control.Category (Category, (.), id)-import Control.Arrow (Arrow, arr, first)--import Text.Digestive.Result-import Text.Digestive.Types---- | A transformer that transforms a value of type a to a value of type b----newtype Transformer m e a b = Transformer- { unTransformer :: a -> m (Either [e] b)- }--instance Monad m => Category (Transformer m e) where- id = Transformer $ return . Right- f . g = Transformer $ either (return . Left) (unTransformer f)- <=< unTransformer g--instance Monad m => Arrow (Transformer m e) where- arr f = Transformer $ return . Right . f- first t = Transformer $ \(x, y) -> unTransformer t x >>=- return . either Left (Right . (flip (,) y))---- | Apply a transformer to a form----transform :: Monad m => Form m i e v a -> Transformer m e a b -> Form m i e v b-transform form transformer = Form $ do- (v, r) <- unForm form- range <- getFormRange- return (v, r >>= transform' range)- where- -- We already have an error, cannot continue- transform' _ (Error e) = return (Error e)- -- Apply transformer- transform' range (Ok x) = do- ex <- unTransformer transformer x- return $ case ex of- -- Attach the range information to the errors- Left e -> Error $ map ((,) range) e- -- All fine- Right x' -> Ok x'---- | Apply a transformer to a formlet----transformFormlet :: Monad m- => (b -> a) -- ^ Needed to produce defaults- -> Formlet m i e v a -- ^ Formlet to transform- -> Transformer m e a b -- ^ Transformer- -> Formlet m i e v b -- ^ Resulting formlet-transformFormlet f formlet transformer def =- formlet (fmap f def) `transform` transformer---- | Build a transformer from a simple function that returns an 'Either' result.----transformEither :: Monad m => (a -> Either e b) -> Transformer m e a b-transformEither f = transformEitherM $ return . f---- | A monadic version of 'transformEither'----transformEitherM :: Monad m => (a -> m (Either e b)) -> Transformer m e a b-transformEitherM f = Transformer $ - return . either (Left . return) (Right . id) <=< f---- | Create a transformer for any value of type a that is an instance of 'Read'----transformRead :: (Monad m, Read a)- => e -- ^ Error given if read fails- -> Transformer m e String a -- ^ Resulting transformer-transformRead error' = transformEither $ \str -> case readsPrec 1 str of- [(x, "")] -> Right x- _ -> Left error'---- | A transformer that converts 'Maybe a' to 'a'.-required :: (Monad m) => - e -- ^ error to return if value is 'Nothing'- -> Transformer m e (Maybe a) a-required err = transformEither $ maybe (Left err) Right
src/Text/Digestive/Types.hs view
@@ -1,232 +1,70 @@--- | Core types----{-# LANGUAGE GeneralizedNewtypeDeriving #-}+{-# LANGUAGE OverloadedStrings #-} module Text.Digestive.Types- ( View (..)- , Environment (..)- , fromList- , FormState- , getFormId- , getFormRange- , getFormInput- , getFormInput'- , isFormInput- , Form (..)- , Formlet- , bracketState- , incState- , view- , (++>)- , (<++)- , mapView- , runForm- , runViewForm- , eitherForm- , viewForm+ ( Result (..)+ , resultMapError+ , Path+ , toPath+ , fromPath+ , Method (..)+ , FormInput (..)+ , Env ) where -import Data.Monoid (Monoid (..))-import Control.Arrow (first)-import Control.Monad (liftM, liftM2, mplus)-import Control.Monad.Reader (ReaderT, ask, runReaderT)-import Control.Monad.State (StateT, get, put, evalStateT)-import Control.Monad.Trans (lift) import Control.Applicative (Applicative (..))--import Text.Digestive.Result---- | A view represents a visual representation of a form. It is composed of a--- function which takes a list of all errors and then produces a new view----newtype View e v = View- { unView :: [(FormRange, e)] -> v- } deriving (Monoid)--instance Functor (View e) where- fmap f (View g) = View $ f . g---- | The environment is where you get the actual input per form. The environment--- itself is optional----data Environment m i = Environment (FormId -> m (Maybe i))- | NoEnvironment--instance Monad m => Monoid (Environment m i) where- mempty = NoEnvironment- NoEnvironment `mappend` x = x- x `mappend` NoEnvironment = x- (Environment env1) `mappend` (Environment env2) = Environment $ \id' ->- liftM2 mplus (env1 id') (env2 id')---- | Create an environment from a lookup table----fromList :: Monad m => [(FormId, i)] -> Environment m i-fromList list = Environment $ return . flip lookup list---- | The form state is a state monad under which our applicatives are composed----type FormState m i a = ReaderT (Environment m i) (StateT FormRange m) a---- | Utility function: returns the current 'FormId'. This will only make sense--- if the form is not composed----getFormId :: Monad m => FormState m i FormId-getFormId = do- FormRange x _ <- get- return x---- | Utility function: Get the current range----getFormRange :: Monad m => FormState m i FormRange-getFormRange = get---- | Utility function: Get the current input----getFormInput :: Monad m => FormState m i (Maybe i)-getFormInput = getFormId >>= getFormInput'---- | Gets the input of an arbitrary FormId.----getFormInput' :: Monad m => FormId -> FormState m i (Maybe i)-getFormInput' id' = do- env <- ask- case env of Environment f -> lift $ lift $ f id'- NoEnvironment -> return Nothing---- | Check if any form input is present----isFormInput :: Monad m => FormState m i Bool-isFormInput = ask >>= \env -> return $ case env of- Environment _ -> True- NoEnvironment -> False---- | A form represents a number of composed fields----newtype Form m i e v a = Form- { unForm :: FormState m i (View e v, m (Result e a))- }---- | A function for generating forms with an optional default value.----type Formlet m i e v a = Maybe a -> Form m i e v a--instance Monad m => Functor (Form m i e v) where- fmap f form = Form $ do- (view', result) <- unForm form- return (view', liftM (fmap f) result)--instance (Monad m, Monoid v) => Applicative (Form m i e v) where- pure x = Form $ return (mempty, return (return x))- f1 <*> f2 = Form $ do- -- Assuming f1 already has a valid ID- ((v1,r1), (v2,r2)) <- bracketState $ do- res1 <- unForm f1- incState- res2 <- unForm f2- return (res1, res2)- return (v1 `mappend` v2, liftM2 (<*>) r1 r2)+import Data.Monoid (Monoid, mappend) -bracketState :: Monad m => FormState m i a -> FormState m i a-bracketState k = do- FormRange startF1 _ <- get- res <- k- FormRange _ endF2 <- get- put $ FormRange startF1 endF2- return res+import Data.Text (Text)+import qualified Data.Text as T -incState :: Monad m => FormState m i ()-incState = do- FormRange _ endF1 <- get- put $ unitRange endF1+-- | A mostly internally used type for representing Success/Error, with a+-- special applicative instance+data Result v a+ = Success a+ | Error v+ deriving (Show) --- | Insert a view into the functor----view :: Monad m- => v -- ^ View to insert- -> Form m i e v () -- ^ Resulting form-view view' = Form $ return (View (const view'), return (Ok ()))+instance Functor (Result v) where+ fmap f (Success x) = Success (f x)+ fmap _ (Error x) = Error x --- | Append a unit form to the left. This is useful for adding labels or error--- fields----(++>) :: (Monad m, Monoid v)- => Form m i e v ()- -> Form m i e v a- -> Form m i e v a-f1 ++> f2 = Form $ do- -- Evaluate the form that matters first, so we have a correct range set- (v2, r) <- unForm f2- (v1, _) <- unForm f1- return (v1 `mappend` v2, r)+instance Monoid v => Applicative (Result v) where+ pure x = Success x+ Error x <*> Error y = Error $ mappend x y+ Error x <*> Success _ = Error x+ Success _ <*> Error y = Error y+ Success x <*> Success y = Success (x y) -infixl 6 ++>+instance Monad (Result v) where+ return x = Success x+ (Error x) >>= _ = Error x+ (Success x) >>= f = f x --- | Append a unit form to the right. See '++>'.----(<++) :: (Monad m, Monoid v)- => Form m i e v a- -> Form m i e v ()- -> Form m i e v a-f1 <++ f2 = Form $ do- -- Evaluate the form that matters first, so we have a correct range set- (v1, r) <- unForm f1- (v2, _) <- unForm f2- return (v1 `mappend` v2, r)+-- | Map over the error type of a 'Result'+resultMapError :: (v -> w) -> Result v a -> Result w a+resultMapError f (Error x) = Error (f x)+resultMapError _ (Success x) = Success x -infixr 5 <+++-- | Describes a path to a subform+type Path = [Text] --- | Change the view of a form using a simple function----mapView :: (Monad m, Functor m)- => (v -> w) -- ^ Manipulator- -> Form m i e v a -- ^ Initial form- -> Form m i e w a -- ^ Resulting form-mapView f = Form . fmap (first $ fmap f) . unForm+-- | Create a 'Path' from some text+toPath :: Text -> Path+toPath = filter (not . T.null) . T.split (== '.') --- | Run a form----runForm :: Monad m- => Form m i e v a -- ^ Form to run- -> String -- ^ Identifier for the form- -> Environment m i -- ^ Input environment- -> m (View e v, m (Result e a)) -- ^ Result-runForm form id' env = evalStateT (runReaderT (unForm form) env) $- unitRange $ zeroId id'+-- | Serialize a 'Path' to 'Text'+fromPath :: Path -> Text+fromPath = T.intercalate "." --- | Evaluate a form to it's view if it fails----eitherForm :: Monad m- => Form m i e v a -- ^ Form to run- -> String -- ^ Identifier for the form- -> Environment m i -- ^ Input environment- -> m (Either v a) -- ^ Result-eitherForm form id' env = do- (view', mresult) <- runForm form id' env- result <- mresult- return $ case result of- Error e -> Left $ unView view' e- Ok x -> Right x+-- | The HTTP methods+data Method = Get | Post+ deriving (Eq, Ord, Show) --- | Evaluate a form, return view and a result if successful-runViewForm :: Monad m- => Form m i e v a- -> String- -> Environment m i- -> m (v, Maybe a)-runViewForm form id' env = do- (view', mresult) <- runForm form id' env- result <- mresult- return $ case result of- Error e -> (unView view' e, Nothing)- Ok x -> (unView view' [], Just x)+-- | The different input types sent by the browser+data FormInput+ = TextInput Text+ | FileInput FilePath+ deriving (Show) --- | Just evaluate the form to a view. This usually maps to a GET request in the--- browser.----viewForm :: Monad m- => Form m i e v a -- ^ Form to run- -> String -- ^ Identifier for the form- -> m v -- ^ Result-viewForm form id' = do- (view', _) <- runForm form id' NoEnvironment- return $ unView view' []+-- | An environment (e.g. a server) from which we can read input parameters. A+-- single key might be associated with multiple text values (multi-select).+type Env m = Path -> m [FormInput]
+ src/Text/Digestive/Util.hs view
@@ -0,0 +1,9 @@+module Text.Digestive.Util+ ( readMaybe+ ) where++-- | 'read' in the 'Maybe' monad.+readMaybe :: Read a => String -> Maybe a+readMaybe str = case readsPrec 1 str of+ [(x, "")] -> Just x+ _ -> Nothing
− src/Text/Digestive/Validate.hs
@@ -1,62 +0,0 @@--- | Validators that can be attached to forms----module Text.Digestive.Validate- ( Validator- , validate- , validateMany- , check- , checkM- ) where--import Prelude hiding (id)--import Control.Monad (liftM2)-import Data.Monoid (Monoid (..))-import Control.Category (id)--import Text.Digestive.Types-import Text.Digestive.Transform---- | A validator. Invariant: the validator should not modify the result value,--- only check it.----newtype Validator m e a = Validator {unValidator :: Transformer m e a a}--instance Monad m => Monoid (Validator m e a) where- mempty = Validator id- v1 `mappend` v2 = Validator $ Transformer $ \inp ->- liftM2 eitherPlus (unTransformer (unValidator v1) inp)- (unTransformer (unValidator v2) inp)- where- eitherPlus (Left e) (Left i) = Left $ e ++ i- eitherPlus (Left e) (Right _) = Left e- eitherPlus (Right _) (Left e) = Left e- eitherPlus (Right a) (Right _) = Right a---- | Attach a validator to a form.----validate :: Monad m => Form m i e v a -> Validator m e a -> Form m i e v a-validate form = transform form . unValidator---- | Attach multiple validators to a form.----validateMany :: Monad m => Form m i e v a -> [Validator m e a] -> Form m i e v a-validateMany form = validate form . mconcat---- | Easy way to create a pure validator----check :: Monad m- => e -- ^ Error message- -> (a -> Bool) -- ^ Actual validation- -> Validator m e a -- ^ Resulting validator-check error' = checkM error' . (return .)---- | Easy way to create a monadic validator----checkM :: Monad m- => e -- ^ Error message- -> (a -> m Bool) -- ^ Actual validation- -> Validator m e a -- ^ Resulting validator-checkM error' f = Validator $ Transformer $ \x -> do- valid <- f x- return $ if valid then Right x else Left [error']
+ src/Text/Digestive/View.hs view
@@ -0,0 +1,148 @@+{-# LANGUAGE ExistentialQuantification, GADTs, OverloadedStrings,+ ScopedTypeVariables #-}+module Text.Digestive.View+ ( View (..)++ -- * Obtaining a view+ , getForm+ , postForm++ -- * Operations on views+ , subView++ -- * Querying a view+ -- ** Low-level+ , absolutePath++ -- ** Form encoding+ , viewEncType++ -- ** Input+ , fieldInputText+ , fieldInputChoice+ , fieldInputBool+ , fieldInputFile++ -- ** Errors+ , errors+ , childErrors+ ) where++import Control.Arrow (second)+import Data.List (findIndex, isPrefixOf)+import Data.Maybe (fromMaybe)++import Data.Text (Text)++import Text.Digestive.Field+import Text.Digestive.Form.Encoding+import Text.Digestive.Form.Internal+import Text.Digestive.Types++data View v = forall a m. Monad m => View+ { viewName :: Text+ , viewContext :: Path+ , viewForm :: Form v m a+ , viewInput :: [(Path, FormInput)]+ , viewErrors :: [(Path, v)]+ , viewMethod :: Method+ }++instance Functor View where+ fmap f (View name ctx form input errs method) = View+ name ctx (formMapView f form) input (map (second f) errs) method++instance Show v => Show (View v) where+ show (View name ctx form input errs method) =+ "View " ++ show name ++ " " ++ show ctx ++ " " ++ show form ++ " " +++ show input ++ " " ++ show errs ++ " " ++ show method++getForm :: Monad m => Text -> Form v m a -> View v+getForm name form = View name [] form [] [] Get++postForm :: Monad m => Text -> Form v m a -> Env m -> m (View v, Maybe a)+postForm name form env = eval Post env' form >>= \(r, inp) -> return $ case r of+ Error errs -> (View name [] form inp errs Post, Nothing)+ Success x -> (View name [] form inp [] Post, Just x)+ where+ env' = env . (name :)++subView :: Text -> View v -> View v+subView ref (View name ctx form input errs method) =+ View name (ctx ++ path) form input errs method+ where+ path = toPath ref++-- | Determine an absolute 'Path' for a field in the form+absolutePath :: Text -> View v -> Path+absolutePath ref view@(View name _ _ _ _ _) = name : viewPath ref view++-- | 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 _ _ _) = formEncType form++lookupInput :: Path -> [(Path, FormInput)] -> [FormInput]+lookupInput path = map snd . filter ((== path) . fst)++fieldInputText :: forall v. Text -> View v -> Text+fieldInputText ref view@(View _ _ form input _ method) =+ queryField path form eval'+ where+ path = viewPath ref view+ givenInput = lookupInput path input++ eval' :: Field v b -> Text+ eval' field = case field of+ Text t -> evalField method givenInput (Text t)+ _ -> "" -- TODO: perhaps throw error?++fieldInputChoice :: forall v. Text -> View v -> ([v], Int)+fieldInputChoice ref view@(View _ _ form input _ method) =+ queryField path form eval'+ where+ path = viewPath ref view+ givenInput = lookupInput path input++ eval' :: Field v b -> ([v], Int)+ eval' field = case field of+ Choice xs i ->+ let x = evalField method givenInput (Choice xs i)+ idx = fromMaybe 0 $ findIndex (== x) (map fst xs)+ in (map snd xs, idx)+ _ -> ([], 0) -- TODO: perhaps throw error?++fieldInputBool :: forall v. Text -> View v -> Bool+fieldInputBool ref view@(View _ _ form input _ method) =+ queryField path form eval'+ where+ path = viewPath ref view+ givenInput = lookupInput path input++ eval' :: Field v b -> Bool+ eval' field = case field of+ Bool x -> evalField method givenInput (Bool x)+ _ -> False -- TODO: perhaps throw error?++fieldInputFile :: forall v. Text -> View v -> Maybe FilePath+fieldInputFile ref view@(View _ _ form input _ method) =+ queryField path form eval'+ where+ path = viewPath ref view+ givenInput = lookupInput path input++ eval' :: Field v b -> Maybe FilePath+ eval' field = case field of+ File -> evalField method givenInput File+ _ -> Nothing -- TODO: perhaps throw error?++errors :: Text -> View v -> [v]+errors ref view = map snd $ filter ((== viewPath ref view) . fst) $+ viewErrors view++childErrors :: Text -> View v -> [v]+childErrors ref view = map snd $+ filter ((viewPath ref view `isPrefixOf`) . fst) $ viewErrors view
+ tests/TestSuite.hs view
@@ -0,0 +1,16 @@+module Main+ ( main+ ) where++import Test.Framework (defaultMain)++import qualified Text.Digestive.Field.Tests (tests)+import qualified Text.Digestive.Form.Encoding.Tests (tests)+import qualified Text.Digestive.View.Tests (tests)++main :: IO ()+main = defaultMain+ [ Text.Digestive.Field.Tests.tests+ , Text.Digestive.Form.Encoding.Tests.tests+ , Text.Digestive.View.Tests.tests+ ]