Encode URLs into ASCII.
[cookbook.git] / cookbook / server.py
1 # Copyright (C) 2010 W. Trevor King <wking@drexel.edu>
2 #
3 # This file is part of Cookbook.
4 #
5 # Cookbook is free software: you can redistribute it and/or modify it
6 # under the terms of the GNU General Public License as published by the
7 # Free Software Foundation, either version 3 of the License, or (at your
8 # option) any later version.
9 #
10 # Cookbook is distributed in the hope that it will be useful,
11 # but WITHOUT ANY WARRANTY; without even the implied warranty of
12 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13 # GNU General Public License for more details.
14 #
15 # You should have received a copy of the GNU General Public License
16 # along with Cookbook.  If not, see <http://www.gnu.org/licenses/>.
17
18 """Serve cookbooks over HTTP.
19 """
20
21 from __future__ import with_statement
22
23 import os
24 import random
25 import re
26 import types
27 from xml.sax import saxutils
28
29 import cherrypy
30 from jinja2 import Environment, FileSystemLoader
31
32
33 class Server (object):
34     """Cookbook web interface."""
35
36     def __init__(self, cookbook, template_root):
37         self.cookbook = cookbook
38         self.cookbook.make_index()
39         self.env = Environment(loader=FileSystemLoader(template_root))
40         self.tag_regexp = re.compile('[a-zA-Z./ ].*')  # allowed characters
41
42     def cleanup(self):
43         #self.cookbook.save('new-recipe')
44         pass
45
46     @cherrypy.expose
47     def index(self, tag=None):
48         """Recipe index page.
49
50         Recipes can be filtered by tag.
51         """
52         if isinstance(tag, types.StringTypes):
53             tag = [tag]
54         template = self.env.get_template('recipes.html')
55         return template.render(cookbook=self.cookbook,
56                                recipes=list(self.cookbook.tagged(tag)),
57                                selected_tags=tag)
58
59     @cherrypy.expose
60     def recipe(self, name=None):
61         """Single recipe page.
62         """
63         if name == None:
64             recipe = random.choice(self.cookbook)
65         else:
66             if isinstance(name, types.StringType):
67                 name = unicode(name, 'utf-8')
68             recipe = self.cookbook.index[name]
69         template = self.env.get_template('recipe.html')
70         return template.render(cookbook=self.cookbook, recipe=recipe)
71
72     @cherrypy.expose
73     def add_tag(self, name, tag):
74         """Add a tag to a single recipe."""
75         if isinstance(name, types.StringType):
76             name = unicode(name, 'utf-8')
77         recipe = self.cookbook.index[name]
78         if recipe.tags == None:
79             recipe.tags = []
80         tag = self._clean_tag(tag)
81         if tag != None and tag not in recipe.tags:
82             recipe.tags.append(tag)
83             with open(recipe.path, 'w') as f:
84                 recipe.save(f)
85         raise cherrypy.HTTPRedirect(
86             u'recipe?name=%s' % recipe.clean_name(ascii=True), status=302)
87
88     @cherrypy.expose
89     def remove_tag(self, name, tag):
90         """Remove a tag from a single recipe."""
91         if isinstance(name, types.StringType):
92             name = unicode(name, 'utf-8')
93         recipe = self.cookbook.index[name]
94         if recipe.tags == None:
95             return
96         tag = self._clean_tag(tag)
97         if tag != None and tag in recipe.tags:
98             recipe.tags.remove(tag)
99             with open(recipe.path, 'w') as f:
100                 recipe.save(f)
101         raise cherrypy.HTTPRedirect(
102             u'recipe?name=%s' % recipe.clean_name(ascii=True), status=302)
103
104     def _clean_tag(self, tag):
105         """Sanitize tag."""
106         if not isinstance(tag, types.StringType):
107             if len(tag) == 2 and '' in tag:
108                 # User used either dropdown or textbox
109                 tag.remove('')
110                 tag = tag[0]
111             else:
112                 # User used both dropdown and textbox
113                 return None
114         m = self.tag_regexp.match(tag)
115         if m != None:
116             return m.group()
117         return None
118
119
120 def test():
121     import doctest
122     doctest.testmod()