2 # -*- encoding: utf-8 -*-
6 """Represent a cookbook and recipes with Python classes.
17 def string_for_yaml(unicode_):
19 >>> string_for_yaml(None)
20 >>> string_for_yaml('all ascii')
22 >>> string_for_yaml(u'all ascii')
24 >>> string_for_yaml(u'½ ascii')
30 string = unicode_.encode('ascii')
32 except UnicodeEncodeError:
35 def to_yaml_object(obj):
37 >>> to_yaml_object(None)
38 >>> to_yaml_object('all ascii')
40 >>> to_yaml_object(u'all ascii')
42 >>> to_yaml_object('all ascii')
44 >>> to_yaml_object(u'½ ascii')
47 ... def to_yaml(self):
48 ... return 'to_yaml return value'
49 >>> to_yaml_object(x())
50 'to_yaml return value'
51 >>> to_yaml_object([u'all ascii', u'½ ascii'])
52 ['all ascii', u'\\xc2\\xbd ascii']
56 if hasattr(obj, 'to_yaml'):
58 if type(obj) in types.StringTypes:
59 return string_for_yaml(obj)
60 if hasattr(obj, '__len__'):
63 ret.append(to_yaml_object(item))
65 raise NotImplementedError(
66 'cannot convert %s to YAMLable dict:\n%s' % (type(obj), unicode(obj)))
69 class Amount (object):
72 >>> a = Amount(value='1', units='T.')
75 >>> pprint.pprint(a.to_yaml())
76 {'alternatives': [], 'range_': None, 'units': 'T.', 'value': '1'}
78 >>> x.from_yaml(a.to_yaml())
81 >>> b = Amount(value='1')
84 >>> pprint.pprint(b.to_yaml())
85 {'alternatives': [], 'range_': None, 'units': None, 'value': '1'}
86 >>> x.from_yaml(b.to_yaml())
89 >>> c = Amount(value='15', units='mL', alternatives=[a])
92 >>> pprint.pprint(c.to_yaml())
93 {'alternatives': [{'alternatives': [],
100 >>> x.from_yaml(c.to_yaml())
103 >>> d = Amount(units='T.', range_=['1','2'])
106 >>> pprint.pprint(d.to_yaml())
107 {'alternatives': [], 'range_': ['1', '2'], 'units': 'T.', 'value': None}
108 >>> x.from_yaml(d.to_yaml())
112 def __init__(self, value=None, units=None, range_=None, alternatives=None):
116 if alternatives == None:
118 self.alternatives = alternatives
121 return str(self.__unicode__())
123 def __unicode__(self):
124 if self.range_ == None:
127 value = '-'.join(self.range_)
129 if self.units != None:
130 ret.append(self.units)
131 if len(self.alternatives) > 0:
132 ret.append('(%s)' % ', '.join(
133 [unicode(a) for a in self.alternatives]))
138 for key in ['value', 'range_', 'units', 'alternatives']:
139 d[key] = to_yaml_object(getattr(self, key))
142 def from_yaml(self, dict):
143 for key in ['value', 'range_', 'units']:
144 setattr(self, key, dict.get(key, None))
145 self.alternatives = []
146 for a in dict.get('alternatives', []):
149 self.alternatives.append(amount)
152 class Ingredient (object):
155 >>> i = Ingredient('eye of newt', Amount('1'))
158 >>> pprint.pprint(i.to_yaml())
159 {'amount': {'alternatives': [], 'range_': None, 'units': None, 'value': '1'},
160 'name': 'eye of newt',
163 >>> x.from_yaml(i.to_yaml())
166 >>> j = Ingredient('salamanders', Amount('2'), 'diced fine')
168 '2 salamanders, diced fine'
169 >>> pprint.pprint(j.to_yaml())
170 {'amount': {'alternatives': [], 'range_': None, 'units': None, 'value': '2'},
171 'name': 'salamanders',
172 'note': 'diced fine'}
173 >>> x.from_yaml(j.to_yaml())
175 '2 salamanders, diced fine'
177 def __init__(self, name=None, amount=None, note=None):
183 return str(self.__unicode__())
185 def __unicode__(self):
186 if self.note == None:
187 return '%s %s' % (unicode(self.amount), self.name)
188 return '%s %s, %s' % (unicode(self.amount), self.name, self.note)
192 for key in ['name', 'amount', 'note']:
193 d[key] = to_yaml_object(getattr(self, key))
196 def from_yaml(self, dict):
197 for key in ['name', 'note']:
198 setattr(self, key, dict.get(key, None))
199 self.amount = Amount()
200 self.amount.from_yaml(dict.get('amount', {}))
203 class IngredientBlock (list):
206 >>> ib = IngredientBlock(None, [
207 ... Ingredient('eye of newt', Amount('1')),
208 ... Ingredient('salamanders', Amount('2'), 'diced fine')])
212 2 salamanders, diced fine
213 >>> pprint.pprint(ib.to_yaml())
214 {'ingredients': [{'amount': {'alternatives': [],
218 'name': 'eye of newt',
220 {'amount': {'alternatives': [],
224 'name': 'salamanders',
225 'note': 'diced fine'}],
227 >>> x = IngredientBlock()
228 >>> x.from_yaml(ib.to_yaml())
232 2 salamanders, diced fine
233 >>> ib.name = 'Dressing'
237 2 salamanders, diced fine
238 >>> pprint.pprint(ib.to_yaml())
239 {'ingredients': [{'amount': {'alternatives': [],
243 'name': 'eye of newt',
245 {'amount': {'alternatives': [],
249 'name': 'salamanders',
250 'note': 'diced fine'}],
252 >>> x = IngredientBlock()
253 >>> x.from_yaml(ib.to_yaml())
257 2 salamanders, diced fine
259 def __init__(self, name=None, *args, **kwargs):
261 super(IngredientBlock, self).__init__(*args, **kwargs)
264 if self.name == None:
269 return str(self.__unicode__())
271 def __unicode__(self):
272 ret = [unicode(i) for i in self]
273 ret.insert(0, '%s:' % self.heading())
274 return '\n '.join(ret)
279 d[key] = to_yaml_object(getattr(self, key))
280 d['ingredients'] = to_yaml_object(list(self))
283 def from_yaml(self, dict):
284 self.name = dict.get('name', None)
287 for i in dict.get('ingredients', []):
288 ingredient = Ingredient()
289 ingredient.from_yaml(i)
290 self.append(ingredient)
293 class Directions (list):
296 >>> d = Directions(['paragraph 1', 'paragraph 2', 'paragraph 3'])
303 >>> pprint.pprint(d.to_yaml())
304 'paragraph 1\\n\\nparagraph 2\\n\\nparagraph 3'
306 >>> x.from_yaml(d.to_yaml())
313 >>> pprint.pprint(x.to_yaml())
314 'paragraph 1\\n\\nparagraph 2\\n\\nparagraph 3'
317 return str(self.__unicode__())
319 def __unicode__(self):
320 return '\n\n'.join(self)
322 def wrapped_paragraphs(self, *args, **kwargs):
328 for paragraph in self]
330 def wrap(self, *args, **kwargs):
331 return '\n\n'.join(self.wrapped_paragraphs(*args, **kwargs))
334 return string_for_yaml('\n\n'.join([paragraph.rstrip('\n')
335 for paragraph in self]))
337 def from_yaml(self, string):
340 for paragraph in string.split('\n\n'):
341 self.append(paragraph)
344 class Recipe (object):
347 >>> r = Recipe('Hot Newt Dressing',
348 ... [IngredientBlock(None, [
349 ... Ingredient('eye of newt', Amount('1')),
350 ... Ingredient('salamanders', Amount('2'), 'diced fine')])],
351 ... Directions(['Mix ingredients until salamander starts to smoke',
352 ... 'Serve warm over rice']),
353 ... yield_='enough for one apprentice',
355 ... source='CENSORED',
356 ... url='http://merlin.uk/recipes/1234/',
357 ... tags=['dinner', 'easy', 'apprentice', 'eye', 'newt', 'salamander'])
359 Recipe: Hot Newt Dressing
360 Yield: enough for one apprentice
363 URL: http://merlin.uk/recipes/1234/
366 2 salamanders, diced fine
367 Mix ingredients until salamander starts to smoke
370 >>> pprint.pprint(r.to_yaml())
372 'directions': 'Mix ingredients until salamander starts to smoke\\n\\nServe warm over rice',
373 'ingredient_blocks': [{'ingredients': [{'amount': {'alternatives': [],
377 'name': 'eye of newt',
379 {'amount': {'alternatives': [],
383 'name': 'salamanders',
384 'note': 'diced fine'}],
386 'name': 'Hot Newt Dressing',
387 'source': 'CENSORED',
388 'tags': ['dinner', 'easy', 'apprentice', 'eye', 'newt', 'salamander'],
389 'url': 'http://merlin.uk/recipes/1234/',
390 'yield_': 'enough for one apprentice'}
392 >>> x.from_yaml(r.to_yaml())
394 Recipe: Hot Newt Dressing
395 Yield: enough for one apprentice
398 URL: http://merlin.uk/recipes/1234/
401 2 salamanders, diced fine
402 Mix ingredients until salamander starts to smoke
406 def __init__(self, name=None, ingredient_blocks=None, directions=None,
407 yield_=None, author=None, source=None, url=None, tags=None):
409 self.ingredient_blocks = ingredient_blocks
410 self.directions = directions
417 def clean_name(self):
419 for from_,to in [(' ','_'), ('/', '_'),
420 (',', ''), (u'\xe2\x80\x99', ''),
422 name = name.replace(from_, to)
426 return str(self.__unicode__())
428 def __unicode__(self):
430 'Recipe: %s' % self.name,
431 'Yield: %s' % self.yield_,
432 'From: %s' % self.author,
433 'Source: %s' % self.source,
434 'URL: %s' % self.url,
435 '\n'.join([unicode(ib) for ib in self.ingredient_blocks]),
436 unicode(self.directions),
441 for key in ['name', 'ingredient_blocks', 'directions', 'yield_',
442 'author', 'source', 'url', 'tags']:
443 d[key] = to_yaml_object(getattr(self, key))
446 def from_yaml(self, dict):
447 for key in ['name', 'yield_', 'author', 'source', 'url', 'tags']:
448 setattr(self, key, dict.get(key, None))
449 self.ingredient_blocks = []
450 for ib in dict.get('ingredient_blocks', []):
451 ingredient_block = IngredientBlock()
452 ingredient_block.from_yaml(ib)
453 self.ingredient_blocks.append(ingredient_block)
454 self.directions = Directions()
455 self.directions.from_yaml(dict.get('directions', None))
457 def save(self, stream):
458 yaml.dump(self.to_yaml(), stream,
459 default_flow_style=False, allow_unicode=True, width=78)
461 def load(self, stream):
462 dict = yaml.load(stream)
466 class Cookbook (list):
470 def __init__(self, name="Mom's cookbook", *args, **kwargs):
472 super(Cookbook, self).__init__(*args, **kwargs)
474 def save(self, dir='recipe'):
475 if not os.path.isdir(dir):
479 base_path = recipe.clean_name()
483 path = '%s_%d' % (base_path, i)
489 with open(os.path.join(dir, path), 'w') as f:
492 def load(self, dir='recipe'):
493 for path in os.listdir(dir):
495 with open(os.path.join(dir, path), 'r') as f:
499 def make_index(self):
502 self.index[recipe.name] = recipe
503 self.index[recipe.clean_name()] = recipe