def render(template_name, variables, template_dir='templates', file_ext=".mustache"): """ Render a mustache template given a dict representing its variables. Args: template_name (str): the name of the template to be rendered variables (dict): a string -> any dict holding values of variables used in the template template_dir (str): the template directory, relative to the GitDox root directory. Defaults to 'templates' file_ext (str): the file extension of templates. Defaults to '.mustache' Returns: str: rendered HTML. """ template_dir = prefix + template_dir # load Mustache templates so we can reference them in our large templates templates = dict([(filename[:-len(file_ext)], open(template_dir + os.sep + filename, 'r').read()) for filename in os.listdir(template_dir) if filename.endswith(file_ext)]) renderer = Renderer(partials=templates) variables['skin_stylesheet'] = config['skin'] variables['navbar_html'] = get_menu() return renderer.render_path( template_dir + os.sep + template_name + file_ext, variables)
class wsgiwrapper(object): """\ Creates a WSGI application from a CLI program that uses ArgumentParser. An HTTP GET request will return a form describing the parameters. The form will contain a fieldset for each action group in the parser. Each fieldset will contain an <input> element for each action. Several buttons will be added. An HTTP POST request will parse the data returned in the form, using it to create an argparse.Namespace object. That object will be passed to the 'process()' function of the CLI program. """ registry = b64id() defaults = { 'form_name': '', 'prefix': None, 'hooks': {}, 'skip_groups': [], 'submit_actions': { argparse._HelpAction, argparse._VersionAction, }, 'use_tables': False, } type_lookup = { basestring: 'text', int: 'number', float: 'number', } @print_where.tracing def __init__(self, parser, runapp, **kwargs): from collections import Counter self.parser = parser # The argparse object to turn into an HTML form. self.runapp = runapp # The app to run when the form is POSTed. self.renderer = Renderer() # TODO: decouple pystache self.script = set() self.toolbox = [] for name, default in self.defaults.items(): setattr(self, name, kwargs.get(name, default)) input_files, output_files = {}, {} cntr = Counter() form = Form(method='post', enctype='multipart/form-data', Class="form") if parser.description: form += P(parser.description, Class="description") button_bar = Div(Class="button_bar") self.buttons = [] templates = Div(Style="display:none") for action_group in parser._action_groups: if not action_group._group_actions: continue if self.use_tables: my_grid = Table(Class="table") else: my_grid = Fieldset( style= "display:grid; grid-template:auto / 1fr 3fr 5fr; grid-gap:10px" ) row_count = 0 for action in action_group._group_actions: dest = action.dest cntr[dest] += 1 dest_id = "%s.%d" % (dest, cntr[dest]) placeholder = get_placeholder(action) try: if isinstance(action, tuple(self.submit_actions)): print_where('action in submit_actions') button_bar += Input(type="submit", name=dest, value=cgi.escape(dest.title()), formnovalidate=None) self.buttons.append(action) continue triplet = isrange( action.choices) if action.choices else False print_where('triplet = %r' % (triplet, )) if action.choices is not None and not triplet: print_where('action requires <select>') input = mk_select(action) elif isinstance(action, argparse._StoreConstAction): ticket = self.registry.register(action.const) input = Input(type='checkbox', value=cgi.escape(ticket), style="justify-self:left") elif isinstance(action, argparse._StoreAction): print_where('action is a _StoreAction') if isinstance(action.type, argparse.FileType): if 'r' in action.type._mode: input = Input(type='file', id=dest_id) input_files[dest] = input else: placeholder = 'Output file name' input = Input(id=dest_id) output_files[dest] = input else: input = Input( type=self.type_lookup.get(action.type, 'text')) if action.default: input.setAttribute( 'value', cgi.escape(str(action.default))) if triplet: input.setAttribute('min', triplet[0]) input.setAttribute('max', triplet[1]) input.setAttribute('step', triplet[2]) if action.type == float: input.setAttribute('step', 'any') elif isinstance(action, argparse._AppendAction): print_where('action is a _AppendAction') continue # TODO: implement this elif isinstance(action, argparse._AppendConstAction): print_where('action is a _AppendConstAction') continue # TODO: implement this elif isinstance(action, argparse._CountAction): print_where('action is a _CountAction') input = Input(type='number', min=0) if action.default: input.setAttribute('value', cgi.escape(str(action.default))) else: print_where('should never be here') continue # TODO: can we ever get here? except TypeError as err: from traceback import format_exc input = Pre(format_exc()) input.setAttribute('name', dest) input.setAttribute('placeholder', placeholder) if action.required: input.setAttribute('required', None) handlers = self.hooks.get(dest + '.handlers', {}) for event, jscmd in handlers.items(): input.setAttribute(event, jscmd % dest) mobj = re.search(r'(\w+)\(', jscmd) if mobj: self.script.add(mobj.group(1)) nargs = action.nargs if nargs is None or input.tagName == 'SELECT' or input.getAttribute( 'type') == 'checkbox': item = input elif nargs == argparse.OPTIONAL: item = Ul(id=dest_id + '.ul', Class='input-ul') item += Li(PlusButton(onclick='hide_li(this);add_li("' + dest_id + '")'), id=dest_id + '.add') self.toolbox += Li(MinusButton( onclick='rm_li(this);show_li("' + dest_id + '")'), input, id=dest_id + '.li') self.script.add('hide_li') self.script.add('show_li') self.script.add('add_li') self.script.add('rm_li') elif nargs == argparse.ZERO_OR_MORE: item = Ul(id=dest_id + '.ul', Class='input-ul') item += Li(PlusButton(onclick='add_li("' + dest_id + '")'), id=dest_id + '.add') self.toolbox += Li(MinusButton(onclick='rm_li(this)'), input, id=dest_id + '.li') self.script.add('add_li') self.script.add('rm_li') elif nargs == argparse.ONE_OR_MORE: first = copy.deepcopy(input) first.setAttribute('required', None) item = Ul(id=dest_id + '.ul', Class='input-ul') item += Li(PlusButton(onclick='add_li("' + dest_id + '")'), first) self.toolbox += Li(MinusButton(onclick='rm_li(this)'), input, id=dest_id + '.li') self.script.add('add_li') self.script.add('rm_li') elif nargs == argparse.REMAINDER: raise NotImplementedError('nargs = %r' % nargs) elif nargs == argparse.PARSER: raise NotImplementedError('nargs = %r' % nargs) else: item = Ul(id=dest_id + '.ul', Class='input-ul') for _ in range(nargs): item += Li(copy.deepcopy(input)) if self.use_tables: my_row = Tr() my_row += Td( Label(dest.replace('_', ' ').title(), For=dest, Class='label')) my_row += Td(item) else: my_grid += NL, Label(dest.replace('_', ' ').title(), For=dest, Style='grid-area:%d/1' % (row_count + 1), Class='label') item.setAttribute( 'style', 'grid-area:%d/2' % (row_count + 1) + (';' + item.getAttribute('style') if item.hasAttribute('style') else '')) my_grid += NL, item if action.help: params = dict(vars(action), prog='XYZZY') for key, value in list(params.items()): if value == argparse.SUPPRESS: del params[key] elif hasattr(value, '__name__'): params[name] = value.__name__ else: pass if params.get('choices') is not None: params['choices'] = ', '.join( [str(c) for c in params['choices']]) if isinstance(params.get('default'), list): params['default'] = ', '.join( [str(c) for c in params['default']]) if self.use_tables: my_row += Td(action.help % params) else: my_grid += NL, Span(action.help % params, Style='grid-area:%d/3' % (row_count + 1)) if self.use_tables: my_grid += my_row row_count += 1 if row_count > 0 and action_group.title not in self.skip_groups: form += NL, my_grid Descr = Caption if self.use_tables else Legend descr = Descr(action_group.title, Class='legend') if action_group.description: descr += Br(), I(action_group.description) my_grid += descr else: del my_grid # avoid memory leaks? assert len(output_files) < 2 button_bar += Input(type="submit"), Input(type="reset") form += NL form += button_bar if parser.epilog: form += P(parser.epilog, Class="epilog") self.form = form @print_where.tracing def mk_form(self, *context, **kwargs): """Overridable method to generate our form. Returns a list of headers and a generator for the actual form data, which we can discard if we are processing, e.g., a HEAD request.""" return TEXT_HTML, [ str( self.renderer.render_path( # TODO: decouple pystache 'wsgiwrapper/wsgiwrapper.mustache', *context, **kwargs)) ] @print_where.tracing def __call__(self, environ, start_response): """Display (GET) or processs (POST) our form.""" self.environ = environ self.start_response = start_response parser = self.parser # Did we receive a GET or HEAD reques? # Display our form. # TODO: treat GET with query as a post? req_method = environ['REQUEST_METHOD'] if req_method in {'GET', 'HEAD'}: path_info = environ['PATH_INFO'] if path_info == '/favicon.ico': try: with open(path_info[1:], 'rb') as favicon: self.start_response( status200, IMAGE_ICON, ) return [favicon.read()] except: pass if path_info != '/': self.start_response( status404, TEXT_PLAIN, ) return ['Not found'] headers, form_iter = self.mk_form( self.environ, script=[js_library[func] for func in self.script], toolbox=self.toolbox, form=self.form, ) self.start_response(status200, headers) return [] if req_method == 'HEAD' else form_iter # The only other acceptable request is a POST. if req_method != 'POST': self.start_response('405 Method Not Allowed', TEXT_PLAIN) return [] # Guard against errors while working... with Backstop(environ, self.start_response): # Parse the submitted data. print_where('Parse the submitted data.') try: fieldstorage = cgi.FieldStorage(fp=environ['wsgi.input'], environ=environ, keep_blank_values=True) except Exception: # The only error I've seen generated is when # wsgiref.validate is being used. See # http://python.6.x6.nabble.com/Revising-environ-wsgi-input-readline-in-the-WSGI-specification-td2211999.html#a2212023 from traceback import format_exc print_where(format_exc()) return [] # Did the user click on a button? print_where('Did the user click on a button?') for action in self.buttons: if action.dest in fieldstorage: if isinstance(action, argparse._HelpAction): self.start_response(status200, TEXT_PLAIN) return [parser.format_help().encode()] elif isinstance(action, argparse._VersionAction): formatter = parser.formatter_class(parser.prog) formatter.add_text(action.version) self.start_response(status200, TEXT_PLAIN) return [formatter.format_help().encode()] else: self.start_response(status200, TEXT_PLAIN) return [str(action.__class__).encode()] return # Create an argparse.Namespace from the fieldstorage. print_where('Create an argparse.Namespace from the fieldstorage.') new_args = argparse.Namespace() self.output_files = {} for action in parser._actions: print_where('action =', action) if action in self.buttons: continue dest = action.dest if isinstance(action, argparse._StoreConstAction): # this is a checkbox, so ignore nargs value = fieldstorage.getfirst(dest, action.default) value = self.registry.redeem(value) elif action.nargs is None: # no nargs means there can be only one value = fieldstorage.getfirst(dest, action.default) else: value = fieldstorage.getlist(dest) or [action.default] if dest + '.split' in self.hooks: value = value[0].split() print_where('value =', repr(value)[:240]) if action.type: if isinstance(action.type, argparse.FileType): field = fieldstorage[dest] print_where('field =', repr(field)[:240]) if 'r' in action.type._mode: # need to read from a file-like object filename = field.filename print_where('filename =', repr(filename)[:240]) if filename: value = StringIO(field.value) value.name = filename else: value = None print_where('value =', repr(value)[:240]) else: # create a file-like object from our text input value = self.output_files[dest] = StringIO() filename = field.value if filename: value.name = filename else: value = None else: try: value = action.type(value) except: value = action.type() print_where('value =', repr(value)[:240]) # add this to our Namespace object setattr(new_args, dest, value) if self.prefix is not None: # drop hints that we're a web app setattr(new_args, self.prefix + 'environ', environ) setattr(new_args, self.prefix + 'start_response', self.start_response) newout = StringIO() # build the rest of the execution environment try: try: _stdin, sys.stdin = sys.stdin, open(os.devnull, 'r') _stdout, sys.stdout = sys.stdout, newout _stderr, sys.stderr = sys.stderr, newout ### THEN THE MAGIC HAPPENS ### sys.exit(self.runapp(new_args)) finally: sys.stdin = _stdin sys.stdout = _stdout sys.stderr = _stderr except SystemExit as err: return self.do_sys_exit(err, newout) except Exception as err: return self.do_exception(err) @print_where.tracing def do_sys_exit(self, err, newout): with Backstop(self.environ, self.start_response): buffer = newout.getvalue() if err.code: status = '400 Bad Request' headers, form_iter = self.mk_form( self.environ, script=[js_library[func] for func in self.script], form=self.form, errors=buffer) elif self.output_files: status = status200 assert len(self.output_files) == 1 for outfile in self.output_files.values(): filename = outfile.name content = outfile.getvalue() content_length = len(content) headers = [ ('Content-Length', str(content_length)), ('Content-Disposition', 'attachment; filename="' + filename + '"'), ('Last-Modified', format_date_time(time.time())), ] content_type, encoding = guess_type(filename) if content_type: headers.append(('Content-Type', content_type)) if encoding: headers.append(('Content-Encoding', encoding)) form_iter = [content] else: status = status200 headers = TEXT_PLAIN form_iter = [buffer] self.start_response(status, headers) return form_iter @print_where.tracing def do_exception(self, err): """Overridable method to handle miscellaeous exceptions.""" from traceback import format_exc self.start_response(status200, TEXT_PLAIN) print_where(format_exc()) return []
# test_obj = { # 'title': 'A f*****g title' # } # # title_partial = {'page-title': 'Some cinder crap | {{title}}'} # renderer = Renderer(partials=title_partial) # renderer.search_dirs.append(TEMPLATE_DIR) # path = os.path.join(TEMPLATE_DIR, "master-template.mustache") # actual = renderer.render_path(path, test_obj) # print actual # step 1: render content in template content_renderer = Renderer() path = os.path.join(CLASS_TEMPLATE_DIR, "main-content-template.mustache") content_renderer.search_dirs.append(TEMPLATE_DIR) rendered_content = content_renderer.render_path(path, class_obj) # step 2: place rendered content into main template # - should have the following custom partials: # - page title (define in object for page templates) # - page content (rendered page content) # - any other common partials that may lie outside the basic content area test_obj = {"title": class_obj["name"]} loader = Loader() # template = loader.read("title") title_partial = loader.load_name(os.path.join(CLASS_TEMPLATE_DIR, "title")) partials = {"page-title": title_partial, "main-content": rendered_content} renderer = Renderer(partials=partials)
def render_mustache(template, data): pystache = Renderer(search_dirs=DATA_PATH + '/templates') html = pystache.render_path(template, data) return html
# test_obj = { # 'title': 'A f*****g title' # } # # title_partial = {'page-title': 'Some cinder crap | {{title}}'} # renderer = Renderer(partials=title_partial) # renderer.search_dirs.append(TEMPLATE_DIR) # path = os.path.join(TEMPLATE_DIR, "master-template.mustache") # actual = renderer.render_path(path, test_obj) # print actual # step 1: render content in template content_renderer = Renderer() path = os.path.join(CLASS_TEMPLATE_DIR, "main-content-template.mustache") content_renderer.search_dirs.append(TEMPLATE_DIR) rendered_content = content_renderer.render_path(path, class_obj) # step 2: place rendered content into main template # - should have the following custom partials: # - page title (define in object for page templates) # - page content (rendered page content) # - any other common partials that may lie outside the basic content area test_obj = {'title': class_obj["name"]} loader = Loader() # template = loader.read("title") title_partial = loader.load_name(os.path.join(CLASS_TEMPLATE_DIR, "title")) partials = {'page-title': title_partial, 'main-content': rendered_content} renderer = Renderer(partials=partials)