packages feed

regular-web (empty) → 0.1

raw patch · 10 files changed

+461/−0 lines, 10 filesdep +applicative-extrasdep +basedep +fclabelssetup-changedbinary-added

Dependencies added: applicative-extras, base, fclabels, formlets, json, mtl, regular, xhtml

Files

+ .DS_Store view

binary file changed (absent → 6148 bytes)

+ LICENSE view
@@ -0,0 +1,28 @@+All rights reserved.++Redistribution and use in source and binary forms, with or without+modification, are permitted provided that the following conditions are met:++    * Redistributions of source code must retain the above copyright+      notice, this list of conditions and the following disclaimer.++    * Redistributions in binary form must reproduce the above+      copyright notice, this list of conditions and the following+      disclaimer in the documentation and/or other materials provided+      with the distribution.++    * Neither the name of Tupil nor the names of other+      contributors may be used to endorse or promote products derived+      from this software without specific prior written permission.++THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS+"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT+LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR+A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT+OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,+SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT+LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,+DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY+THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT+(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ Setup.hs view
@@ -0,0 +1,2 @@+import Distribution.Simple+main = defaultMain
+ regular-web.cabal view
@@ -0,0 +1,78 @@+Name:            regular-web+Version:         0.1+Synopsis:        Generic programming for the web+License:         BSD3+License-file:    LICENSE+Category:        Generics, Web+Copyright:       (c) Chris Eidhof+Author:          Chris Eidhof+Maintainer:      Chris Eidhof <chris+hackage@eidhof.nl>+Homepage:        http://github.com/chriseidhof/basil+Exposed-Modules:   Generics.Regular.Formlets+                 , Generics.Regular.Views+                 , Generics.Regular.JSON+Other-Modules:   Generics.Regular.Extras+Build-Type:      Simple+hs-source-dirs:  src+Build-Depends:   base >= 4 && < 5, +                 mtl,+                 xhtml,+                 formlets == 0.6.1,+                 applicative-extras,+                 regular >= 0.1.0.2,+                 fclabels >= 0.4.2,+                 json >= 0.4.3++Description: +  This package implements generic functions for web programming.+  Based on the @regular@ library [1], we provide generic functions for generating @HTML@, @Formlets@, and @JSON@.+  For a larger example, see the @Example.lhs@ [2] file on github.+  .+    1. <http://hackage.haskell.org/package/regular>+  .+    2. <http://github.com/chriseidhof/regular-web/blob/master/Example.lhs>+  .+  /Example/+  .+  Consider the following datatypes:+  .+  > data Person = Person {+  >     _name   :: String+  >   , _age    :: Int+  >   , _isMale :: Bool+  >   , _place  :: Place+  >   }+  +  > data Place = Place {+  >     _city      :: String+  >   , _country   :: String+  >   , _continent :: String+  > }+  .+  We can now derive a @Regular@ instance for the @Person@ datatype using Template+  Haskell:+  .+  > $(deriveAll ''Place  "PFPlace")+  > $(deriveAll ''Person "PFPerson")+  .+  >+  > type instance PF Place  = PFPlace+  > type instance PF Person = PFPerson+  .+  We can construct an example person:+  .+  > location :: Place+  > location = Place "Utrecht" "The Netherlands" "Europe"+  > chris    :: Person+  > chris    = Person "chris" 25 True location+  .+  > And, as an example, we can generate |HTML| and |JSON| values:+  .+  > locationHtml :: X.Html+  > locationHtml = ghtml location+  .+  > personHtml :: X.Html+  > personHtml = ghtml chris+  .+  > locationJSON :: JSValue+  > locationJSON = gto location
+ src/.DS_Store view

binary file changed (absent → 6148 bytes)

+ src/Generics/.DS_Store view

binary file changed (absent → 6148 bytes)

