from operator import attrgetter from datetime import datetime import os from jinja2 import Environment, FileSystemLoader from jinja2.exceptions import TemplateNotFound from pelican.utils import update_dict, copytree from pelican.contents import Article, Page, is_valid_content from pelican.readers import read_file _TEMPLATES = ('index', 'tag', 'tags', 'article', 'category', 'categories', 'archives', 'page') _DIRECT_TEMPLATES = ('index', 'tags', 'categories', 'archives') class Generator(object): """Baseclass generator""" def __init__(self, *args, **kwargs): for idx, item in enumerate(('context', 'settings', 'path', 'theme', 'output_path', 'markup')): setattr(self, item, args[idx]) for arg, value in kwargs.items(): setattr(self, arg, value) def get_templates(self): """Return the templates to use. Use self.theme to get the templates to use, and return a list of templates ready to use with Jinja2. """ path = os.path.expanduser(os.path.join(self.theme, 'templates')) env = Environment(loader=FileSystemLoader(path)) templates = {} for template in _TEMPLATES: try: templates[template] = env.get_template('%s.html' % template) except TemplateNotFound: raise Exception('[templates] unable to load %s.html from %s' % ( template, path)) return templates def get_files(self, path, exclude=[], extensions=None): """Return a list of files to use, based on rules :param path: the path to search the file on :param exclude: the list of path to exclude """ if not extensions: extensions = self.markup files = [] for root, dirs, temp_files in os.walk(path, followlinks=True): for e in exclude: if e in dirs: dirs.remove(e) files.extend([os.sep.join((root, f)) for f in temp_files if True in [f.endswith(ext) for ext in extensions]]) return files def _update_context(self, items): """Update the context with the given items from the currrent processor. """ for item in items: value = getattr(self, item) if hasattr(value, 'items'): value = value.items() self.context[item] = value class ArticlesGenerator(Generator): """Generate blog articles""" def __init__(self, *args, **kwargs): """initialize properties""" self.articles = [] self.dates = {} self.tags = {} self.categories = {} super(ArticlesGenerator, self).__init__(*args, **kwargs) def generate_feeds(self, writer): """Generate the feeds from the current context, and output files.""" writer.write_feed(self.articles, self.context, self.settings['FEED']) if 'FEED_RSS' in self.settings: writer.write_feed(self.articles, self.context, self.settings['FEED_RSS'], feed_type='rss') for cat, arts in self.categories.items(): arts.sort(key=attrgetter('date'), reverse=True) writer.write_feed(arts, self.context, self.settings['CATEGORY_FEED'] % cat) if 'CATEGORY_FEED_RSS' in self.settings: writer.write_feed(arts, self.context, self.settings['CATEGORY_FEED_RSS'] % cat, feed_type='rss') if 'TAG_FEED' in self.settings: for tag, arts in self.tags.items(): arts.sort(key=attrgetter('date'), reverse=True) writer.write_feed(arts, self.context, self.settings['TAG_FEED'] % tag) if 'TAG_FEED_RSS' in self.settings: writer.write_feed(arts, self.context, self.settings['TAG_FEED_RSS'] % tag, feed_type='rss') def generate_pages(self, writer): """Generate the pages on the disk TODO: change the name""" templates = self.get_templates() write = writer.write_file for template in _DIRECT_TEMPLATES: write('%s.html' % template, templates[template], self.context, blog=True) for tag, articles in self.tags.items(): write('tag/%s.html' % tag, templates['tag'], self.context, tag=tag, articles=articles) for cat in self.categories: write('category/%s.html' % cat, templates['category'], self.context, category=cat, articles=self.categories[cat]) for article in self.articles: write('%s' % article.url, templates['article'], self.context, article=article, category=article.category) def generate_context(self): """change the context""" # return the list of files to use files = self.get_files(self.path, exclude=['pages',]) for f in files: content, metadatas = read_file(f) # if no category is set, use the name of the path as a category if 'category' not in metadatas.keys(): category = os.path.dirname(f).replace( os.path.expanduser(self.path)+'/', '') if category == self.path: category = self.settings['DEFAULT_CATEGORY'] if category != '': metadatas['category'] = unicode(category) if 'date' not in metadatas.keys()\ and self.settings['FALLBACK_ON_FS_DATE']: metadatas['date'] = datetime.fromtimestamp(os.stat(f).st_ctime) article = Article(content, metadatas, settings=self.settings, filename=f) if not is_valid_content(article, f): continue update_dict(self.categories, article.category, article) if hasattr(article, 'tags'): for tag in article.tags: update_dict(self.tags, tag, article) self.articles.append(article) # sort the articles by date self.articles.sort(key=attrgetter('date'), reverse=True) self.dates = list(self.articles) self.dates.sort(key=attrgetter('date'), reverse=self.context['REVERSE_ARCHIVE_ORDER']) # and generate the output :) self._update_context(('articles', 'dates', 'tags', 'categories')) def generate_output(self, writer): self.generate_feeds(writer) self.generate_pages(writer) class PagesGenerator(Generator): """Generate pages""" def __init__(self, *args, **kwargs): self.pages = [] super(PagesGenerator, self).__init__(*args, **kwargs) def generate_context(self): for f in self.get_files(os.sep.join((self.path, 'pages'))): content, metadatas = read_file(f) page = Page(content, metadatas, settings=self.settings, filename=f) if not is_valid_content(page, f): continue self.pages.append(page) self._update_context(('pages', )) self.context['PAGES'] = self.pages def generate_output(self, writer): templates = self.get_templates() for page in self.pages: writer.write_file('pages/%s' % page.url, templates['page'], self.context, page=page) class StaticGenerator(Generator): """copy static paths (what you want to cpy, like images, medias etc. to output""" def _copy_paths(self, paths, source, destination, output_path, final_path=None): for path in paths: copytree(path, source, os.path.join(output_path, destination), final_path) def generate_output(self, writer): self._copy_paths(self.settings['STATIC_PATHS'], self.path, 'static', self.output_path) self._copy_paths(self.settings['THEME_STATIC_PATHS'], self.theme, 'theme', self.output_path, '.') class PdfGenerator(Generator): """Generate PDFs on the output dir, for all articles and pages coming from rst""" def __init__(self, *args, **kwargs): try: from rst2pdf.createpdf import RstToPdf self.pdfcreator = RstToPdf(breakside=0, stylesheets=['twelvepoint']) except ImportError: raise Exception("unable to find rst2pdf") super(PdfGenerator, self).__init(*args, **kwargs) def _create_pdf(self, obj, output_path): if obj.filename.endswith(".rst"): self.pdfcreator.createPdf(text=open(obj.filename).read(), output=os.path.join(output_path, "%s.pdf" % obj.slug)) def generate_context(self): pdf_path = os.path.join(self.output_path, 'pdf') try: os.mkdir(pdf_path) except OSError: pass for article in self.context['articles']: self._create_pdf(article, pdf_path) for page in self.context['pages']: self._create_pdf(page, pdf_path)