X-Git-Url: http://git.tremily.us/?p=cookbook.git;a=blobdiff_plain;f=cookbook%2Fserver.py;fp=cookbook%2Fserver.py;h=0000000000000000000000000000000000000000;hp=e94e7693e95977d03441d8e6b424ceafd82eb799;hb=364935709467c654cc1c079212ec939438935d6f;hpb=458baf4d02a354dab67843b6228b158d4148f2c1 diff --git a/cookbook/server.py b/cookbook/server.py deleted file mode 100644 index e94e769..0000000 --- a/cookbook/server.py +++ /dev/null @@ -1,440 +0,0 @@ -# Copyright (C) 2010 W. Trevor King -# -# This file is part of Cookbook. -# -# Cookbook is free software: you can redistribute it and/or modify it -# under the terms of the GNU General Public License as published by the -# Free Software Foundation, either version 3 of the License, or (at your -# option) any later version. -# -# Cookbook is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with Cookbook. If not, see . - -"""Serve cookbooks over HTTP. -""" - -from __future__ import absolute_import -from __future__ import with_statement - -import os -import random -import re -import types -from urllib import urlencode -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.""" - - def __init__(self, cookbook, template_root): - self.cookbook = cookbook - self.cookbook.make_index() - self.env = Environment(loader=FileSystemLoader(template_root)) - # 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') - pass - - @cherrypy.expose - def index(self, tag=None): - """Recipe index page. - - Recipes can be filtered by tag. - """ - if isinstance(tag, types.StringTypes): - tag = [tag] - template = self.env.get_template('recipes.html') - return template.render(cookbook=self.cookbook, - recipes=list(self.cookbook.tagged(tag)), - selected_tags=tag) - - @cherrypy.expose - def recipe(self, name=None): - """Single recipe page. - """ - if name == None: - recipe = random.choice(self.cookbook) - else: - recipe = self.cookbook.index[name] - tag_links = [ - '%s' % (urlencode({'tag':t}), t) - for t in (recipe.tags or [])] - template = self.env.get_template('recipe.html') - return template.render(cookbook=self.cookbook, recipe=recipe, - tag_links=tag_links) - - @cherrypy.expose - def add_tag(self, name, tag): - """Add a tag to a single recipe.""" - recipe = self.cookbook.index[name] - if recipe.tags == None: - recipe.tags = [] - tag = self._clean_tag(tag) - if tag != None and tag not in recipe.tags: - recipe.tags.append(tag) - with open(recipe.path, 'w') as f: - recipe.save(f) - raise cherrypy.HTTPRedirect( - u'recipe?name=%s' % recipe.clean_name(ascii=True), status=302) - - @cherrypy.expose - def remove_tag(self, name, tag): - """Remove a tag from a single recipe.""" - recipe = self.cookbook.index[name] - if recipe.tags == None: - return - tag = self._clean_tag(tag) - if tag != None and tag in recipe.tags: - recipe.tags.remove(tag) - with open(recipe.path, 'w') as f: - recipe.save(f) - raise cherrypy.HTTPRedirect( - u'recipe?name=%s' % recipe.clean_name(ascii=True), status=302) - - def _clean_tag(self, tag): - """Sanitize tag.""" - if not isinstance(tag, types.StringTypes): - if len(tag) == 2 and '' in tag: - # User used either dropdown or textbox - tag.remove('') - tag = tag[0] - else: - # User used both dropdown and textbox - return None - 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 == 'remove': - recipe = self.cookbook.index[name] - os.remove(recipe.path) - self.cookbook.remove(recipe) - self.cookbook.make_index() - raise cherrypy.HTTPRedirect(u'.', status=302) - elif 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() - self.cookbook.sort(key=lambda r: r.path) - 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) - kws = {'ingredient_block_map': ingredient_block_map, - 'ingredient_map': ingredient_map} - action = handler(recipe, action, value, *m.groups(), **kws) - 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) - kws = {'ingredient_block_map': ingredient_block_map, - 'ingredient_map': ingredient_map} - action = handler(recipe, action, *m.groups(), **kws) - 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 - doctest.testmod()