+ src/Generics/Regular/Extras.hs view
@@ -0,0 +1,23 @@+{-# LANGUAGE KindSignatures #-}+module Generics.Regular.Extras where++import Generics.Regular+import Data.Char (isAlpha, toUpper)++prodFst :: (:*:) l r t1 -> l t1+prodSnd :: (:*:) l r t1 -> r t1+prodFst (x :*: _) = x+prodSnd (_ :*: y) = y++-- | Capitalizes the first letter and filters out all the non-alpha characters.+humanReadable :: String -> String+humanReadable = filter isAlpha . capitalize++-- | Capitalize the first letter of the string.+capitalize :: String -> String+capitalize "" = ""+capitalize (c:cs) = toUpper c : cs++-- | Generates a human-readable version of a selector.+h :: Selector s => t s (f :: * -> *) r -> String	+h = humanReadable . selName
+ src/Generics/Regular/Formlets.hs view
@@ -0,0 +1,153 @@+{-# LANGUAGE FlexibleContexts, FlexibleInstances, TypeOperators, TypeSynonymInstances #-}++-----------------------------------------------------------------------------+-- |+-- Module      :  Generics.Regular.Formlets+-- Copyright   :  (c) 2010 Chris Eidhof+-- License     :  BSD3+--+-- Maintainer  :  chris@eidhof.nl+-- Stability   :  experimental+-- Portability :  non-portable+--+-- Generic generation of formlets <http://hackage.haskell.org/package/formlets>. +-- These functions are only defined for record datatypes that contain+-- a single constructor.+--+-- Consider the datatype @Person@:+--  +-- > data Person = Person {+-- >    _name   :: String+-- >  , _age    :: Int+-- >  , _isMale :: Bool+-- > } deriving (Show, Eq)+--+-- We prefix all our fields with an underscore (@_@), so that our datatype will play nice with @fclabels@. +-- +-- > $(deriveAll ''Person "PFPerson")+-- +-- > type instance PF Person = PFPerson+-- +-- We can construct an example person:+-- +-- > chris    :: Person+-- > chris    = Person "chris" 25 True+-- +-- > personForm :: XFormlet Identity Person+-- > personForm = gformlet+-- +-- We can print @formHtml@ to get the @Html@ of the form with the @chris@ value+-- already filled in:+-- +-- > formHtml :: X.Html+-- > (_, Identity formHtml, _) = F.runFormState [] (personForm (Just chris))+-----------------------------------------------------------------------------+module Generics.Regular.Formlets (+  -- * Generic forms+  gform,+  gformlet,+  GFormlet,+  XForm,+  XFormlet,+  -- * Generic forms with fclabels+  projectedForm,+  -- * Default Formlet typeclass+  Formlet (..),+  -- * Extra form types+  -- | Currently, this section is very limited. We expect to add more types in the future, suggestions are welcome.+  YesNo (..),+  boolToYesNo+  ) where++import Control.Applicative+import Control.Monad.Identity+import Text.XHtml.Strict ((+++), (<<))+import qualified Text.XHtml.Strict as X+import qualified Text.XHtml.Strict.Formlets as F+import Generics.Regular+import Generics.Regular.Extras+import Data.Record.Label+++gform :: (Regular a, GFormlet (PF a), Functor m, Applicative m, Monad m) => Maybe a -> XForm m a+gform x = to <$> (gformf gformlet (from <$> x))++gformlet :: (Regular a, GFormlet (PF a), Functor m, Applicative m, Monad m) => XFormlet m a+gformlet x = to <$> (gformf gformlet (from <$> x))++-- |+-- Generic forms almost never match the real world. If you want to change a generic form, you can either implement it from scratch, or use the 'projectedForm' function.+-- +-- As an example, we will to remove the 'age' field from the form, and change the '_isMale' field to a Yes\/No choice instead of a True\/False choice. The datatype 'YesNo' is defined in this module.+-- +-- > data PersonView = PersonView {+-- >    __name   :: String+-- >  , __isMale :: YesNo+-- > }+-- +-- +-- > $(deriveAll ''PersonView "PFPersonView")+-- > type instance PF PersonView = PFPersonView+-- +-- We can now use @fclabels@ to convert back and forth between @Person@ and+-- @PersonView@. First, we use Template Haskell to generate some accessor functions:+-- +-- > $(mkLabels [''Person])+-- +-- This is the bidirectional function between @Person@ and @PersonView@. How to write such a function is explained in the well-documented @fclabels@ package at <http://hackage.haskell.org/package/fclabels>.+-- +-- > toView :: Person :-> PersonView+-- > toView = Label (PersonView <$> __name `for` name <*> __isMale `for` (boolToYesNo `iso` isMale))+-- +-- Now that we have a function with type @Person :-> PersonView@, we can render a+-- form for @personView@ and update the original person. Note that the argument is+-- not a @Maybe@ value, in contrast with the @gformlet@ function.+-- +-- > personForm' :: Person -> XForm Identity Person+-- > personForm' = projectedForm toView+-- +-- > formHtml' :: X.Html+-- > (_, Identity formHtml', _) = F.runFormState [] (personForm' chris)++projectedForm :: (Regular a, GFormlet (PF a), Applicative m, Monad m) +              => (b :-> a) -> b -> XForm m b+projectedForm toView x = (flip (set toView) x) <$> (gform (get toView <$> (Just x)))++type XForm m a = F.XHtmlForm m a+type XFormlet m a = F.XHtmlFormlet m a++class    Formlet a      where  formlet :: (Functor m, Applicative m, Monad m) => XFormlet m a++instance Formlet Bool   where  formlet   = F.enumSelect []+instance Formlet Int    where  formlet x = fromIntegral <$> F.inputInteger (toInteger <$> x)+instance Formlet String where  formlet   = F.input++class GFormlet f where+  gformf :: (Functor m, Applicative m, Monad m) => XFormlet m a -> XFormlet m (f a)++instance (Constructor c, GFormlet f) => GFormlet (C c f) where+  gformf f x = C <$> (gformf f $ unC <$> x)++instance Formlet a => GFormlet (K a) where+  gformf _ x = K <$> (formlet (unK <$> x))++instance (GFormlet (S s f), GFormlet g) => GFormlet ((S s f) :*: g) where+  gformf f x = (:*:) <$> (gformf f (prodFst <$> x)) <* F.xml X.br <*> (gformf f (prodSnd <$> x))++ +instance (Selector s, GFormlet f) => GFormlet (S s f) where+  gformf f x = F.plug ((X.label << (h (fromJust x) ++ ": ")) +++) $ S <$> gformf f (unS <$> x)+   where fromJust (Just y) = y+         fromJust _        = error "Generic formlets fromJust should not be computed."++-- | This datatype is used to display 'Bool' values as @Yes@ or @No@.+data YesNo = Yes | No +  deriving (Eq, Show, Bounded, Enum)+instance Formlet YesNo where formlet = F.enumSelect []++-- | This is an @fclabels@ function that converts between 'Bool' and 'YesNo' values.+boolToYesNo :: Bool :<->: YesNo+boolToYesNo = to <-> from+ where  from Yes = True+        from No  = False+        to x  = if x then Yes else No
+ src/Generics/Regular/JSON.hs view
@@ -0,0 +1,73 @@+{-# LANGUAGE FlexibleInstances #-}+{-# LANGUAGE FlexibleContexts #-}+{-# LANGUAGE TypeOperators #-}+{-# LANGUAGE ScopedTypeVariables #-}++-----------------------------------------------------------------------------+-- |+-- Module      :  Generics.Regular.Views+-- Copyright   :  (c) 2010 Chris Eidhof+-- License     :  BSD3+--+-- Maintainer  :  chris@eidhof.nl+-- Stability   :  experimental+-- Portability :  non-portable+--+-- Generic generation of 'JSON' values. Note that the generic+-- functions are only defined for record datatypes that contain+-- a single constructor.+--+-- The code that is generated by 'gto' should be parseable by 'gfrom'.+-----------------------------------------------------------------------------+module Generics.Regular.JSON (gfrom, gto, GJSON) where++import Text.JSON+import Generics.Regular+import Generics.Regular.Extras+import Control.Applicative+import Data.List (unionBy)++-- | The function 'gfrom' tries to parse a 'JSValue'. The 'Result' datatype is used for error-messages if parsing fails.+gfrom :: (Regular a, GJSON (PF a)) => JSValue -> Result a+gfrom = fmap to . gfrom'++-- | The function 'gto' generates a 'JSValue' for all types that are an instance of 'GJSON'.+gto :: (Regular a, GJSON (PF a)) => a -> JSValue+gto = gto' . from++-- | This class is used for both generation and parsing of 'JSON'.+class GJSON f where+  gto'   :: f a -> JSValue+  gfrom' :: JSValue -> Result (f a)++instance GJSON U where+  gto'   U = JSNull+  gfrom' JSNull = Ok U+  gfrom' _      = Error "could not parse U"++instance JSON a => GJSON (K a) where+  gto' (K x) = showJSON x+  gfrom' x = K <$> readJSON x++instance (GJSON (S s f), GJSON g) => GJSON ((S s f) :*: g) where+  gto' (a :*: b) = merge (gto' a) (gto' b)+  gfrom' x       = do (:*:) <$> gfrom' x <*> gfrom' x++instance (Selector s, GJSON f) => GJSON (S s f) where+  gto' s@(S x) = JSObject $ toJSObject [(humanReadable $ selName s, gto' x)]+  gfrom' (JSObject obj) = let s = humanReadable $ selName (undefined :: S s f x) +                         in case valFromObj s obj of+                                 Ok x    -> S <$> gfrom' x+                                 Error e -> Error e+  gfrom' x              = Error $ "Expected json object, got " ++ show x++instance GJSON f => GJSON (C c f) where+  gto' (C x) = gto' x+  gfrom' x   = C <$> gfrom' x++merge :: JSValue -> JSValue -> JSValue+merge (JSObject l) (JSObject r) = JSObject (toJSObject $ mergeList (fromJSObject l) (fromJSObject r))+merge _            _            = error "Cannot merge objects."++mergeList :: [(String, JSValue)] -> [(String, JSValue)] -> [(String, JSValue)]+mergeList = unionBy (\x y -> fst x == fst y)
+ src/Generics/Regular/Views.hs view
@@ -0,0 +1,104 @@+{-# LANGUAGE FlexibleContexts #-}+{-# LANGUAGE TypeOperators #-}+{-# LANGUAGE TypeSynonymInstances #-}++-----------------------------------------------------------------------------+-- |+-- Module      :  Generics.Regular.Views+-- Copyright   :  (c) 2010 Chris Eidhof+-- License     :  BSD3+--+-- Maintainer  :  chris@eidhof.nl+-- Stability   :  experimental+-- Portability :  non-portable+--+-- Summary: Functions for generating HTML.+-----------------------------------------------------------------------------+++module Generics.Regular.Views (+  -- * Generic HTML generation.+  ghtml, +  Html (..),+  GHtml+  -- gtable, +  -- gtableRow,+  -- Table (..),+  -- GTable+  ) where++import Text.XHtml.Strict ((+++), (<<))+import qualified Text.XHtml.Strict as X++import Generics.Regular+import Generics.Regular.Extras++-- | The function 'ghtml' converts an 'a' value into 'X.Html'+ghtml :: (Regular a, GHtml (PF a)) => a -> X.Html+ghtml x = ghtmlf ghtml (from x)++-- | The function 'gtable' converts a list of 'a's into an 'X.Html' table with a row for each element.+gtable :: (Regular a, GTable (PF a)) => [a] -> X.Html+gtable xs = X.table << map gtableRow xs++-- | The class 'Html' converts a simple value 'a' into 'X.Html'.+class Html a where+  html :: a -> X.Html++instance Html Float  where html = X.toHtml . show+instance Html Int    where html = X.toHtml . show+instance Html Bool   where html = X.toHtml . show+instance Html String where html = X.toHtml ++-- | The class 'GHtml' converts a simple value 'a' into 'X.Html'.+class GHtml f where+  ghtmlf :: (a -> X.Html) -> f a -> X.Html++instance GHtml I where+  ghtmlf f (I r) = f r++instance (Constructor c, GHtml f) => GHtml (C c f) where+  ghtmlf f cx@(C x) = (X.h1 << capitalize (conName cx)) +++ ghtmlf f x++instance Html a => GHtml (K a) where+  ghtmlf _ (K x) = html x++instance (GHtml f, GHtml g) => GHtml (f :*: g) where+  ghtmlf f (x :*: y) = ghtmlf f x +++ X.br +++ ghtmlf f y++instance (GHtml f, GHtml g) => GHtml (f :+: g) where+  ghtmlf f (L x) = ghtmlf f x+  ghtmlf f (R y) = ghtmlf f y++instance (Selector s, GHtml f) => GHtml (S s f) where+  ghtmlf f s@(S x) = X.label << ((h s) ++ ": ") +++ ghtmlf f x+++class Table a where+  table :: a -> X.Html++instance Table Float  where table = html+instance Table String where table = html+instance Table Int    where table = html+instance Table Bool   where table = html++class GTable f where+  gtablef :: (a -> X.Html) -> f a -> X.Html++instance GTable I where+  gtablef f (I r) = f r++instance (Constructor c, GTable f) => GTable (C c f) where+  gtablef f (C x) = X.tr << (gtablef f x)++instance Table a => GTable (K a) where+  gtablef _ (K x) = table x++instance (GTable f, GTable g) => GTable (f :*: g) where+  gtablef f (x :*: y) = gtablef f x +++ gtablef f y++instance (Selector s, GTable f) => GTable (S s f) where+  gtablef f (S x) = X.td << gtablef f x++gtableRow :: (Regular a, GTable (PF a)) => a -> X.Html+gtableRow x = gtablef gtableRow (from x)