diff --git a/.hgignore b/.hgignore deleted file mode 100644 index a0f6b7c5..00000000 --- a/.hgignore +++ /dev/null @@ -1,9 +0,0 @@ -syntax: glob -output/* -*.pyc -MANIFEST -build -dist -docs/_build -Paste-* -*.egg-info diff --git a/.hgtags b/.hgtags deleted file mode 100644 index 6d4c8d99..00000000 --- a/.hgtags +++ /dev/null @@ -1,29 +0,0 @@ -7acafbf7e47b1287525026ad8b4f1efe443d5403 1.2 -7acafbf7e47b1287525026ad8b4f1efe443d5403 1.2 -ae850ab0fd62a98a98da7ce74ac794319c6a5066 1.2 -54a0309f79d6c5b54d8e1e3b5e3f744856b68a73 1.1 -8f5e0eb037768351eb08840e588a4364266a69b3 1.1.1 -bb986ed591734ca469f726753cbc48ebbfce0dcc 1.2.1 -8a3dad99cbfa6bb5d0ef073213d0d86e0b4c5dba 1.2.2 -4a20105a242ab154f6202aa6651979bfbb4cf95e 1.2.3 -803aa0976cca3dd737777c640722988b1f3769fe 1.2.4 -703c4511105fd9c8b85afda951a294c194e7cf3e 1.2.5 -6e46a40aaa850a979f5d09dd95d02791ec7ab0ef 2.0 -bf14d1a5c1fae9475447698f0f9b8d35c551f732 2.1 -da86343ebd543e5865050e47ecb0937755528d13 2.1.1 -760187f048bb23979402f950ecb5d3c5493995b1 2.2 -20aa16fe4daa3b70f6c063f170edc916b49837ed 2.3 -f9c1d94081504f21f5b2ba147a38099e45db1769 2.4 -e65199a0b2706d2fb48f7a3c015e869716e0bec1 2.4.1 -89dbd7b6f114508eae62fc821326f4797dfc8b23 2.4.2 -979b4473af56a191a278c83058bc9c8fa1fde30e 2.4.3 -26a444fbb78becae358afa0a5b47587db8739b21 2.4.4 -3542b65fd1963ae7065b6a3bc912fbb6c150e98c 2.4.5 -87745dfdd51b96bf18eaaf6c402effa902c1b856 2.5.0 -294a2830a393d5a97671dc211dbdb5254a15e604 2.5.1 -294a2830a393d5a97671dc211dbdb5254a15e604 2.5.1 -92b31e41134cb2c1a156ce623338cf634d2ebc3e 2.5.1 -7d728f8e771cbbc802ce81e424e08a8eecbd48dc 2.5.2 -7d728f8e771cbbc802ce81e424e08a8eecbd48dc 2.5.2 -6d368a1739a4ce48d2d04b00db04fa538e2bf90a 2.5.2 -1f9dd44b546425216b1fa35fd88d3d532da8916b 2.5.3 diff --git a/docs/faq.rst b/docs/faq.rst index a8043e07..2cd363d8 100644 --- a/docs/faq.rst +++ b/docs/faq.rst @@ -90,12 +90,16 @@ For reStructuredText, this metadata should of course be prefixed with a colon:: :Modified: 2012-08-08 -This metadata can then be accessed in the template:: +This metadata can then be accessed in templates such as ``article.html`` via:: {% if article.modified %} Last modified: {{ article.modified }} {% endif %} +If you want to include metadata in templates outside the article context (e.g., ``base.html``), the ``if`` statement should instead be: + + {% if article and article.modified %} + How do I assign custom templates on a per-page basis? ===================================================== diff --git a/docs/getting_started.rst b/docs/getting_started.rst index b8ffbf43..eb503295 100644 --- a/docs/getting_started.rst +++ b/docs/getting_started.rst @@ -41,7 +41,7 @@ method:: If you have Git installed and prefer to install the latest bleeding-edge version of Pelican rather than a stable release, use the following command:: - $ pip install -e git://github.com/getpelican/pelican#egg=pelican + $ pip install -e git+https://github.com/getpelican/pelican.git#egg=pelican If you plan on using Markdown as a markup format, you'll need to install the Markdown library as well:: @@ -331,11 +331,11 @@ interprets the HTML in a very straightforward manner, reading metadata from My super title - - - - - + + + + + This is the content of my super blog post. @@ -418,8 +418,8 @@ In this example, ``article1.rst`` could look like:: See below intra-site link examples in reStructuredText format. - `a link relative to content root <|filename|/cat/article2.md>`_ - `a link relative to current file <|filename|cat/article2.md>`_ + `a link relative to content root <|filename|/cat/article2.rst>`_ + `a link relative to current file <|filename|cat/article2.rst>`_ and ``article2.md``:: @@ -428,8 +428,8 @@ and ``article2.md``:: See below intra-site link examples in Markdown format. - [a link relative to content root](|filename|/article1.rst) - [a link relative to current file](|filename|../article1.rst) + [a link relative to content root](|filename|/article1.md) + [a link relative to current file](|filename|../article1.md) Embedding non-article or non-page content is slightly different in that the directories need to be specified in ``pelicanconf.py`` file. The ``images`` diff --git a/docs/index.rst b/docs/index.rst index eceb407f..914c2a7e 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -22,7 +22,6 @@ Pelican currently supports: * Publication of articles in multiple languages * Atom/RSS feeds * Code syntax highlighting -* PDF generation of the articles/pages (optional) * Import from WordPress, Dotclear, or RSS feeds * Integration with external tools: Twitter, Google Analytics, etc. (optional) diff --git a/docs/plugins.rst b/docs/plugins.rst index 9bf08ff3..93307afb 100644 --- a/docs/plugins.rst +++ b/docs/plugins.rst @@ -123,3 +123,69 @@ request if you need them! static_generate_context static_generator_context static_generate_preread static_generator_preread ========================== =========================== + +Recipes +======= + +We eventually realised some of the recipes to create plugins would be best +shared in the documentation somewhere, so here they are! + +How to create a new reader +-------------------------- + +One thing you might want is to add the support for your very own input +format. While it might make sense to add this feature in pelican core, we +wisely chose to avoid this situation, and have the different readers defined in +plugins. + +The rationale behind this choice is mainly that plugins are really easy to +write and don't slow down pelican itself when they're not active. + +No more talking, here is the example:: + + from pelican import signals + from pelican.readers import EXTENSIONS, Reader + + # Create a new reader class, inheriting from the pelican.reader.Reader + class NewReader(Reader): + enabled = True # Yeah, you probably want that :-) + + # The list of extensions you want this reader to match with. + # In the case multiple readers use the same extensions, the latest will + # win (so the one you're defining here, most probably). + file_extensions = ['yeah'] + + # You need to have a read method, which takes a filename and returns + # some content and the associated metadata. + def read(self, filename): + metadata = {'title': 'Oh yeah', + 'category': 'Foo', + 'date': '2012-12-01'} + + parsed = {} + for key, value in metadata.items(): + parsed[key] = self.process_metadata(key, value) + + return "Some content", parsed + + def add_reader(arg): + EXTENSIONS['yeah'] = NewReader + + # this is how pelican works. + def register(): + signals.initialized.connect(add_reader) + + +Adding a new generator +---------------------- + +Adding a new generator is also really easy. You might want to have a look at +:doc:`internals` for more information on how to create your own generator. + +:: + + def get_generators(generators): + # define a new generator here if you need to + return generators + + signals.get_generators.connect(get_generators) diff --git a/docs/settings.rst b/docs/settings.rst index 3be6621d..44585b32 100644 --- a/docs/settings.rst +++ b/docs/settings.rst @@ -153,6 +153,8 @@ Setting name (default value) What doe These templates need to use ``DIRECT_TEMPLATES`` setting. `ASCIIDOC_OPTIONS` (``[]``) A list of options to pass to AsciiDoc. See the `manpage `_ +`WITH_FUTURE_DATES` (``True``) If disabled, content with dates in the future will get a + default status of draft. ===================================================================== ===================================================================== .. [#] Default is the system locale. @@ -477,8 +479,32 @@ Setting name (default value) What does it do? `DEFAULT_PAGINATION` (``False``) The maximum number of articles to include on a page, not including orphans. False to disable pagination. +`PAGINATION_PATTERNS` A set of patterns that are used to determine advanced + pagination output. ================================================ ===================================================== +Using Pagination Patterns +------------------------- + +The ``PAGINATION_PATTERNS`` setting can be used to configure where +subsequent pages are created. The setting is a sequence of three +element tuples, where each tuple consists of:: + + (minimum page, URL setting, SAVE_AS setting,) + +For example, if you wanted the first page to just be ``/``, and the +second (and subsequent) pages to be ``/page/2/``, you would set +``PAGINATION_PATTERNS`` as follows:: + + PAGINATION_PATTERNS = ( + (1, '{base_name}/', '{base_name}/index.html'), + (2, '{base_name}/page/{number}/', '{base_name}/page/{number}/index.html'), + ) + +This would cause the first page to be written to +``{base_name}/index.html``, and subsequent ones would be written into +``page/{number}`` directories. + Tag cloud ========= @@ -555,6 +581,9 @@ Setting name (default value) What does it do? or absolute path to a theme folder, or the name of a default theme or a theme installed via ``pelican-themes`` (see below). +`THEME_STATIC_DIR` (``'theme'``) Destination directory in the output path where + Pelican will place the files collected from + `THEME_STATIC_PATHS`. Default is `theme`. `THEME_STATIC_PATHS` (``['static']``) Static theme paths you want to copy. Default value is `static`, but if your theme has other static paths, you can put them here. diff --git a/pelican/__init__.py b/pelican/__init__.py index 53216421..9bce4926 100644 --- a/pelican/__init__.py +++ b/pelican/__init__.py @@ -14,8 +14,8 @@ import collections from pelican import signals from pelican.generators import (ArticlesGenerator, PagesGenerator, - StaticGenerator, PdfGenerator, - SourceFileGenerator, TemplatePagesGenerator) + StaticGenerator, SourceFileGenerator, + TemplatePagesGenerator) from pelican.log import init from pelican.settings import read_settings from pelican.utils import clean_output_dir, folder_watcher, file_watcher @@ -168,16 +168,16 @@ class Pelican(object): ) for cls in self.get_generator_classes() ] - for p in generators: - if hasattr(p, 'generate_context'): - p.generate_context() - # erase the directory if it is not the source and if that's # explicitely asked if (self.delete_outputdir and not os.path.realpath(self.path).startswith(self.output_path)): clean_output_dir(self.output_path, self.output_retention) + for p in generators: + if hasattr(p, 'generate_context'): + p.generate_context() + writer = self.get_writer() for p in generators: @@ -199,8 +199,6 @@ class Pelican(object): if self.settings['TEMPLATE_PAGES']: generators.append(TemplatePagesGenerator) - if self.settings['PDF_GENERATOR']: - generators.append(PdfGenerator) if self.settings['OUTPUT_SOURCES']: generators.append(SourceFileGenerator) @@ -390,7 +388,7 @@ def main(): # so convert the message to unicode with the correct encoding msg = str(e) if not six.PY3: - msg = msg.decode(locale.getpreferredencoding(False)) + msg = msg.decode(locale.getpreferredencoding()) logger.critical(msg) diff --git a/pelican/contents.py b/pelican/contents.py index d56335dd..ed213c31 100644 --- a/pelican/contents.py +++ b/pelican/contents.py @@ -141,14 +141,21 @@ class Content(object): """Returns the URL, formatted with the proper values""" metadata = copy.copy(self.metadata) path = self.metadata.get('path', self.get_relative_source_path()) + default_category = self.settings['DEFAULT_CATEGORY'] + slug_substitutions = self.settings.get('SLUG_SUBSTITUTIONS', ()) metadata.update({ 'path': path_to_url(path), 'slug': getattr(self, 'slug', ''), 'lang': getattr(self, 'lang', 'en'), 'date': getattr(self, 'date', datetime.now()), - 'author': getattr(self, 'author', ''), - 'category': getattr(self, 'category', - self.settings['DEFAULT_CATEGORY']), + 'author': slugify( + getattr(self, 'author', ''), + slug_substitutions + ), + 'category': slugify( + getattr(self, 'category', default_category), + slug_substitutions + ) }) return metadata diff --git a/pelican/generators.py b/pelican/generators.py index 0dc3667f..1444c95c 100644 --- a/pelican/generators.py +++ b/pelican/generators.py @@ -56,6 +56,7 @@ class Generator(object): "themes", "simple", "templates")) self.env = Environment( trim_blocks=True, + lstrip_blocks=True, loader=ChoiceLoader([ FileSystemLoader(self._templates_path), simple_loader, # implicit inheritance @@ -167,7 +168,8 @@ class TemplatePagesGenerator(Generator): try: template = self.env.get_template(source) rurls = self.settings['RELATIVE_URLS'] - writer.write_file(dest, template, self.context, rurls) + writer.write_file(dest, template, self.context, rurls, + override_output=True) finally: del self.env.loader.loaders[0] @@ -261,7 +263,8 @@ class ArticlesGenerator(Generator): """Generate the articles.""" for article in chain(self.translations, self.articles): write(article.save_as, self.get_template(article.template), - self.context, article=article, category=article.category) + self.context, article=article, category=article.category, + override_output=hasattr(article, 'override_save_as')) def generate_period_archives(self, write): """Generate per-year, per-month, and per-day archives.""" @@ -334,6 +337,7 @@ class ArticlesGenerator(Generator): """Generate category pages.""" category_template = self.get_template('category') for cat, articles in self.categories: + articles.sort(key=attrgetter('date'), reverse=True) dates = [article for article in self.dates if article in articles] write(cat.save_as, category_template, self.context, category=cat, articles=articles, dates=dates, @@ -344,6 +348,7 @@ class ArticlesGenerator(Generator): """Generate Author pages.""" author_template = self.get_template('author') for aut, articles in self.authors: + articles.sort(key=attrgetter('date'), reverse=True) dates = [article for article in self.dates if article in articles] write(aut.save_as, author_template, self.context, author=aut, articles=articles, dates=dates, @@ -530,7 +535,8 @@ class PagesGenerator(Generator): self.hidden_translations, self.hidden_pages): writer.write_file(page.save_as, self.get_template(page.template), self.context, page=page, - relative_urls=self.settings['RELATIVE_URLS']) + relative_urls=self.settings['RELATIVE_URLS'], + override_output=hasattr(page, 'override_save_as')) class StaticGenerator(Generator): @@ -564,7 +570,8 @@ class StaticGenerator(Generator): def generate_output(self, writer): self._copy_paths(self.settings['THEME_STATIC_PATHS'], self.theme, - 'theme', self.output_path, os.curdir) + self.settings['THEME_STATIC_DIR'], self.output_path, + os.curdir) # copy all Static files for sc in self.staticfiles: source_path = os.path.join(self.path, sc.source_path) @@ -574,52 +581,6 @@ class StaticGenerator(Generator): logger.info('copying {} to {}'.format(sc.source_path, sc.save_as)) -class PdfGenerator(Generator): - """Generate PDFs on the output dir, for all articles and pages coming from - rst""" - def __init__(self, *args, **kwargs): - super(PdfGenerator, self).__init__(*args, **kwargs) - try: - from rst2pdf.createpdf import RstToPdf - pdf_style_path = os.path.join(self.settings['PDF_STYLE_PATH']) - pdf_style = self.settings['PDF_STYLE'] - self.pdfcreator = RstToPdf(breakside=0, - stylesheets=[pdf_style], - style_path=[pdf_style_path]) - except ImportError: - raise Exception("unable to find rst2pdf") - - def _create_pdf(self, obj, output_path): - if obj.source_path.endswith('.rst'): - filename = obj.slug + ".pdf" - output_pdf = os.path.join(output_path, filename) - # print('Generating pdf for', obj.source_path, 'in', output_pdf) - with open(obj.source_path) as f: - self.pdfcreator.createPdf(text=f.read(), output=output_pdf) - logger.info(' [ok] writing %s' % output_pdf) - - def generate_context(self): - pass - - def generate_output(self, writer=None): - # we don't use the writer passed as argument here - # since we write our own files - logger.info(' Generating PDF files...') - pdf_path = os.path.join(self.output_path, 'pdf') - if not os.path.exists(pdf_path): - try: - os.mkdir(pdf_path) - except OSError: - logger.error("Couldn't create the pdf output folder in " + - pdf_path) - - for article in self.context['articles']: - self._create_pdf(article, pdf_path) - - for page in self.context['pages']: - self._create_pdf(page, pdf_path) - - class SourceFileGenerator(Generator): def generate_context(self): self.output_extension = self.settings['OUTPUT_SOURCES_EXTENSION'] diff --git a/pelican/paginator.py b/pelican/paginator.py index 067215c2..df8606ec 100644 --- a/pelican/paginator.py +++ b/pelican/paginator.py @@ -1,15 +1,37 @@ # -*- coding: utf-8 -*- from __future__ import unicode_literals, print_function +import six # From django.core.paginator +from collections import namedtuple +import functools +import logging +import os + from math import ceil +logger = logging.getLogger(__name__) + + +PaginationRule = namedtuple( + 'PaginationRule', + 'min_page URL SAVE_AS', +) + class Paginator(object): - def __init__(self, object_list, per_page, orphans=0): + def __init__(self, name, object_list, settings): + self.name = name self.object_list = object_list - self.per_page = per_page - self.orphans = orphans + self.settings = settings + + if settings.get('DEFAULT_PAGINATION'): + self.per_page = settings.get('DEFAULT_PAGINATION') + self.orphans = settings.get('DEFAULT_ORPHANS') + else: + self.per_page = len(object_list) + self.orphans = 0 + self._num_pages = self._count = None def page(self, number): @@ -18,7 +40,8 @@ class Paginator(object): top = bottom + self.per_page if top + self.orphans >= self.count: top = self.count - return Page(self.object_list[bottom:top], number, self) + return Page(self.name, self.object_list[bottom:top], number, self, + self.settings) def _get_count(self): "Returns the total number of objects, across all pages." @@ -45,10 +68,12 @@ class Paginator(object): class Page(object): - def __init__(self, object_list, number, paginator): + def __init__(self, name, object_list, number, paginator, settings): + self.name = name self.object_list = object_list self.number = number self.paginator = paginator + self.settings = settings def __repr__(self): return '' % (self.number, self.paginator.num_pages) @@ -87,3 +112,48 @@ class Page(object): if self.number == self.paginator.num_pages: return self.paginator.count return self.number * self.paginator.per_page + + def _from_settings(self, key): + """Returns URL information as defined in settings. Similar to + URLWrapper._from_settings, but specialized to deal with pagination + logic.""" + + rule = None + + # find the last matching pagination rule + for p in self.settings['PAGINATION_PATTERNS']: + if p.min_page <= self.number: + rule = p + + if not rule: + return '' + + prop_value = getattr(rule, key) + + if not isinstance(prop_value, six.string_types): + logger.warning('%s is set to %s' % (key, prop_value)) + return prop_value + + # URL or SAVE_AS is a string, format it with a controlled context + context = { + 'name': self.name, + 'object_list': self.object_list, + 'number': self.number, + 'paginator': self.paginator, + 'settings': self.settings, + 'base_name': os.path.dirname(self.name), + 'number_sep': '/', + } + + if self.number == 1: + # no page numbers on the first page + context['number'] = '' + context['number_sep'] = '' + + ret = prop_value.format(**context) + if ret[0] == '/': + ret = ret[1:] + return ret + + url = property(functools.partial(_from_settings, key='URL')) + save_as = property(functools.partial(_from_settings, key='SAVE_AS')) diff --git a/pelican/readers.py b/pelican/readers.py index bd9f5914..3923245e 100644 --- a/pelican/readers.py +++ b/pelican/readers.py @@ -5,6 +5,7 @@ import datetime import logging import os import re + try: import docutils import docutils.core @@ -14,7 +15,7 @@ try: # import the directives to have pygments support from pelican import rstdirectives # NOQA except ImportError: - core = False + docutils = False try: from markdown import Markdown except ImportError: @@ -36,7 +37,6 @@ except ImportError: from pelican.contents import Page, Category, Tag, Author from pelican.utils import get_date, pelican_open - logger = logging.getLogger(__name__) METADATA_PROCESSORS = { @@ -47,6 +47,8 @@ METADATA_PROCESSORS = { 'author': Author, } +logger = logging.getLogger(__name__) + class Reader(object): enabled = True @@ -101,6 +103,12 @@ class PelicanHTMLTranslator(HTMLTranslator): def depart_abbreviation(self, node): self.body.append('') + def visit_image(self, node): + # set an empty alt if alt is not specified + # avoids that alt is taken from src + node['alt'] = node.get('alt', '') + return HTMLTranslator.visit_image(self, node) + class RstReader(Reader): enabled = bool(docutils) @@ -199,7 +207,7 @@ class HTMLReader(Reader): enabled = True class _HTMLParser(HTMLParser): - def __init__(self, settings): + def __init__(self, settings, filename): HTMLParser.__init__(self) self.body = '' self.metadata = {} @@ -207,6 +215,8 @@ class HTMLReader(Reader): self._data_buffer = '' + self._filename = filename + self._in_top_level = True self._in_head = False self._in_title = False @@ -275,7 +285,11 @@ class HTMLReader(Reader): def _handle_meta_tag(self, attrs): name = self._attr_value(attrs, 'name').lower() - contents = self._attr_value(attrs, 'contents', '') + contents = self._attr_value(attrs, 'content', '') + if not contents: + contents = self._attr_value(attrs, 'contents', '') + if contents: + logger.warning("Meta tag attribute 'contents' used in file %s, should be changed to 'content'", self._filename) if name == 'keywords': name = 'tags' @@ -288,7 +302,7 @@ class HTMLReader(Reader): def read(self, filename): """Parse content and metadata of HTML files""" with pelican_open(filename) as content: - parser = self._HTMLParser(self.settings) + parser = self._HTMLParser(self.settings, filename) parser.feed(content) parser.close() @@ -382,6 +396,41 @@ def read_file(base_path, path, content_class=Page, fmt=None, content, reader_metadata = reader.read(path) metadata.update(reader_metadata) + # create warnings for all images with empty alt (up to a certain number) + # as they are really likely to be accessibility flaws + if content: + # find images with empty alt + imgs = re.compile(r""" + (?: + # src before alt + ]* + src=(['"])(.*)\1 + [^\>]* + alt=(['"])\3 + )|(?: + # alt before src + ]* + alt=(['"])\4 + [^\>]* + src=(['"])(.*)\5 + ) + """, re.X) + matches = re.findall(imgs, content) + # find a correct threshold + nb_warnings = 10 + if len(matches) == nb_warnings + 1: + nb_warnings += 1 # avoid bad looking case + # print one warning per image with empty alt until threshold + for match in matches[:nb_warnings]: + logger.warning('Empty alt attribute for image {} in {}'.format( + os.path.basename(match[1] + match[5]), path)) + # print one warning for the other images with empty alt + if len(matches) > nb_warnings: + logger.warning('{} other images with empty alt attributes'.format( + len(matches) - nb_warnings)) + # eventually filter the content with typogrify if asked so if content and settings and settings['TYPOGRIFY']: from typogrify.filters import typogrify diff --git a/pelican/rstdirectives.py b/pelican/rstdirectives.py index fb4a6c93..1fdd09d6 100644 --- a/pelican/rstdirectives.py +++ b/pelican/rstdirectives.py @@ -32,7 +32,7 @@ class Pygments(Directive): # no lexer found - use the text one instead of an exception lexer = TextLexer() # take an arbitrary option if more than one is given - formatter = self.options and VARIANTS[self.options.keys()[0]] \ + formatter = self.options and VARIANTS[list(self.options.keys())[0]] \ or DEFAULT parsed = highlight('\n'.join(self.content), lexer, formatter) return [nodes.raw('', parsed, format='html')] diff --git a/pelican/settings.py b/pelican/settings.py index 01203504..0f37c98d 100644 --- a/pelican/settings.py +++ b/pelican/settings.py @@ -35,6 +35,7 @@ DEFAULT_CONFIG = { 'OUTPUT_PATH': 'output', 'MARKUP': ('rst', 'md'), 'STATIC_PATHS': ['images', ], + 'THEME_STATIC_DIR': 'theme', 'THEME_STATIC_PATHS': ['static', ], 'FEED_ALL_ATOM': os.path.join('feeds', 'all.atom.xml'), 'CATEGORY_FEED_ATOM': os.path.join('feeds', '%s.atom.xml'), @@ -44,7 +45,6 @@ DEFAULT_CONFIG = { 'SITENAME': 'A Pelican Blog', 'DISPLAY_PAGES_ON_MENU': True, 'DISPLAY_CATEGORIES_ON_MENU': True, - 'PDF_GENERATOR': False, 'OUTPUT_SOURCES': False, 'OUTPUT_SOURCES_EXTENSION': '.text', 'USE_FOLDER_AS_CATEGORY': True, @@ -65,6 +65,7 @@ DEFAULT_CONFIG = { 'PAGE_LANG_SAVE_AS': os.path.join('pages', '{slug}-{lang}.html'), 'STATIC_URL': '{path}', 'STATIC_SAVE_AS': '{path}', + 'PDF_GENERATOR': False, 'PDF_STYLE_PATH': '', 'PDF_STYLE': 'twelvepoint', 'CATEGORY_URL': 'category/{slug}.html', @@ -73,6 +74,9 @@ DEFAULT_CONFIG = { 'TAG_SAVE_AS': os.path.join('tag', '{slug}.html'), 'AUTHOR_URL': 'author/{slug}.html', 'AUTHOR_SAVE_AS': os.path.join('author', '{slug}.html'), + 'PAGINATION_PATTERNS': [ + (0, '{name}{number}.html', '{name}{number}.html'), + ], 'YEAR_ARCHIVE_SAVE_AS': False, 'MONTH_ARCHIVE_SAVE_AS': False, 'DAY_ARCHIVE_SAVE_AS': False, @@ -90,7 +94,7 @@ DEFAULT_CONFIG = { 'MD_EXTENSIONS': ['codehilite(css_class=highlight)', 'extra'], 'JINJA_EXTENSIONS': [], 'JINJA_FILTERS': {}, - 'LOCALE': [], # defaults to user locale + 'LOCALE': [''], # defaults to user locale 'DEFAULT_PAGINATION': False, 'DEFAULT_ORPHANS': 0, 'DEFAULT_METADATA': (), @@ -236,6 +240,19 @@ def configure_settings(settings): 'http://docs.getpelican.com/en/latest/settings.html#timezone ' 'for more information') + # fix up pagination rules + from pelican.paginator import PaginationRule + pagination_rules = [ + PaginationRule(*r) for r in settings.get( + 'PAGINATION_PATTERNS', + DEFAULT_CONFIG['PAGINATION_PATTERNS'], + ) + ] + settings['PAGINATION_PATTERNS'] = sorted( + pagination_rules, + key=lambda r: r[0], + ) + # Save people from accidentally setting a string rather than a list path_keys = ( 'ARTICLE_EXCLUDES', diff --git a/pelican/tests/content/article_with_keywords.html b/pelican/tests/content/article_with_keywords.html index c869f514..0744c754 100644 --- a/pelican/tests/content/article_with_keywords.html +++ b/pelican/tests/content/article_with_keywords.html @@ -1,6 +1,6 @@ This is a super article ! - + diff --git a/pelican/tests/content/article_with_metadata.html b/pelican/tests/content/article_with_metadata.html index b108ac8a..b501ea29 100644 --- a/pelican/tests/content/article_with_metadata.html +++ b/pelican/tests/content/article_with_metadata.html @@ -1,12 +1,12 @@ This is a super article ! - - - - - - + + + + + + Multi-line metadata should be supported diff --git a/pelican/tests/content/article_with_metadata_and_contents.html b/pelican/tests/content/article_with_metadata_and_contents.html new file mode 100644 index 00000000..b108ac8a --- /dev/null +++ b/pelican/tests/content/article_with_metadata_and_contents.html @@ -0,0 +1,15 @@ + + + This is a super article ! + + + + + + + + + Multi-line metadata should be supported + as well as inline markup. + + diff --git a/pelican/tests/content/article_with_uppercase_metadata.html b/pelican/tests/content/article_with_uppercase_metadata.html index 4fe5a9ee..b4cedf39 100644 --- a/pelican/tests/content/article_with_uppercase_metadata.html +++ b/pelican/tests/content/article_with_uppercase_metadata.html @@ -1,6 +1,6 @@ This is a super article ! - + diff --git a/pelican/tests/default_conf.py b/pelican/tests/default_conf.py index 80a990b5..62594894 100644 --- a/pelican/tests/default_conf.py +++ b/pelican/tests/default_conf.py @@ -9,7 +9,6 @@ GITHUB_URL = 'http://github.com/ametaireau/' DISQUS_SITENAME = "blog-notmyidea" PDF_GENERATOR = False REVERSE_CATEGORY_ORDER = True -LOCALE = "" DEFAULT_PAGINATION = 2 FEED_RSS = 'feeds/all.rss.xml' diff --git a/pelican/tests/output/basic/a-markdown-powered-article.html b/pelican/tests/output/basic/a-markdown-powered-article.html index 94b6e4ca..b7a90f02 100644 --- a/pelican/tests/output/basic/a-markdown-powered-article.html +++ b/pelican/tests/output/basic/a-markdown-powered-article.html @@ -4,8 +4,8 @@ A markdown powered article - - + + @@ -15,46 +15,47 @@ -
+
-
+