summaryrefslogtreecommitdiffstats
path: root/nikola/plugins/task/listings.py
diff options
context:
space:
mode:
Diffstat (limited to 'nikola/plugins/task/listings.py')
-rw-r--r--nikola/plugins/task/listings.py266
1 files changed, 170 insertions, 96 deletions
diff --git a/nikola/plugins/task/listings.py b/nikola/plugins/task/listings.py
index 79f6763..b913330 100644
--- a/nikola/plugins/task/listings.py
+++ b/nikola/plugins/task/listings.py
@@ -1,6 +1,6 @@
# -*- coding: utf-8 -*-
-# Copyright © 2012-2014 Roberto Alsina and others.
+# Copyright © 2012-2015 Roberto Alsina and others.
# Permission is hereby granted, free of charge, to any
# person obtaining a copy of this software and associated
@@ -26,74 +26,115 @@
from __future__ import unicode_literals, print_function
+import sys
import os
from pygments import highlight
from pygments.lexers import get_lexer_for_filename, TextLexer
-from pygments.formatters import HtmlFormatter
import natsort
-import re
from nikola.plugin_categories import Task
from nikola import utils
-# FIXME: (almost) duplicated with mdx_nikola.py
-CODERE = re.compile('<div class="code"><pre>(.*?)</pre></div>', flags=re.MULTILINE | re.DOTALL)
-
-
class Listings(Task):
"""Render pretty listings."""
name = "render_listings"
+ def register_output_name(self, input_folder, rel_name, rel_output_name):
+ """Register proper and improper file mappings."""
+ if rel_name not in self.improper_input_file_mapping:
+ self.improper_input_file_mapping[rel_name] = []
+ self.improper_input_file_mapping[rel_name].append(rel_output_name)
+ self.proper_input_file_mapping[os.path.join(input_folder, rel_name)] = rel_output_name
+ self.proper_input_file_mapping[rel_output_name] = rel_output_name
+
def set_site(self, site):
site.register_path_handler('listing', self.listing_path)
+
+ # We need to prepare some things for the listings path handler to work.
+
+ self.kw = {
+ "default_lang": site.config["DEFAULT_LANG"],
+ "listings_folders": site.config["LISTINGS_FOLDERS"],
+ "output_folder": site.config["OUTPUT_FOLDER"],
+ "index_file": site.config["INDEX_FILE"],
+ "strip_indexes": site.config['STRIP_INDEXES'],
+ "filters": site.config["FILTERS"],
+ }
+
+ # Verify that no folder in LISTINGS_FOLDERS appears twice (on output side)
+ appearing_paths = set()
+ for source, dest in self.kw['listings_folders'].items():
+ if source in appearing_paths or dest in appearing_paths:
+ problem = source if source in appearing_paths else dest
+ utils.LOGGER.error("The listings input or output folder '{0}' appears in more than one entry in LISTINGS_FOLDERS, exiting.".format(problem))
+ sys.exit(1)
+ appearing_paths.add(source)
+ appearing_paths.add(dest)
+
+ # improper_input_file_mapping maps a relative input file (relative to
+ # its corresponding input directory) to a list of the output files.
+ # Since several input directories can contain files of the same name,
+ # a list is needed. This is needed for compatibility to previous Nikola
+ # versions, where there was no need to specify the input directory name
+ # when asking for a link via site.link('listing', ...).
+ self.improper_input_file_mapping = {}
+
+ # proper_input_file_mapping maps relative input file (relative to CWD)
+ # to a generated output file. Since we don't allow an input directory
+ # to appear more than once in LISTINGS_FOLDERS, we can map directly to
+ # a file name (and not a list of files).
+ self.proper_input_file_mapping = {}
+
+ for input_folder, output_folder in self.kw['listings_folders'].items():
+ for root, dirs, files in os.walk(input_folder, followlinks=True):
+ # Compute relative path; can't use os.path.relpath() here as it returns "." instead of ""
+ rel_path = root[len(input_folder):]
+ if rel_path[:1] == os.sep:
+ rel_path = rel_path[1:]
+
+ for f in files + [self.kw['index_file']]:
+ rel_name = os.path.join(rel_path, f)
+ rel_output_name = os.path.join(output_folder, rel_path, f)
+ # Register file names in the mapping.
+ self.register_output_name(input_folder, rel_name, rel_output_name)
+
return super(Listings, self).set_site(site)
def gen_tasks(self):
"""Render pretty code listings."""
- kw = {
- "default_lang": self.site.config["DEFAULT_LANG"],
- "listings_folder": self.site.config["LISTINGS_FOLDER"],
- "output_folder": self.site.config["OUTPUT_FOLDER"],
- "index_file": self.site.config["INDEX_FILE"],
- }
# Things to ignore in listings
ignored_extensions = (".pyc", ".pyo")
- def render_listing(in_name, out_name, folders=[], files=[]):
+ def render_listing(in_name, out_name, input_folder, output_folder, folders=[], files=[]):
if in_name:
with open(in_name, 'r') as fd:
try:
lexer = get_lexer_for_filename(in_name)
except:
lexer = TextLexer()
- code = highlight(fd.read(), lexer,
- HtmlFormatter(cssclass='code',
- linenos="table", nowrap=False,
- lineanchors=utils.slugify(in_name, force=True),
- anchorlinenos=True))
- # the pygments highlighter uses <div class="codehilite"><pre>
- # for code. We switch it to reST's <pre class="code">.
- code = CODERE.sub('<pre class="code literal-block">\\1</pre>', code)
+ code = highlight(fd.read(), lexer, utils.NikolaPygmentsHTML(in_name))
title = os.path.basename(in_name)
else:
code = ''
- title = ''
+ title = os.path.split(os.path.dirname(out_name))[1]
crumbs = utils.get_crumbs(os.path.relpath(out_name,
- kw['output_folder']),
+ self.kw['output_folder']),
is_file=True)
permalink = self.site.link(
'listing',
- os.path.relpath(
- out_name,
- os.path.join(
- kw['output_folder'],
- kw['listings_folder'])))
- if self.site.config['COPY_SOURCES']:
- source_link = permalink[:-5]
+ os.path.join(
+ input_folder,
+ os.path.relpath(
+ out_name[:-5], # remove '.html'
+ os.path.join(
+ self.kw['output_folder'],
+ output_folder))))
+ if self.site.config['COPY_SOURCES'] and in_name:
+ source_link = permalink[:-5] # remove '.html'
else:
source_link = None
context = {
@@ -101,88 +142,121 @@ class Listings(Task):
'title': title,
'crumbs': crumbs,
'permalink': permalink,
- 'lang': kw['default_lang'],
- 'folders': natsort.natsorted(folders),
- 'files': natsort.natsorted(files),
+ 'lang': self.kw['default_lang'],
+ 'folders': natsort.natsorted(
+ folders, alg=natsort.ns.F | natsort.ns.IC),
+ 'files': natsort.natsorted(
+ files, alg=natsort.ns.F | natsort.ns.IC),
'description': title,
'source_link': source_link,
}
- self.site.render_template('listing.tmpl', out_name,
- context)
+ self.site.render_template('listing.tmpl', out_name, context)
yield self.group_task()
template_deps = self.site.template_system.template_deps('listing.tmpl')
- for root, dirs, files in os.walk(kw['listings_folder'], followlinks=True):
- files = [f for f in files if os.path.splitext(f)[-1] not in ignored_extensions]
-
- uptodate = {'c': self.site.GLOBAL_CONTEXT}
-
- for k, v in self.site.GLOBAL_CONTEXT['template_hooks'].items():
- uptodate['||template_hooks|{0}||'.format(k)] = v._items
-
- for k in self.site._GLOBAL_CONTEXT_TRANSLATABLE:
- uptodate[k] = self.site.GLOBAL_CONTEXT[k](kw['default_lang'])
-
- # save navigation links as dependencies
- uptodate['navigation_links'] = uptodate['c']['navigation_links'](kw['default_lang'])
-
- uptodate2 = uptodate.copy()
- uptodate2['f'] = files
- uptodate2['d'] = dirs
-
- # Render all files
- out_name = os.path.join(
- kw['output_folder'],
- root, kw['index_file']
- )
- yield {
- 'basename': self.name,
- 'name': out_name,
- 'file_dep': template_deps,
- 'targets': [out_name],
- 'actions': [(render_listing, [None, out_name, dirs, files])],
- # This is necessary to reflect changes in blog title,
- # sidebar links, etc.
- 'uptodate': [utils.config_changed(uptodate2)],
- 'clean': True,
- }
- for f in files:
- ext = os.path.splitext(f)[-1]
- if ext in ignored_extensions:
- continue
- in_name = os.path.join(root, f)
- out_name = os.path.join(
- kw['output_folder'],
- root,
- f) + '.html'
- yield {
+
+ for input_folder, output_folder in self.kw['listings_folders'].items():
+ for root, dirs, files in os.walk(input_folder, followlinks=True):
+ files = [f for f in files if os.path.splitext(f)[-1] not in ignored_extensions]
+
+ uptodate = {'c': self.site.GLOBAL_CONTEXT}
+
+ for k, v in self.site.GLOBAL_CONTEXT['template_hooks'].items():
+ uptodate['||template_hooks|{0}||'.format(k)] = v._items
+
+ for k in self.site._GLOBAL_CONTEXT_TRANSLATABLE:
+ uptodate[k] = self.site.GLOBAL_CONTEXT[k](self.kw['default_lang'])
+
+ # save navigation links as dependencies
+ uptodate['navigation_links'] = uptodate['c']['navigation_links'](self.kw['default_lang'])
+
+ uptodate['kw'] = self.kw
+
+ uptodate2 = uptodate.copy()
+ uptodate2['f'] = files
+ uptodate2['d'] = dirs
+
+ # Compute relative path; can't use os.path.relpath() here as it returns "." instead of ""
+ rel_path = root[len(input_folder):]
+ if rel_path[:1] == os.sep:
+ rel_path = rel_path[1:]
+
+ rel_name = os.path.join(rel_path, self.kw['index_file'])
+ rel_output_name = os.path.join(output_folder, rel_path, self.kw['index_file'])
+
+ # Render all files
+ out_name = os.path.join(self.kw['output_folder'], rel_output_name)
+ yield utils.apply_filters({
'basename': self.name,
'name': out_name,
- 'file_dep': template_deps + [in_name],
+ 'file_dep': template_deps,
'targets': [out_name],
- 'actions': [(render_listing, [in_name, out_name])],
+ 'actions': [(render_listing, [None, out_name, input_folder, output_folder, dirs, files])],
# This is necessary to reflect changes in blog title,
# sidebar links, etc.
- 'uptodate': [utils.config_changed(uptodate)],
+ 'uptodate': [utils.config_changed(uptodate2, 'nikola.plugins.task.listings:folder')],
'clean': True,
- }
- if self.site.config['COPY_SOURCES']:
- out_name = os.path.join(
- kw['output_folder'],
- root,
- f)
- yield {
+ }, self.kw["filters"])
+ for f in files:
+ ext = os.path.splitext(f)[-1]
+ if ext in ignored_extensions:
+ continue
+ in_name = os.path.join(root, f)
+ # Record file names
+ rel_name = os.path.join(rel_path, f + '.html')
+ rel_output_name = os.path.join(output_folder, rel_path, f + '.html')
+ self.register_output_name(input_folder, rel_name, rel_output_name)
+ # Set up output name
+ out_name = os.path.join(self.kw['output_folder'], rel_output_name)
+ # Yield task
+ yield utils.apply_filters({
'basename': self.name,
'name': out_name,
- 'file_dep': [in_name],
+ 'file_dep': template_deps + [in_name],
'targets': [out_name],
- 'actions': [(utils.copy_file, [in_name, out_name])],
+ 'actions': [(render_listing, [in_name, out_name, input_folder, output_folder])],
+ # This is necessary to reflect changes in blog title,
+ # sidebar links, etc.
+ 'uptodate': [utils.config_changed(uptodate, 'nikola.plugins.task.listings:source')],
'clean': True,
- }
+ }, self.kw["filters"])
+ if self.site.config['COPY_SOURCES']:
+ rel_name = os.path.join(rel_path, f)
+ rel_output_name = os.path.join(output_folder, rel_path, f)
+ self.register_output_name(input_folder, rel_name, rel_output_name)
+ out_name = os.path.join(self.kw['output_folder'], rel_output_name)
+ yield utils.apply_filters({
+ 'basename': self.name,
+ 'name': out_name,
+ 'file_dep': [in_name],
+ 'targets': [out_name],
+ 'actions': [(utils.copy_file, [in_name, out_name])],
+ 'clean': True,
+ }, self.kw["filters"])
- def listing_path(self, name, lang):
- if not name.endswith('.html'):
+ def listing_path(self, namep, lang):
+ namep = namep.replace('/', os.sep)
+ nameh = namep + '.html'
+ for name in (namep, nameh):
+ if name in self.proper_input_file_mapping:
+ # If the name shows up in this dict, everything's fine.
+ name = self.proper_input_file_mapping[name]
+ break
+ elif name in self.improper_input_file_mapping:
+ # If the name shows up in this dict, we have to check for
+ # ambiguities.
+ if len(self.improper_input_file_mapping[name]) > 1:
+ utils.LOGGER.error("Using non-unique listing name '{0}', which maps to more than one listing name ({1})!".format(name, str(self.improper_input_file_mapping[name])))
+ sys.exit(1)
+ if len(self.site.config['LISTINGS_FOLDERS']) > 1:
+ utils.LOGGER.notice("Using listings names in site.link() without input directory prefix while configuration's LISTINGS_FOLDERS has more than one entry.")
+ name = self.improper_input_file_mapping[name][0]
+ break
+ else:
+ utils.LOGGER.error("Unknown listing name {0}!".format(namep))
+ sys.exit(1)
+ if not name.endswith(os.sep + self.site.config["INDEX_FILE"]):
name += '.html'
- path_parts = [self.site.config['LISTINGS_FOLDER']] + list(os.path.split(name))
+ path_parts = name.split(os.sep)
return [_f for _f in path_parts if _f]