def get_macro_obj_from_id(macro_id, macro=None):
    '''Get a processed macro from the macro id.  Raises an exception
    on error.  Assumes valid input.  Incrments the view counter
    for this macro.'''

    # First check memcache for a processed macro object:
    macro_obj = memcache.get(MACRO_PROC_KEY % macro_id)
    if not macro_obj:
        # Nothing from memcache, load from data store.
        if not macro:
            saved_macro_entity = SavedMacroOps.get_macro_entity(macro_id)
            # If saved_macro_entity is still none, we failed
            # in the datastore.
            if saved_macro_entity is None:
                raise NoInputError("Macro id '%s' not found." % macro_id)
            macro = saved_macro_entity.macro

        # Process the macro, lazily importing.
        from macro.interpret.interpreter import MacroInterpreter
        macro_obj = MacroInterpreter().interpret_macro(macro)

        # Save macro in memcached.
        memcache.add(MACRO_PROC_KEY % macro_id,
                     macro_obj,
                     MEMCACHED_MACRO_PROC)
    return macro_obj
def generate_search_page(path, terms, page, sort, page_size=DEF_SEARCH_RESULTS):
    '''
    Generate a search results page.
    '''
    error = None

    # Make sure page is a number, failing on bad input
    prev_page = None
    if not page:
        page = 1
    else:
        page = int(page)
        prev_page = page - 1
        
    # Do the search
    # TODO: Add column sort
    results = []
    is_next_page = False
    if (len(terms) < SINGLE_TAG_MAX_LENGTH):
        (results, is_next_page) = SavedMacroOps.search(terms, page=page, num=page_size, sort=sort)
    else:
        error = "Query term too long."
        terms = terms[:SINGLE_TAG_MAX_LENGTH] + "..."

    # If the number of results is less than that of page_size,
    # then there is no next page.
    next_page = None
    if is_next_page: next_page = page + 1

    # If there are no results, add an error.
    if not error and len(results) == 0:
        error = "No results found."

    # TODO: Hook up template controls to sort results.
    # TODO: Hook up template controls to page forward/back.
    
    # Return generated search page.
    return render_template('base.template',
                            {'query'  : terms,
                             'content': render_template('search.template',
                                                        {'search_error'    : error,
                                                         'curr_version'    : "%s.%s.%s" % (MAJOR_VERSION,
                                                                                           MINOR_VERSION,
                                                                                           PATCH_VERSION),
                                                         
                                                         'query'      : terms,
                                                         'q_esc'      : FORM_QUERY_ESC,
                                                         'results'    : results,
                                                         'sort'       : sort,
                                                          'page_var'   : FORM_SEARCH_PAGE,
                                                         
                                                         # Only give a prev page if we're over page 1.
                                                         'prev_page'  : prev_page,
                                                         'page'       : page,
                                                         'next_page'  : next_page,
                                                         },
                                                        path)},
                           path)
 def __do_rating(self, macro_id, rating):
   ''' Helper function to do the rating. '''
   # Increment macro rating, and return the
   # rating, rounded to the nearest half-star.
   saved_macro = SavedMacroOps(macro_id)
   return saved_macro.add_rating(rating)
