fsClass to fsAttrs

This commit is contained in:
Michael Snoyman 2012-03-25 17:57:19 +02:00
parent 2bb39530d6
commit 46308c8d1f
7 changed files with 52 additions and 57 deletions

View File

@ -70,7 +70,6 @@ import Text.Blaze.Renderer.String (renderHtml)
import qualified Data.ByteString as S import qualified Data.ByteString as S
import qualified Data.ByteString.Lazy as L import qualified Data.ByteString.Lazy as L
import Data.Text (Text, unpack, pack) import Data.Text (Text, unpack, pack)
import qualified Data.Text as T
import qualified Data.Text.Read import qualified Data.Text.Read
import qualified Data.Map as Map import qualified Data.Map as Map
@ -99,8 +98,8 @@ intField = Field
Right (a, "") -> Right a Right (a, "") -> Right a
_ -> Left $ MsgInvalidInteger s _ -> Left $ MsgInvalidInteger s
, fieldView = \theId name theClass val isReq -> toWidget [hamlet| , fieldView = \theId name attrs val isReq -> toWidget [hamlet|
<input id="#{theId}" name="#{name}" :not (null theClass):class="#{T.intercalate " " theClass}" type="number" :isReq:required="" value="#{showVal val}"> <input id="#{theId}" name="#{name}" *{attrs} type="number" :isReq:required="" value="#{showVal val}">
|] |]
} }
where where
@ -114,8 +113,8 @@ doubleField = Field
Right (a, "") -> Right a Right (a, "") -> Right a
_ -> Left $ MsgInvalidNumber s _ -> Left $ MsgInvalidNumber s
, fieldView = \theId name theClass val isReq -> toWidget [hamlet| , fieldView = \theId name attrs val isReq -> toWidget [hamlet|
<input id="#{theId}" name="#{name}" :not (null theClass):class="#{T.intercalate " " theClass}" type="text" :isReq:required="" value="#{showVal val}"> <input id="#{theId}" name="#{name}" *{attrs} type="text" :isReq:required="" value="#{showVal val}">
|] |]
} }
where showVal = either id (pack . show) where showVal = either id (pack . show)
@ -123,8 +122,8 @@ doubleField = Field
dayField :: RenderMessage master FormMessage => Field sub master Day dayField :: RenderMessage master FormMessage => Field sub master Day
dayField = Field dayField = Field
{ fieldParse = blank $ parseDate . unpack { fieldParse = blank $ parseDate . unpack
, fieldView = \theId name theClass val isReq -> toWidget [hamlet| , fieldView = \theId name attrs val isReq -> toWidget [hamlet|
<input id="#{theId}" name="#{name}" :not (null theClass):class="#{T.intercalate " " theClass}" type="date" :isReq:required="" value="#{showVal val}"> <input id="#{theId}" name="#{name}" *{attrs} type="date" :isReq:required="" value="#{showVal val}">
|] |]
} }
where showVal = either id (pack . show) where showVal = either id (pack . show)
@ -132,8 +131,8 @@ dayField = Field
timeField :: RenderMessage master FormMessage => Field sub master TimeOfDay timeField :: RenderMessage master FormMessage => Field sub master TimeOfDay
timeField = Field timeField = Field
{ fieldParse = blank $ parseTime . unpack { fieldParse = blank $ parseTime . unpack
, fieldView = \theId name theClass val isReq -> toWidget [hamlet| , fieldView = \theId name attrs val isReq -> toWidget [hamlet|
<input id="#{theId}" name="#{name}" :not (null theClass):class="#{T.intercalate "" theClass}" :isReq:required="" value="#{showVal val}"> <input id="#{theId}" name="#{name}" *{attrs} :isReq:required="" value="#{showVal val}">
|] |]
} }
where where
@ -146,9 +145,9 @@ timeField = Field
htmlField :: RenderMessage master FormMessage => Field sub master Html htmlField :: RenderMessage master FormMessage => Field sub master Html
htmlField = Field htmlField = Field
{ fieldParse = blank $ Right . preEscapedText . sanitizeBalance { fieldParse = blank $ Right . preEscapedText . sanitizeBalance
, fieldView = \theId name theClass val _isReq -> toWidget [hamlet| , fieldView = \theId name attrs val _isReq -> toWidget [hamlet|
-- FIXME: There was a class="html" attribute, for what purpose? -- FIXME: There was a class="html" attribute, for what purpose?
<textarea id="#{theId}" name="#{name}" :not (null theClass):class=#{T.intercalate " " theClass}>#{showVal val} <textarea id="#{theId}" name="#{name}" *{attrs}>#{showVal val}
|] |]
} }
where showVal = either id (pack . renderHtml) where showVal = either id (pack . renderHtml)
@ -174,33 +173,33 @@ instance ToHtml Textarea where
textareaField :: RenderMessage master FormMessage => Field sub master Textarea textareaField :: RenderMessage master FormMessage => Field sub master Textarea
textareaField = Field textareaField = Field
{ fieldParse = blank $ Right . Textarea { fieldParse = blank $ Right . Textarea
, fieldView = \theId name theClass val _isReq -> toWidget [hamlet| , fieldView = \theId name attrs val _isReq -> toWidget [hamlet|
<textarea id="#{theId}" name="#{name}" :not (null theClass):class="#{T.intercalate " " theClass}">#{either id unTextarea val} <textarea id="#{theId}" name="#{name}" *{attrs}>#{either id unTextarea val}
|] |]
} }
hiddenField :: RenderMessage master FormMessage => Field sub master Text hiddenField :: RenderMessage master FormMessage => Field sub master Text
hiddenField = Field hiddenField = Field
{ fieldParse = blank $ Right { fieldParse = blank $ Right
, fieldView = \theId name theClass val _isReq -> toWidget [hamlet| , fieldView = \theId name attrs val _isReq -> toWidget [hamlet|
<input type="hidden" id="#{theId}" name="#{name}" :not (null theClass):class="#{T.intercalate " " theClass}" value="#{either id id val}"> <input type="hidden" id="#{theId}" name="#{name}" *{attrs} value="#{either id id val}">
|] |]
} }
textField :: RenderMessage master FormMessage => Field sub master Text textField :: RenderMessage master FormMessage => Field sub master Text
textField = Field textField = Field
{ fieldParse = blank $ Right { fieldParse = blank $ Right
, fieldView = \theId name theClass val isReq -> , fieldView = \theId name attrs val isReq ->
[whamlet| [whamlet|
<input id="#{theId}" name="#{name}" :not (null theClass):class="#{T.intercalate " " theClass}" type="text" :isReq:required value="#{either id id val}"> <input id="#{theId}" name="#{name}" *{attrs} type="text" :isReq:required value="#{either id id val}">
|] |]
} }
passwordField :: RenderMessage master FormMessage => Field sub master Text passwordField :: RenderMessage master FormMessage => Field sub master Text
passwordField = Field passwordField = Field
{ fieldParse = blank $ Right { fieldParse = blank $ Right
, fieldView = \theId name theClass val isReq -> toWidget [hamlet| , fieldView = \theId name attrs val isReq -> toWidget [hamlet|
<input id="#{theId}" name="#{name}" :not (null theClass):class="#{T.intercalate " " theClass}" type="password" :isReq:required="" value="#{either id id val}"> <input id="#{theId}" name="#{name}" *{attrs} type="password" :isReq:required="" value="#{either id id val}">
|] |]
} }
@ -248,8 +247,8 @@ emailField = Field
\s -> if Email.isValid (unpack s) \s -> if Email.isValid (unpack s)
then Right s then Right s
else Left $ MsgInvalidEmail s else Left $ MsgInvalidEmail s
, fieldView = \theId name theClass val isReq -> toWidget [hamlet| , fieldView = \theId name attrs val isReq -> toWidget [hamlet|
<input id="#{theId}" name="#{name}" :not (null theClass):class="#{T.intercalate " " theClass}" type="email" :isReq:required="" value="#{either id id val}"> <input id="#{theId}" name="#{name}" *{attrs} type="email" :isReq:required="" value="#{either id id val}">
|] |]
} }
@ -257,9 +256,9 @@ type AutoFocus = Bool
searchField :: RenderMessage master FormMessage => AutoFocus -> Field sub master Text searchField :: RenderMessage master FormMessage => AutoFocus -> Field sub master Text
searchField autoFocus = Field searchField autoFocus = Field
{ fieldParse = blank Right { fieldParse = blank Right
, fieldView = \theId name theClass val isReq -> do , fieldView = \theId name attrs val isReq -> do
[whamlet|\ [whamlet|\
<input id="#{theId}" name="#{name}" :not (null theClass):class="#{T.intercalate " " theClass}" type="search" :isReq:required="" :autoFocus:autofocus="" value="#{either id id val}"> <input id="#{theId}" name="#{name}" *{attrs} type="search" :isReq:required="" :autoFocus:autofocus="" value="#{either id id val}">
|] |]
when autoFocus $ do when autoFocus $ do
-- we want this javascript to be placed immediately after the field -- we want this javascript to be placed immediately after the field
@ -276,9 +275,9 @@ urlField = Field
case parseURI $ unpack s of case parseURI $ unpack s of
Nothing -> Left $ MsgInvalidUrl s Nothing -> Left $ MsgInvalidUrl s
Just _ -> Right s Just _ -> Right s
, fieldView = \theId name theClass val isReq -> , fieldView = \theId name attrs val isReq ->
[whamlet| [whamlet|
<input ##{theId} name=#{name} :not (null theClass):class="#{T.intercalate " " theClass}" type=url :isReq:required value=#{either id id val}> <input ##{theId} name=#{name} *{attrs} type=url :isReq:required value=#{either id id val}>
|] |]
} }
@ -289,7 +288,7 @@ selectField :: (Eq a, RenderMessage master FormMessage) => GHandler sub master (
selectField = selectFieldHelper selectField = selectFieldHelper
(\theId name inside -> [whamlet|<select ##{theId} name=#{name}>^{inside}|]) -- outside (\theId name inside -> [whamlet|<select ##{theId} name=#{name}>^{inside}|]) -- outside
(\_theId _name isSel -> [whamlet|<option value=none :isSel:selected>_{MsgSelectNone}|]) -- onOpt (\_theId _name isSel -> [whamlet|<option value=none :isSel:selected>_{MsgSelectNone}|]) -- onOpt
(\_theId _name theClass value isSel text -> [whamlet|<option value=#{value} :isSel:selected :not (null theClass):class="#{T.intercalate " " theClass}">#{text}|]) -- inside (\_theId _name attrs value isSel text -> [whamlet|<option value=#{value} :isSel:selected *{attrs}>#{text}|]) -- inside
multiSelectFieldList :: (Eq a, RenderMessage master FormMessage, RenderMessage master msg) => [(msg, a)] -> Field sub master [a] multiSelectFieldList :: (Eq a, RenderMessage master FormMessage, RenderMessage master msg) => [(msg, a)] -> Field sub master [a]
multiSelectFieldList = multiSelectField . optionsPairs multiSelectFieldList = multiSelectField . optionsPairs
@ -307,11 +306,11 @@ multiSelectField ioptlist =
Nothing -> return $ Left "Error parsing values" Nothing -> return $ Left "Error parsing values"
Just res -> return $ Right $ Just res Just res -> return $ Right $ Just res
view theId name theClass val isReq = do view theId name attrs val isReq = do
opts <- fmap olOptions $ lift ioptlist opts <- fmap olOptions $ lift ioptlist
let selOpts = map (id &&& (optselected val)) opts let selOpts = map (id &&& (optselected val)) opts
[whamlet| [whamlet|
<select ##{theId} name=#{name} :isReq:required multiple :not (null theClass):class=#{T.intercalate " " theClass}> <select ##{theId} name=#{name} :isReq:required multiple *{attrs}>
$forall (opt, optsel) <- selOpts $forall (opt, optsel) <- selOpts
<option value=#{optionExternalValue opt} :optsel:selected>#{optionDisplay opt} <option value=#{optionExternalValue opt} :optsel:selected>#{optionDisplay opt}
|] |]
@ -330,25 +329,25 @@ radioField = selectFieldHelper
<input id=#{theId}-none type=radio name=#{name} value=none :isSel:checked> <input id=#{theId}-none type=radio name=#{name} value=none :isSel:checked>
<label for=#{theId}-none>_{MsgSelectNone} <label for=#{theId}-none>_{MsgSelectNone}
|]) |])
(\theId name theClass value isSel text -> [whamlet| (\theId name attrs value isSel text -> [whamlet|
<div> <div>
<input id=#{theId}-#{value} type=radio name=#{name} value=#{value} :isSel:checked :not (null theClass):class="#{T.intercalate " " theClass}"> <input id=#{theId}-#{value} type=radio name=#{name} value=#{value} :isSel:checked *{attrs}>
<label for=#{theId}-#{value}>#{text} <label for=#{theId}-#{value}>#{text}
|]) |])
boolField :: RenderMessage master FormMessage => Field sub master Bool boolField :: RenderMessage master FormMessage => Field sub master Bool
boolField = Field boolField = Field
{ fieldParse = return . boolParser { fieldParse = return . boolParser
, fieldView = \theId name theClass val isReq -> [whamlet| , fieldView = \theId name attrs val isReq -> [whamlet|
$if not isReq $if not isReq
<input id=#{theId}-none :not (null theClass):class="#{T.intercalate " " theClass}" type=radio name=#{name} value=none checked> <input id=#{theId}-none *{attrs} type=radio name=#{name} value=none checked>
<label for=#{theId}-none>_{MsgSelectNone} <label for=#{theId}-none>_{MsgSelectNone}
<input id=#{theId}-yes :not (null theClass):class="#{T.intercalate " " theClass}" type=radio name=#{name} value=yes :showVal id val:checked> <input id=#{theId}-yes *{attrs} type=radio name=#{name} value=yes :showVal id val:checked>
<label for=#{theId}-yes>_{MsgBoolYes} <label for=#{theId}-yes>_{MsgBoolYes}
<input id=#{theId}-no :not (null theClass):class="#{T.intercalate " " theClass}" type=radio name=#{name} value=no :showVal not val:checked> <input id=#{theId}-no *{attrs} type=radio name=#{name} value=no :showVal not val:checked>
<label for=#{theId}-no>_{MsgBoolNo} <label for=#{theId}-no>_{MsgBoolNo}
|] |]
} }
@ -372,8 +371,8 @@ boolField = Field
checkBoxField :: RenderMessage m FormMessage => Field s m Bool checkBoxField :: RenderMessage m FormMessage => Field s m Bool
checkBoxField = Field checkBoxField = Field
{ fieldParse = return . checkBoxParser { fieldParse = return . checkBoxParser
, fieldView = \theId name theClass val _ -> [whamlet| , fieldView = \theId name attrs val _ -> [whamlet|
<input id=#{theId} :not (null theClass):class="#{T.intercalate " " theClass}" type=checkbox name=#{name} value=yes :showVal id val:checked> <input id=#{theId} *{attrs} type=checkbox name=#{name} value=yes :showVal id val:checked>
|] |]
} }
@ -435,20 +434,20 @@ selectFieldHelper
:: (Eq a, RenderMessage master FormMessage) :: (Eq a, RenderMessage master FormMessage)
=> (Text -> Text -> GWidget sub master () -> GWidget sub master ()) => (Text -> Text -> GWidget sub master () -> GWidget sub master ())
-> (Text -> Text -> Bool -> GWidget sub master ()) -> (Text -> Text -> Bool -> GWidget sub master ())
-> (Text -> Text -> [Text] -> Text -> Bool -> Text -> GWidget sub master ()) -> (Text -> Text -> [(Text, Text)] -> Text -> Bool -> Text -> GWidget sub master ())
-> GHandler sub master (OptionList a) -> Field sub master a -> GHandler sub master (OptionList a) -> Field sub master a
selectFieldHelper outside onOpt inside opts' = Field selectFieldHelper outside onOpt inside opts' = Field
{ fieldParse = \x -> do { fieldParse = \x -> do
opts <- opts' opts <- opts'
return $ selectParser opts x return $ selectParser opts x
, fieldView = \theId name theClass val isReq -> do , fieldView = \theId name attrs val isReq -> do
opts <- fmap olOptions $ lift opts' opts <- fmap olOptions $ lift opts'
outside theId name $ do outside theId name $ do
unless isReq $ onOpt theId name $ not $ render opts val `elem` map optionExternalValue opts unless isReq $ onOpt theId name $ not $ render opts val `elem` map optionExternalValue opts
flip mapM_ opts $ \opt -> inside flip mapM_ opts $ \opt -> inside
theId theId
name name
theClass attrs
(optionExternalValue opt) (optionExternalValue opt)
((render opts val) == optionExternalValue opt) ((render opts val) == optionExternalValue opt)
(optionDisplay opt) (optionDisplay opt)
@ -482,13 +481,12 @@ fileAFormReq fs = AForm $ \(master, langs) menvs ints -> do
let t = renderMessage master langs MsgValueRequired let t = renderMessage master langs MsgValueRequired
in (FormFailure [t], Just $ toHtml t) in (FormFailure [t], Just $ toHtml t)
Just fi -> (FormSuccess fi, Nothing) Just fi -> (FormSuccess fi, Nothing)
let theClass = fsClass fs
let fv = FieldView let fv = FieldView
{ fvLabel = toHtml $ renderMessage master langs $ fsLabel fs { fvLabel = toHtml $ renderMessage master langs $ fsLabel fs
, fvTooltip = fmap (toHtml . renderMessage master langs) $ fsTooltip fs , fvTooltip = fmap (toHtml . renderMessage master langs) $ fsTooltip fs
, fvId = id' , fvId = id'
, fvInput = [whamlet| , fvInput = [whamlet|
<input type=file name=#{name} ##{id'} :not (null theClass):class="#{T.intercalate " " theClass}"> <input type=file name=#{name} ##{id'} *{fsAttrs fs}>
|] |]
, fvErrors = errs , fvErrors = errs
, fvRequired = True , fvRequired = True
@ -511,13 +509,12 @@ fileAFormOpt fs = AForm $ \(master, langs) menvs ints -> do
case Map.lookup name fenv of case Map.lookup name fenv of
Nothing -> (FormSuccess Nothing, Nothing) Nothing -> (FormSuccess Nothing, Nothing)
Just fi -> (FormSuccess $ Just fi, Nothing) Just fi -> (FormSuccess $ Just fi, Nothing)
let theClass = fsClass fs
let fv = FieldView let fv = FieldView
{ fvLabel = toHtml $ renderMessage master langs $ fsLabel fs { fvLabel = toHtml $ renderMessage master langs $ fsLabel fs
, fvTooltip = fmap (toHtml . renderMessage master langs) $ fsTooltip fs , fvTooltip = fmap (toHtml . renderMessage master langs) $ fsTooltip fs
, fvId = id' , fvId = id'
, fvInput = [whamlet| , fvInput = [whamlet|
<input type=file name=#{name} ##{id'} :not (null theClass):class="#{T.intercalate " " theClass}"> <input type=file name=#{name} ##{id'} *{fsAttrs fs}>
|] |]
, fvErrors = errs , fvErrors = errs
, fvRequired = False , fvRequired = False

View File

@ -132,7 +132,7 @@ mhelper Field {..} FieldSettings {..} mdef onMissing onFound isReq = do
{ fvLabel = toHtml $ mr2 fsLabel { fvLabel = toHtml $ mr2 fsLabel
, fvTooltip = fmap toHtml $ fmap mr2 fsTooltip , fvTooltip = fmap toHtml $ fmap mr2 fsTooltip
, fvId = theId , fvId = theId
, fvInput = fieldView theId name fsClass val isReq , fvInput = fieldView theId name fsAttrs val isReq
, fvErrors = , fvErrors =
case res of case res of
FormFailure [e] -> Just $ toHtml e FormFailure [e] -> Just $ toHtml e

View File

@ -18,7 +18,6 @@ import Yesod.Core (Route)
import Yesod.Form import Yesod.Form
import Yesod.Widget import Yesod.Widget
import Data.Time (Day) import Data.Time (Day)
import qualified Data.Text as T
import Data.Default import Data.Default
import Text.Hamlet (shamlet) import Text.Hamlet (shamlet)
import Text.Julius (julius) import Text.Julius (julius)
@ -63,9 +62,9 @@ jqueryDayField jds = Field
Right Right
. readMay . readMay
. unpack . unpack
, fieldView = \theId name theClass val isReq -> do , fieldView = \theId name attrs val isReq -> do
toWidget [shamlet| toWidget [shamlet|
<input id="#{theId}" name="#{name}" :not (null theClass):class="#{T.intercalate " " theClass}" type="date" :isReq:required="" value="#{showVal val}"> <input id="#{theId}" name="#{name}" *{attrs} type="date" :isReq:required="" value="#{showVal val}">
|] |]
addScript' urlJqueryJs addScript' urlJqueryJs
addScript' urlJqueryUiJs addScript' urlJqueryUiJs
@ -102,9 +101,9 @@ jqueryAutocompleteField :: (RenderMessage master FormMessage, YesodJquery master
=> Route master -> Field sub master Text => Route master -> Field sub master Text
jqueryAutocompleteField src = Field jqueryAutocompleteField src = Field
{ fieldParse = blank $ Right { fieldParse = blank $ Right
, fieldView = \theId name theClass val isReq -> do , fieldView = \theId name attrs val isReq -> do
toWidget [shamlet| toWidget [shamlet|
<input id="#{theId}" name="#{name}" :not (null theClass):class="#{T.intercalate " " theClass}" type="text" :isReq:required="" value="#{either id id val}" .autocomplete> <input id="#{theId}" name="#{name}" *{attrs} type="text" :isReq:required="" value="#{either id id val}" .autocomplete>
|] |]
addScript' urlJqueryJs addScript' urlJqueryJs
addScript' urlJqueryUiJs addScript' urlJqueryUiJs

View File

@ -101,7 +101,7 @@ withDelete af = do
, fsTooltip = Nothing , fsTooltip = Nothing
, fsName = Just deleteName , fsName = Just deleteName
, fsId = Nothing , fsId = Nothing
, fsClass = [] , fsAttrs = []
} $ Just False } $ Just False
(res, xml) <- aFormToForm af (res, xml) <- aFormToForm af
return $ Right (res, xml $ xml2 []) return $ Right (res, xml $ xml2 [])

View File

@ -19,7 +19,6 @@ import Text.Julius (julius)
import Text.Blaze.Renderer.String (renderHtml) import Text.Blaze.Renderer.String (renderHtml)
import Text.Blaze (preEscapedText) import Text.Blaze (preEscapedText)
import Data.Text (Text, pack) import Data.Text (Text, pack)
import qualified Data.Text as T
import Data.Maybe (listToMaybe) import Data.Maybe (listToMaybe)
class Yesod a => YesodNic a where class Yesod a => YesodNic a where
@ -30,9 +29,9 @@ class Yesod a => YesodNic a where
nicHtmlField :: YesodNic master => Field sub master Html nicHtmlField :: YesodNic master => Field sub master Html
nicHtmlField = Field nicHtmlField = Field
{ fieldParse = return . Right . fmap (preEscapedText . sanitizeBalance) . listToMaybe { fieldParse = return . Right . fmap (preEscapedText . sanitizeBalance) . listToMaybe
, fieldView = \theId name theClass val _isReq -> do , fieldView = \theId name attrs val _isReq -> do
toWidget [shamlet| toWidget [shamlet|
<textarea id="#{theId}" :not (null theClass):class="#{T.intercalate " " theClass}" name="#{name}" .html>#{showVal val} <textarea id="#{theId}" *{attrs} name="#{name}" .html>#{showVal val}
|] |]
addScript' urlNicEdit addScript' urlNicEdit
master <- lift getYesod master <- lift getYesod

View File

@ -99,7 +99,7 @@ data FieldSettings msg = FieldSettings
, fsTooltip :: Maybe msg , fsTooltip :: Maybe msg
, fsId :: Maybe Text , fsId :: Maybe Text
, fsName :: Maybe Text , fsName :: Maybe Text
, fsClass :: [Text] , fsAttrs :: [(Text, Text)]
} }
instance (a ~ Text) => IsString (FieldSettings a) where instance (a ~ Text) => IsString (FieldSettings a) where
@ -116,10 +116,10 @@ data FieldView sub master = FieldView
data Field sub master a = Field data Field sub master a = Field
{ fieldParse :: [Text] -> GHandler sub master (Either (SomeMessage master) (Maybe a)) { fieldParse :: [Text] -> GHandler sub master (Either (SomeMessage master) (Maybe a))
-- | ID, name, class, (invalid text OR legimiate result), required? -- | ID, name, attrs, (invalid text OR legimiate result), required?
, fieldView :: Text , fieldView :: Text
-> Text -> Text
-> [Text] -> [(Text, Text)]
-> Either Text a -> Either Text a
-> Bool -> Bool
-> GWidget sub master () -> GWidget sub master ()

View File

@ -1,5 +1,5 @@
name: yesod-form name: yesod-form
version: 1.0.0.20120316 version: 1.0.0.20120325
license: BSD3 license: BSD3
license-file: LICENSE license-file: LICENSE
author: Michael Snoyman <michael@snoyman.com> author: Michael Snoyman <michael@snoyman.com>