Manual pass on sources for better standards.

This commit is contained in:
Alexis Métaireau 2013-03-03 20:12:31 -08:00
commit 519dcdbcb3
9 changed files with 136 additions and 105 deletions

View file

@ -293,8 +293,15 @@ def main():
# restriction; all files are recursively checked if they # restriction; all files are recursively checked if they
# have changed, no matter what extension the filenames # have changed, no matter what extension the filenames
# have. # have.
if files_changed(pelican.path, pelican.markup, pelican.ignore_files) or \ if (files_changed(
files_changed(pelican.theme, [''], pelican.ignore_files): pelican.path,
pelican.markup,
pelican.ignore_files)
or files_changed(
pelican.theme,
[''],
pelican.ignore_files
)):
if not files_found_error: if not files_found_error:
files_found_error = True files_found_error = True
pelican.run() pelican.run()
@ -318,8 +325,7 @@ def main():
time.sleep(1) # sleep to avoid cpu load time.sleep(1) # sleep to avoid cpu load
except Exception as e: except Exception as e:
logger.warning( logger.warning(
"Caught exception \"{}\". Reloading.".format(e) 'Caught exception "{0}". Reloading.'.format(e))
)
continue continue
else: else:
pelican.run() pelican.run()

View file

@ -11,7 +11,6 @@ import re
import sys import sys
from datetime import datetime from datetime import datetime
from sys import platform, stdin
from pelican.settings import _DEFAULT_CONFIG from pelican.settings import _DEFAULT_CONFIG
@ -92,7 +91,8 @@ class Page(object):
if isinstance(self.date_format, tuple): if isinstance(self.date_format, tuple):
locale_string = self.date_format[0] locale_string = self.date_format[0]
if sys.version_info < (3, ) and isinstance(locale_string, six.text_type): if sys.version_info < (3, ) and isinstance(locale_string,
six.text_type):
locale_string = locale_string.encode('ascii') locale_string = locale_string.encode('ascii')
locale.setlocale(locale.LC_ALL, locale_string) locale.setlocale(locale.LC_ALL, locale_string)
self.date_format = self.date_format[1] self.date_format = self.date_format[1]
@ -289,9 +289,12 @@ class URLWrapper(object):
def _from_settings(self, key, get_page_name=False): def _from_settings(self, key, get_page_name=False):
"""Returns URL information as defined in settings. """Returns URL information as defined in settings.
When get_page_name=True returns URL without anything after {slug}
e.g. if in settings: CATEGORY_URL="cat/{slug}.html" this returns "cat/{slug}" When get_page_name=True returns URL without anything after {slug} e.g.
Useful for pagination.""" if in settings: CATEGORY_URL="cat/{slug}.html" this returns
"cat/{slug}" Useful for pagination.
"""
setting = "%s_%s" % (self.__class__.__name__.upper(), key) setting = "%s_%s" % (self.__class__.__name__.upper(), key)
value = self.settings[setting] value = self.settings[setting]
if not isinstance(value, six.string_types): if not isinstance(value, six.string_types):
@ -303,7 +306,8 @@ class URLWrapper(object):
else: else:
return value.format(**self.as_dict()) return value.format(**self.as_dict())
page_name = property(functools.partial(_from_settings, key='URL', get_page_name=True)) page_name = property(functools.partial(_from_settings, key='URL',
get_page_name=True))
url = property(functools.partial(_from_settings, key='URL')) url = property(functools.partial(_from_settings, key='URL'))
save_as = property(functools.partial(_from_settings, key='SAVE_AS')) save_as = property(functools.partial(_from_settings, key='SAVE_AS'))

View file

