def readfile(filename, request): entry_data = {} lines = open(filename).readlines() if len(lines) == 0: return {"title": "", "body": ""} title = lines.pop(0).strip() # absorb meta data while lines and lines[0].startswith("#"): meta = lines.pop(0) # remove the hash meta = meta[1:].strip() meta = meta.split(" ", 1) # if there's no value, we append a 1 if len(meta) == 1: meta.append("1") entry_data[meta[0].strip()] = meta[1].strip() body = parse(''.join(lines), request) entry_data["title"] = title entry_data["body"] = body # Call the postformat callbacks tools.run_callback('postformat', {'request': request, 'entry_data': entry_data}) return entry_data
def run_callback(self, callback="help"): """This method executes the start callback (initializing plugins), executes the requested callback, and then executes the end callback. This is useful for scripts outside of Douglas that need to do things inside of the Douglas framework. If you want to run a callback from a plugin, use ``tools.run_callback`` instead. :param callback: the name of the callback to execute. :returns: the results of the callback. """ self.initialize() # run the start callback tools.run_callback("start", {'request': self._request}) # invoke all callbacks for the 'callback' handled = tools.run_callback(callback, {'request': self._request}, mappingfunc=lambda x,y:x, donefunc=lambda x:x) # do end callback tools.run_callback("end", {'request': self._request}) return handled
def cmd_buildtags(command, argv): """Command for building the tags index.""" cfg = import_config() datadir = cfg['datadir'] sep = cfg.get('tags_separator', ',') tagsfile = get_tagsfile(cfg) from douglas import tools from douglas.app import Douglas, initialize from douglas.entries import fileentry # Build a douglas object, initialize it, and run the start # callback. This gives entry parsing related plugins a chance to # get their stuff together so that they work correctly. initialize(cfg) p = Douglas(cfg, {}) p.initialize() req = p.get_request() tools.run_callback("start", {"request": req}) # Grab all the entries in the datadir entrylist = [fileentry.FileEntry(req, e, datadir) for e in tools.get_entries(cfg, datadir)] tags_to_files = {} for mem in entrylist: tagsline = mem["tags"] if not tagsline: continue tagsline = [t.strip() for t in tagsline.split(sep)] for t in tagsline: tags_to_files.setdefault(t, []).append(mem["filename"]) savefile(tagsfile, tags_to_files) return 0
def blosxom_entry_parser(filename, request): """Open up a ``.txt`` file and read its contents. The first line becomes the title of the entry. The other lines are the body of the entry. :param filename: a filename to extract data and metadata from :param request: a standard request object :returns: dict containing parsed data and meta data with the particular file (and plugin) """ config = request.get_configuration() entry_data = {} f = open(filename, "r") lines = f.readlines() f.close() # the file has nothing in it... so we're going to return a blank # entry data object. if len(lines) == 0: return {"title": "", "body": ""} # the first line is the title entry_data["title"] = lines.pop(0).strip() # absorb meta data lines which begin with a # while lines and lines[0].startswith("#"): meta = lines.pop(0) # remove the hash meta = meta[1:].strip() meta = meta.split(" ", 1) # if there's no value, we append a 1 if len(meta) == 1: meta.append("1") entry_data[meta[0].strip()] = meta[1].strip() # call the preformat function args = {'parser': entry_data.get('parser', config.get('parser', 'plain')), 'story': lines, 'request': request} entry_data["body"] = tools.run_callback( 'preformat', args, donefunc=lambda x: x != None, defaultfunc=lambda x: ''.join(x['story'])) # call the postformat callbacks tools.run_callback('postformat', {'request': request, 'entry_data': entry_data}) return entry_data
def blosxom_file_list_handler(args): """This is the default handler for getting entries. It takes the request object in and figures out which entries based on the default behavior that we want to show and generates a list of EntryBase subclass objects which it returns. :param args: dict containing the incoming Request object :returns: the content we want to render """ request = args["request"] data = request.get_data() config = request.get_configuration() if data['bl_type'] == 'dir': filelist = tools.walk(request, data['root_datadir'], int(config.get("depth", "0"))) elif data['bl_type'] == 'file': filelist = [data['root_datadir']] else: filelist = [] entrylist = [FileEntry(request, e, data["root_datadir"]) for e in filelist] # if we're looking at a set of archives, remove all the entries # that aren't in the archive if data.get("pi_yr", ""): tmp_pi_mo = data.get("pi_mo", "") datestr = "%s%s%s" % (data.get("pi_yr", ""), tools.month2num.get(tmp_pi_mo, tmp_pi_mo), data.get("pi_da", "")) entrylist = [x for x in entrylist if time.strftime("%Y%m%d%H%M%S", x["timetuple"]).startswith(datestr)] args = {"request": request, "entry_list": entrylist} entrylist = tools.run_callback("sortlist", args, donefunc=lambda x: x != None, defaultfunc=blosxom_sort_list_handler) args = {"request": request, "entry_list": entrylist} entrylist = tools.run_callback("truncatelist", args, donefunc=lambda x: x != None, defaultfunc=blosxom_truncate_list_handler) return entrylist
def category_to_tags(command, argv): """Goes through all entries and converts the category to tags metadata. It adds the tags line as the second line. It maintains the mtime for the file. """ import config datadir = config.py.get("datadir") if not datadir: raise ValueError("config.py has no datadir property.") sep = config.py.get("tags_separator", ",") from douglas import tools from douglas.app import blosxom_entry_parser, Request data = {} # register entryparsers so that we parse all possible file types. data["extensions"] = tools.run_callback("entryparser", {"txt": blosxom_entry_parser}, mappingfunc=lambda x, y: y, defaultfunc=lambda x: x) req = Request(config.py, {}, data) # grab all the entries in the datadir filelist = tools.walk(req, datadir) if not datadir.endswith(os.sep): datadir = datadir + os.sep for mem in filelist: print "working on %s..." % mem category = os.path.dirname(mem)[len(datadir):] tags = category.split(os.sep) print " adding tags %s" % tags tags = "#tags %s\n" % (sep.join(tags)) atime, mtime = os.stat(mem)[7:9] fp = open(mem, "r") data = fp.readlines() fp.close() data.insert(1, tags) fp = open(mem, "w") fp.write("".join(data)) fp.close() os.utime(mem, (atime, mtime)) return 0
def initialize(self): """The initialize step further initializes the Request by setting additional information in the ``data`` dict, registering plugins, and entryparsers. """ data = self._request.get_data() pyhttp = self._request.get_http() config = self._request.get_configuration() # initialize the locale, if wanted (will silently fail if locale # is not available) if config.get('locale', None): try: locale.setlocale(locale.LC_ALL, config['locale']) except locale.Error: # invalid locale pass # initialize the tools module tools.initialize() data["douglas_version"] = __version__ data['pi_bl'] = '' # if the user specifies base_url in config, we use that. # otherwise we compose it from SCRIPT_NAME in the environment # or we leave it blank. if not "base_url" in config: if pyhttp.has_key('SCRIPT_NAME'): # allow http and https config['base_url'] = '%s://%s%s' % \ (pyhttp['wsgi.url_scheme'], pyhttp['HTTP_HOST'], pyhttp['SCRIPT_NAME']) else: config["base_url"] = "" # take off the trailing slash for base_url if config['base_url'].endswith("/"): config['base_url'] = config['base_url'][:-1] datadir = config["datadir"] if datadir.endswith("/") or datadir.endswith("\\"): datadir = datadir[:-1] config['datadir'] = datadir # import and initialize plugins plugin_utils.initialize_plugins(config.get("plugin_dirs", []), config.get("load_plugins", None)) # entryparser callback is run here first to allow other # plugins register what file extensions can be used data['extensions'] = tools.run_callback("entryparser", {'txt': blosxom_entry_parser}, mappingfunc=lambda x,y:y, defaultfunc=lambda x:x)
def render(self, render_headers=True): """ Do final rendering. :arg render_headers: whether (True) or not (False) to show the headers """ # if we've already rendered, then we don't want to do so again if self.rendered: return config = self._request.get_configuration() data = self._request.get_data() themedir = config['themedir'] theme = data.get("theme") or "html" data['content-type'] = self.get_content_type(themedir, theme) if render_headers: self.add_header('Content-type', data['content-type']) self.show_headers() if self._content: content = self._content if not isinstance(content, list): content = [content] context = self.build_context() context['content'] = content # Allow plugins to alter the context adding additional # bits args = { 'context': context, 'request': self._request, } args = run_callback( "context_processor", args, mappingfunc=lambda x,y:y, defaultfunc=lambda x:x) context = args['context'] env = build_environment(themedir, theme) template_name = context.get('bl_type', 'entry') + '.' + theme template = env.get_template(template_name) output = template.render(context) self.write(output.encode(config['blog_encoding'])) self.rendered = 1
def cmd_persistdate(command, argv): from douglas.cmdline import import_config config = import_config() datadir = config.py.get('datadir') if not datadir: raise ValueError('config.py has no datadir property.') from douglas import tools from douglas.app import Douglas p = Douglas(config.py, {}) p.initialize() req = p.get_request() tools.run_callback('start', {'request': req}) filelist = tools.get_entries(config, datadir) print '%d files' % len(filelist) for fn in filelist: lines = open(fn, 'r').readlines() try: metadata = get_metadata(lines) except IndexError as exc: print '%s errored out: %s' % (fn, exc) continue if 'published' in metadata: print '%s already has metadata...' % fn continue print 'working on %s...' % fn timetuple = tools.filestat(req, fn) published = time.strftime('%Y-%m-%d %H:%M:%S', timetuple) lines.insert(1, '#published %s\n' % published) fp = open(fn, 'w') fp.write(''.join(lines)) fp.close()
def buildtags(command, argv): """Command for building the tags index.""" import config datadir = config.py.get("datadir") if not datadir: raise ValueError("config.py has no datadir property.") sep = config.py.get("tags_separator", ",") tagsfile = get_tagsfile(config.py) from douglas import tools from douglas.app import Douglas from douglas.entries import fileentry # build a douglas object, initialize it, and run the start # callback. this gives entry parsing related plugins a chance to # get their stuff together so that they work correctly. p = Douglas(config.py, {}) p.initialize() req = p.get_request() tools.run_callback("start", {"request": req}) # grab all the entries in the datadir filelist = tools.walk(req, datadir) entrylist = [fileentry.FileEntry(req, e, datadir) for e in filelist] tags_to_files = {} for mem in entrylist: tagsline = mem["tags"] if not tagsline: continue tagsline = [t.strip() for t in tagsline.split(sep)] for t in tagsline: tags_to_files.setdefault(t, []).append(mem["filename"]) savefile(tagsfile, tags_to_files) return 0
def run(self, static=False): """This is the main loop for Douglas. This method will run the handle callback to allow registered handlers to handle the request. If nothing handles the request, then we use the ``default_blosxom_handler``. :param static: True if Douglas should execute in "static rendering mode" and False otherwise. """ self.initialize() # buffer the input stream in a StringIO instance if dynamic # rendering is used. This is done to have a known/consistent # way of accessing incomming data. if static == False: self.get_request().buffer_input_stream() # run the start callback tools.run_callback("start", {'request': self._request}) # allow anyone else to handle the request at this point handled = tools.run_callback("handle", {'request': self._request}, mappingfunc=lambda x,y:x, donefunc=lambda x:x) if not handled == 1: blosxom_handler(self._request) # do end callback tools.run_callback("end", {'request': self._request}) # we're done, clean up. # only call this if we're not in static rendering mode. if static == False: self.cleanup()
def cb_filelist(args): from douglas import tools from douglas.app import blosxom_truncate_list_handler # handles /trigger/tag to show all the entries tagged that # way req = args["request"] pyhttp = req.get_http() data = req.get_data() config = req.get_configuration() trigger = "/" + config.get("tags_trigger", "tag") if not pyhttp["PATH_INFO"].startswith(trigger): return datadir = config["datadir"] tagsfile = get_tagsfile(config) tagsdata = loadfile(tagsfile) tag = pyhttp["PATH_INFO"][len(trigger) + 1:] filelist = tagsdata.get(tag, []) if not filelist: tag, ext = os.path.splitext(tag) filelist = tagsdata.get(tag, []) if filelist: data["theme"] = ext[1:] from douglas.entries import fileentry entrylist = [fileentry.FileEntry(req, e, datadir) for e in filelist] # sort the list by mtime entrylist = [(e._mtime, e) for e in entrylist] entrylist.sort() entrylist.reverse() entrylist = [e[1] for e in entrylist] data["truncate"] = config.get("truncate_tags", True) args = {"request": req, "entry_list": entrylist} entrylist = tools.run_callback("truncatelist", args, donefunc=lambda x: x != None, defaultfunc=blosxom_truncate_list_handler) return entrylist
def cb_filelist(args): from douglas import tools from douglas.app import blosxom_truncate_list_handler # Handles /trigger/tag to show all the entries tagged that way req = args['request'] pyhttp = req.get_http() data = req.get_data() config = req.get_configuration() trigger = '/' + config.get('tags_trigger', 'tag') if not pyhttp['PATH_INFO'].startswith(trigger): return datadir = config['datadir'] tagsfile = get_tagsfile(config) tagsdata = loadfile(tagsfile) tag = pyhttp['PATH_INFO'][len(trigger)+1:] filelist = tagsdata.get(tag, []) if not filelist: tag, ext = os.path.splitext(tag) filelist = tagsdata.get(tag, []) if filelist: data['theme'] = ext[1:] from douglas.entries import fileentry entrylist = [fileentry.FileEntry(req, e, datadir) for e in filelist] # sort the list by mtime entrylist.sort(key=lambda entry: entry._mtime, reverse=True) data['truncate'] = config.get('truncate_tags', True) args = {'request': req, 'entry_list': entrylist} entrylist = tools.run_callback('truncatelist', args, donefunc=lambda x: x != None, defaultfunc=blosxom_truncate_list_handler) return entrylist
def initialize(cfg): # import and initialize plugins plugin_utils.initialize_plugins(cfg['plugin_dirs'], cfg['load_plugins']) # entryparser callback is run here first to allow other # plugins register what file extensions can be used extensions = tools.run_callback( "entryparser", {'txt': blosxom_entry_parser}, mappingfunc=lambda x, y: y, defaultfunc=lambda x: x) # go through the config.py and override entryparser extensions for ext, parser_module in cfg['entryparsers'].items(): module, callable_name = parser_module.rsplit(':', 1) module = tools.importname(None, module) extensions[ext] = getattr(module, callable_name) # FIXME - this is a lousy place to store this cfg['extensions'] = extensions
def get_handlers(): try: from config import py as cfg plugin_utils.initialize_plugins(cfg.get("plugin_dirs", []), cfg.get("load_plugins", None)) except ImportError: pass handlers_dict = dict([(v[0], (v[1], v[2])) for v in DEFAULT_HANDLERS]) handlers_dict = run_callback("commandline", handlers_dict, mappingfunc=lambda x, y: y, defaultfunc=lambda x: x) # test the handlers, drop any that aren't the right return type, # and print a warning. handlers = [] for k, v in handlers_dict.items(): if not len(v) == 2 or not callable(v[0]) or not isinstance(v[1], str): print "Plugin returned '%s' for commandline." % ((k, v),) continue handlers.append((k, v[0], v[1])) return handlers
def run_compile(self, incremental=False): """Compiles the blog into an HTML site. This will go through all possible things in the blog and compile the blog to the ``compiledir`` specified in the config file. This figures out all the possible ``path_info`` settings and calls ``self.run()`` a bazillion times saving each file. :param incremental: Whether (True) or not (False) to compile incrementally. If we're incrementally compiling, then only the urls that are likely to have changed get re-compiled. """ self.initialize() cfg = self._request.get_configuration() compiledir = cfg['compiledir'] datadir = cfg['datadir'] if not compiledir: print 'Error: You must set compiledir in your config file.' return 0 print 'Compiling to "{0}".'.format(compiledir) if incremental: print 'Incremental is set.' print '' themes = cfg['compile_themes'] index_themes = cfg['compile_index_themes'] dayindexes = cfg['day_indexes'] monthindexes = cfg['month_indexes'] yearindexes = cfg['year_indexes'] renderme = [] dates = {} categories = {} # first we handle entries and categories listing = tools.get_entries(cfg, datadir) for mem in listing: # Skip files that have extensions we don't know what to do # with. ext = os.path.splitext(mem)[1].lstrip('.') if not ext in cfg['extensions'].keys(): continue # Get the mtime of the entry. mtime = time.mktime(tools.filestat(self._request, mem)) # remove the datadir from the front and the bit at the end mem = mem[len(datadir):mem.rfind('.')] # This is the compiled file filename. fn = os.path.normpath(compiledir + mem) if incremental: # If we're incrementally rendering, we check the mtime # for the compiled file for one of the themes. If the entry # is more recent than the compiled version, we recompile. # Otherwise we skip it. try: smtime = os.stat(fn + '.' + themes[0])[8] if smtime < mtime or not incremental: continue except (IOError, OSError): pass # Figure out category indexes to re-render. temp = os.path.dirname(mem).split(os.sep) for i in range(len(temp)+1): p = os.sep.join(temp[0:i]) categories[p] = 0 # Figure out year/month/day indexes to re-render. mtime = time.localtime(mtime) year = time.strftime('%Y', mtime) month = time.strftime('%m', mtime) day = time.strftime('%d', mtime) if yearindexes: dates[year] = 1 if monthindexes: dates[year + '/' + month] = 1 if dayindexes: dates[year + '/' + month + '/' + day] = 1 # Toss each theme for this entry in the render queue. for f in themes: renderme.append((mem + '.' + f, '')) print '- Found {0} entry(es) ...'.format(len(renderme)) if categories: categories = sorted(categories.keys()) # if they have stuff in their root category, it'll add a "/" # to the category list and we want to remove that because it's # a duplicate of "". if '/' in categories: categories.remove('/') print '- Found {0} category index(es) ...'.format(len(categories)) for mem in categories: mem = os.path.normpath(mem + '/index.') for f in index_themes: renderme.append((mem + f, '')) if dates: dates = ['/' + d for d in sorted(dates.keys())] print '- Found {0} date index(es) ...'.format(len(dates)) for mem in dates: mem = os.path.normpath(mem + '/index.') for f in index_themes: renderme.append((mem + f, '')) additional_stuff = cfg['compile_urls'] if additional_stuff: print '- Found {0} arbitrary url(s) ...'.format( len(additional_stuff)) for mem in additional_stuff: if mem.find('?') != -1: url = mem[:mem.find('?')] query = mem[mem.find('?')+1:] else: url = mem query = '' renderme.append((url, query)) # Pass the complete render list to all the plugins via # cb_compile_filelist and they can add to the filelist any # (url, query) tuples they want rendered. total = len(renderme) tools.run_callback('compile_filelist', {'request': self._request, 'filelist': renderme, 'themes': themes, 'incremental': incremental}) renderme = sorted(set(renderme)) print '- Found {0} url(s) specified by plugins ...'.format( len(renderme) - total) print '' print 'Compiling {0} url(s) total.'.format(len(renderme)) print '' print 'Rendering files ...' for url, q in renderme: url = url.replace(os.sep, '/') print ' Rendering {0} ...'.format(url) tools.render_url_statically(dict(cfg), url, q) # We're done, clean up self.cleanup()
def blosxom_handler(request): """This is the default blosxom handler. It calls the renderer callback to get a renderer. If there is no renderer, it uses the blosxom renderer. It calls the pathinfo callback to process the path_info http variable. It calls the filelist callback to build a list of entries to display. It calls the prepare callback to do any additional preparation before rendering the entries. Then it tells the renderer to render the entries. :param request: the request object. """ config = request.get_configuration() data = request.get_data() # go through the renderer callback to see if anyone else wants to # render. this renderer gets stored in the data dict for # downstream processing. rend = tools.run_callback('renderer', {'request': request}, donefunc = lambda x: x != None, defaultfunc = lambda x: None) if not rend: # get the renderer we want to use rend = config.get("renderer", "jinjarenderer") # import the renderer rend = tools.importname("douglas.renderers", rend) # get the renderer object rend = rend.Renderer(request, config.get("stdoutput", sys.stdout)) data['renderer'] = rend # generate the timezone variable data["timezone"] = time.tzname[time.localtime()[8]] # process the path info to determine what kind of blog entry(ies) # this is tools.run_callback("pathinfo", {"request": request}, donefunc=lambda x:x != None, defaultfunc=blosxom_process_path_info) # call the filelist callback to generate a list of entries data["entry_list"] = tools.run_callback( "filelist", {"request": request}, donefunc=lambda x:x != None, defaultfunc=blosxom_file_list_handler) # figure out the blog-level mtime which is the mtime of the head # of the entry_list entry_list = data["entry_list"] if isinstance(entry_list, list) and len(entry_list) > 0: mtime = entry_list[0].get("mtime", time.time()) else: mtime = time.time() mtime_tuple = time.localtime(mtime) mtime_gmtuple = time.gmtime(mtime) data["latest_date"] = time.strftime('%a, %d %b %Y', mtime_tuple) # Make sure we get proper 'English' dates when using standards loc = locale.getlocale(locale.LC_ALL) locale.setlocale(locale.LC_ALL, 'C') data["latest_w3cdate"] = time.strftime('%Y-%m-%dT%H:%M:%SZ', mtime_gmtuple) data['latest_rfc822date'] = time.strftime('%a, %d %b %Y %H:%M GMT', mtime_gmtuple) # set the locale back locale.setlocale(locale.LC_ALL, loc) # we pass the request with the entry_list through the prepare # callback giving everyone a chance to transform the data. the # request is modified in place. tools.run_callback("prepare", {"request": request}) # now we pass the entry_list through the renderer entry_list = data["entry_list"] renderer = data['renderer'] if renderer and not renderer.rendered: if entry_list: renderer.set_content(entry_list) else: renderer.add_header('Status', '404 Not Found') renderer.set_content( {'title': 'The page you are looking for is not available', 'body': 'Somehow I cannot find the page you want. ' + 'Go Back to <a href="%s">%s</a>?' % (config["base_url"], config["blog_title"])}) renderer.render() elif not renderer: output = config.get('stdoutput', sys.stdout) output.write("Content-Type: text/plain\n\n" + "There is something wrong with your setup.\n" + "Check your config files and verify that your " + "configuration is correct.\n")
def run_static_renderer(self, incremental=False): """This will go through all possible things in the blog and statically render everything to the ``static_dir`` specified in the config file. This figures out all the possible ``path_info`` settings and calls ``self.run()`` a bazillion times saving each file. :param incremental: Whether (True) or not (False) to incrementally render the pages. If we're incrementally rendering pages, then we render only the ones that have changed. """ self.initialize() config = self._request.get_configuration() data = self._request.get_data() print "Performing static rendering." if incremental: print "Incremental is set." staticdir = config.get("static_dir", "") datadir = config["datadir"] if not staticdir: print "Error: You must set static_dir in your config file." return 0 themes = config.get("static_themes", ["html"]) index_themes = config.get("static_index_themes", ["html"]) renderme = [] monthnames = config.get("static_monthnames", True) monthnumbers = config.get("static_monthnumbers", False) yearindexes = config.get("static_yearindexes", True) dates = {} categories = {} # first we handle entries and categories listing = tools.walk(self._request, datadir) for mem in listing: # skip the ones that have bad extensions ext = mem[mem.rfind(".")+1:] if not ext in data["extensions"].keys(): continue # grab the mtime of the entry file mtime = time.mktime(tools.filestat(self._request, mem)) # remove the datadir from the front and the bit at the end mem = mem[len(datadir):mem.rfind(".")] # this is the static filename fn = os.path.normpath(staticdir + mem) # grab the mtime of one of the statically rendered file try: smtime = os.stat(fn + "." + themes[0])[8] except: smtime = 0 # if the entry is more recent than the static, we want to # re-render if smtime < mtime or not incremental: # grab the categories temp = os.path.dirname(mem).split(os.sep) for i in range(len(temp)+1): p = os.sep.join(temp[0:i]) categories[p] = 0 # grab the date mtime = time.localtime(mtime) year = time.strftime("%Y", mtime) month = time.strftime("%m", mtime) day = time.strftime("%d", mtime) if yearindexes: dates[year] = 1 if monthnumbers: dates[year + "/" + month] = 1 dates[year + "/" + month + "/" + day] = 1 if monthnames: monthname = tools.num2month[month] dates[year + "/" + monthname] = 1 dates[year + "/" + monthname + "/" + day] = 1 # toss in the render queue for f in themes: renderme.append((mem + "." + f, "")) print "rendering %d entries." % len(renderme) # handle categories categories = categories.keys() categories.sort() # if they have stuff in their root category, it'll add a "/" # to the category list and we want to remove that because it's # a duplicate of "". if "/" in categories: categories.remove("/") print "rendering %d category indexes." % len(categories) for mem in categories: mem = os.path.normpath(mem + "/index.") for f in index_themes: renderme.append((mem + f, "")) # now we handle dates dates = dates.keys() dates.sort() dates = ["/" + d for d in dates] print "rendering %d date indexes." % len(dates) for mem in dates: mem = os.path.normpath(mem + "/index.") for f in index_themes: renderme.append((mem + f, "")) # now we handle arbitrary urls additional_stuff = config.get("static_urls", []) print "rendering %d arbitrary urls." % len(additional_stuff) for mem in additional_stuff: if mem.find("?") != -1: url = mem[:mem.find("?")] query = mem[mem.find("?")+1:] else: url = mem query = "" renderme.append((url, query)) # now we pass the complete render list to all the plugins via # cb_staticrender_filelist and they can add to the filelist # any (url, query) tuples they want rendered. print "(before) building %s files." % len(renderme) tools.run_callback("staticrender_filelist", {'request': self._request, 'filelist': renderme, 'themes': themes, 'incremental': incremental}) renderme = sorted(set(renderme)) print "building %s files." % len(renderme) for url, q in renderme: url = url.replace(os.sep, "/") print "rendering '%s' ..." % url tools.render_url_statically(dict(config), url, q) # we're done, clean up self.cleanup()