Rewrite to Django from scratch. Now it's much more user-friendly.
[cookbook.git] / cookbook / cookbook.py
diff --git a/cookbook/cookbook.py b/cookbook/cookbook.py
deleted file mode 100644 (file)
index 24a70fe..0000000
+++ /dev/null
@@ -1,578 +0,0 @@
-#!/usr/bin/python
-# -*- encoding: utf-8 -*-
-#
-# 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/>.
-
-"""Represent a cookbook and recipes with Python classes.
-"""
-
-from __future__ import with_statement
-
-import os
-import os.path
-import textwrap
-import types
-from urllib import quote_plus
-
-import yaml
-
-
-def string_for_yaml(unicode_):
-    """
-    >>> string_for_yaml(None)
-    >>> string_for_yaml('all ascii')
-    'all ascii'
-    >>> string_for_yaml(u'all ascii')
-    'all ascii'
-    >>> string_for_yaml(u'½ ascii')
-    u'\\xc2\\xbd ascii'
-    """
-    if unicode_ == None:
-        return unicode_
-    try:
-        string = unicode_.encode('ascii')
-        return string
-    except UnicodeEncodeError:
-        return unicode_
-
-def to_yaml_object(obj):
-    """
-    >>> to_yaml_object(None)
-    >>> to_yaml_object('all ascii')
-    'all ascii'
-    >>> to_yaml_object(u'all ascii')
-    'all ascii'
-    >>> to_yaml_object('all ascii')
-    'all ascii'
-    >>> to_yaml_object(u'½ ascii')
-    u'\\xc2\\xbd ascii'
-    >>> class x (object):
-    ...     def to_yaml(self):
-    ...         return 'to_yaml return value'
-    >>> to_yaml_object(x())
-    'to_yaml return value'
-    >>> to_yaml_object([u'all ascii', u'½ ascii'])
-    ['all ascii', u'\\xc2\\xbd ascii']
-    """
-    if obj == None:
-        return obj
-    if hasattr(obj, 'to_yaml'):
-        return obj.to_yaml()
-    if type(obj) in types.StringTypes:
-        return string_for_yaml(obj)
-    if hasattr(obj, '__len__'):
-        ret = []
-        for item in obj:
-            ret.append(to_yaml_object(item))
-        return ret
-    raise NotImplementedError(
-        'cannot convert %s to YAMLable dict:\n%s' % (type(obj), unicode(obj)))
-
-
-class Amount (object):
-    """
-    >>> import pprint
-    >>> a = Amount(value='1', units='T.')
-    >>> str(a)
-    '1 T.'
-    >>> pprint.pprint(a.to_yaml())
-    {'alternatives': [], 'range_': None, 'units': 'T.', 'value': '1'}
-    >>> x = Amount()
-    >>> x.from_yaml(a.to_yaml())
-    >>> str(x)
-    '1 T.'
-    >>> b = Amount(value='1')
-    >>> str(b)
-    '1'
-    >>> pprint.pprint(b.to_yaml())
-    {'alternatives': [], 'range_': None, 'units': None, 'value': '1'}
-    >>> x.from_yaml(b.to_yaml())
-    >>> str(x)
-    '1'
-    >>> c = Amount(value='15', units='mL', alternatives=[a])
-    >>> str(c)
-    '15 mL (1 T.)'
-    >>> pprint.pprint(c.to_yaml())
-    {'alternatives': [{'alternatives': [],
-                       'range_': None,
-                       'units': 'T.',
-                       'value': '1'}],
-     'range_': None,
-     'units': 'mL',
-     'value': '15'}
-    >>> x.from_yaml(c.to_yaml())
-    >>> str(x)
-    '15 mL (1 T.)'
-    >>> d = Amount(units='T.', range_=['1','2'])
-    >>> str(d)
-    '1-2 T.'
-    >>> pprint.pprint(d.to_yaml())
-    {'alternatives': [], 'range_': ['1', '2'], 'units': 'T.', 'value': None}
-    >>> x.from_yaml(d.to_yaml())
-    >>> str(x)
-    '1-2 T.'
-    """
-    def __init__(self, value=None, units=None, range_=None, alternatives=None):
-        self.value = value
-        self.units = units
-        self.range_ = range_
-        if alternatives == None:
-            alternatives = []
-        self.alternatives = alternatives
-
-    def __str__(self):
-        return str(self.__unicode__())
-
-    def __unicode__(self):
-        if self.range_ == None:
-            value = self.value
-        else:
-            value = '-'.join(self.range_)
-        ret = [value]
-        if self.units != None:
-            ret.append(self.units)
-        if len(self.alternatives) > 0:
-            ret.append('(%s)' % ', '.join(
-                    [unicode(a) for a in self.alternatives]))
-        return ' '.join([x for x in ret if x != None])
-
-    def to_yaml(self):
-        d = {}
-        for key in ['value', 'range_', 'units', 'alternatives']:
-            d[key] = to_yaml_object(getattr(self, key))
-        return d
-
-    def from_yaml(self, dict):
-        for key in ['value', 'range_', 'units']:
-            setattr(self, key, dict.get(key, None))
-        self.alternatives = []
-        for a in dict.get('alternatives', []):
-            amount = Amount()
-            amount.from_yaml(a)
-            self.alternatives.append(amount)
-
-
-class Ingredient (object):
-    """
-    >>> import pprint
-    >>> i = Ingredient('eye of newt', Amount('1'))
-    >>> str(i)
-    '1 eye of newt'
-    >>> pprint.pprint(i.to_yaml())
-    {'amount': {'alternatives': [], 'range_': None, 'units': None, 'value': '1'},
-     'name': 'eye of newt',
-     'note': None}
-    >>> x = Ingredient()
-    >>> x.from_yaml(i.to_yaml())
-    >>> str(x)
-    '1 eye of newt'
-    >>> j = Ingredient('salamanders', Amount('2'), 'diced fine')
-    >>> str(j)
-    '2 salamanders, diced fine'
-    >>> pprint.pprint(j.to_yaml())
-    {'amount': {'alternatives': [], 'range_': None, 'units': None, 'value': '2'},
-     'name': 'salamanders',
-     'note': 'diced fine'}
-    >>> x.from_yaml(j.to_yaml())
-    >>> str(x)
-    '2 salamanders, diced fine'
-    """
-    def __init__(self, name=None, amount=None, note=None):
-        self.name = name
-        self.amount = amount
-        self.note = note
-
-    def __str__(self):
-        return str(self.__unicode__())
-
-    def __unicode__(self):
-        if self.note == None:
-            return '%s %s' % (unicode(self.amount or ''), self.name)
-        return '%s %s, %s' % (unicode(self.amount or ''), self.name, self.note)
-
-    def to_yaml(self):
-        d = {}
-        for key in ['name', 'amount', 'note']:
-            d[key] = to_yaml_object(getattr(self, key))
-        return d
-
-    def from_yaml(self, dict):
-        for key in ['name', 'note']:
-            setattr(self, key, dict.get(key, None))
-        self.amount = Amount()
-        self.amount.from_yaml(dict.get('amount', {}))
-
-
-class IngredientBlock (list):
-    """
-    >>> import pprint
-    >>> ib = IngredientBlock(None, [
-    ...         Ingredient('eye of newt', Amount('1')),
-    ...         Ingredient('salamanders', Amount('2'), 'diced fine')])
-    >>> print str(ib)
-    Ingredients:
-      1 eye of newt
-      2 salamanders, diced fine
-    >>> pprint.pprint(ib.to_yaml())
-    {'ingredients': [{'amount': {'alternatives': [],
-                                 'range_': None,
-                                 'units': None,
-                                 'value': '1'},
-                      'name': 'eye of newt',
-                      'note': None},
-                     {'amount': {'alternatives': [],
-                                 'range_': None,
-                                 'units': None,
-                                 'value': '2'},
-                      'name': 'salamanders',
-                      'note': 'diced fine'}],
-     'name': None}
-    >>> x = IngredientBlock()
-    >>> x.from_yaml(ib.to_yaml())
-    >>> print str(x)
-    Ingredients:
-      1 eye of newt
-      2 salamanders, diced fine
-    >>> ib.name = 'Dressing'
-    >>> print str(ib)
-    Dressing:
-      1 eye of newt
-      2 salamanders, diced fine
-    >>> pprint.pprint(ib.to_yaml())
-    {'ingredients': [{'amount': {'alternatives': [],
-                                 'range_': None,
-                                 'units': None,
-                                 'value': '1'},
-                      'name': 'eye of newt',
-                      'note': None},
-                     {'amount': {'alternatives': [],
-                                 'range_': None,
-                                 'units': None,
-                                 'value': '2'},
-                      'name': 'salamanders',
-                      'note': 'diced fine'}],
-     'name': 'Dressing'}
-    >>> x = IngredientBlock()
-    >>> x.from_yaml(ib.to_yaml())
-    >>> print str(x)
-    Dressing:
-      1 eye of newt
-      2 salamanders, diced fine
-    """
-    def __init__(self, name=None, *args, **kwargs):
-        self.name = name
-        super(IngredientBlock, self).__init__(*args, **kwargs)
-
-    def heading(self):
-        if self.name == None:
-            return 'Ingredients'
-        return self.name
-
-    def __str__(self):
-        return str(self.__unicode__())
-
-    def __unicode__(self):
-        ret = [unicode(i) for i in self]
-        ret.insert(0, '%s:' % self.heading())
-        return '\n  '.join(ret)
-
-    def to_yaml(self):
-        d = {}
-        for key in ['name']:
-            d[key] = to_yaml_object(getattr(self, key))
-        d['ingredients'] = to_yaml_object(list(self))
-        return d
-
-    def from_yaml(self, dict):
-        self.name = dict.get('name', None)
-        while len(self) > 0:
-            self.pop()
-        for i in dict.get('ingredients', []):
-            ingredient = Ingredient()
-            ingredient.from_yaml(i)
-            self.append(ingredient)
-
-
-class Directions (list):
-    """
-    >>> import pprint
-    >>> d = Directions(['paragraph 1', 'paragraph 2', 'paragraph 3'])
-    >>> print str(d)
-    paragraph 1
-    <BLANKLINE>
-    paragraph 2
-    <BLANKLINE>
-    paragraph 3
-    >>> pprint.pprint(d.to_yaml())
-    'paragraph 1\\n\\nparagraph 2\\n\\nparagraph 3'
-    >>> x = Directions()
-    >>> x.from_yaml(d.to_yaml())
-    >>> print str(x)
-    paragraph 1
-    <BLANKLINE>
-    paragraph 2
-    <BLANKLINE>
-    paragraph 3
-    >>> pprint.pprint(x.to_yaml())
-    'paragraph 1\\n\\nparagraph 2\\n\\nparagraph 3'
-    """
-    def __str__(self):
-        return str(self.__unicode__())
-
-    def __unicode__(self):
-        return '\n\n'.join(self)
-
-    def wrapped_paragraphs(self, *args, **kwargs):
-        return ['\n'.join(
-                textwrap.wrap(
-                    paragraph,
-                    *args,
-                    **kwargs))
-                for paragraph in self]
-
-    def wrap(self, *args, **kwargs):
-        return '\n\n'.join(self.wrapped_paragraphs(*args, **kwargs))
-
-    def to_yaml(self):
-        return string_for_yaml('\n\n'.join([paragraph.rstrip('\n')
-                                            for paragraph in self]))
-
-    def from_yaml(self, string):
-        if string == None:
-            return
-        while len(self) > 0:
-            self.pop()
-        for paragraph in string.split('\n\n'):
-            self.append(paragraph)
-
-
-class Recipe (object):
-    """
-    >>> import pprint
-    >>> r = Recipe('Hot Newt Dressing',
-    ...     [IngredientBlock(None, [
-    ...         Ingredient('eye of newt', Amount('1')),
-    ...         Ingredient('salamanders', Amount('2'), 'diced fine')])],
-    ...     Directions(['Mix ingredients until salamander starts to smoke',
-    ...                 'Serve warm over rice']),
-    ...     yield_='enough for one apprentice',
-    ...     author='Merlin',
-    ...     source='CENSORED',
-    ...     url='http://merlin.uk/recipes/1234/',
-    ...     tags=['dinner', 'easy', 'apprentice', 'eye', 'newt', 'salamander'])
-    >>> print str(r)
-    Recipe: Hot Newt Dressing
-    Yield: enough for one apprentice
-    From: Merlin
-    Source: CENSORED
-    URL: http://merlin.uk/recipes/1234/
-    Ingredients:
-      1 eye of newt
-      2 salamanders, diced fine
-    Mix ingredients until salamander starts to smoke
-    <BLANKLINE>
-    Serve warm over rice
-    >>> r.matches_tags(None)
-    True
-    >>> r.matches_tags([])
-    True
-    >>> r.matches_tags(['dinner', 'apprentice'])
-    True
-    >>> r.matches_tags(['dinner', 'dragon'])
-    False
-    >>> pprint.pprint(r.to_yaml())
-    {'author': 'Merlin',
-     'directions': 'Mix ingredients until salamander starts to smoke\\n\\nServe warm over rice',
-     'ingredient_blocks': [{'ingredients': [{'amount': {'alternatives': [],
-                                                        'range_': None,
-                                                        'units': None,
-                                                        'value': '1'},
-                                             'name': 'eye of newt',
-                                             'note': None},
-                                            {'amount': {'alternatives': [],
-                                                        'range_': None,
-                                                        'units': None,
-                                                        'value': '2'},
-                                             'name': 'salamanders',
-                                             'note': 'diced fine'}],
-                            'name': None}],
-     'name': 'Hot Newt Dressing',
-     'source': 'CENSORED',
-     'tags': ['dinner', 'easy', 'apprentice', 'eye', 'newt', 'salamander'],
-     'url': 'http://merlin.uk/recipes/1234/',
-     'yield_': 'enough for one apprentice'}
-    >>> x = Recipe()
-    >>> x.from_yaml(r.to_yaml())
-    >>> print str(x)
-    Recipe: Hot Newt Dressing
-    Yield: enough for one apprentice
-    From: Merlin
-    Source: CENSORED
-    URL: http://merlin.uk/recipes/1234/
-    Ingredients:
-      1 eye of newt
-      2 salamanders, diced fine
-    Mix ingredients until salamander starts to smoke
-    <BLANKLINE>
-    Serve warm over rice
-    """
-    def __init__(self, name=None, ingredient_blocks=None, directions=None,
-                 yield_=None, author=None, source=None, url=None, tags=None):
-        self.name = name
-        self.ingredient_blocks = ingredient_blocks
-        self.directions = directions
-        self.yield_ = yield_
-        self.author = author
-        self.source = source
-        self.url = url
-        self.tags = tags
-        self.path = None
-
-    def clean_name(self, ascii=False):
-        name = self.name
-        for from_,to in [(' ','_'), ('/', '_'), (',', ''), ('&', 'and')]:
-            name = name.replace(from_, to)
-        if ascii == True:
-            return quote_plus(name.encode('utf-8'))
-        return name
-
-    def matches_tags(self, tags):
-        """Return True if this recipe is tagges with each of the tags in tags.
-        """
-        if tags in [None, []]:
-            return True
-        if self.tags == None:
-            return False
-        for t in tags:
-            if t not in self.tags:
-                return False
-        return True
-
-    def __str__(self):
-        return str(self.__unicode__())
-
-    def __unicode__(self):
-        return '\n'.join([
-                'Recipe: %s' % self.name,
-                'Yield: %s' % self.yield_,
-                'From: %s' % self.author,
-                'Source: %s' % self.source,
-                'URL: %s' % self.url,
-                '\n'.join([unicode(ib) for ib in self.ingredient_blocks]),
-                unicode(self.directions),
-                ])
-
-    def to_yaml(self):
-        d = {}
-        for key in ['name', 'ingredient_blocks', 'directions', 'yield_',
-                    'author', 'source', 'url', 'tags']:
-            d[key] = to_yaml_object(getattr(self, key))
-        return d
-
-    def from_yaml(self, dict):
-        for key in ['name', 'yield_', 'author', 'source', 'url', 'tags']:
-            setattr(self, key, dict.get(key, None))
-        self.ingredient_blocks = []
-        for ib in dict.get('ingredient_blocks', []):
-            ingredient_block = IngredientBlock()
-            ingredient_block.from_yaml(ib)
-            self.ingredient_blocks.append(ingredient_block)
-        self.directions = Directions()
-        self.directions.from_yaml(dict.get('directions', None))
-
-    def save(self, stream, path=None):
-        yaml.dump(self.to_yaml(), stream,
-                  default_flow_style=False, allow_unicode=True, width=78)
-        if path != None:
-            self.path = path
-
-    def load(self, stream, path=None):
-        dict = yaml.load(stream)
-        self.from_yaml(dict)
-        if path != None:
-            self.path = path
-
-
-class Cookbook (list):
-    """
-    TODO: doctests
-    """
-    def __init__(self, name="Mom's cookbook", *args, **kwargs):
-        self.name = name
-        self.save_dir = u'recipe'
-        super(Cookbook, self).__init__(*args, **kwargs)
-
-    def save(self):
-        if not os.path.isdir(self.save_dir):
-            os.mkdir(self.save_dir)
-        paths = []
-        for recipe in self:
-            p = self._free_path(recipe, paths)
-            paths.append(p)
-            with open(p, 'w') as f:
-                recipe.save(f, p)
-
-    def load(self):
-        for path in sorted(os.listdir(self.save_dir)):
-            r = Recipe()
-            p = os.path.join(self.save_dir, path)
-            with open(p, 'r') as f:
-                r.load(f, p)
-            self.append(r)
-
-    def make_index(self):
-        self.index = {}
-        paths = [recipe.path for recipe in self]
-        for recipe in self:
-            self.index[recipe.name] = recipe
-            self.index[recipe.clean_name()] = recipe
-            self.index[recipe.clean_name(ascii=True)] = recipe
-            if recipe.path == None:
-                p = self._free_path(recipe, paths)
-                paths.append(p)
-                recipe.path = p
-
-    def _free_path(self, recipe, paths):
-        base_path = os.path.join(self.save_dir, recipe.clean_name())
-        path = base_path
-        i = 2
-        while path in paths:
-            path = '%s_%d' % (base_path, i)
-            i += 1
-        return path
-
-    def tags(self):
-        """List all tags used in this cookbook.
-        """
-        tags = set()
-        for recipe in self:
-            if recipe.tags != None:
-                tags = tags.union(set(recipe.tags))
-        return sorted(tags)
-
-    def tagged(self, tags=None):
-        """Iterate through all recipes matching the given list of tags.
-        """
-        for recipe in self:
-            if recipe.matches_tags(tags):
-                yield recipe
-
-
-def test():
-    import doctest
-    doctest.testmod()