X-Git-Url: http://git.tremily.us/?p=cookbook.git;a=blobdiff_plain;f=cookbook%2Fserver.py;fp=cookbook%2Fserver.py;h=76dded630ff4d3dafd4c90b9fdc9c66fbf63d51a;hp=b5992fcfc15dedf1b5296836d596579f6880c26e;hb=20d3a594448e3e932fe935799be6db059d91f205;hpb=a2b08a880b4b8b1f224b69b12aa46fe496159abd diff --git a/cookbook/server.py b/cookbook/server.py index b5992fc..76dded6 100644 --- a/cookbook/server.py +++ b/cookbook/server.py @@ -18,17 +18,21 @@ """Serve cookbooks over HTTP. """ +from __future__ import absolute_import from __future__ import with_statement import os import random import re import types +import uuid from xml.sax import saxutils import cherrypy from jinja2 import Environment, FileSystemLoader +from .cookbook import Recipe, Directions, IngredientBlock, Ingredient, Amount + class Server (object): """Cookbook web interface.""" @@ -37,7 +41,21 @@ class Server (object): self.cookbook = cookbook self.cookbook.make_index() self.env = Environment(loader=FileSystemLoader(template_root)) - self.tag_regexp = re.compile('[a-zA-Z./ ].*') # allowed characters + # name regular expressions + self._action_ingredient_block_regexp = re.compile( + 'ingredient block ([0-9]*) ([a-z ]*)') + self._action_ingredient_regexp = re.compile( + 'ingredient ([0-9]*) ([0-9]*) ([a-z ]*)') + # value regular expressions + self._tag_regexp = re.compile('[a-zA-Z./ ].*') # allowed characters + self._action_add_ingredient_block_regexp = re.compile( + 'add ingredient block') + self._action_remove_ingredient_block_regexp = re.compile( + 'remove ingredient block ([0-9]*)') + self._action_add_ingredient_regexp = re.compile( + 'add ingredient ([0-9]*)') + self._action_remove_ingredient_regexp = re.compile( + 'remove ingredient ([0-9]*) ([0-9]*)') def cleanup(self): #self.cookbook.save('new-recipe') @@ -105,11 +123,305 @@ class Server (object): else: # User used both dropdown and textbox return None - m = self.tag_regexp.match(tag) + m = self._tag_regexp.match(tag) if m != None: return m.group() return None + @cherrypy.expose + def edit(self, name=None, **kwargs): + """Remove a tag from a single recipe.""" + name,recipe,action = self._normalize_edit_params(name, **kwargs) + if action.startswith('edit'): + self._update_recipe(name, recipe) + if action == 'edit and redirect': # done editing this recipe + raise cherrypy.HTTPRedirect( + u'recipe?name=%s' % recipe.clean_name(ascii=True), + status=302) + elif recipe.name != name: + raise cherrypy.HTTPRedirect( + u'edit?name=%s' % recipe.clean_name(ascii=True), + status=302) + template = self.env.get_template('edit-recipe.html') + return template.render(cookbook=self.cookbook, + recipe=recipe, + form='\n'.join(self._recipe_form(recipe))) + + def _update_recipe(self, name, recipe): + if len(recipe.ingredient_blocks) == 0: + recipe.ingredient_blocks = None + if name not in self.cookbook.index: # new recipe + print 'new' + self.cookbook.append(recipe) + self.cookbook.make_index() + elif name != recipe.name: # renamed recipe + print 'rename' + os.remove(recipe.path) + recipe.path = None + self.cookbook.make_index() + with open(recipe.path, 'w') as f: + recipe.save(f) + + def _normalize_edit_params(self, name, **kwargs): + action = 'view form' + if name in self.cookbook.index: + recipe = self.cookbook.index[name] + name = recipe.name # get the canonical name for this recipe + else: + # Use uuid4() to generate a random name if name is None + recipe = Recipe(name=(name or str(uuid.uuid4()))) + recipe.path = None + # non-ingredient text updates + for attr in ['name','directions','yield_','author','source','url']: + n = self._recipe_attr_to_name(attr) + if n not in kwargs: + continue + v = kwargs.get(n).strip() + if v == '': + v = None + elif attr == 'directions': + d = Directions() + v = v.replace('\r\n', '\n').replace('\r', '\n') + for p in v.split('\n\n'): + p = p.strip() + if len(p) > 0: + d.append(p) + v = d + if v != getattr(recipe, attr): + setattr(recipe, attr, v) + action = 'edit' + if recipe.ingredient_blocks == None: + recipe.ingredient_blocks = [] + # protect against index-changing operations + ingredient_block_map = dict( + [(i,i) for i in range(len(recipe.ingredient_blocks))]) + ingredient_map = dict( + [(i, dict([(j,j) for j in range(len(ib))])) + for i,ib in enumerate(recipe.ingredient_blocks)]) + # ingredient text updates + for key,value in kwargs.items(): + for k in ['ingredient_block', 'ingredient']: + regexp = getattr(self, '_action_%s_regexp' % k) + m = regexp.match(key) + if m != None: + handler = getattr(self, '_action_%s' % k) + action = handler(recipe, action, value, *m.groups(), + ingredient_block_map=ingredient_block_map, + ingredient_map=ingredient_map) + break + # button updates + action = kwargs.get('action', action) + if action == 'submit': + action = 'edit and redirect' + for a in ['add_ingredient_block', 'remove_ingredient_block', + 'add_ingredient', 'remove_ingredient']: + regexp = getattr(self, '_action_%s_regexp' % a) + m = regexp.match(action) + if m != None: + handler = getattr(self, '_action_%s' % a) + action = handler(recipe, action, *m.groups(), + ingredient_block_map=ingredient_block_map, + ingredient_map=ingredient_map) + break + return (name, recipe, action) + + def _action_ingredient_block(self, recipe, action, value, + block_index, block_action, + ingredient_block_map, **kwargs): + block_index = ingredient_block_map[int(block_index)] + ib = recipe.ingredient_blocks[block_index] + if value == '': + value = None + if block_action == 'position' and value not in [None, '']: + value = int(value) + if value != block_index: + ibs = recipe.ingredient_blocks + ibs.insert(value, ibs.pop(block_index)) + for i in range(min(block_index, value), + max(block_index, value)+1): + ingredient_block_map[i] = i+1 + ingredient_block_map[block_index] = value + return 'edit' + elif block_action == 'name': + if value != ib.name: + ib.name = value + return 'edit' + return action + + def _action_ingredient(self, recipe, action, value, + block_index, index, ingredient_action, + ingredient_block_map, ingredient_map, **kwargs): + block_index = ingredient_block_map[int(block_index)] + index = ingredient_map[block_index][int(index)] + ingredient = recipe.ingredient_blocks[block_index][index] + if value == '': + value = None + if ingredient_action == 'position' and value != None: + value = int(value) + if value != block_index: + ibs = recipe.ingredient_blocks + ibs.insert(value, ibs.pop(block_index)) + for i in range(min(block_index, value), + max(block_index, value)+1): + ingredient_block_map[i] = i+1 + ingredient_block_map[block_index] = value + return 'edit' + elif ingredient_action in ['name', 'note']: + if value != getattr(ingredient, ingredient_action): + setattr(ingredient, ingredient_action, value) + return 'edit' + elif ingredient_action in ['value', 'units']: + if value != getattr(ingredient.amount, ingredient_action): + setattr(ingredient.amount, ingredient_action, value) + return 'edit' + return action + + def _action_add_ingredient_block(self, recipe, action, + ingredient_block_map, + ingredient_map, **kwargs): + recipe.ingredient_blocks.append(IngredientBlock()) + block_index = len(recipe.ingredient_blocks)-1 + ingredient_block_map[block_index] = block_index + ingredient_map[block_index] = {} + return 'edit' + + def _action_remove_ingredient_block(self, recipe, action, block_index, + ingredient_block_map, **kwargs): + block_index = ingredient_block_map[int(block_index)] + recipe.ingredient_blocks.pop(block_index) + for k,v in ingredient_block_map.items(): + if v >= block_index: + ingredient_block_map[k] = v-1 + return 'edit' + + def _action_add_ingredient(self, recipe, action, block_index, + ingredient_block_map, + ingredient_map, **kwargs): + block_index = ingredient_block_map[int(block_index)] + recipe.ingredient_blocks[block_index].append(Ingredient( + amount=Amount())) + index = len(recipe.ingredient_blocks[block_index])-1 + ingredient_map[block_index][index] = index + return 'edit' + + def _action_remove_ingredient(self, recipe, action, block_index, index, + ingredient_block_map, + ingredient_map, **kwargs): + block_index = ingredient_block_map[int(block_index)] + index = ingredient_map[block_index][int(index)] + recipe.ingredient_blocks[block_index].pop(index) + for k,v in ingredient_map[block_index].items(): + if v >= index: + ingredient_map[block_index][k] = v-1 + return 'edit' + + def _recipe_form(self, recipe): + lines = [ + '
', + '', + ] + for attr in ['name', 'yield_', 'author', 'source', 'url']: + lines.extend(self._text_form( + self._recipe_attr_to_name(attr), + self._recipe_attr_to_user(attr), + getattr(recipe, attr))) + for i,ib in enumerate(recipe.ingredient_blocks): + lines.extend(self._ingredient_block_form(ib, i)) + lines.extend(self._submit_form('add ingredient block')) + lines.extend(self._textarea_form( + self._recipe_attr_to_name('directions'), + self._recipe_attr_to_user('directions'), + '\n\n'.join(recipe.directions or Directions()))) + lines.extend(self._submit_form('submit')) + lines.extend([ + '
', + '
', + ]) + return lines + + def _ingredient_block_form(self, ingredient_block, index): + lines = self._submit_form( + 'remove ingredient block %d' % index, + note='Ingredient block %d' % index) + for name,user,value in [ + ('ingredient block %d position' % index, + 'Position (%d)' % index, + None), + ('ingredient block %d name' % index, + 'Name', + ingredient_block.name), + ]: + lines.extend(self._text_form(name, user, value)) + for i,ingredient in enumerate(ingredient_block): + lines.extend(self._ingredient_form(ingredient, index, i)) + lines.extend(self._submit_form('add ingredient %d' % index)) + return lines + + def _ingredient_form(self, ingredient, block_index, index): + lines = self._submit_form( + 'remove ingredient %d %d' % (block_index, index), + note='Ingredient %d %d' % (block_index, index)) + for name,user,value in [ + ('ingredient %d %d position' % (block_index, index), + 'Position (%d)' % index, + ''), + ('ingredient %d %d value' % (block_index, index), + 'Value', + ingredient.amount.value), + ('ingredient %d %d units' % (block_index, index), + 'Units', + ingredient.amount.units), + ('ingredient %d %d name' % (block_index, index), + 'Name', + ingredient.name), + ('ingredient %d %d note' % (block_index, index), + 'Note', + ingredient.note), + ]: + lines.extend(self._text_form(name, user, value)) + # TODO: Amount: range_, alternatives + return lines + + def _submit_form(self, value, note=''): + return [ + ' ', + ' %s' % note, + ' ' + % value, + ' ', + ] + + def _text_form(self, name, user, value): + return [ + ' ', + ' ' % (name, user), + ' ' + % (name, value or ''), + ' ', + ] + + def _textarea_form(self, name, user, value): + return [ + ' ', + ' ' % (name, user), + ' ' + ' ', + ] + + def _recipe_attr_to_name(self, attr): + if attr == 'yield_': + return 'yield' + elif attr == 'name': + return 'new_name' + return attr + + def _recipe_attr_to_user(self, attr): + if attr == 'yield_': + attr = 'yield' + return attr.capitalize() + def test(): import doctest