docs: add documentation for validation functions
3 files changed, 145 insertions(+), 15 deletions(-)

M doc/index.rst
A => doc/validating.rst
M morf/validators.py
M doc/index.rst +1 -0
@@ 10,6 10,7 @@ Morf Documentation
    :maxdepth: 2
 
    rendering
+   validating
    form
    fields
    widgets

          
A => doc/validating.rst +101 -0
@@ 0,0 1,101 @@ 
+Validating and cleaning data
+=============================
+
+Processors
+----------
+
+Before validation, any processing functions are run. These allow you do simple cleaning operations (eg calling `strip` on string values) before any validation rules are checked.
+Processors can be configured by setting :attr:`~morf.fields.Field.processors`:
+
+.. code:: python
+
+    class MyForm(Form):
+
+        email = fields.Str(processors=[lambda s: s.lower().strip()])
+
+
+Validators
+----------
+
+Morf includes various common validator functions that can be
+configured by setting :attr:`~morf.fields.Field.validators`:
+
+.. code:: python
+
+    from morf import Form, fields, validators
+
+    class MyForm(Form):
+
+        email = fields.Str(validators=[validators.is_email()])
+        message = fields.Str(validators=[validators.minlen(10)])
+
+
+Refer to :mod:`~morf.validators` for the list of built-in validation functions.
+
+
+Ad-hoc validation functions
+---------------------------
+
+:meth:`~morf.form.validates` lets you create ad-hoc validation functions:
+
+
+.. code:: python
+
+    from morf import Form
+    from morf import validates
+
+    class MyForm(Form):
+
+        number1 = fields.Int()
+        number2 = fields.Int()
+
+        @validates(number1)
+        def check_first_number_is_even(self, n1):
+            if n1 % 2 != 0:
+                self.fail("Even numbers only!")
+
+        @validates(number1, number2)
+        def check_second_number_is_multiple_of_first(self, n1, n2):
+            if n2 % n1 != 0
+                self.fail("Enter a multiple of the first number")
+
+        @cleans(word)
+        def clean_word(self, word):
+            return word.lower()
+
+
+:meth:`~morf.form.cleans` works in a similar way for data cleaning functions,
+which are expected to return the cleaned data:
+
+.. code:: python
+
+    from morf import Form
+    from morf import cleans
+
+    class MyForm(Form):
+
+        word = fields.Str()
+
+        @cleans(word)
+        def clean_word(self, word):
+            return word.lower()
+
+
+A validator or cleaner may also act on the whole form object:
+
+.. code:: python
+
+
+        @cleans
+        def clean_everything(self):
+            self.data['word'] = self.data['word'].lower()
+
+
+
+Validators reference
+---------------------
+
+
+.. automodule:: morf.validators
+    :members:
+

          
M morf/validators.py +43 -15
@@ 66,45 66,75 @@ def assert_false(predicate, message):
     return True
 
 
-def minlen(l, message=None):
-    return lambda v: assert_true(len(v) >= l, message)
+def minlen(n, message=None):
+    """
+    Check that ``len(value) ≥ n``.
+    """
+    return lambda v: assert_true(len(v) >= n, message)
 
 
-def maxlen(l, message=None):
-    return lambda v: assert_true(len(v) <= l, message)
+def maxlen(n, message=None):
+    """
+    Check that ``len(value) ≤ n``.
+    """
+    return lambda v: assert_true(len(v) <= n, message)
 
 
-def gt(l, message=None):
-    return lambda v: assert_true(v > l, message)
+def gt(n, message=None):
+    """
+    Check that the value is > ``n``.
+    """
+    return lambda v: assert_true(v > n, message)
 
 
-def gte(l, message=None):
-    return lambda v: assert_true(v >= l, message)
+def gte(n, message=None):
+    """
+    Check that the value is ≥ ``n``.
+    """
+    return lambda v: assert_true(v >= n, message)
 
 
-def lt(l, message=None):
-    return lambda v: assert_true(v < l, message)
+def lt(n, message=None):
+    """
+    Check that the value is < ``n``.
+    """
+    return lambda v: assert_true(v < n, message)
 
 
-def lte(l, message=None):
-    return lambda v: assert_true(v <= l, message)
+def lte(n, message=None):
+    """
+    Check that the value is ≤ ``n``.
+    """
+    return lambda v: assert_true(v <= n, message)
 
 
 def matches(p, message=None):
+    """
+    Check that the string value matches the given regular expression ``p``.
+    """
     if isinstance(p, _strtypes):
         p = re.compile(p)
     return lambda v: assert_true(p.search(v) is not None, message)
 
 
 def notempty(message=None):
+    """
+    Check that the value is a non-empty string
+    """
     return matches(re.compile(r"\S"), message)
 
 
 def eq(expected, message=None):
+    """
+    Check that the value equals ``expected``.
+    """
     return lambda v: assert_true(v == expected, message)
 
 
 def is_in(allowed, message=None):
+    """
+    Check that the value is contained in the list ``allowed``.
+    """
     return lambda v: assert_true(v in allowed, message)
 
 

          
@@ 161,9 191,7 @@ def is_luhn_valid(message="Enter a valid
 
         digits = [int(x) for x in cc if x in string.digits]
         result = (
-            sum(
-                digits[::-2] + [sum(divmod(d * 2, 10)) for d in digits[-2::-2]]
-            )
+            sum(digits[::-2] + [sum(divmod(d * 2, 10)) for d in digits[-2::-2]])
             % 10
         )
         return assert_true(result == 0, message)