Sort cookbook after adding a new recipe.
[cookbook.git] / cookbook / cookbook.py
index b3e3ed0c045f7b100dc0103002604d7986768029..24a70feeda9eb34ac151543eadadbf4810267534 100644 (file)
@@ -27,6 +27,7 @@ import os
 import os.path
 import textwrap
 import types
+from urllib import quote_plus
 
 import yaml
 
@@ -148,7 +149,7 @@ class Amount (object):
         if len(self.alternatives) > 0:
             ret.append('(%s)' % ', '.join(
                     [unicode(a) for a in self.alternatives]))
-        return ' '.join(ret)
+        return ' '.join([x for x in ret if x != None])
 
     def to_yaml(self):
         d = {}
@@ -201,8 +202,8 @@ class Ingredient (object):
 
     def __unicode__(self):
         if self.note == None:
-            return '%s %s' % (unicode(self.amount), self.name)
-        return '%s %s, %s' % (unicode(self.amount), self.name, self.note)
+            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 = {}
@@ -352,6 +353,8 @@ class Directions (list):
                                             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'):
@@ -384,6 +387,14 @@ class Recipe (object):
     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',
@@ -430,15 +441,28 @@ class Recipe (object):
         self.source = source
         self.url = url
         self.tags = tags
+        self.path = None
 
-    def clean_name(self):
+    def clean_name(self, ascii=False):
         name = self.name
-        for from_,to in [(' ','_'), ('/', '_'),
-                         (',', ''), (u'\xe2\x80\x99', ''),
-                         ('&', 'and')]:
+        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__())
 
@@ -471,13 +495,17 @@ class Recipe (object):
         self.directions = Directions()
         self.directions.from_yaml(dict.get('directions', None))
 
-    def save(self, stream):
+    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):
+    def load(self, stream, path=None):
         dict = yaml.load(stream)
         self.from_yaml(dict)
+        if path != None:
+            self.path = path
 
 
 class Cookbook (list):
@@ -486,40 +514,63 @@ class Cookbook (list):
     """
     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, dir='recipe'):
-        if not os.path.isdir(dir):
-            os.mkdir(dir)
+    def save(self):
+        if not os.path.isdir(self.save_dir):
+            os.mkdir(self.save_dir)
         paths = []
         for recipe in self:
-            base_path = recipe.clean_name()
-            path = base_path
-            i = 2
-            while path in paths:
-                path = '%s_%d' % (base_path, i)
-                i += 1
-            for x in ['large']:
-                if x in path:
-                    print paths[-4:]
-            paths.append(path)
-            with open(os.path.join(dir, path), 'w') as f:
-                recipe.save(f)
-
-    def load(self, dir='recipe'):
-        for path in os.listdir(dir):
+            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(dir, path)
+            p = os.path.join(self.save_dir, path)
             with open(p, 'r') as f:
-                r.load(f)
-            r.path = p
+                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():