Rewrite to Django from scratch. Now it's much more user-friendly.
[cookbook.git] / cookbook / server.py
diff --git a/cookbook/server.py b/cookbook/server.py
deleted file mode 100644 (file)
index e94e769..0000000
+++ /dev/null
@@ -1,440 +0,0 @@
-# Copyright (C) 2010 W. Trevor King <wking@drexel.edu>
-#
-# 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 <http://www.gnu.org/licenses/>.
-
-"""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 = [
-            '<a href="./?%s">%s</a>' % (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 = [
-            '<form action="" method="post">',
-            '<table>',
-            ]
-        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([
-                '</table>',
-                '</form>',
-                ])
-        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 [
-            '  <tr>',
-            '    <td>%s</td>' % note,
-            '    <td><input type="submit" name="action" value="%s"/></td>'
-            % value,
-            '  </tr>',
-            ]
-
-    def _text_form(self, name, user, value):
-        return [
-            '  <tr>',
-            '    <td><label for="%s">%s</label></td>' % (name, user),
-            '    <td><input type="text" name="%s" size="60" value="%s"/></td>'
-            % (name, value or ''),
-            '  </tr>',
-            ]
-
-    def _textarea_form(self, name, user, value):
-        return [
-            '  <tr>',
-            '    <td><label for="%s">%s</label></td>' % (name, user),
-            '    <td><textarea name="%s" rows="20" cols="60"/>' % name,
-            value,
-            '        </textarea></td>'
-            '  </tr>',
-            ]
-
-    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()