@ -14,11 +14,14 @@ from functools import partial
from itertools import chain from itertools import chain
from operator import attrgetter, itemgetter from operator import attrgetter, itemgetter
from jinja2 import (Environment, FileSystemLoader, PrefixLoader, ChoiceLoader, from jinja2 import (
BaseLoader, TemplateNotFound) Environment, FileSystemLoader, PrefixLoader, ChoiceLoader, BaseLoader,
TemplateNotFound
)
from pelican.contents import Article, Page, Category, StaticContent, \ from pelican.contents import (
is_valid_content Article, Page, Category, StaticContent, is_valid_content
)
from pelican.readers import read_file from pelican.readers import read_file
from pelican.utils import copy, process_translations, mkdir_p from pelican.utils import copy, process_translations, mkdir_p
from pelican import signals from pelican import signals
@ -76,8 +79,9 @@ class Generator(object):
try: try:
self._templates[name] = self.env.get_template(name + '.html') self._templates[name] = self.env.get_template(name + '.html')
except TemplateNotFound: except TemplateNotFound:
raise Exception('[templates] unable to load %s.html from %s' \ raise Exception(
% (name, self._templates_path)) ('[templates] unable to load %s.html from %s'
% (name, self._templates_path)))
return self._templates[name] return self._templates[name]
def get_files(self, path, exclude=[], extensions=None): def get_files(self, path, exclude=[], extensions=None):
@ -165,8 +169,8 @@ class ArticlesGenerator(Generator):
self.categories = defaultdict(list) self.categories = defaultdict(list)
self.related_posts = [] self.related_posts = []
self.authors = defaultdict(list) self.authors = defaultdict(list)
super(ArticlesGenerator, self).__init__(*args, **kwargs)
self.drafts = [] self.drafts = []
super(ArticlesGenerator, self).__init__(*args, **kwargs)
signals.article_generator_init.send(self) signals.article_generator_init.send(self)
def generate_feeds(self, writer): def generate_feeds(self, writer):
@ -180,8 +184,8 @@ class ArticlesGenerator(Generator):
writer.write_feed(self.articles, self.context, writer.write_feed(self.articles, self.context,
self.settings['FEED_RSS'], feed_type='rss') self.settings['FEED_RSS'], feed_type='rss')
if self.settings.get('FEED_ALL_ATOM') or \ if (self.settings.get('FEED_ALL_ATOM')
self.settings.get('FEED_ALL_RSS'): or self.settings.get('FEED_ALL_RSS')):
all_articles = list(self.articles) all_articles = list(self.articles)
for article in self.articles: for article in self.articles:
all_articles.extend(article.translations) all_articles.extend(article.translations)
@ -193,7 +197,8 @@ class ArticlesGenerator(Generator):
if self.settings.get('FEED_ALL_RSS'): if self.settings.get('FEED_ALL_RSS'):
writer.write_feed(all_articles, self.context, writer.write_feed(all_articles, self.context,
self.settings['FEED_ALL_RSS'], feed_type='rss') self.settings['FEED_ALL_RSS'],
feed_type='rss')
for cat, arts in self.categories: for cat, arts in self.categories:
arts.sort(key=attrgetter('date'), reverse=True) arts.sort(key=attrgetter('date'), reverse=True)
@ -206,8 +211,8 @@ class ArticlesGenerator(Generator):
self.settings['CATEGORY_FEED_RSS'] % cat, self.settings['CATEGORY_FEED_RSS'] % cat,
feed_type='rss') feed_type='rss')
if self.settings.get('TAG_FEED_ATOM') \ if (self.settings.get('TAG_FEED_ATOM')
or self.settings.get('TAG_FEED_RSS'): or self.settings.get('TAG_FEED_RSS')):
for tag, arts in self.tags.items(): for tag, arts in self.tags.items():
arts.sort(key=attrgetter('date'), reverse=True) arts.sort(key=attrgetter('date'), reverse=True)
if self.settings.get('TAG_FEED_ATOM'): if self.settings.get('TAG_FEED_ATOM'):
@ -219,8 +224,8 @@ class ArticlesGenerator(Generator):
self.settings['TAG_FEED_RSS'] % tag, self.settings['TAG_FEED_RSS'] % tag,
feed_type='rss') feed_type='rss')
if self.settings.get('TRANSLATION_FEED_ATOM') or \ if (self.settings.get('TRANSLATION_FEED_ATOM')
self.settings.get('TRANSLATION_FEED_RSS'): or self.settings.get('TRANSLATION_FEED_RSS')):
translations_feeds = defaultdict(list) translations_feeds = defaultdict(list)
for article in chain(self.articles, self.translations): for article in chain(self.articles, self.translations):
translations_feeds[article.lang].append(article) translations_feeds[article.lang].append(article)
@ -376,7 +381,7 @@ class ArticlesGenerator(Generator):
# only main articles are listed in categories, not translations # only main articles are listed in categories, not translations
self.categories[article.category].append(article) self.categories[article.category].append(article)
# ignore blank authors as well as undefined # ignore blank authors as well as undefined
if hasattr(article,'author') and article.author.name != '': if hasattr(article, 'author') and article.author.name != '':
self.authors[article.author].append(article) self.authors[article.author].append(article)
# sort the articles by date # sort the articles by date
@ -470,7 +475,8 @@ class PagesGenerator(Generator):
repr(f))) repr(f)))
self.pages, self.translations = process_translations(all_pages) self.pages, self.translations = process_translations(all_pages)
self.hidden_pages, self.hidden_translations = process_translations(hidden_pages) self.hidden_pages, self.hidden_translations = (
process_translations(hidden_pages))
self._update_context(('pages', )) self._update_context(('pages', ))
self.context['PAGES'] = self.pages self.context['PAGES'] = self.pages
@ -574,6 +580,7 @@ class PdfGenerator(Generator):
for page in self.context['pages']: for page in self.context['pages']:
self._create_pdf(page, pdf_path) self._create_pdf(page, pdf_path)
class SourceFileGenerator(Generator): class SourceFileGenerator(Generator):
def generate_context(self): def generate_context(self):
self.output_extension = self.settings['OUTPUT_SOURCES_EXTENSION'] self.output_extension = self.settings['OUTPUT_SOURCES_EXTENSION']

