Mercurial > hg > markup
changeset 0:5b6f8024e177
initial commit
author | Jeff Hammel <k0scist@gmail.com> |
---|---|
date | Tue, 03 Nov 2020 13:01:49 -0800 |
parents | |
children | dacffabe885f |
files | PKG-INFO TODO markup/__init__.py markup/form.py markup/markup.py setup.cfg setup.py |
diffstat | 7 files changed, 457 insertions(+), 0 deletions(-) [+] |
line wrap: on
line diff
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/PKG-INFO Tue Nov 03 13:01:49 2020 -0800 @@ -0,0 +1,10 @@ +Metadata-Version: 1.0 +Name: markup +Version: 0.2 +Summary: generate HTML markup programmatically +Home-page: http://www.openplans.org/people/k0s/ +Author: Jeff Hammel +Author-email: jhammel@openplans.org +License: GPL +Description: UNKNOWN +Platform: UNKNOWN
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/TODO Tue Nov 03 13:01:49 2020 -0800 @@ -0,0 +1,16 @@ +Block level elements should be distinguished from non-block level elements: +For instance, div('foo') should render: + +<div> +foo +</div> + +Versus b('foo') should result in + +<b>foo</b> + +Quoting - smart quoting should be implemented: + +"foo" -> “foo” + +'foo' -> ‘foo’ \ No newline at end of file
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/markup/__init__.py Tue Nov 03 13:01:49 2020 -0800 @@ -0,0 +1,1 @@ +from markup import *
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/markup/form.py Tue Nov 03 13:01:49 2020 -0800 @@ -0,0 +1,232 @@ +import markup +from cStringIO import StringIO + +class Form(object): + """a simple class for HTML forms""" + + type_validators = {} # validators inherit to type + + def __init__(self, method='post', action=None, submit='Submit', post_html=''): + """ + * post_html: html to include before the submit button + """ + self.method = method + self.action = action + self.submit = submit + self.post_html = post_html + self.elements = [] + self.validators = {} # keys=tuples of names; values=validators + + def __call__(self, errors=None): + """render the form""" + retval = StringIO() + print >> retval + + def name_field(element): + if not element['name']: + return '' + title={} + help = element.get('help') + if help: + title['title'] = help + return markup.span(markup.strong('%s:' % element['name']), **title) + + # print the form as a table + table = [ [ name_field(element), element['html'] ] + for element in self.elements ] + if errors: + for row, element in zip(table, self.elements): + error = errors.get(element['name'], '') + if error: + if not isinstance(error, basestring): + if len(error) == 1: + error = error[0] + else: + error = markup.listify(error) + error = markup.div(error, **{ 'class': 'error' }) + row.append(error) + + print >> retval, markup.tablify(table) + print >> retval, self.post_html + + # each form has a submit button + # XXX this should probably be more customizable + print >> retval, markup.input(None, type='submit', name='submit', + value=self.submit), + + args = { 'method': self.method, + 'enctype': 'multipart/form-data'} + if self.action is not None: + args['action'] = self.action + return markup.form(retval.getvalue(), **args) + + ### functions for validation + + def validate(self, post): + """validate the form from the (POST) data + post should be a dictionary (MultiDict) + returns a dictionary of errors (empty dict == success) + validator functions can denote failure in three ways: + * raise an exception (currently any[!]) + * return a string -- assumed to be an error string + * return False -- the error + """ + + errors = {} + + def add_error(name, error): + if not errors.has_key(name): + errors[name] = [] + errors[name].append(error) + + for names, validators in self.validators.items(): + args = [ post.get(arg) for arg in names ] + error = None + + for validator in validators: + try: + validation = validator(*args) + except Exception, e: # horrible! + error = str(e) + else: + if isinstance(validation, basestring): + error = validation # error string + elif validation == False: + error = "Illegal value for %s" % name + if error: + for name in names: + add_error(name, error) + + return errors + + ### functions to add form elements + # each of these should be decorated to have: + # * a name/label + # * a validator (optional) + # (depending on type, an additional validator may be implied) + + def textfield(self, name, value=None, **kw): + kwargs = dict(name=name, type='text') + if value is not None: + kwargs['value'] = value + kwargs['size'] = '%d' % len(value) + kwargs.update(kw) + return markup.input(None, **kwargs) + + def password(self, name): + return markup.input(None, type='password', name=name) + + def hidden(self, name, value): + return markup.input(None, type='hidden', name=name, value=value) + + def file_upload(self, name): + return markup.input(None, type='file', name=name) + + def menu(self, name, items): + # first item is selected by default + retval = '\n'.join([ markup.option(item) for item in items]) + return markup.select('\n%s\n' % retval, name=name) + + def checkboxes(self, name, items, checked=set()): + retval = [] + checked = set(checked) + for item in items: + kw = { 'name': name, 'value': item, 'type': 'checkbox' } + if item in checked: + kw['checked'] = 'checked' + + # XXX hacky; ideally, one would use a list with no bullets + retval.append('%s%s<br/>' % (markup.input(None, **kw), item)) + + return '\n'.join(retval) + + def radiobuttons(self, name, items, checked=None, joiner='<br/>'): + if checked is None: + checked = items[0] + joiner = "%s\n" % joiner + retval = [] + for item in items: + title = None + if hasattr(item, '__iter__'): + item, title = item + kw = dict(type='radio', name=name, value=item) + if item == checked: + kw['checked'] = None + + # display contextual help + if title: + title = dict(title=title) + else: + title = {} + + retval.append(markup.span('%s%s' % (item, markup.input(**kw)), + **title)) + + return joiner.join(retval) + + def add_password_confirmation(self, password='Password', + confirmation='Confirm password', + validators=None): + """add password and confirm password fields""" + if validators is None: + validators = [] + validators.append(lambda x: bool(x.strip()) or "Please provide a password") + self.add_element('password', password) + self.add_element('password', confirmation) + self.validators[(password,)] = validators + match = lambda x, y: (x == y) or "Fields don't match" + self.validators[(password, confirmation)] = [ match ] + + + def add_element(self, func, name, *args, **kwargs): + + if isinstance(func, basestring): + func_name = func + func = getattr(self, func) + else: + func_name = func.func_name + + # form validators + validators = self.type_validators.get(func_name, []) + validators.extend(kwargs.pop('validators', [])) + self.validators[(name, )] = validators + + # don't diplay hidden elements + if func_name == 'hidden': + self.elements.append(dict(name='', + html=func(name, *args, **kwargs))) + return + + # allow contextual help + help = kwargs.pop('help', None) + + # allow alternate input names + # XXX breaks validation? + input_name = kwargs.pop('input_name', name) + + self.elements.append(dict(name=name, + html=func(input_name, *args, **kwargs), + help=help)) + + # TODO: add validation + def add_elements(self, name, funcs, args, kwargs, help=None): + """add multiple elements to the form + * name: master name; element names will be derived from this + * funcs: list of html-returning functions to add + * args: list of positional args: [ [ args1 ], [ args2], ... ] + * kwargs: dictionary of kwargs: [ { kwargs1 }, { kwargs2 }, ... ] + """ + html = StringIO() + + for func, index in zip(funcs, range(len(funcs))): + # no validation (yet) + if isinstance(func, basestring): + func = getattr(self, func) + print >> html, func('%s-%d' % (name, index), *args[index], + **kwargs[index]) + self.elements.append(dict(name=name, + html=html.getvalue(), + help=help)) + + +
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/markup/markup.py Tue Nov 03 13:01:49 2020 -0800 @@ -0,0 +1,167 @@ +from cStringIO import StringIO + +def HTMLmarkup(tag, text=None, **attributes): + """ + markups the text with the tag and + attributes + """ + + # ideally, this woulod be cached for cascading calls + s = StringIO() + + s.write('<%s' % tag) + for attribute, value in attributes.items(): + if value is None: + s.write(' %s' % attribute) + else: + s.write(' %s="%s"' % (attribute, value)) + + if text: + if tag in block_level: + s.write('>\n%s\n</%s>' % (text, tag)) + else: + s.write('>%s</%s>' % (text, tag)) + else: + s.write('/>') + return s.getvalue() + +tags = [ 'a', + 'b', 'body', 'br', + 'center', + 'dd', 'div', 'dl', 'dt', 'em', + 'form', + 'h1', 'h2', 'h3', 'head', 'html', + 'i', 'img', 'input', + 'li', 'lh', + 'ol', 'option', + 'p', + 'select', 'span', 'strong', + 'table', 'td', 'textarea', 'th', 'title', 'tr', + 'ul', + ] + +for _i in tags: + globals()[_i] = lambda x=None, _i=_i, **y: HTMLmarkup(_i, x, **y) + +# block-level elements +# from http://htmlhelp.com/reference/html40/block.html +block_level = set(['address', + 'blockquote', + 'center', + 'dir', 'div', 'dl', + 'fieldset', 'form', + 'h1', 'h2', 'h3', 'h4', 'h5', 'h6', 'hr', + 'isindex', + 'menu', + 'noframes', 'noscript', + 'ol', + 'p', 'pre', + 'table', + 'ul', + # not really block level, but act like it is + 'body', + 'dd', 'dt', + 'frameset', + 'head', 'html', + 'iframe', + 'tbody', 'tfoot', 'th', 'thead', 'tr']) + +### front ends to tags to make our lives easier +### these don't stomp on tags -- they're just front ends +### (these should go in a separate file) + +def image(src, **attributes): + attributes['src'] = src + return img(**attributes) + +def link(location, description=None, **attributes): + if description is None: + description = location + attributes['href'] = location + return a(description, **attributes) + +def listify(items, ordered=False, **attributes): + """ + return a HTML list of iterable items + * ordered: whether the list is a <ol> (True) or an <ul> (False) + * item_attributes: attributes applied to each list item + """ + + # type of list + if ordered: + func = ol + else: + func = ul + + item_attributes = attributes.pop('item_attributes', {}) + listitems = [ li(item, **item_attributes) for item in items ] + return func('\n'.join(listitems), **attributes) + +def definition_list(items, header=None, **attributes): + """definition list + items can be a dictionary or a list of 2-tuples""" + # XXX no attributes for header, dts, dds (yet) + + if header is None: + header = '', + else: + header = '%s\n' % lh(header) + + # convert dicts to lists of 2-tuples + if hasattr(items, 'items'): + items = items.items() + + items = [ dt(term) + dd(definition) for term, definition in items ] + return dl(('\n%s%s\n' % ( header, '\n'.join(items))), **attributes) + +def tablify(rows, header=False, item_attributes=None, + **attributes): + """return an HTML table from a iterable of iterable rows""" + + if item_attributes is None: + item_attributes = {} + + retval = [] + if header: + markup = th + else: + markup = td + + for row in rows: + retval.append('\n'.join([markup(str(item)) for item in row])) + markup = td + + return table('\n\n'.join([tr(row) for row in retval])) + +def wrap(string, pagetitle=None, stylesheets=(), icon=None, head_markup=()): + """wrap a string in a webpage""" + + _head = '' + if pagetitle: + _head += title(pagetitle) + rel = 'stylesheet' + for i in stylesheets: + attributes = dict(rel=rel, + type='text/css') + if hasattr(i, '__iter__'): + # assume a 2-tuple + attributes['href'] = i[0] + attributes['title'] = i[1] + else: + attributes['href'] = i + _head += '\n' + HTMLmarkup('link', None, **attributes) + rel = 'alternate stylesheet' # first stylesheet is default + if icon: + _head += '\n' + HTMLmarkup('link', None, href=icon) + + if head_markup: + # additional markup for <head> + if isinstance(head_markup, basestring): + _head += '\n' + head_markup + else: + for item in head_markup: + _head += '\n' + item + if _head: + _head = head(_head) + + return html('%s\n\n%s' % ( _head, body(string) ) )
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/setup.cfg Tue Nov 03 13:01:49 2020 -0800 @@ -0,0 +1,5 @@ +[egg_info] +tag_build = +tag_date = 0 +tag_svn_revision = 0 +
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/setup.py Tue Nov 03 13:01:49 2020 -0800 @@ -0,0 +1,26 @@ +from setuptools import setup, find_packages +import sys, os + +version = '0.2' + +setup(name='markup', + version=version, + description="generate HTML markup programmatically", + long_description="""\ +""", + classifiers=[], # Get strings from http://pypi.python.org/pypi?%3Aaction=list_classifiers + keywords='', + author='Jeff Hammel', + author_email='jhammel@openplans.org', + url='http://www.openplans.org/people/k0s/', + license='GPL', + packages=find_packages(exclude=['ez_setup', 'examples', 'tests']), + include_package_data=True, + zip_safe=False, + install_requires=[ + # -*- Extra requirements: -*- + ], + entry_points=""" + # -*- Entry points: -*- + """, + )