def generate_view_page(path, macro_id, host_url, save_values={}):
    '''
    Generate a macro view page. Returns None on error.
    Propogates exceptions up.
    '''
    ret_page = None

    # Make sure we got valid input.
    if not macro_id: raise NoInputError("You entered an invalid macro ID.  Try again?")

    # Ensure we have a saved macro.  Will throw exception on fail.
    saved_macro = SavedMacroOps(macro_id)

    # View pages are extremely heavy, and the majority of their
    # data doesn't change.  Use helper to handle this.
    macro_form_template_values = get_view_dict_from_macro_id(macro_id, saved_macro)

    # Add in edit and view links.
    # Add in the author link.
    # Add in the form ids.
    page_values = {'macro_link'      : '%s/%s'    % (host_url,
                                                    macro_id),
                   'macro_edit'      : '%s?%s=%s' % (host_url,
                                                     GET_MACRO_LINK,
                                                     macro_id),
                   'author'          : _format_author(saved_macro.entity.name,
                                                      saved_macro.entity.server),
                   'send_email'      : FORM_MACRO_EMAIL,
                   'send_input_form' : FORM_EMAIL_INPUT,
                   'to'              : FORM_EMAIL_TO,
                   'from'            : FORM_EMAIL_FROM,
                   }
    macro_form_template_values.update(page_values)
    
    # Generate the dynamic parts of the page:
    #   1. Increment the view counter on this macro.
    #   2. Get the rating in terms of stars for this macro.
    dynamic_page_vals = {
        'stars'           : [i + 1 for i in range(MAX_RATING)],
        'num_rates'       : saved_macro.entity.num_rates,
        'views'           : saved_macro.add_to_view_count(),
        'rating'          : saved_macro.get_rating_dict(saved_macro.get_rating()),
        }
    macro_form_template_values.update(dynamic_page_vals)
    
    # Is this another save attempt after errors?  If so, update
    # with errors and previous values.
    macro_form_template_values.update(save_values)

    # Call helper to interpret the macro behind the id.
    macro_obj = get_macro_obj_from_id(macro_id, saved_macro.entity.macro)

    # Populate the templace with the processed macro.
    macro_form_template_values['processed_macro_html'] = \
         render_macro(macro_obj, path)

    # Get the copy and paste form of the macro
    macro_form_template_values['copy_paste'] = \
        _render_copy_cmd(macro_obj)

    # Render the macro view template html.
    ret_page  = render_template('view.template',
                                macro_form_template_values,
                                path)
    return render_template('base.template',
                           {'content':  ret_page},
                           path)
    def __render_form_page(self, macro):
        # Validate form errors and save
        form_errors = {}
        opt_fields    = {
            'title_show': 'none',
            'title_hide': 'block',
            'notes_show': 'none',
            'notes_hide': 'block',
            }
            
        # Title is optional.
        title  = self.request.get(FORM_SAVE_TITLE, default_value="")[0:TITLE_MAX_LENGTH]

        # Author is optional.
        name   = self.request.get(FORM_SAVE_NAME, default_value="")[0:NAME_MAX_LENGTH]
        
        # Server is optional.
        server = self.request.get(FORM_SAVE_SERVER, default_value="")[0:SERVER_MAX_LENGTH]

        # Notes are optional
        notes  = self.request.get(FORM_SAVE_NOTES, default_value="")[0:NOTES_TEXT_LENGTH]

        # Update optional field status
        if valid(title) or valid(name) or valid(server):
            opt_fields['title_show'] = "block"
            opt_fields['title_hide'] = "none"
        if valid(notes):
            opt_fields['notes_show'] = "block"
            opt_fields['notes_hide'] = "none"        

        # Version defaults to current version.
        version = "%s.%s.%s" % (MAJOR_VERSION,
                                MINOR_VERSION,
                                PATCH_VERSION)
        
        # Must have at least one class, and all must be recognized.
        classes = []
        for c in CLASS_LIST:
            if self.request.get(c.replace(" ", "_")) == '1': classes.append(c)
        if len(classes) == 0:
            form_errors['class_error'] = get_form_error(FORM_SAVE_CLASSES)

        # Must have at least one tag, and all must be recognized.
        # De-dup tags via a set.
        tags = list(set([t for t in re.split("\s*,\s*",
                                             self.request.get(FORM_SAVE_TAGS)) if t]))
        if len(tags) == 0:
            form_errors['tag_error'] = get_form_error(FORM_SAVE_TAGS)
        else:
            if len(self.request.get(FORM_SAVE_TAGS)) > ALL_TAGS_MAX_LENGTH:
                form_errors['tag_error'] = "Too many tags!"
            else:
                longest_tag = max(tags, key=len)
                if len(longest_tag) > SINGLE_TAG_MAX_LENGTH:
                    form_errors['tag_error'] = "Tag %s exceeds the max tag length of %s chars!" % (longest_tag, SINGLE_TAG_MAX_LENGTH)

        # Use memcached to throttle people to saving one macro every 10
        # seconds.    Only do this AFTER the user has fixed errors.
        if len(form_errors) == 0:
            secs_left = throttle_action("save", self.request.remote_addr)
            if secs_left > 0:
                form_errors['spam'] = "You must wait %s seconds before you may save another macro." % (secs_left)

        # Were there any errors?    If success, save and redirect
        if len(form_errors) == 0:
            # Return an error page if something really wrong happens.
            link = None
            link = SavedMacroOps.save_macro(macro, cgi.escape(notes), title, name, classes, tags, version, server)
            # Redirect to the intepreter with the link.
            self.redirect("/%s" % link)

        # Error in validation--display.
        else:
            # Create template data based on input.
            input_vals = {
                'title_data'      : title,
                'name_data'       : name,
                'macro_notes'     : notes,
                'curr_version'    : version,
                'note_limit'      : NOTES_TEXT_LENGTH,
                'note_ch_left'    : NOTES_TEXT_LENGTH - len(notes),
                'class_list'      : translate_classmap(sel=set(classes)),
                # Server list lives in a template.
                'server_list'     : template.render(os.path.join(_TEMPLATE_PATH,
                                                                 'servers.template'),
                                                    {}),
                'tag_def_list'    : TAG_LIST,
                'selected_server' : server,
                'tag_list'        : ",".join(tags),
                }
            # Add in errors.
            input_vals.update(form_errors)

            # Add in optional field status.
            input_vals.update(opt_fields)
            
            # Write out the page
            return generate_edit_page(_TEMPLATE_PATH,
                                      macro,
                                      save_values=input_vals)