View file

@ -31,11 +31,10 @@ def ansi(color, text):
class ANSIFormatter(Formatter): class ANSIFormatter(Formatter):
""" """Convert a `logging.LogRecord' object into colored text, using ANSI
Convert a `logging.LogRecord' object into colored text, using ANSI escape sequences. escape sequences.
"""
## colors:
"""
def format(self, record): def format(self, record):
msg = str(record.msg) msg = str(record.msg)
if record.levelname == 'INFO': if record.levelname == 'INFO':
@ -67,8 +66,8 @@ class TextFormatter(Formatter):
def init(level=None, logger=getLogger(), handler=StreamHandler()): def init(level=None, logger=getLogger(), handler=StreamHandler()):
logger = logging.getLogger() logger = logging.getLogger()
if os.isatty(sys.stdout.fileno()) \ if (os.isatty(sys.stdout.fileno())
and not sys.platform.startswith('win'): and not sys.platform.startswith('win')):
fmt = ANSIFormatter() fmt = ANSIFormatter()
else: else:
fmt = TextFormatter() fmt = TextFormatter()

View file

@ -1,6 +1,5 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
from __future__ import unicode_literals, print_function from __future__ import unicode_literals, print_function
import six
import os import os
import re import re
@ -23,7 +22,6 @@ try:
asciidoc = True asciidoc = True
except ImportError: except ImportError:
asciidoc = False asciidoc = False
import re
import cgi import cgi
try: try:
@ -168,6 +166,7 @@ class MarkdownReader(Reader):
metadata = self._parse_metadata(md.Meta) metadata = self._parse_metadata(md.Meta)
return content, metadata return content, metadata
class HTMLReader(Reader): class HTMLReader(Reader):
"""Parses HTML files as input, looking for meta, title, and body tags""" """Parses HTML files as input, looking for meta, title, and body tags"""
file_extensions = ['htm', 'html'] file_extensions = ['htm', 'html']
@ -240,7 +239,7 @@ class HTMLReader(Reader):
def build_tag(self, tag, attrs, close_tag): def build_tag(self, tag, attrs, close_tag):
result = '<{}'.format(cgi.escape(tag)) result = '<{}'.format(cgi.escape(tag))
for k,v in attrs: for k, v in attrs:
result += ' ' + cgi.escape(k) result += ' ' + cgi.escape(k)
if v is not None: if v is not None:
result += '="{}"'.format(cgi.escape(v)) result += '="{}"'.format(cgi.escape(v))
@ -272,6 +271,7 @@ class HTMLReader(Reader):
metadata[k] = self.process_metadata(k, parser.metadata[k]) metadata[k] = self.process_metadata(k, parser.metadata[k])
return parser.body, metadata return parser.body, metadata
class AsciiDocReader(Reader): class AsciiDocReader(Reader):
enabled = bool(asciidoc) enabled = bool(asciidoc)
file_extensions = ['asc'] file_extensions = ['asc']

