0001"""
0002Extension to ``htmlfill`` that can parse out schema-defining
0003statements.
0004
0005You can either pass ``SchemaBuilder`` to ``htmlfill.render`` (the
0006``listen`` argument), or call ``parse_schema`` to just parse out a
0007``Schema`` object.
0008"""
0009
0010import validators, schema, compound
0011
0012__all__ = ['parse_schema', 'SchemaBuilder']
0013
0014def parse_schema(form):
0015    """
0016    Given an HTML form, parse out the schema defined in it and return
0017    that schema.
0018    """
0019    listener = htmlfill_schemabuilder.SchemaBuilder()
0020    p = htmlfill.FillingParser(
0021        defaults={}, listener=listener)
0022    p.feed(self.form)
0023    p.close()
0024    return listener.schema()
0025
0026default_validators = dict(
0027    [(name.lower(), getattr(validators, name))
0028     for name in dir(validators)])
0029
0030def get_messages(cls, message):
0031    if not message:
0032        return {}
0033    else:
0034        return dict([(k, message) for k in cls._messages.keys()])
0035
0036def to_bool(value):
0037    value = value.strip().lower()
0038    if value in ('true', 't', 'yes', 'y', 'on', '1'):
0039        return True
0040    elif value in ('false', 'f', 'no', 'n', 'off', '0'):
0041        return False
0042    else:
0043        raise ValueError("Not a boolean value: %r (use 'true'/'false')")
0044
0045def force_list(v):
0046    """
0047    Force single items into a list. This is useful for checkboxes.
0048    """
0049    if isinstance(v, list):
0050        return v
0051    elif isinstance(v, tuple):
0052        return list(v)
0053    else:
0054        return [v]
0055
0056class SchemaBuilder(object):
0057
0058    def __init__(self, validators=default_validators):
0059        self.validators = validators
0060        self._schema = None
0061
0062    def reset(self):
0063        self._schema = schema.Schema()
0064
0065    def schema(self):
0066        return self._schema
0067
0068    def listen_input(self, parser, tag, attrs):
0069        get_attr = parser.get_attr
0070        name = get_attr(attrs, 'name')
0071        if not name:
0072            # @@: should warn if you try to validate unnamed fields
0073            return
0074        v = compound.All(validators.Identity())
0075        add_to_end = None
0076        # for checkboxes, we must set if_missing = False
0077        if tag.lower() == "input":
0078            type_attr = get_attr(attrs, "type").lower().strip()
0079            if type_attr == "submit":
0080                v.validators.append(validators.Bool())
0081            elif type_attr == "checkbox":
0082                v.validators.append(validators.Wrapper(to_python = force_list))
0083            elif type_attr == "file":
0084                add_to_end = validators.FieldStorageUploadConverter()
0085        message = get_attr(attrs, 'form:message')
0086        required = to_bool(get_attr(attrs, 'form:required', 'false'))
0087        if required:
0088            v.validators.append(
0089                validators.NotEmpty(
0090                messages=get_messages(validators.NotEmpty, message)))
0091        else:
0092            v.validators[0].if_missing = False
0093        if add_to_end:
0094            v.validators.append(add_to_end)
0095        v_type = get_attr(attrs, 'form:validate', None)
0096        if v_type:
0097            pos = v_type.find(':')
0098            if pos != -1:
0099                # @@: should parse args
0100                args = (v_type[pos+1:],)
0101                v_type = v_type[:pos]
0102            else:
0103                args = ()
0104            v_type = v_type.lower()
0105            v_class = self.validators.get(v_type)
0106            if not v_class:
0107                raise ValueError("Invalid validation type: %r" % v_type)
0108            kw_args={'messages': get_messages(v_class, message)}
0109            v_inst = v_class(
0110                *args, **kw_args)
0111            v.validators.append(v_inst)
0112        self._schema.add_field(name, v)