root/trunk/blox.py

Revision 214 (checked in by llimllib, 10 months ago)

fixing utf-8

Line 
1 #!/usr/bin/env python
2 import datetime, time, os, sys, traceback
3 import cherrypy as cpy
4 import FileCabinet
5 from buffet import BuffetTool
6 from utils import config, run_callback
7
8 class BlogRoot(object):
9     _cp_config = {"tools.buffet.on": True,
10                   "tools.staticdir.root": os.path.abspath(os.curdir),
11                   "tools.encode.on": True,
12                   "tools.encode.encoding": config("blog_encoding", "utf-8")}
13
14     def __init__(self):
15         self.months = ['jan', 'feb', 'mar', 'apr', 'may', 'jun', 'jul', 'aug',
16             'sep', 'oct', 'nov', 'dec']
17         self.timeformats =  [["%Y", "%d", "%m", "%b", "%B"],
18             ["%Y %b", "%Y %m", "%Y %b", "%Y %B", "%m %d", "%b %d", "%B %d"],
19             ["%Y %m %d", "%Y %b %d", "%Y %B %d"]]
20         self.plugins = [] #contains all loaded plugins
21
22         #turn on the templating engine
23         cpy.tools.buffet = BuffetTool(config("template_engine"))
24
25         #set the output encoding
26         self._cp_config["cpy.tools.encode.encoding"] = "utf-8"
27
28         self.now = datetime.datetime.now
29         self.last_update = self.now()
30         self.num_entries = config('num_entries')
31         self.datadir = config('datadir')
32         self.ignore_directories = config('ignore_directories')
33         self.fp = '' #a cache of the front page content
34         self.index() #thus, we don't have to parse the metadata of the front
35                      #page article when the second request comes in
36         self.init_plugins(config('plugins'))
37
38     def files(self, offset):
39         return FileCabinet.get_most_recent(self.datadir, self.num_entries, \
40             self.ignore_directories, offset)
41
42     @cpy.expose
43     def index(self, offset=0):
44         try:
45             offset = int(offset)
46         except ValueError:
47             offset = 0
48         return self.render_page(self.files(offset), 'index', offset)
49
50     def init_plugins(self, pluginlist):
51         """
52         Initialize plugins. Assumes that each plugin contains a class of the
53         same name as the file. If it does, this function attaches an instance
54         of that class to self, and adds that instance to the plugins array.
55         """
56         plugindir = config('plugin_dir', None)
57         if not plugindir or not os.path.isdir(plugindir):
58             cpy.log("Invalid Plugin Directory, no plugins loaded")
59             return
60         else:
61             sys.path.append(plugindir)
62
63         #XXX: Should we just scan for *.py files in the plugin dir? Should the
64         # mechanism to remove a plugin be renaming it or taking it out of
65         # conf?
66         for p in pluginlist:
67             try:
68                 mod = __import__(p)
69                 if not hasattr(self, p):
70                     instance = getattr(mod, p)(self)
71                     setattr(self, p, instance)
72                     self.plugins.append(instance)
73                     cpy.log("successfully imported plugin module %s" % p)
74                 else:
75                     raise ImportError
76             #bare except, because the modules could raise any number of errors
77             #on import, and we want them not to kill our server
78             except:
79                 cpy.log("import failed on module %s, module not loaded" % p)
80                 cpy.log("%s" % sys.exc_info()[0])
81                 cpy.log("%s" % traceback.format_exc())
82
83     def error_page(self, error, status=404):
84         cpy.response.status = status
85         ns = cpy.config.get('/')
86         ns.update({'error': error})
87         return (('head', ns), ('error', ns), ('foot', ns))
88
89     def render_page(self, entries, pagename='', offset=0):
90         """renders a collection of entries into a web page"""
91         page = []
92         #namespace for the template substitution (starts with config opts)
93         ns = cpy.config.get('/').copy()
94
95         #pagename is the page we're offsetting. see below comment.
96         ns['pagename'] = pagename
97
98         #in order to make an offset url, we need
99         #$base_url/$pagename?offset=$offset&othervars=$othervars
100         ns['offset'] = offset
101         if len(entries) == ns['num_entries']:
102             ns['offset_next'] = offset + ns['num_entries']
103
104         #cb_add_data is the plugin's chance to add data to the story template
105         #            it should return a list of
106         for dict_ in run_callback(self.plugins, 'cb_add_data'):
107             for key, val in dict_.iteritems():
108                 ns[key] = val
109
110         #let the header access the first article's info
111         if len(entries):
112             for key in ('title', 'relpath', 'tmstr'):
113                 ns[key] = getattr(entries[0], key)
114         page.append(('head', ns))
115
116         #cb_story_start is a plugin's chance to put text between a header
117         #               and all stories. Any text returned by it will be
118         #               inserted there. It must return zero or more template
119         #               tuples. A template tuple consists of
120         #               (template, data_dictionary) which is (string, dict)
121         page.extend(run_callback(self.plugins, 'cb_story_start', entries))
122
123         for e in entries:
124             #cb_story lets a plugin insert variables just for one particular
125             #         entry. It is given the story object, and may modify it.
126             run_callback(self.plugins, 'cb_story', e)
127
128             #append all story variables that do not start with "_"
129             storyns = ns.copy()
130             storyns.update([(key, getattr(e, key))
131                         for key in dir(e) if not key.startswith('_')])
132
133             page.append(('story', storyns))
134
135         #cb_story_end is a plugin's chance to put text between all stories
136         #               and the footer, similar to cb_story_start. It must
137         #               return a template tuple.
138         page.extend(run_callback(self.plugins, 'cb_story_end', entries))
139         page.append(('foot', ns))
140
141         #cb_page_end is a plugin's chance to clean up data
142         run_callback(self.plugins, 'cb_page_end')
143
144         return page
145
146     def stripall(self, str_, *strippers):
147         """return a string stripped of all extensions in strippers"""
148         for stripper in strippers:
149             if str_.endswith(stripper):
150                 str_ = str_[:-len(stripper)]
151         return str_
152
153     @cpy.expose
154     def default(self, *args, **kwargs):
155         #allow a plugin to handle a default url if it wants; it needs to return
156         #a tuple (pagename, [Entry objects]) if it does
157         call_result = run_callback(self.plugins, 'cb_default', args)
158         if call_result != []: return self.render_page(call_result[1:], call_result[0])
159
160         try:
161             offset = int(kwargs.get('offset', 0))
162         except ValueError:
163             offset = 0
164
165         z = args[0]
166         l = len(args)
167         if l <= len(self.timeformats):
168             #check to see if args represent a date
169             for fmt in self.timeformats[l-1]:
170                 try:
171                     t = time.strptime(' '.join(args), fmt)
172                     if "%Y" in fmt:
173                         year = t[0]
174                     else:
175                         year = self.now().year
176                     if "%m" in fmt or "%b" in fmt or "%B" in fmt:
177                         month = t[1]
178                     else:
179                         month = None
180                     if "%d" in fmt:
181                         day = t[2]
182                     else:
183                         day = None
184                     entries = FileCabinet.get_entries_by_date(year, month, day)
185                     if entries:
186                         entries = entries[offset:offset + config('num_entries')]
187                         return self.render_page(entries, ' '.join(args), offset)
188                 except ValueError:
189                     #not a date - move on
190                     pass
191         z = os.path.join(*args)
192         fname = self.stripall(z, '.html', '.htm', '.txt')
193         e = FileCabinet.get_one(fname, self.datadir)
194         if e:
195             return self.render_page([e])
196         return self.error_page('Page Not Found', 404)
197
198 if __name__ == '__main__':
199     #set our current directory to the dir with blox.py in it
200     os.chdir(os.path.dirname(os.path.abspath(__file__)))
201
202     #Because I need config before instantiating BlogRoot, I need to have a
203     #separate site config file; config otherwise discards everything but the
204     #"global" section. Sucky.
205     cpy.config.update("site.conf")
206     #we need to load the config before instantiating the BlogRoot object
207     cpy.config.update("cherryblossom.conf")
208
209     cpy.quickstart(BlogRoot(), "/", "cherryblossom.conf")
Note: See TracBrowser for help on using the browser.