View file

@ -2,12 +2,12 @@ from __future__ import print_function
try: try:
import SimpleHTTPServer as srvmod import SimpleHTTPServer as srvmod
except ImportError: except ImportError:
import http.server as srvmod import http.server as srvmod # NOQA
try: try:
import SocketServer as socketserver import SocketServer as socketserver
except ImportError: except ImportError:
import socketserver import socketserver # NOQA
PORT = 8000 PORT = 8000
@ -17,4 +17,3 @@ httpd = socketserver.TCPServer(("", PORT), Handler)
print("serving at port", PORT) print("serving at port", PORT)
httpd.serve_forever() httpd.serve_forever()

View file

@ -62,7 +62,8 @@ _DEFAULT_CONFIG = {'PATH': '.',
'DEFAULT_LANG': 'en', 'DEFAULT_LANG': 'en',
'TAG_CLOUD_STEPS': 4, 'TAG_CLOUD_STEPS': 4,
'TAG_CLOUD_MAX_ITEMS': 100, 'TAG_CLOUD_MAX_ITEMS': 100,
'DIRECT_TEMPLATES': ('index', 'tags', 'categories', 'archives'), 'DIRECT_TEMPLATES': ('index', 'tags', 'categories',
'archives'),
'EXTRA_TEMPLATES_PATHS': [], 'EXTRA_TEMPLATES_PATHS': [],
'PAGINATED_DIRECT_TEMPLATES': ('index', ), 'PAGINATED_DIRECT_TEMPLATES': ('index', ),
'PELICAN_CLASS': 'pelican.Pelican', 'PELICAN_CLASS': 'pelican.Pelican',
@ -171,7 +172,8 @@ def configure_settings(settings):
if (siteurl.endswith('/')): if (siteurl.endswith('/')):
settings['SITEURL'] = siteurl[:-1] settings['SITEURL'] = siteurl[:-1]
logger.warn("Removed extraneous trailing slash from SITEURL.") logger.warn("Removed extraneous trailing slash from SITEURL.")
# If SITEURL is defined but FEED_DOMAIN isn't, set FEED_DOMAIN = SITEURL # If SITEURL is defined but FEED_DOMAIN isn't,
# set FEED_DOMAIN to SITEURL
if not 'FEED_DOMAIN' in settings: if not 'FEED_DOMAIN' in settings:
settings['FEED_DOMAIN'] = settings['SITEURL'] settings['FEED_DOMAIN'] = settings['SITEURL']
@ -185,36 +187,45 @@ def configure_settings(settings):
if any(settings.get(k) for k in feed_keys): if any(settings.get(k) for k in feed_keys):
if not settings.get('FEED_DOMAIN'): if not settings.get('FEED_DOMAIN'):
logger.warn("Since feed URLs should always be absolute, you should specify " logger.warn(
"FEED_DOMAIN in your settings. (e.g., 'FEED_DOMAIN = " "Since feed URLs should always be absolute, you should specify"
"http://www.example.com')") " FEED_DOMAIN in your settings. (e.g., 'FEED_DOMAIN = "
"http://www.example.com')")
if not settings.get('SITEURL'): if not settings.get('SITEURL'):
logger.warn("Feeds generated without SITEURL set properly may not be valid") logger.warn('Feeds generated without SITEURL set properly may not'
' be valid')
if not 'TIMEZONE' in settings: if not 'TIMEZONE' in settings:
logger.warn("No timezone information specified in the settings. Assuming" logger.warn(
" your timezone is UTC for feed generation. Check " 'No timezone information specified in the settings. Assuming'
"http://docs.notmyidea.org/alexis/pelican/settings.html#timezone " ' your timezone is UTC for feed generation. Check '
"for more information") 'http://docs.notmyidea.org/alexis/pelican/settings.html#timezone '
'for more information')
if 'LESS_GENERATOR' in settings: if 'LESS_GENERATOR' in settings:
logger.warn("The LESS_GENERATOR setting has been removed in favor " logger.warn(
"of the Webassets plugin") 'The LESS_GENERATOR setting has been removed in favor '
'of the Webassets plugin')
if 'OUTPUT_SOURCES_EXTENSION' in settings: if 'OUTPUT_SOURCES_EXTENSION' in settings:
if not isinstance(settings['OUTPUT_SOURCES_EXTENSION'], six.string_types): if not isinstance(settings['OUTPUT_SOURCES_EXTENSION'],
settings['OUTPUT_SOURCES_EXTENSION'] = _DEFAULT_CONFIG['OUTPUT_SOURCES_EXTENSION'] six.string_types):
logger.warn("Detected misconfiguration with OUTPUT_SOURCES_EXTENSION." settings['OUTPUT_SOURCES_EXTENSION'] = (
" falling back to the default extension " + _DEFAULT_CONFIG['OUTPUT_SOURCES_EXTENSION'])
_DEFAULT_CONFIG['OUTPUT_SOURCES_EXTENSION']) logger.warn(
'Detected misconfiguration with OUTPUT_SOURCES_EXTENSION, '
'falling back to the default extension ' +
_DEFAULT_CONFIG['OUTPUT_SOURCES_EXTENSION'])
filename_metadata = settings.get('FILENAME_METADATA') filename_metadata = settings.get('FILENAME_METADATA')
if filename_metadata and not isinstance(filename_metadata, six.string_types): if filename_metadata and not isinstance(filename_metadata,
logger.error("Detected misconfiguration with FILENAME_METADATA" six.string_types):
" setting (must be string or compiled pattern), falling" logger.error(
"back to the default") 'Detected misconfiguration with FILENAME_METADATA '
settings['FILENAME_METADATA'] = \ 'setting (must be string or compiled pattern), falling '
_DEFAULT_CONFIG['FILENAME_METADATA'] 'back to the default')
settings['FILENAME_METADATA'] = (
_DEFAULT_CONFIG['FILENAME_METADATA'])
return settings return settings

View file

@ -34,7 +34,8 @@ def strftime(date, date_format):
chars in the format string. So if e.g. your format string contains 'ø' or chars in the format string. So if e.g. your format string contains 'ø' or
'ä', the result will be 'o' and 'a'. 'ä', the result will be 'o' and 'a'.
See here for an `extensive testcase <https://github.com/dmdm/test_strftime>`_. See here for an `extensive testcase
<https://github.com/dmdm/test_strftime>`_.
:param date: Any object that sports a :meth:`strftime()` method. :param date: Any object that sports a :meth:`strftime()` method.
:param date_format: Format string, can always be unicode. :param date_format: Format string, can always be unicode.
@ -67,18 +68,12 @@ def strftime(date, date_format):
result = unicode(result) result = unicode(result)
# Convert XML character references back to unicode characters. # Convert XML character references back to unicode characters.
if "&#" in result: if "&#" in result:
result = re.sub(r'&#(?P<num>\d+);' result = re.sub(r'&#(?P<num>\d+);',
, lambda m: unichr(int(m.group('num'))) lambda m: unichr(int(m.group('num'))),
, result result)
)
return result return result
#----------------------------------------------------------------------------
# Stolen from Django: django.utils.encoding
#
def python_2_unicode_compatible(klass): def python_2_unicode_compatible(klass):
""" """
A decorator that defines __unicode__ and __str__ methods under Python 2. A decorator that defines __unicode__ and __str__ methods under Python 2.
@ -86,43 +81,48 @@ def python_2_unicode_compatible(klass):
To support Python 2 and 3 with a single code base, define a __str__ method To support Python 2 and 3 with a single code base, define a __str__ method
returning text and apply this decorator to the class. returning text and apply this decorator to the class.
From django.utils.encoding.
""" """
if not six.PY3: if not six.PY3:
klass.__unicode__ = klass.__str__ klass.__unicode__ = klass.__str__
klass.__str__ = lambda self: self.__unicode__().encode('utf-8') klass.__str__ = lambda self: self.__unicode__().encode('utf-8')
return klass return klass
#----------------------------------------------------------------------------
class NoFilesError(Exception): class NoFilesError(Exception):
pass pass
class memoized(object): class memoized(object):
'''Decorator. Caches a function's return value each time it is called. """Function decorator to cache return values.
If called later with the same arguments, the cached value is returned
(not reevaluated). If called later with the same arguments, the cached value is returned
''' (not reevaluated).
def __init__(self, func):
self.func = func """
self.cache = {} def __init__(self, func):
def __call__(self, *args): self.func = func
if not isinstance(args, Hashable): self.cache = {}
# uncacheable. a list, for instance.
# better to not cache than blow up. def __call__(self, *args):
return self.func(*args) if not isinstance(args, Hashable):
if args in self.cache: # uncacheable. a list, for instance.
return self.cache[args] # better to not cache than blow up.
else: return self.func(*args)
value = self.func(*args) if args in self.cache:
self.cache[args] = value return self.cache[args]
return value else:
def __repr__(self): value = self.func(*args)
'''Return the function's docstring.''' self.cache[args] = value
return self.func.__doc__ return value
def __get__(self, obj, objtype):
'''Support instance methods.''' def __repr__(self):
return partial(self.__call__, obj) return self.func.__doc__
def __get__(self, obj, objtype):
'''Support instance methods.'''
return partial(self.__call__, obj)
def deprecated_attribute(old, new, since=None, remove=None, doc=None): def deprecated_attribute(old, new, since=None, remove=None, doc=None):
@ -166,6 +166,7 @@ def deprecated_attribute(old, new, since=None, remove=None, doc=None):
return decorator return decorator
def get_date(string): def get_date(string):
"""Return a datetime object from a string. """Return a datetime object from a string.
@ -196,6 +197,7 @@ class pelican_open(object):
def __exit__(self, exc_type, exc_value, traceback): def __exit__(self, exc_type, exc_value, traceback):
pass pass
def slugify(value): def slugify(value):
""" """
Normalizes string, converts to lowercase, removes non-alpha characters, Normalizes string, converts to lowercase, removes non-alpha characters,
@ -262,6 +264,7 @@ def copy(path, source, destination, destination_path=None, overwrite=False):
else: else:
logger.warning('skipped copy %s to %s' % (source_, destination_)) logger.warning('skipped copy %s to %s' % (source_, destination_))
def clean_output_dir(path): def clean_output_dir(path):
"""Remove all the files from the output directory""" """Remove all the files from the output directory"""
@ -414,6 +417,7 @@ def process_translations(content_list):
LAST_MTIME = 0 LAST_MTIME = 0
def files_changed(path, extensions, ignores=[]): def files_changed(path, extensions, ignores=[]):
"""Return True if the files have changed since the last check""" """Return True if the files have changed since the last check"""
@ -423,7 +427,8 @@ def files_changed(path, extensions, ignores=[]):
dirs[:] = [x for x in dirs if x[0] != '.'] dirs[:] = [x for x in dirs if x[0] != '.']
for f in files: for f in files:
if any(f.endswith(ext) for ext in extensions) \ if any(f.endswith(ext) for ext in extensions) \
and not any(fnmatch.fnmatch(f, ignore) for ignore in ignores): and not any(fnmatch.fnmatch(f, ignore)
for ignore in ignores):
yield os.stat(os.path.join(root, f)).st_mtime yield os.stat(os.path.join(root, f)).st_mtime
global LAST_MTIME global LAST_MTIME

View file

@ -78,11 +78,11 @@ class Writer(object):
os.makedirs(os.path.dirname(complete_path)) os.makedirs(os.path.dirname(complete_path))
except Exception: except Exception:
pass pass
fp = open(complete_path, 'w', encoding='utf-8' if six.PY3 else None)
feed.write(fp, 'utf-8')
logger.info('writing %s' % complete_path)
fp.close() encoding = 'utf-8' if six.PY3 else None
with open(complete_path, 'w', encoding=encoding) as fp:
feed.write(fp, 'utf-8')
logger.info('writing %s' % complete_path)
return feed return feed
finally: finally:
locale.setlocale(locale.LC_ALL, old_locale) locale.setlocale(locale.LC_ALL, old_locale)