PU Pelican in Python

Feb 01, 2017

The idea of pelican is simple:

  1. two types of content: pages and articles.
  2. inside articles, there are plenty of metadata, e.g. title, date and tag.
  3. pelican analysis articles and pages, get all the those informations in memory.
  4. pelican use corresponding template to render those informations in memory.
    1. e.g. page.html for pages, article.html for article
    2. e.g. archives.html for archive of articles.
    3. this means pelican can only handle pre-defined contracts.
  5. in conclusion:
    1. pelican gets variables form markdown files and setting file.
    2. a lot of contracts have been made on theme structure and metadata.
    3. pelican applys all these variables on theme templates and generate htmls.

/usr/local/bin/pelican:

#!/usr/local/bin/python3.5

# -*- coding: utf-8 -*-
import re
import sys

from pelican import main

if __name__ == '__main__':
    sys.argv[0] = re.sub(r'(-script.pyw|.exe)?$', '', sys.argv[0])
    sys.exit(main())

main() is in /usr/local/lib/python3.5/site-packages/pelican/__init__.py.

def main():
    args = parse_arguments()
    ...
        pelican, settings = get_instance(args)
        ...
            pelican.run()
    ...

pelican.run() is in /usr/local/lib/python3.5/site-packages/pelican/__init__.py.

def run(self):
    ...
    for p in generators:
        if hasattr(p, 'generate_context'):
            p.generate_context()
    ...
    for p in generators:
        if hasattr(p, 'generate_output'):
            p.generate_output(writer)
    ...

Normally, 3 generators are used:

  • ArticlesGenerator
  • PagesGenerator
  • StaticGenerator

ArticlesGenerator.generate_context():

def generate_context(self):
    ...
    for f in self.get_files( self.settings['ARTICLE_PATHS'], exclude=self.settings['ARTICLE_EXCLUDES']): 
            ...
                article_or_draft = self.readers.read_file(
                    base_path=self.path, path=f, content_class=Article,
                    context=self.context,
                    preread_signal=signals.article_generator_preread,
                    preread_sender=self,
                    context_signal=signals.article_generator_context,
                    context_sender=self)
            ...
            all_articles.append(article_or_draft)
            ...
    self.articles, self.translations = process_translations(
        all_articles,
        order_by=self.settings['ARTICLE_ORDER_BY'])
    self.drafts, self.drafts_translations = 
        process_translations(all_drafts)
    ...
    for article in self.articles:
        self.categories[article.category].append(article)
        if hasattr(article, 'tags'):
            for tag in article.tags:
                self.tags[tag].append(article)
        for author in getattr(article, 'authors', []):
            self.authors[author].append(article)
    self.dates = list(self.articles)
    self.dates.sort(key=attrgetter('date'),
                    reverse=self.context['NEWEST_FIRST_ARCHIVES'])
    self.categories = list(self.categories.items())
    self.categories.sort(
        reverse=self.settings['REVERSE_CATEGORY_ORDER'])
    self.authors = list(self.authors.items())
    self.authors.sort()
    self._update_context(('articles', 'dates', 'tags', 'categories',
                          'authors', 'related_posts', 'drafts'))
    ...

Basically, ArticlesGenerator.generate_context() reads file and puts all the data into context.

ArticlesGenerator.generate_output():

def generate_output(self, writer):
    ...
    self.generate_pages(writer)
    ...
def generate_pages(self, writer):
    ...
    write = partial(writer.write_file, relative_urls=self.settings['RELATIVE_URLS'])
    self.generate_articles(write)
    self.generate_period_archives(write)
    self.generate_tags(write)
    self.generate_categories(write)
    ...
def generate_articles(self, write):
    for article in chain(self.translations, self.articles):
        ...
        write(article.save_as, self.get_template(article.template),
              self.context, article=article, category=article.category,
              override_output=hasattr(article, 'override_save_as'),
              blog=True)

write is Writer.write_file() in /usr/local/lib/python3.5/site-packages/pelican/writers.py.

def write_file(self, name, template, context, relative_urls=False,
               paginated=None, override_output=False, **kwargs):
    ...
    def _write_file(template, localcontext, output_path, name, override):
        ...
        output = template.render(localcontext)
        path = os.path.join(output_path, name)
        ...
        with self._open_w(path, 'utf-8', override=override) as f:
            f.write(output)
        ...
        if paginated:
            ...
            localcontext = _get_localcontext( context, page.save_as, paginated_kwargs, relative_urls)
            _write_file(template, localcontext, self.output_path, page.save_as, override_output)
    ...

Jobs done.