This commit is contained in:
Keita Watanabe 2015-08-20 07:20:07 +00:00
commit bc2d7fb675
26 changed files with 415 additions and 317 deletions

View file

@ -191,22 +191,22 @@ class Pelican(object):
pluralized_articles = maybe_pluralize( pluralized_articles = maybe_pluralize(
len(articles_generator.articles) + len(articles_generator.articles) +
len(articles_generator.translations), len(articles_generator.translations),
'article', 'article',
'articles') 'articles')
pluralized_drafts = maybe_pluralize( pluralized_drafts = maybe_pluralize(
len(articles_generator.drafts) + len(articles_generator.drafts) +
len(articles_generator.drafts_translations), len(articles_generator.drafts_translations),
'draft', 'draft',
'drafts') 'drafts')
pluralized_pages = maybe_pluralize( pluralized_pages = maybe_pluralize(
len(pages_generator.pages) + len(pages_generator.pages) +
len(pages_generator.translations), len(pages_generator.translations),
'page', 'page',
'pages') 'pages')
pluralized_hidden_pages = maybe_pluralize( pluralized_hidden_pages = maybe_pluralize(
len(pages_generator.hidden_pages) + len(pages_generator.hidden_pages) +
len(pages_generator.hidden_translations), len(pages_generator.hidden_translations),
'hidden page', 'hidden page',
'hidden pages') 'hidden pages')
@ -243,8 +243,8 @@ class Pelican(object):
return generators return generators
def get_writer(self): def get_writer(self):
writers = [ w for (_, w) in signals.get_writer.send(self) writers = [w for (_, w) in signals.get_writer.send(self)
if isinstance(w, type) ] if isinstance(w, type)]
writers_found = len(writers) writers_found = len(writers)
if writers_found == 0: if writers_found == 0:
return Writer(self.output_path, settings=self.settings) return Writer(self.output_path, settings=self.settings)
@ -354,7 +354,8 @@ def get_config(args):
# argparse returns bytes in Py2. There is no definite answer as to which # argparse returns bytes in Py2. There is no definite answer as to which
# encoding argparse (or sys.argv) uses. # encoding argparse (or sys.argv) uses.
# "Best" option seems to be locale.getpreferredencoding() # "Best" option seems to be locale.getpreferredencoding()
# ref: http://mail.python.org/pipermail/python-list/2006-October/405766.html # ref:
# http://mail.python.org/pipermail/python-list/2006-October/405766.html
if not six.PY3: if not six.PY3:
enc = locale.getpreferredencoding() enc = locale.getpreferredencoding()
for key in config: for key in config:
@ -367,7 +368,7 @@ def get_instance(args):
config_file = args.settings config_file = args.settings
if config_file is None and os.path.isfile(DEFAULT_CONFIG_NAME): if config_file is None and os.path.isfile(DEFAULT_CONFIG_NAME):
config_file = DEFAULT_CONFIG_NAME config_file = DEFAULT_CONFIG_NAME
settings = read_settings(config_file, override=get_config(args)) settings = read_settings(config_file, override=get_config(args))

View file

@ -15,6 +15,7 @@ logger = logging.getLogger(__name__)
class FileDataCacher(object): class FileDataCacher(object):
"""Class that can cache data contained in files""" """Class that can cache data contained in files"""
def __init__(self, settings, cache_name, caching_policy, load_policy): def __init__(self, settings, cache_name, caching_policy, load_policy):
@ -77,6 +78,7 @@ class FileDataCacher(object):
class FileStampDataCacher(FileDataCacher): class FileStampDataCacher(FileDataCacher):
"""Subclass that also caches the stamp of the file""" """Subclass that also caches the stamp of the file"""
def __init__(self, settings, cache_name, caching_policy, load_policy): def __init__(self, settings, cache_name, caching_policy, load_policy):

View file

@ -27,6 +27,7 @@ logger = logging.getLogger(__name__)
@python_2_unicode_compatible @python_2_unicode_compatible
class Content(object): class Content(object):
"""Represents a content. """Represents a content.
:param content: the string to parse, containing the original content. :param content: the string to parse, containing the original content.
@ -66,7 +67,7 @@ class Content(object):
# also keep track of the metadata attributes available # also keep track of the metadata attributes available
self.metadata = local_metadata self.metadata = local_metadata
#default template if it's not defined in page # default template if it's not defined in page
self.template = self._get_template() self.template = self._get_template()
# First, read the authors from "authors", if not, fallback to "author" # First, read the authors from "authors", if not, fallback to "author"
@ -96,11 +97,11 @@ class Content(object):
if not hasattr(self, 'slug'): if not hasattr(self, 'slug'):
if settings['SLUGIFY_SOURCE'] == 'title' and hasattr(self, 'title'): if settings['SLUGIFY_SOURCE'] == 'title' and hasattr(self, 'title'):
self.slug = slugify(self.title, self.slug = slugify(self.title,
settings.get('SLUG_SUBSTITUTIONS', ())) settings.get('SLUG_SUBSTITUTIONS', ()))
elif settings['SLUGIFY_SOURCE'] == 'basename' and source_path != None: elif settings['SLUGIFY_SOURCE'] == 'basename' and source_path != None:
basename = os.path.basename(os.path.splitext(source_path)[0]) basename = os.path.basename(os.path.splitext(source_path)[0])
self.slug = slugify(basename, self.slug = slugify(basename,
settings.get('SLUG_SUBSTITUTIONS', ())) settings.get('SLUG_SUBSTITUTIONS', ()))
self.source_path = source_path self.source_path = source_path
@ -234,14 +235,14 @@ class Content(object):
linked_content.attach_to(self) linked_content.attach_to(self)
else: else:
logger.warning("%s used {attach} link syntax on a " logger.warning("%s used {attach} link syntax on a "
"non-static file. Use {filename} instead.", "non-static file. Use {filename} instead.",
self.get_relative_source_path()) self.get_relative_source_path())
origin = '/'.join((siteurl, linked_content.url)) origin = '/'.join((siteurl, linked_content.url))
origin = origin.replace('\\', '/') # for Windows paths. origin = origin.replace('\\', '/') # for Windows paths.
else: else:
logger.warning( logger.warning(
"Unable to find `%s`, skipping url replacement.", "Unable to find `%s`, skipping url replacement.",
value.geturl(), extra = { value.geturl(), extra={
'limit_msg': ("Other resources were not found " 'limit_msg': ("Other resources were not found "
"and their urls not replaced")}) "and their urls not replaced")})
elif what == 'category': elif what == 'category':
@ -337,7 +338,8 @@ class Content(object):
return posixize_path( return posixize_path(
os.path.relpath( os.path.relpath(
os.path.abspath(os.path.join(self.settings['PATH'], source_path)), os.path.abspath(
os.path.join(self.settings['PATH'], source_path)),
os.path.abspath(self.settings['PATH']) os.path.abspath(self.settings['PATH'])
)) ))
@ -371,6 +373,7 @@ class Quote(Page):
@python_2_unicode_compatible @python_2_unicode_compatible
class Static(Page): class Static(Page):
def __init__(self, *args, **kwargs): def __init__(self, *args, **kwargs):
super(Static, self).__init__(*args, **kwargs) super(Static, self).__init__(*args, **kwargs)
self._output_location_referenced = False self._output_location_referenced = False
@ -424,10 +427,10 @@ class Static(Page):
def _log_reason(reason): def _log_reason(reason):
logger.warning("The {attach} link in %s cannot relocate %s " logger.warning("The {attach} link in %s cannot relocate %s "
"because %s. Falling back to {filename} link behavior instead.", "because %s. Falling back to {filename} link behavior instead.",
content.get_relative_source_path(), content.get_relative_source_path(),
self.get_relative_source_path(), reason, self.get_relative_source_path(), reason,
extra={'limit_msg': "More {attach} warnings silenced."}) extra={'limit_msg': "More {attach} warnings silenced."})
# We never override an override, because we don't want to interfere # We never override an override, because we don't want to interfere
# with user-defined overrides that might be in EXTRA_PATH_METADATA. # with user-defined overrides that might be in EXTRA_PATH_METADATA.
@ -452,5 +455,6 @@ def is_valid_content(content, f):
content.check_properties() content.check_properties()
return True return True
except NameError as e: except NameError as e:
logger.error("Skipping %s: could not find information about '%s'", f, e) logger.error(
"Skipping %s: could not find information about '%s'", f, e)
return False return False

View file

@ -31,8 +31,10 @@ logger = logging.getLogger(__name__)
class PelicanTemplateNotFound(Exception): class PelicanTemplateNotFound(Exception):
pass pass
@python_2_unicode_compatible @python_2_unicode_compatible
class Generator(object): class Generator(object):
"""Baseclass generator""" """Baseclass generator"""
def __init__(self, context, settings, path, theme, output_path, def __init__(self, context, settings, path, theme, output_path,
@ -58,7 +60,7 @@ class Generator(object):
theme_path = os.path.dirname(os.path.abspath(__file__)) theme_path = os.path.dirname(os.path.abspath(__file__))
simple_loader = FileSystemLoader(os.path.join(theme_path, simple_loader = FileSystemLoader(os.path.join(theme_path,
"themes", "simple", "templates")) "themes", "simple", "templates"))
self.env = Environment( self.env = Environment(
trim_blocks=True, trim_blocks=True,
lstrip_blocks=True, lstrip_blocks=True,
@ -91,7 +93,7 @@ class Generator(object):
self._templates[name] = self.env.get_template(name + '.html') self._templates[name] = self.env.get_template(name + '.html')
except TemplateNotFound: except TemplateNotFound:
raise PelicanTemplateNotFound('[templates] unable to load %s.html from %s' raise PelicanTemplateNotFound('[templates] unable to load %s.html from %s'
% (name, self._templates_path)) % (name, self._templates_path))
return self._templates[name] return self._templates[name]
def _include_path(self, path, extensions=None): def _include_path(self, path, extensions=None):
@ -105,7 +107,7 @@ class Generator(object):
extensions = tuple(self.readers.extensions) extensions = tuple(self.readers.extensions)
basename = os.path.basename(path) basename = os.path.basename(path)
#check IGNORE_FILES # check IGNORE_FILES
ignores = self.settings['IGNORE_FILES'] ignores = self.settings['IGNORE_FILES']
if any(fnmatch.fnmatch(basename, ignore) for ignore in ignores): if any(fnmatch.fnmatch(basename, ignore) for ignore in ignores):
return False return False
@ -123,7 +125,7 @@ class Generator(object):
extensions are allowed) extensions are allowed)
""" """
if isinstance(paths, six.string_types): if isinstance(paths, six.string_types):
paths = [paths] # backward compatibility for older generators paths = [paths] # backward compatibility for older generators
# group the exclude dir names by parent path, for use with os.walk() # group the exclude dir names by parent path, for use with os.walk()
exclusions_by_dirpath = {} exclusions_by_dirpath = {}
@ -196,6 +198,7 @@ class Generator(object):
class CachingGenerator(Generator, FileStampDataCacher): class CachingGenerator(Generator, FileStampDataCacher):
'''Subclass of Generator and FileStampDataCacher classes '''Subclass of Generator and FileStampDataCacher classes
enables content caching, either at the generator or reader level enables content caching, either at the generator or reader level
@ -211,7 +214,8 @@ class CachingGenerator(Generator, FileStampDataCacher):
readers_cache_name=(cls_name + '-Readers'), readers_cache_name=(cls_name + '-Readers'),
**kwargs) **kwargs)
cache_this_level = self.settings['CONTENT_CACHING_LAYER'] == 'generator' cache_this_level = self.settings[
'CONTENT_CACHING_LAYER'] == 'generator'
caching_policy = cache_this_level and self.settings['CACHE_CONTENT'] caching_policy = cache_this_level and self.settings['CACHE_CONTENT']
load_policy = cache_this_level and self.settings['LOAD_CONTENT_CACHE'] load_policy = cache_this_level and self.settings['LOAD_CONTENT_CACHE']
FileStampDataCacher.__init__(self, self.settings, cls_name, FileStampDataCacher.__init__(self, self.settings, cls_name,
@ -255,6 +259,7 @@ class TemplatePagesGenerator(Generator):
class ArticlesGenerator(CachingGenerator): class ArticlesGenerator(CachingGenerator):
"""Generate blog articles""" """Generate blog articles"""
def __init__(self, *args, **kwargs): def __init__(self, *args, **kwargs):
@ -266,7 +271,7 @@ class ArticlesGenerator(CachingGenerator):
self.categories = defaultdict(list) self.categories = defaultdict(list)
self.related_posts = [] self.related_posts = []
self.authors = defaultdict(list) self.authors = defaultdict(list)
self.drafts = [] # only drafts in default language self.drafts = [] # only drafts in default language
self.drafts_translations = [] self.drafts_translations = []
super(ArticlesGenerator, self).__init__(*args, **kwargs) super(ArticlesGenerator, self).__init__(*args, **kwargs)
signals.article_generator_init.send(self) signals.article_generator_init.send(self)
@ -472,9 +477,9 @@ class ArticlesGenerator(CachingGenerator):
"""Generate drafts pages.""" """Generate drafts pages."""
for draft in chain(self.drafts_translations, self.drafts): for draft in chain(self.drafts_translations, self.drafts):
write(draft.save_as, self.get_template(draft.template), write(draft.save_as, self.get_template(draft.template),
self.context, article=draft, category=draft.category, self.context, article=draft, category=draft.category,
override_output=hasattr(draft, 'override_save_as'), override_output=hasattr(draft, 'override_save_as'),
blog=True, all_articles=self.articles) blog=True, all_articles=self.articles)
def generate_pages(self, writer): def generate_pages(self, writer):
"""Generate the pages on the disk""" """Generate the pages on the disk"""
@ -503,7 +508,8 @@ class ArticlesGenerator(CachingGenerator):
exclude=self.settings['ARTICLE_EXCLUDES']): exclude=self.settings['ARTICLE_EXCLUDES']):
article_or_draft = self.get_cached_data(f, None) article_or_draft = self.get_cached_data(f, None)
if article_or_draft is None: if article_or_draft is None:
#TODO needs overhaul, maybe nomad for read_file solution, unified behaviour # TODO needs overhaul, maybe nomad for read_file solution,
# unified behaviour
try: try:
article_or_draft = self.readers.read_file( article_or_draft = self.readers.read_file(
base_path=self.path, path=f, content_class=Article, base_path=self.path, path=f, content_class=Article,
@ -514,7 +520,7 @@ class ArticlesGenerator(CachingGenerator):
context_sender=self) context_sender=self)
except Exception as e: except Exception as e:
logger.error('Could not process %s\n%s', f, e, logger.error('Could not process %s\n%s', f, e,
exc_info=self.settings.get('DEBUG', False)) exc_info=self.settings.get('DEBUG', False))
self._add_failed_source_path(f) self._add_failed_source_path(f)
continue continue
@ -536,7 +542,7 @@ class ArticlesGenerator(CachingGenerator):
all_drafts.append(article_or_draft) all_drafts.append(article_or_draft)
else: else:
logger.error("Unknown status '%s' for file %s, skipping it.", logger.error("Unknown status '%s' for file %s, skipping it.",
article_or_draft.status, f) article_or_draft.status, f)
self._add_failed_source_path(f) self._add_failed_source_path(f)
continue continue
@ -544,9 +550,8 @@ class ArticlesGenerator(CachingGenerator):
self.add_source_path(article_or_draft) self.add_source_path(article_or_draft)
self.articles, self.translations = process_translations(all_articles, self.articles, self.translations = process_translations(all_articles,
order_by=self.settings['ARTICLE_ORDER_BY']) order_by=self.settings['ARTICLE_ORDER_BY'])
self.drafts, self.drafts_translations = \ self.drafts, self.drafts_translations = \
process_translations(all_drafts) process_translations(all_drafts)
@ -589,6 +594,7 @@ class ArticlesGenerator(CachingGenerator):
class PagesGenerator(CachingGenerator): class PagesGenerator(CachingGenerator):
"""Generate pages""" """Generate pages"""
def __init__(self, *args, **kwargs): def __init__(self, *args, **kwargs):
@ -616,7 +622,7 @@ class PagesGenerator(CachingGenerator):
context_sender=self) context_sender=self)
except Exception as e: except Exception as e:
logger.error('Could not process %s\n%s', f, e, logger.error('Could not process %s\n%s', f, e,
exc_info=self.settings.get('DEBUG', False)) exc_info=self.settings.get('DEBUG', False))
self._add_failed_source_path(f) self._add_failed_source_path(f)
continue continue
@ -630,7 +636,7 @@ class PagesGenerator(CachingGenerator):
hidden_pages.append(page) hidden_pages.append(page)
else: else:
logger.error("Unknown status '%s' for file %s, skipping it.", logger.error("Unknown status '%s' for file %s, skipping it.",
page.status, f) page.status, f)
self._add_failed_source_path(f) self._add_failed_source_path(f)
continue continue
@ -639,7 +645,7 @@ class PagesGenerator(CachingGenerator):
self.add_source_path(page) self.add_source_path(page)
self.pages, self.translations = process_translations(all_pages, self.pages, self.translations = process_translations(all_pages,
order_by=self.settings['PAGE_ORDER_BY']) order_by=self.settings['PAGE_ORDER_BY'])
self.hidden_pages, self.hidden_translations = ( self.hidden_pages, self.hidden_translations = (
process_translations(hidden_pages)) process_translations(hidden_pages))
@ -661,6 +667,7 @@ class PagesGenerator(CachingGenerator):
class StaticGenerator(Generator): class StaticGenerator(Generator):
"""copy static paths (what you want to copy, like images, medias etc. """copy static paths (what you want to copy, like images, medias etc.
to output""" to output"""

View file

@ -14,13 +14,16 @@ from collections import defaultdict, Mapping
import six import six
class BaseFormatter(logging.Formatter): class BaseFormatter(logging.Formatter):
def __init__(self, fmt=None, datefmt=None): def __init__(self, fmt=None, datefmt=None):
FORMAT = '%(customlevelname)s %(message)s' FORMAT = '%(customlevelname)s %(message)s'
super(BaseFormatter, self).__init__(fmt=FORMAT, datefmt=datefmt) super(BaseFormatter, self).__init__(fmt=FORMAT, datefmt=datefmt)
def format(self, record): def format(self, record):
record.__dict__['customlevelname'] = self._get_levelname(record.levelname) record.__dict__['customlevelname'] = self._get_levelname(
record.levelname)
# format multiline messages 'nicely' to make it clear they are together # format multiline messages 'nicely' to make it clear they are together
record.msg = record.msg.replace('\n', '\n | ') record.msg = record.msg.replace('\n', '\n | ')
return super(BaseFormatter, self).format(record) return super(BaseFormatter, self).format(record)
@ -69,6 +72,7 @@ class ANSIFormatter(BaseFormatter):
class TextFormatter(BaseFormatter): class TextFormatter(BaseFormatter):
""" """
Convert a `logging.LogRecord' object into text. Convert a `logging.LogRecord' object into text.
""" """
@ -81,6 +85,7 @@ class TextFormatter(BaseFormatter):
class LimitFilter(logging.Filter): class LimitFilter(logging.Filter):
""" """
Remove duplicates records, and limit the number of records in the same Remove duplicates records, and limit the number of records in the same
group. group.
@ -124,6 +129,7 @@ class LimitFilter(logging.Filter):
class SafeLogger(logging.Logger): class SafeLogger(logging.Logger):
""" """
Base Logger which properly encodes Exceptions in Py2 Base Logger which properly encodes Exceptions in Py2
""" """
@ -132,13 +138,13 @@ class SafeLogger(logging.Logger):
def _log(self, level, msg, args, exc_info=None, extra=None): def _log(self, level, msg, args, exc_info=None, extra=None):
# if the only argument is a Mapping, Logger uses that for formatting # if the only argument is a Mapping, Logger uses that for formatting
# format values for that case # format values for that case
if args and len(args)==1 and isinstance(args[0], Mapping): if args and len(args) == 1 and isinstance(args[0], Mapping):
args = ({k: self._decode_arg(v) for k, v in args[0].items()},) args = ({k: self._decode_arg(v) for k, v in args[0].items()},)
# otherwise, format each arg # otherwise, format each arg
else: else:
args = tuple(self._decode_arg(arg) for arg in args) args = tuple(self._decode_arg(arg) for arg in args)
super(SafeLogger, self)._log(level, msg, args, super(SafeLogger, self)._log(level, msg, args,
exc_info=exc_info, extra=extra) exc_info=exc_info, extra=extra)
def _decode_arg(self, arg): def _decode_arg(self, arg):
''' '''
@ -158,6 +164,7 @@ class SafeLogger(logging.Logger):
class LimitLogger(SafeLogger): class LimitLogger(SafeLogger):
""" """
A logger which adds LimitFilter automatically A logger which adds LimitFilter automatically
""" """

View file

@ -20,6 +20,7 @@ PaginationRule = namedtuple(
class Paginator(object): class Paginator(object):
def __init__(self, name, object_list, settings): def __init__(self, name, object_list, settings):
self.name = name self.name = name
self.object_list = object_list self.object_list = object_list
@ -68,6 +69,7 @@ class Paginator(object):
class Page(object): class Page(object):
def __init__(self, name, object_list, number, paginator, settings): def __init__(self, name, object_list, number, paginator, settings):
self.name, self.extension = os.path.splitext(name) self.name, self.extension = os.path.splitext(name)
self.object_list = object_list self.object_list = object_list

View file

@ -28,6 +28,7 @@ from pelican.cache import FileStampDataCacher
from pelican.contents import Page, Category, Tag, Author from pelican.contents import Page, Category, Tag, Author
from pelican.utils import get_date, pelican_open, SafeDatetime, posixize_path from pelican.utils import get_date, pelican_open, SafeDatetime, posixize_path
def ensure_metadata_list(text): def ensure_metadata_list(text):
"""Canonicalize the format of a list of authors or tags. This works """Canonicalize the format of a list of authors or tags. This works
the same way as Docutils' "authors" field: if it's already a list, the same way as Docutils' "authors" field: if it's already a list,
@ -86,7 +87,9 @@ def _filter_discardable_metadata(metadata):
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
class BaseReader(object): class BaseReader(object):
"""Base class to read files. """Base class to read files.
This class is used to process static files, and it can be inherited for This class is used to process static files, and it can be inherited for
@ -159,12 +162,14 @@ class PelicanHTMLTranslator(HTMLTranslator):
class RstReader(BaseReader): class RstReader(BaseReader):
"""Reader for reStructuredText files""" """Reader for reStructuredText files"""
enabled = bool(docutils) enabled = bool(docutils)
file_extensions = ['rst'] file_extensions = ['rst']
class FileInput(docutils.io.FileInput): class FileInput(docutils.io.FileInput):
"""Patch docutils.io.FileInput to remove "U" mode in py3. """Patch docutils.io.FileInput to remove "U" mode in py3.
Universal newlines is enabled by default and "U" mode is deprecated Universal newlines is enabled by default and "U" mode is deprecated
@ -238,6 +243,7 @@ class RstReader(BaseReader):
class MarkdownReader(BaseReader): class MarkdownReader(BaseReader):
"""Reader for Markdown files""" """Reader for Markdown files"""
enabled = bool(Markdown) enabled = bool(Markdown)
@ -268,7 +274,7 @@ class MarkdownReader(BaseReader):
elif name in METADATA_PROCESSORS: elif name in METADATA_PROCESSORS:
if len(value) > 1: if len(value) > 1:
logger.warning('Duplicate definition of `%s` ' logger.warning('Duplicate definition of `%s` '
'for %s. Using first one.', name, self._source_path) 'for %s. Using first one.', name, self._source_path)
output[name] = self.process_metadata(name, value[0]) output[name] = self.process_metadata(name, value[0])
elif len(value) > 1: elif len(value) > 1:
# handle list metadata as list of string # handle list metadata as list of string
@ -291,12 +297,14 @@ class MarkdownReader(BaseReader):
class HTMLReader(BaseReader): class HTMLReader(BaseReader):
"""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']
enabled = True enabled = True
class _HTMLParser(HTMLParser): class _HTMLParser(HTMLParser):
def __init__(self, settings, filename): def __init__(self, settings, filename):
try: try:
# Python 3.4+ # Python 3.4+
@ -380,7 +388,8 @@ class HTMLReader(BaseReader):
def _handle_meta_tag(self, attrs): def _handle_meta_tag(self, attrs):
name = self._attr_value(attrs, 'name') name = self._attr_value(attrs, 'name')
if name is None: if name is None:
attr_serialized = ', '.join(['{}="{}"'.format(k, v) for k, v in attrs]) attr_serialized = ', '.join(
['{}="{}"'.format(k, v) for k, v in attrs])
logger.warning("Meta tag in file %s does not have a 'name' " logger.warning("Meta tag in file %s does not have a 'name' "
"attribute, skipping. Attributes: %s", "attribute, skipping. Attributes: %s",
self._filename, attr_serialized) self._filename, attr_serialized)
@ -420,6 +429,7 @@ class HTMLReader(BaseReader):
class Readers(FileStampDataCacher): class Readers(FileStampDataCacher):
"""Interface for all readers. """Interface for all readers.
This class contains a mapping of file extensions / Reader classes, to know This class contains a mapping of file extensions / Reader classes, to know
@ -475,7 +485,7 @@ class Readers(FileStampDataCacher):
path = os.path.abspath(os.path.join(base_path, path)) path = os.path.abspath(os.path.join(base_path, path))
source_path = posixize_path(os.path.relpath(path, base_path)) source_path = posixize_path(os.path.relpath(path, base_path))
logger.debug('Read file %s -> %s', logger.debug('Read file %s -> %s',
source_path, content_class.__name__) source_path, content_class.__name__)
if not fmt: if not fmt:
_, ext = os.path.splitext(os.path.basename(path)) _, ext = os.path.splitext(os.path.basename(path))
@ -487,7 +497,7 @@ class Readers(FileStampDataCacher):
if preread_signal: if preread_signal:
logger.debug('Signal %s.send(%s)', logger.debug('Signal %s.send(%s)',
preread_signal.name, preread_sender) preread_signal.name, preread_sender)
preread_signal.send(preread_sender) preread_signal.send(preread_sender)
reader = self.readers[fmt] reader = self.readers[fmt]
@ -540,7 +550,7 @@ class Readers(FileStampDataCacher):
if context_signal: if context_signal:
logger.debug('Signal %s.send(%s, <metadata>)', logger.debug('Signal %s.send(%s, <metadata>)',
context_signal.name, context_sender) context_signal.name, context_sender)
context_signal.send(context_sender, metadata=metadata) context_signal.send(context_sender, metadata=metadata)
return content_class(content=content, metadata=metadata, return content_class(content=content, metadata=metadata,

View file

@ -12,6 +12,7 @@ import pelican.settings as pys
class Pygments(Directive): class Pygments(Directive):
""" Source code syntax highlighting. """ Source code syntax highlighting.
""" """
required_arguments = 1 required_arguments = 1

View file

@ -54,12 +54,12 @@ if __name__ == '__main__':
socketserver.TCPServer.allow_reuse_address = True socketserver.TCPServer.allow_reuse_address = True
try: try:
httpd = socketserver.TCPServer((SERVER, PORT), ComplexHTTPRequestHandler) httpd = socketserver.TCPServer(
(SERVER, PORT), ComplexHTTPRequestHandler)
except OSError as e: except OSError as e:
logging.error("Could not listen on port %s, server %s.", PORT, SERVER) logging.error("Could not listen on port %s, server %s.", PORT, SERVER)
sys.exit(getattr(e, 'exitcode', 1)) sys.exit(getattr(e, 'exitcode', 1))
logging.info("Serving at port %s, server %s.", PORT, SERVER) logging.info("Serving at port %s, server %s.", PORT, SERVER)
try: try:
httpd.serve_forever() httpd.serve_forever()

View file

@ -131,7 +131,7 @@ DEFAULT_CONFIG = {
'LOAD_CONTENT_CACHE': False, 'LOAD_CONTENT_CACHE': False,
'WRITE_SELECTED': [], 'WRITE_SELECTED': [],
'FORMATTED_FIELDS': ['summary'], 'FORMATTED_FIELDS': ['summary'],
} }
PYGMENTS_RST_OPTIONS = None PYGMENTS_RST_OPTIONS = None
@ -158,8 +158,8 @@ def read_settings(path=None, override=None):
"has been deprecated (should be a list)") "has been deprecated (should be a list)")
local_settings['PLUGIN_PATHS'] = [local_settings['PLUGIN_PATHS']] local_settings['PLUGIN_PATHS'] = [local_settings['PLUGIN_PATHS']]
elif local_settings['PLUGIN_PATHS'] is not None: elif local_settings['PLUGIN_PATHS'] is not None:
local_settings['PLUGIN_PATHS'] = [os.path.abspath(os.path.normpath(os.path.join(os.path.dirname(path), pluginpath))) local_settings['PLUGIN_PATHS'] = [os.path.abspath(os.path.normpath(os.path.join(os.path.dirname(path), pluginpath)))
if not isabs(pluginpath) else pluginpath for pluginpath in local_settings['PLUGIN_PATHS']] if not isabs(pluginpath) else pluginpath for pluginpath in local_settings['PLUGIN_PATHS']]
else: else:
local_settings = copy.deepcopy(DEFAULT_CONFIG) local_settings = copy.deepcopy(DEFAULT_CONFIG)
@ -205,7 +205,7 @@ def configure_settings(settings):
# specify the log messages to be ignored # specify the log messages to be ignored
LimitFilter._ignore.update(set(settings.get('LOG_FILTER', LimitFilter._ignore.update(set(settings.get('LOG_FILTER',
DEFAULT_CONFIG['LOG_FILTER']))) DEFAULT_CONFIG['LOG_FILTER'])))
# lookup the theme in "pelican/themes" if the given one doesn't exist # lookup the theme in "pelican/themes" if the given one doesn't exist
if not os.path.isdir(settings['THEME']): if not os.path.isdir(settings['THEME']):
@ -223,19 +223,19 @@ def configure_settings(settings):
settings['WRITE_SELECTED'] = [ settings['WRITE_SELECTED'] = [
os.path.abspath(path) for path in os.path.abspath(path) for path in
settings.get('WRITE_SELECTED', DEFAULT_CONFIG['WRITE_SELECTED']) settings.get('WRITE_SELECTED', DEFAULT_CONFIG['WRITE_SELECTED'])
] ]
# standardize strings to lowercase strings # standardize strings to lowercase strings
for key in [ for key in [
'DEFAULT_LANG', 'DEFAULT_LANG',
]: ]:
if key in settings: if key in settings:
settings[key] = settings[key].lower() settings[key] = settings[key].lower()
# standardize strings to lists # standardize strings to lists
for key in [ for key in [
'LOCALE', 'LOCALE',
]: ]:
if key in settings and isinstance(settings[key], six.string_types): if key in settings and isinstance(settings[key], six.string_types):
settings[key] = [settings[key]] settings[key] = [settings[key]]
@ -243,12 +243,12 @@ def configure_settings(settings):
for key, types in [ for key, types in [
('OUTPUT_SOURCES_EXTENSION', six.string_types), ('OUTPUT_SOURCES_EXTENSION', six.string_types),
('FILENAME_METADATA', six.string_types), ('FILENAME_METADATA', six.string_types),
]: ]:
if key in settings and not isinstance(settings[key], types): if key in settings and not isinstance(settings[key], types):
value = settings.pop(key) value = settings.pop(key)
logger.warn('Detected misconfigured %s (%s), ' logger.warn('Detected misconfigured %s (%s), '
'falling back to the default (%s)', 'falling back to the default (%s)',
key, value, DEFAULT_CONFIG[key]) key, value, DEFAULT_CONFIG[key])
# try to set the different locales, fallback on the default. # try to set the different locales, fallback on the default.
locales = settings.get('LOCALE', DEFAULT_CONFIG['LOCALE']) locales = settings.get('LOCALE', DEFAULT_CONFIG['LOCALE'])
@ -275,11 +275,11 @@ def configure_settings(settings):
# check content caching layer and warn of incompatibilities # check content caching layer and warn of incompatibilities
if (settings.get('CACHE_CONTENT', False) and if (settings.get('CACHE_CONTENT', False) and
settings.get('CONTENT_CACHING_LAYER', '') == 'generator' and settings.get('CONTENT_CACHING_LAYER', '') == 'generator' and
settings.get('WITH_FUTURE_DATES', DEFAULT_CONFIG['WITH_FUTURE_DATES'])): settings.get('WITH_FUTURE_DATES', DEFAULT_CONFIG['WITH_FUTURE_DATES'])):
logger.warning('WITH_FUTURE_DATES conflicts with ' logger.warning('WITH_FUTURE_DATES conflicts with '
"CONTENT_CACHING_LAYER set to 'generator', " "CONTENT_CACHING_LAYER set to 'generator', "
"use 'reader' layer instead") "use 'reader' layer instead")
# Warn if feeds are generated with both SITEURL & FEED_DOMAIN undefined # Warn if feeds are generated with both SITEURL & FEED_DOMAIN undefined
feed_keys = [ feed_keys = [
@ -322,7 +322,7 @@ def configure_settings(settings):
new_key = key + '_PATHS' new_key = key + '_PATHS'
if old_key in settings: if old_key in settings:
logger.warning('Deprecated setting %s, moving it to %s list', logger.warning('Deprecated setting %s, moving it to %s list',
old_key, new_key) old_key, new_key)
settings[new_key] = [settings[old_key]] # also make a list settings[new_key] = [settings[old_key]] # also make a list
del settings[old_key] del settings[old_key]
@ -366,7 +366,7 @@ def configure_settings(settings):
('LESS_GENERATOR', 'the Webassets plugin', None), ('LESS_GENERATOR', 'the Webassets plugin', None),
('FILES_TO_COPY', 'STATIC_PATHS and EXTRA_PATH_METADATA', ('FILES_TO_COPY', 'STATIC_PATHS and EXTRA_PATH_METADATA',
'https://github.com/getpelican/pelican/blob/master/docs/settings.rst#path-metadata'), 'https://github.com/getpelican/pelican/blob/master/docs/settings.rst#path-metadata'),
]: ]:
if old in settings: if old in settings:
message = 'The {} setting has been removed in favor of {}'.format( message = 'The {} setting has been removed in favor of {}'.format(
old, new) old, new)

View file

@ -31,17 +31,16 @@ DEFAULT_METADATA = {'yeah': 'it is'}
# path-specific metadata # path-specific metadata
EXTRA_PATH_METADATA = { EXTRA_PATH_METADATA = {
'extra/robots.txt': {'path': 'robots.txt'}, 'extra/robots.txt': {'path': 'robots.txt'},
} }
# static paths will be copied without parsing their contents # static paths will be copied without parsing their contents
STATIC_PATHS = [ STATIC_PATHS = [
'pictures', 'pictures',
'extra/robots.txt', 'extra/robots.txt',
] ]
FORMATTED_FIELDS = ['summary', 'custom_formatted_field'] FORMATTED_FIELDS = ['summary', 'custom_formatted_field']
# foobar will not be used, because it's not in caps. All configuration keys # foobar will not be used, because it's not in caps. All configuration keys
# have to be in caps # have to be in caps
foobar = "barbaz" foobar = "barbaz"

View file

@ -167,12 +167,13 @@ def get_settings(**kwargs):
Set keyword arguments to override specific settings. Set keyword arguments to override specific settings.
""" """
settings = DEFAULT_CONFIG.copy() settings = DEFAULT_CONFIG.copy()
for key,value in kwargs.items(): for key, value in kwargs.items():
settings[key] = value settings[key] = value
return settings return settings
class LogCountHandler(BufferingHandler): class LogCountHandler(BufferingHandler):
"""Capturing and counting logged messages.""" """Capturing and counting logged messages."""
def __init__(self, capacity=1000): def __init__(self, capacity=1000):
@ -180,12 +181,13 @@ class LogCountHandler(BufferingHandler):
def count_logs(self, msg=None, level=None): def count_logs(self, msg=None, level=None):
return len([l for l in self.buffer return len([l for l in self.buffer
if (msg is None or re.match(msg, l.getMessage())) if (msg is None or re.match(msg, l.getMessage()))
and (level is None or l.levelno == level) and (level is None or l.levelno == level)
]) ])
class LoggedTestCase(unittest.TestCase): class LoggedTestCase(unittest.TestCase):
"""A test case that captures log messages.""" """A test case that captures log messages."""
def setUp(self): def setUp(self):

View file

@ -35,7 +35,6 @@ class TestCache(unittest.TestCase):
settings['CACHE_PATH'] = self.temp_cache settings['CACHE_PATH'] = self.temp_cache
return settings return settings
@unittest.skipUnless(MagicMock, 'Needs Mock module') @unittest.skipUnless(MagicMock, 'Needs Mock module')
def test_article_object_caching(self): def test_article_object_caching(self):
"""Test Article objects caching at the generator level""" """Test Article objects caching at the generator level"""
@ -44,7 +43,6 @@ class TestCache(unittest.TestCase):
settings['DEFAULT_DATE'] = (1970, 1, 1) settings['DEFAULT_DATE'] = (1970, 1, 1)
settings['READERS'] = {'asc': None} settings['READERS'] = {'asc': None}
generator = ArticlesGenerator( generator = ArticlesGenerator(
context=settings.copy(), settings=settings, context=settings.copy(), settings=settings,
path=CONTENT_DIR, theme=settings['THEME'], output_path=None) path=CONTENT_DIR, theme=settings['THEME'], output_path=None)
@ -108,7 +106,8 @@ class TestCache(unittest.TestCase):
path=CONTENT_DIR, theme=settings['THEME'], output_path=None) path=CONTENT_DIR, theme=settings['THEME'], output_path=None)
generator.readers.read_file = MagicMock() generator.readers.read_file = MagicMock()
generator.generate_context() generator.generate_context()
self.assertEqual(generator.readers.read_file.call_count, orig_call_count) self.assertEqual(
generator.readers.read_file.call_count, orig_call_count)
@unittest.skipUnless(MagicMock, 'Needs Mock module') @unittest.skipUnless(MagicMock, 'Needs Mock module')
def test_page_object_caching(self): def test_page_object_caching(self):
@ -181,5 +180,5 @@ class TestCache(unittest.TestCase):
path=CUR_DIR, theme=settings['THEME'], output_path=None) path=CUR_DIR, theme=settings['THEME'], output_path=None)
generator.readers.read_file = MagicMock() generator.readers.read_file = MagicMock()
generator.generate_context() generator.generate_context()
self.assertEqual(generator.readers.read_file.call_count, orig_call_count) self.assertEqual(
generator.readers.read_file.call_count, orig_call_count)

View file

@ -49,7 +49,7 @@ class TestPage(unittest.TestCase):
# them to initialise object's attributes. # them to initialise object's attributes.
metadata = {'foo': 'bar', 'foobar': 'baz', 'title': 'foobar', } metadata = {'foo': 'bar', 'foobar': 'baz', 'title': 'foobar', }
page = Page(TEST_CONTENT, metadata=metadata, page = Page(TEST_CONTENT, metadata=metadata,
context={'localsiteurl': ''}) context={'localsiteurl': ''})
for key, value in metadata.items(): for key, value in metadata.items():
self.assertTrue(hasattr(page, key)) self.assertTrue(hasattr(page, key))
self.assertEqual(value, getattr(page, key)) self.assertEqual(value, getattr(page, key))
@ -141,12 +141,13 @@ class TestPage(unittest.TestCase):
# page.locale_date is a unicode string in both python2 and python3 # page.locale_date is a unicode string in both python2 and python3
dt_date = dt.strftime(DEFAULT_CONFIG['DEFAULT_DATE_FORMAT']) dt_date = dt.strftime(DEFAULT_CONFIG['DEFAULT_DATE_FORMAT'])
# dt_date is a byte string in python2, and a unicode string in python3 # dt_date is a byte string in python2, and a unicode string in python3
# Let's make sure it is a unicode string (relies on python 3.3 supporting the u prefix) # Let's make sure it is a unicode string (relies on python 3.3
# supporting the u prefix)
if type(dt_date) != type(u''): if type(dt_date) != type(u''):
# python2: # python2:
dt_date = unicode(dt_date, 'utf8') dt_date = unicode(dt_date, 'utf8')
self.assertEqual(page.locale_date, dt_date ) self.assertEqual(page.locale_date, dt_date)
page_kwargs['settings'] = get_settings() page_kwargs['settings'] = get_settings()
# I doubt this can work on all platforms ... # I doubt this can work on all platforms ...
@ -390,6 +391,7 @@ class TestPage(unittest.TestCase):
class TestArticle(TestPage): class TestArticle(TestPage):
def test_template(self): def test_template(self):
# Articles default to article, metadata overwrites # Articles default to article, metadata overwrites
default_article = Article(**self.page_kwargs) default_article = Article(**self.page_kwargs)
@ -401,17 +403,19 @@ class TestArticle(TestPage):
def test_slugify_category_author(self): def test_slugify_category_author(self):
settings = get_settings() settings = get_settings()
settings['SLUG_SUBSTITUTIONS'] = [ ('C#', 'csharp') ] settings['SLUG_SUBSTITUTIONS'] = [('C#', 'csharp')]
settings['ARTICLE_URL'] = '{author}/{category}/{slug}/' settings['ARTICLE_URL'] = '{author}/{category}/{slug}/'
settings['ARTICLE_SAVE_AS'] = '{author}/{category}/{slug}/index.html' settings['ARTICLE_SAVE_AS'] = '{author}/{category}/{slug}/index.html'
article_kwargs = self._copy_page_kwargs() article_kwargs = self._copy_page_kwargs()
article_kwargs['metadata']['author'] = Author("O'Brien", settings) article_kwargs['metadata']['author'] = Author("O'Brien", settings)
article_kwargs['metadata']['category'] = Category('C# & stuff', settings) article_kwargs['metadata'][
'category'] = Category('C# & stuff', settings)
article_kwargs['metadata']['title'] = 'fnord' article_kwargs['metadata']['title'] = 'fnord'
article_kwargs['settings'] = settings article_kwargs['settings'] = settings
article = Article(**article_kwargs) article = Article(**article_kwargs)
self.assertEqual(article.url, 'obrien/csharp-stuff/fnord/') self.assertEqual(article.url, 'obrien/csharp-stuff/fnord/')
self.assertEqual(article.save_as, 'obrien/csharp-stuff/fnord/index.html') self.assertEqual(
article.save_as, 'obrien/csharp-stuff/fnord/index.html')
class TestStatic(LoggedTestCase): class TestStatic(LoggedTestCase):
@ -426,7 +430,7 @@ class TestStatic(LoggedTestCase):
self.context = self.settings.copy() self.context = self.settings.copy()
self.static = Static(content=None, metadata={}, settings=self.settings, self.static = Static(content=None, metadata={}, settings=self.settings,
source_path=posix_join('dir', 'foo.jpg'), context=self.context) source_path=posix_join('dir', 'foo.jpg'), context=self.context)
self.context['filenames'] = {self.static.source_path: self.static} self.context['filenames'] = {self.static.source_path: self.static}
@ -437,8 +441,8 @@ class TestStatic(LoggedTestCase):
"""attach_to() overrides a static file's save_as and url. """attach_to() overrides a static file's save_as and url.
""" """
page = Page(content="fake page", page = Page(content="fake page",
metadata={'title': 'fakepage'}, settings=self.settings, metadata={'title': 'fakepage'}, settings=self.settings,
source_path=os.path.join('dir', 'fakepage.md')) source_path=os.path.join('dir', 'fakepage.md'))
self.static.attach_to(page) self.static.attach_to(page)
expected_save_as = os.path.join('outpages', 'foo.jpg') expected_save_as = os.path.join('outpages', 'foo.jpg')
@ -449,7 +453,7 @@ class TestStatic(LoggedTestCase):
"""attach_to() preserves dirs inside the linking document dir. """attach_to() preserves dirs inside the linking document dir.
""" """
page = Page(content="fake page", metadata={'title': 'fakepage'}, page = Page(content="fake page", metadata={'title': 'fakepage'},
settings=self.settings, source_path='fakepage.md') settings=self.settings, source_path='fakepage.md')
self.static.attach_to(page) self.static.attach_to(page)
expected_save_as = os.path.join('outpages', 'dir', 'foo.jpg') expected_save_as = os.path.join('outpages', 'dir', 'foo.jpg')
@ -460,8 +464,8 @@ class TestStatic(LoggedTestCase):
"""attach_to() ignores dirs outside the linking document dir. """attach_to() ignores dirs outside the linking document dir.
""" """
page = Page(content="fake page", page = Page(content="fake page",
metadata={'title': 'fakepage'}, settings=self.settings, metadata={'title': 'fakepage'}, settings=self.settings,
source_path=os.path.join('dir', 'otherdir', 'fakepage.md')) source_path=os.path.join('dir', 'otherdir', 'fakepage.md'))
self.static.attach_to(page) self.static.attach_to(page)
expected_save_as = os.path.join('outpages', 'foo.jpg') expected_save_as = os.path.join('outpages', 'foo.jpg')
@ -472,8 +476,8 @@ class TestStatic(LoggedTestCase):
"""attach_to() does nothing when called a second time. """attach_to() does nothing when called a second time.
""" """
page = Page(content="fake page", page = Page(content="fake page",
metadata={'title': 'fakepage'}, settings=self.settings, metadata={'title': 'fakepage'}, settings=self.settings,
source_path=os.path.join('dir', 'fakepage.md')) source_path=os.path.join('dir', 'fakepage.md'))
self.static.attach_to(page) self.static.attach_to(page)
@ -482,8 +486,8 @@ class TestStatic(LoggedTestCase):
PAGE_SAVE_AS=os.path.join('otherpages', '{slug}.html'), PAGE_SAVE_AS=os.path.join('otherpages', '{slug}.html'),
PAGE_URL='otherpages/{slug}.html')) PAGE_URL='otherpages/{slug}.html'))
otherdir_page = Page(content="other page", otherdir_page = Page(content="other page",
metadata={'title': 'otherpage'}, settings=otherdir_settings, metadata={'title': 'otherpage'}, settings=otherdir_settings,
source_path=os.path.join('dir', 'otherpage.md')) source_path=os.path.join('dir', 'otherpage.md'))
self.static.attach_to(otherdir_page) self.static.attach_to(otherdir_page)
@ -498,8 +502,8 @@ class TestStatic(LoggedTestCase):
original_save_as = self.static.save_as original_save_as = self.static.save_as
page = Page(content="fake page", page = Page(content="fake page",
metadata={'title': 'fakepage'}, settings=self.settings, metadata={'title': 'fakepage'}, settings=self.settings,
source_path=os.path.join('dir', 'fakepage.md')) source_path=os.path.join('dir', 'fakepage.md'))
self.static.attach_to(page) self.static.attach_to(page)
self.assertEqual(self.static.save_as, original_save_as) self.assertEqual(self.static.save_as, original_save_as)
@ -512,8 +516,8 @@ class TestStatic(LoggedTestCase):
original_url = self.static.url original_url = self.static.url
page = Page(content="fake page", page = Page(content="fake page",
metadata={'title': 'fakepage'}, settings=self.settings, metadata={'title': 'fakepage'}, settings=self.settings,
source_path=os.path.join('dir', 'fakepage.md')) source_path=os.path.join('dir', 'fakepage.md'))
self.static.attach_to(page) self.static.attach_to(page)
self.assertEqual(self.static.save_as, self.static.source_path) self.assertEqual(self.static.save_as, self.static.source_path)
@ -524,14 +528,15 @@ class TestStatic(LoggedTestCase):
(For example, by the user with EXTRA_PATH_METADATA) (For example, by the user with EXTRA_PATH_METADATA)
""" """
customstatic = Static(content=None, customstatic = Static(content=None,
metadata=dict(save_as='customfoo.jpg', url='customfoo.jpg'), metadata=dict(
settings=self.settings, save_as='customfoo.jpg', url='customfoo.jpg'),
source_path=os.path.join('dir', 'foo.jpg'), settings=self.settings,
context=self.settings.copy()) source_path=os.path.join('dir', 'foo.jpg'),
context=self.settings.copy())
page = Page(content="fake page", page = Page(content="fake page",
metadata={'title': 'fakepage'}, settings=self.settings, metadata={'title': 'fakepage'}, settings=self.settings,
source_path=os.path.join('dir', 'fakepage.md')) source_path=os.path.join('dir', 'fakepage.md'))
customstatic.attach_to(page) customstatic.attach_to(page)
@ -543,13 +548,13 @@ class TestStatic(LoggedTestCase):
""" """
html = '<a href="{attach}../foo.jpg">link</a>' html = '<a href="{attach}../foo.jpg">link</a>'
page = Page(content=html, page = Page(content=html,
metadata={'title': 'fakepage'}, settings=self.settings, metadata={'title': 'fakepage'}, settings=self.settings,
source_path=os.path.join('dir', 'otherdir', 'fakepage.md'), source_path=os.path.join('dir', 'otherdir', 'fakepage.md'),
context=self.context) context=self.context)
content = page.get_content('') content = page.get_content('')
self.assertNotEqual(content, html, self.assertNotEqual(content, html,
"{attach} link syntax did not trigger URL replacement.") "{attach} link syntax did not trigger URL replacement.")
expected_save_as = os.path.join('outpages', 'foo.jpg') expected_save_as = os.path.join('outpages', 'foo.jpg')
self.assertEqual(self.static.save_as, expected_save_as) self.assertEqual(self.static.save_as, expected_save_as)
@ -573,9 +578,9 @@ class TestStatic(LoggedTestCase):
html = '<a href="{category}foo">link</a>' html = '<a href="{category}foo">link</a>'
page = Page(content=html, page = Page(content=html,
metadata={'title': 'fakepage'}, settings=self.settings, metadata={'title': 'fakepage'}, settings=self.settings,
source_path=os.path.join('dir', 'otherdir', 'fakepage.md'), source_path=os.path.join('dir', 'otherdir', 'fakepage.md'),
context=self.context) context=self.context)
content = page.get_content('') content = page.get_content('')
self.assertNotEqual(content, html) self.assertNotEqual(content, html)

View file

@ -24,6 +24,7 @@ CONTENT_DIR = os.path.join(CUR_DIR, 'content')
class TestGenerator(unittest.TestCase): class TestGenerator(unittest.TestCase):
def setUp(self): def setUp(self):
self.old_locale = locale.setlocale(locale.LC_ALL) self.old_locale = locale.setlocale(locale.LC_ALL)
locale.setlocale(locale.LC_ALL, str('C')) locale.setlocale(locale.LC_ALL, str('C'))
@ -35,7 +36,6 @@ class TestGenerator(unittest.TestCase):
def tearDown(self): def tearDown(self):
locale.setlocale(locale.LC_ALL, self.old_locale) locale.setlocale(locale.LC_ALL, self.old_locale)
def test_include_path(self): def test_include_path(self):
self.settings['IGNORE_FILES'] = {'ignored1.rst', 'ignored2.rst'} self.settings['IGNORE_FILES'] = {'ignored1.rst', 'ignored2.rst'}
@ -53,40 +53,41 @@ class TestGenerator(unittest.TestCase):
""" """
# We use our own Generator so we can give it our own content path # We use our own Generator so we can give it our own content path
generator = Generator(context=self.settings.copy(), generator = Generator(context=self.settings.copy(),
settings=self.settings, settings=self.settings,
path=os.path.join(CUR_DIR, 'nested_content'), path=os.path.join(CUR_DIR, 'nested_content'),
theme=self.settings['THEME'], output_path=None) theme=self.settings['THEME'], output_path=None)
filepaths = generator.get_files(paths=['maindir']) filepaths = generator.get_files(paths=['maindir'])
found_files = {os.path.basename(f) for f in filepaths} found_files = {os.path.basename(f) for f in filepaths}
expected_files = {'maindir.md', 'subdir.md'} expected_files = {'maindir.md', 'subdir.md'}
self.assertFalse(expected_files - found_files, self.assertFalse(expected_files - found_files,
"get_files() failed to find one or more files") "get_files() failed to find one or more files")
# Test string as `paths` argument rather than list # Test string as `paths` argument rather than list
filepaths = generator.get_files(paths='maindir') filepaths = generator.get_files(paths='maindir')
found_files = {os.path.basename(f) for f in filepaths} found_files = {os.path.basename(f) for f in filepaths}
expected_files = {'maindir.md', 'subdir.md'} expected_files = {'maindir.md', 'subdir.md'}
self.assertFalse(expected_files - found_files, self.assertFalse(expected_files - found_files,
"get_files() failed to find one or more files") "get_files() failed to find one or more files")
filepaths = generator.get_files(paths=[''], exclude=['maindir']) filepaths = generator.get_files(paths=[''], exclude=['maindir'])
found_files = {os.path.basename(f) for f in filepaths} found_files = {os.path.basename(f) for f in filepaths}
self.assertNotIn('maindir.md', found_files, self.assertNotIn('maindir.md', found_files,
"get_files() failed to exclude a top-level directory") "get_files() failed to exclude a top-level directory")
self.assertNotIn('subdir.md', found_files, self.assertNotIn('subdir.md', found_files,
"get_files() failed to exclude a subdir of an excluded directory") "get_files() failed to exclude a subdir of an excluded directory")
filepaths = generator.get_files(paths=[''], filepaths = generator.get_files(paths=[''],
exclude=[os.path.join('maindir', 'subdir')]) exclude=[os.path.join('maindir', 'subdir')])
found_files = {os.path.basename(f) for f in filepaths} found_files = {os.path.basename(f) for f in filepaths}
self.assertNotIn('subdir.md', found_files, self.assertNotIn('subdir.md', found_files,
"get_files() failed to exclude a subdirectory") "get_files() failed to exclude a subdirectory")
filepaths = generator.get_files(paths=[''], exclude=['subdir']) filepaths = generator.get_files(paths=[''], exclude=['subdir'])
found_files = {os.path.basename(f) for f in filepaths} found_files = {os.path.basename(f) for f in filepaths}
self.assertIn('subdir.md', found_files, self.assertIn('subdir.md', found_files,
"get_files() excluded a subdirectory by name, ignoring its path") "get_files() excluded a subdirectory by name, ignoring its path")
class TestArticlesGenerator(unittest.TestCase): class TestArticlesGenerator(unittest.TestCase):
@ -96,7 +97,8 @@ class TestArticlesGenerator(unittest.TestCase):
settings['DEFAULT_CATEGORY'] = 'Default' settings['DEFAULT_CATEGORY'] = 'Default'
settings['DEFAULT_DATE'] = (1970, 1, 1) settings['DEFAULT_DATE'] = (1970, 1, 1)
settings['READERS'] = {'asc': None} settings['READERS'] = {'asc': None}
settings['CACHE_CONTENT'] = False # cache not needed for this logic tests # cache not needed for this logic tests
settings['CACHE_CONTENT'] = False
cls.generator = ArticlesGenerator( cls.generator = ArticlesGenerator(
context=settings.copy(), settings=settings, context=settings.copy(), settings=settings,
@ -152,23 +154,28 @@ class TestArticlesGenerator(unittest.TestCase):
['Test mkd File', 'published', 'test', 'article'], ['Test mkd File', 'published', 'test', 'article'],
['This is a super article !', 'published', 'Yeah', 'article'], ['This is a super article !', 'published', 'Yeah', 'article'],
['This is a super article !', 'published', 'Yeah', 'article'], ['This is a super article !', 'published', 'Yeah', 'article'],
['Article with Nonconformant HTML meta tags', 'published', 'Default', 'article'], ['Article with Nonconformant HTML meta tags',
'published', 'Default', 'article'],
['This is a super article !', 'published', 'yeah', 'article'], ['This is a super article !', 'published', 'yeah', 'article'],
['This is a super article !', 'published', 'yeah', 'article'], ['This is a super article !', 'published', 'yeah', 'article'],
['This is a super article !', 'published', 'yeah', 'article'], ['This is a super article !', 'published', 'yeah', 'article'],
['This is a super article !', 'published', 'Default', 'article'], ['This is a super article !', 'published', 'Default', 'article'],
['This is an article with category !', 'published', 'yeah', ['This is an article with category !', 'published', 'yeah',
'article'], 'article'],
['This is an article with multiple authors!', 'published', 'Default', 'article'], ['This is an article with multiple authors!',
['This is an article with multiple authors!', 'published', 'Default', 'article'], 'published', 'Default', 'article'],
['This is an article with multiple authors in list format!', 'published', 'Default', 'article'], ['This is an article with multiple authors!',
['This is an article with multiple authors in lastname, firstname format!', 'published', 'Default', 'article'], 'published', 'Default', 'article'],
['This is an article with multiple authors in list format!',
'published', 'Default', 'article'],
['This is an article with multiple authors in lastname, firstname format!',
'published', 'Default', 'article'],
['This is an article without category !', 'published', 'Default', ['This is an article without category !', 'published', 'Default',
'article'], 'article'],
['This is an article without category !', 'published', ['This is an article without category !', 'published',
'TestCategory', 'article'], 'TestCategory', 'article'],
['An Article With Code Block To Test Typogrify Ignore', ['An Article With Code Block To Test Typogrify Ignore',
'published', 'Default', 'article'], 'published', 'Default', 'article'],
['マックOS X 10.8でパイソンとVirtualenvをインストールと設定', 'published', ['マックOS X 10.8でパイソンとVirtualenvをインストールと設定', 'published',
'指導書', 'article'], '指導書', 'article'],
] ]
@ -292,7 +299,7 @@ class TestArticlesGenerator(unittest.TestCase):
generator.generate_period_archives(write) generator.generate_period_archives(write)
dates = [d for d in generator.dates if d.date.year == 1970] dates = [d for d in generator.dates if d.date.year == 1970]
self.assertEqual(len(dates), 1) self.assertEqual(len(dates), 1)
#among other things it must have at least been called with this # among other things it must have at least been called with this
settings["period"] = (1970,) settings["period"] = (1970,)
write.assert_called_with("posts/1970/index.html", write.assert_called_with("posts/1970/index.html",
generator.get_template("period_archives"), generator.get_template("period_archives"),
@ -300,7 +307,8 @@ class TestArticlesGenerator(unittest.TestCase):
blog=True, dates=dates) blog=True, dates=dates)
del settings["period"] del settings["period"]
settings['MONTH_ARCHIVE_SAVE_AS'] = 'posts/{date:%Y}/{date:%b}/index.html' settings[
'MONTH_ARCHIVE_SAVE_AS'] = 'posts/{date:%Y}/{date:%b}/index.html'
generator = ArticlesGenerator( generator = ArticlesGenerator(
context=settings, settings=settings, context=settings, settings=settings,
path=CONTENT_DIR, theme=settings['THEME'], output_path=None) path=CONTENT_DIR, theme=settings['THEME'], output_path=None)
@ -308,17 +316,18 @@ class TestArticlesGenerator(unittest.TestCase):
write = MagicMock() write = MagicMock()
generator.generate_period_archives(write) generator.generate_period_archives(write)
dates = [d for d in generator.dates if d.date.year == 1970 dates = [d for d in generator.dates if d.date.year == 1970
and d.date.month == 1] and d.date.month == 1]
self.assertEqual(len(dates), 1) self.assertEqual(len(dates), 1)
settings["period"] = (1970, "January") settings["period"] = (1970, "January")
#among other things it must have at least been called with this # among other things it must have at least been called with this
write.assert_called_with("posts/1970/Jan/index.html", write.assert_called_with("posts/1970/Jan/index.html",
generator.get_template("period_archives"), generator.get_template("period_archives"),
settings, settings,
blog=True, dates=dates) blog=True, dates=dates)
del settings["period"] del settings["period"]
settings['DAY_ARCHIVE_SAVE_AS'] = 'posts/{date:%Y}/{date:%b}/{date:%d}/index.html' settings[
'DAY_ARCHIVE_SAVE_AS'] = 'posts/{date:%Y}/{date:%b}/{date:%d}/index.html'
generator = ArticlesGenerator( generator = ArticlesGenerator(
context=settings, settings=settings, context=settings, settings=settings,
path=CONTENT_DIR, theme=settings['THEME'], output_path=None) path=CONTENT_DIR, theme=settings['THEME'], output_path=None)
@ -326,11 +335,11 @@ class TestArticlesGenerator(unittest.TestCase):
write = MagicMock() write = MagicMock()
generator.generate_period_archives(write) generator.generate_period_archives(write)
dates = [d for d in generator.dates if d.date.year == 1970 dates = [d for d in generator.dates if d.date.year == 1970
and d.date.month == 1 and d.date.month == 1
and d.date.day == 1] and d.date.day == 1]
self.assertEqual(len(dates), 1) self.assertEqual(len(dates), 1)
settings["period"] = (1970, "January", 1) settings["period"] = (1970, "January", 1)
#among other things it must have at least been called with this # among other things it must have at least been called with this
write.assert_called_with("posts/1970/Jan/01/index.html", write.assert_called_with("posts/1970/Jan/01/index.html",
generator.get_template("period_archives"), generator.get_template("period_archives"),
settings, settings,
@ -347,11 +356,13 @@ class TestArticlesGenerator(unittest.TestCase):
def test_generate_authors(self): def test_generate_authors(self):
"""Check authors generation.""" """Check authors generation."""
authors = [author.name for author, _ in self.generator.authors] authors = [author.name for author, _ in self.generator.authors]
authors_expected = sorted(['Alexis Métaireau', 'Author, First', 'Author, Second', 'First Author', 'Second Author']) authors_expected = sorted(
['Alexis Métaireau', 'Author, First', 'Author, Second', 'First Author', 'Second Author'])
self.assertEqual(sorted(authors), authors_expected) self.assertEqual(sorted(authors), authors_expected)
# test for slug # test for slug
authors = [author.slug for author, _ in self.generator.authors] authors = [author.slug for author, _ in self.generator.authors]
authors_expected = ['alexis-metaireau', 'author-first', 'author-second', 'first-author', 'second-author'] authors_expected = ['alexis-metaireau', 'author-first',
'author-second', 'first-author', 'second-author']
self.assertEqual(sorted(authors), sorted(authors_expected)) self.assertEqual(sorted(authors), sorted(authors_expected))
def test_standard_metadata_in_default_metadata(self): def test_standard_metadata_in_default_metadata(self):
@ -391,7 +402,8 @@ class TestArticlesGenerator(unittest.TestCase):
settings = get_settings(filenames={}) settings = get_settings(filenames={})
settings['DEFAULT_CATEGORY'] = 'Default' settings['DEFAULT_CATEGORY'] = 'Default'
settings['DEFAULT_DATE'] = (1970, 1, 1) settings['DEFAULT_DATE'] = (1970, 1, 1)
settings['CACHE_CONTENT'] = False # cache not needed for this logic tests # cache not needed for this logic tests
settings['CACHE_CONTENT'] = False
settings['ARTICLE_ORDER_BY'] = 'title' settings['ARTICLE_ORDER_BY'] = 'title'
generator = ArticlesGenerator( generator = ArticlesGenerator(
@ -435,7 +447,8 @@ class TestArticlesGenerator(unittest.TestCase):
settings = get_settings(filenames={}) settings = get_settings(filenames={})
settings['DEFAULT_CATEGORY'] = 'Default' settings['DEFAULT_CATEGORY'] = 'Default'
settings['DEFAULT_DATE'] = (1970, 1, 1) settings['DEFAULT_DATE'] = (1970, 1, 1)
settings['CACHE_CONTENT'] = False # cache not needed for this logic tests # cache not needed for this logic tests
settings['CACHE_CONTENT'] = False
settings['ARTICLE_ORDER_BY'] = 'reversed-title' settings['ARTICLE_ORDER_BY'] = 'reversed-title'
generator = ArticlesGenerator( generator = ArticlesGenerator(
@ -561,7 +574,7 @@ class TestPageGenerator(unittest.TestCase):
are generated correctly on pages are generated correctly on pages
""" """
settings = get_settings(filenames={}) settings = get_settings(filenames={})
settings['PAGE_PATHS'] = ['TestPages'] # relative to CUR_DIR settings['PAGE_PATHS'] = ['TestPages'] # relative to CUR_DIR
settings['CACHE_PATH'] = self.temp_cache settings['CACHE_PATH'] = self.temp_cache
settings['DEFAULT_DATE'] = (1970, 1, 1) settings['DEFAULT_DATE'] = (1970, 1, 1)
@ -586,7 +599,6 @@ class TestTemplatePagesGenerator(unittest.TestCase):
self.old_locale = locale.setlocale(locale.LC_ALL) self.old_locale = locale.setlocale(locale.LC_ALL)
locale.setlocale(locale.LC_ALL, str('C')) locale.setlocale(locale.LC_ALL, str('C'))
def tearDown(self): def tearDown(self):
rmtree(self.temp_content) rmtree(self.temp_content)
rmtree(self.temp_output) rmtree(self.temp_output)
@ -633,21 +645,21 @@ class TestStaticGenerator(unittest.TestCase):
"""Test that StaticGenerator respects STATIC_EXCLUDES. """Test that StaticGenerator respects STATIC_EXCLUDES.
""" """
settings = get_settings(STATIC_EXCLUDES=['subdir'], settings = get_settings(STATIC_EXCLUDES=['subdir'],
PATH=self.content_path, STATIC_PATHS=['']) PATH=self.content_path, STATIC_PATHS=[''])
context = settings.copy() context = settings.copy()
context['filenames'] = {} context['filenames'] = {}
StaticGenerator(context=context, settings=settings, StaticGenerator(context=context, settings=settings,
path=settings['PATH'], output_path=None, path=settings['PATH'], output_path=None,
theme=settings['THEME']).generate_context() theme=settings['THEME']).generate_context()
staticnames = [os.path.basename(c.source_path) staticnames = [os.path.basename(c.source_path)
for c in context['staticfiles']] for c in context['staticfiles']]
self.assertNotIn('subdir_fake_image.jpg', staticnames, self.assertNotIn('subdir_fake_image.jpg', staticnames,
"StaticGenerator processed a file in a STATIC_EXCLUDES directory") "StaticGenerator processed a file in a STATIC_EXCLUDES directory")
self.assertIn('fake_image.jpg', staticnames, self.assertIn('fake_image.jpg', staticnames,
"StaticGenerator skipped a file that it should have included") "StaticGenerator skipped a file that it should have included")
def test_static_exclude_sources(self): def test_static_exclude_sources(self):
"""Test that StaticGenerator respects STATIC_EXCLUDE_SOURCES. """Test that StaticGenerator respects STATIC_EXCLUDE_SOURCES.
@ -655,21 +667,21 @@ class TestStaticGenerator(unittest.TestCase):
# Test STATIC_EXCLUDE_SOURCES=True # Test STATIC_EXCLUDE_SOURCES=True
settings = get_settings(STATIC_EXCLUDE_SOURCES=True, settings = get_settings(STATIC_EXCLUDE_SOURCES=True,
PATH=self.content_path, PAGE_PATHS=[''], STATIC_PATHS=[''], PATH=self.content_path, PAGE_PATHS=[''], STATIC_PATHS=[''],
CACHE_CONTENT=False) CACHE_CONTENT=False)
context = settings.copy() context = settings.copy()
context['filenames'] = {} context['filenames'] = {}
for generator_class in (PagesGenerator, StaticGenerator): for generator_class in (PagesGenerator, StaticGenerator):
generator_class(context=context, settings=settings, generator_class(context=context, settings=settings,
path=settings['PATH'], output_path=None, path=settings['PATH'], output_path=None,
theme=settings['THEME']).generate_context() theme=settings['THEME']).generate_context()
staticnames = [os.path.basename(c.source_path) staticnames = [os.path.basename(c.source_path)
for c in context['staticfiles']] for c in context['staticfiles']]
self.assertFalse(any(name.endswith(".md") for name in staticnames), self.assertFalse(any(name.endswith(".md") for name in staticnames),
"STATIC_EXCLUDE_SOURCES=True failed to exclude a markdown file") "STATIC_EXCLUDE_SOURCES=True failed to exclude a markdown file")
# Test STATIC_EXCLUDE_SOURCES=False # Test STATIC_EXCLUDE_SOURCES=False
@ -679,12 +691,11 @@ class TestStaticGenerator(unittest.TestCase):
for generator_class in (PagesGenerator, StaticGenerator): for generator_class in (PagesGenerator, StaticGenerator):
generator_class(context=context, settings=settings, generator_class(context=context, settings=settings,
path=settings['PATH'], output_path=None, path=settings['PATH'], output_path=None,
theme=settings['THEME']).generate_context() theme=settings['THEME']).generate_context()
staticnames = [os.path.basename(c.source_path) staticnames = [os.path.basename(c.source_path)
for c in context['staticfiles']] for c in context['staticfiles']]
self.assertTrue(any(name.endswith(".md") for name in staticnames), self.assertTrue(any(name.endswith(".md") for name in staticnames),
"STATIC_EXCLUDE_SOURCES=False failed to include a markdown file") "STATIC_EXCLUDE_SOURCES=False failed to include a markdown file")

View file

@ -32,7 +32,6 @@ except ImportError:
LXML = False LXML = False
@skipIfNoExecutable(['pandoc', '--version']) @skipIfNoExecutable(['pandoc', '--version'])
@unittest.skipUnless(BeautifulSoup, 'Needs BeautifulSoup module') @unittest.skipUnless(BeautifulSoup, 'Needs BeautifulSoup module')
class TestWordpressXmlImporter(unittest.TestCase): class TestWordpressXmlImporter(unittest.TestCase):
@ -67,7 +66,8 @@ class TestWordpressXmlImporter(unittest.TestCase):
silent_f2p = mute(True)(fields2pelican) silent_f2p = mute(True)(fields2pelican)
test_post = filter(lambda p: p[0].startswith("Empty Page"), self.posts) test_post = filter(lambda p: p[0].startswith("Empty Page"), self.posts)
with temporary_folder() as temp: with temporary_folder() as temp:
fname = list(silent_f2p(test_post, 'markdown', temp, dirpage=True))[0] fname = list(
silent_f2p(test_post, 'markdown', temp, dirpage=True))[0]
self.assertTrue(fname.endswith('pages%sempty.md' % os.path.sep)) self.assertTrue(fname.endswith('pages%sempty.md' % os.path.sep))
def test_dircat(self): def test_dircat(self):
@ -75,10 +75,11 @@ class TestWordpressXmlImporter(unittest.TestCase):
test_posts = [] test_posts = []
for post in self.posts: for post in self.posts:
# check post kind # check post kind
if len(post[5]) > 0: # Has a category if len(post[5]) > 0: # Has a category
test_posts.append(post) test_posts.append(post)
with temporary_folder() as temp: with temporary_folder() as temp:
fnames = list(silent_f2p(test_posts, 'markdown', temp, dircat=True)) fnames = list(
silent_f2p(test_posts, 'markdown', temp, dircat=True))
index = 0 index = 0
for post in test_posts: for post in test_posts:
name = post[2] name = post[2]
@ -108,9 +109,12 @@ class TestWordpressXmlImporter(unittest.TestCase):
else: else:
cust_data.append((title, kind)) cust_data.append((title, kind))
self.assertEqual(3, len(cust_data)) self.assertEqual(3, len(cust_data))
self.assertEqual(('A custom post in category 4', 'custom1'), cust_data[0]) self.assertEqual(
self.assertEqual(('A custom post in category 5', 'custom1'), cust_data[1]) ('A custom post in category 4', 'custom1'), cust_data[0])
self.assertEqual(('A 2nd custom post type also in category 5', 'custom2'), cust_data[2]) self.assertEqual(
('A custom post in category 5', 'custom1'), cust_data[1])
self.assertEqual(
('A 2nd custom post type also in category 5', 'custom2'), cust_data[2])
def test_custom_posts_put_in_own_dir(self): def test_custom_posts_put_in_own_dir(self):
silent_f2p = mute(True)(fields2pelican) silent_f2p = mute(True)(fields2pelican)
@ -122,7 +126,8 @@ class TestWordpressXmlImporter(unittest.TestCase):
else: else:
test_posts.append(post) test_posts.append(post)
with temporary_folder() as temp: with temporary_folder() as temp:
fnames = list(silent_f2p(test_posts, 'markdown', temp, wp_custpost = True)) fnames = list(
silent_f2p(test_posts, 'markdown', temp, wp_custpost=True))
index = 0 index = 0
for post in test_posts: for post in test_posts:
name = post[2] name = post[2]
@ -144,7 +149,7 @@ class TestWordpressXmlImporter(unittest.TestCase):
test_posts.append(post) test_posts.append(post)
with temporary_folder() as temp: with temporary_folder() as temp:
fnames = list(silent_f2p(test_posts, 'markdown', temp, fnames = list(silent_f2p(test_posts, 'markdown', temp,
wp_custpost=True, dircat=True)) wp_custpost=True, dircat=True))
index = 0 index = 0
for post in test_posts: for post in test_posts:
name = post[2] name = post[2]
@ -157,7 +162,7 @@ class TestWordpressXmlImporter(unittest.TestCase):
index += 1 index += 1
def test_wp_custpost_true_dirpage_false(self): def test_wp_custpost_true_dirpage_false(self):
#pages should only be put in their own directory when dirpage = True # pages should only be put in their own directory when dirpage = True
silent_f2p = mute(True)(fields2pelican) silent_f2p = mute(True)(fields2pelican)
test_posts = [] test_posts = []
for post in self.custposts: for post in self.custposts:
@ -166,7 +171,7 @@ class TestWordpressXmlImporter(unittest.TestCase):
test_posts.append(post) test_posts.append(post)
with temporary_folder() as temp: with temporary_folder() as temp:
fnames = list(silent_f2p(test_posts, 'markdown', temp, fnames = list(silent_f2p(test_posts, 'markdown', temp,
wp_custpost=True, dirpage=False)) wp_custpost=True, dirpage=False))
index = 0 index = 0
for post in test_posts: for post in test_posts:
name = post[2] name = post[2]
@ -175,7 +180,6 @@ class TestWordpressXmlImporter(unittest.TestCase):
out_name = fnames[index] out_name = fnames[index]
self.assertFalse(out_name.endswith(filename)) self.assertFalse(out_name.endswith(filename))
def test_can_toggle_raw_html_code_parsing(self): def test_can_toggle_raw_html_code_parsing(self):
def r(f): def r(f):
with open(f, encoding='utf-8') as infile: with open(f, encoding='utf-8') as infile:
@ -184,20 +188,22 @@ class TestWordpressXmlImporter(unittest.TestCase):
with temporary_folder() as temp: with temporary_folder() as temp:
rst_files = (r(f) for f in silent_f2p(self.posts, 'markdown', temp)) rst_files = (r(f)
for f in silent_f2p(self.posts, 'markdown', temp))
self.assertTrue(any('<iframe' in rst for rst in rst_files)) self.assertTrue(any('<iframe' in rst for rst in rst_files))
rst_files = (r(f) for f in silent_f2p(self.posts, 'markdown', temp, rst_files = (r(f) for f in silent_f2p(self.posts, 'markdown', temp,
strip_raw=True)) strip_raw=True))
self.assertFalse(any('<iframe' in rst for rst in rst_files)) self.assertFalse(any('<iframe' in rst for rst in rst_files))
# no effect in rst # no effect in rst
rst_files = (r(f) for f in silent_f2p(self.posts, 'rst', temp)) rst_files = (r(f) for f in silent_f2p(self.posts, 'rst', temp))
self.assertFalse(any('<iframe' in rst for rst in rst_files)) self.assertFalse(any('<iframe' in rst for rst in rst_files))
rst_files = (r(f) for f in silent_f2p(self.posts, 'rst', temp, rst_files = (r(f) for f in silent_f2p(self.posts, 'rst', temp,
strip_raw=True)) strip_raw=True))
self.assertFalse(any('<iframe' in rst for rst in rst_files)) self.assertFalse(any('<iframe' in rst for rst in rst_files))
def test_decode_html_entities_in_titles(self): def test_decode_html_entities_in_titles(self):
test_posts = [post for post in self.posts if post[2] == 'html-entity-test'] test_posts = [
post for post in self.posts if post[2] == 'html-entity-test']
self.assertEqual(len(test_posts), 1) self.assertEqual(len(test_posts), 1)
post = test_posts[0] post = test_posts[0]
@ -216,14 +222,16 @@ class TestWordpressXmlImporter(unittest.TestCase):
encoded_content = encoded_file.read() encoded_content = encoded_file.read()
with open(WORDPRESS_DECODED_CONTENT_SAMPLE, 'r') as decoded_file: with open(WORDPRESS_DECODED_CONTENT_SAMPLE, 'r') as decoded_file:
decoded_content = decoded_file.read() decoded_content = decoded_file.read()
self.assertEqual(decode_wp_content(encoded_content, br=False), decoded_content) self.assertEqual(
decode_wp_content(encoded_content, br=False), decoded_content)
def test_preserve_verbatim_formatting(self): def test_preserve_verbatim_formatting(self):
def r(f): def r(f):
with open(f, encoding='utf-8') as infile: with open(f, encoding='utf-8') as infile:
return infile.read() return infile.read()
silent_f2p = mute(True)(fields2pelican) silent_f2p = mute(True)(fields2pelican)
test_post = filter(lambda p: p[0].startswith("Code in List"), self.posts) test_post = filter(
lambda p: p[0].startswith("Code in List"), self.posts)
with temporary_folder() as temp: with temporary_folder() as temp:
md = [r(f) for f in silent_f2p(test_post, 'markdown', temp)][0] md = [r(f) for f in silent_f2p(test_post, 'markdown', temp)][0]
self.assertTrue(re.search(r'\s+a = \[1, 2, 3\]', md)) self.assertTrue(re.search(r'\s+a = \[1, 2, 3\]', md))
@ -231,14 +239,16 @@ class TestWordpressXmlImporter(unittest.TestCase):
for_line = re.search(r'\s+for i in zip\(a, b\):', md).group(0) for_line = re.search(r'\s+for i in zip\(a, b\):', md).group(0)
print_line = re.search(r'\s+print i', md).group(0) print_line = re.search(r'\s+print i', md).group(0)
self.assertTrue(for_line.rindex('for') < print_line.rindex('print')) self.assertTrue(
for_line.rindex('for') < print_line.rindex('print'))
def test_code_in_list(self): def test_code_in_list(self):
def r(f): def r(f):
with open(f, encoding='utf-8') as infile: with open(f, encoding='utf-8') as infile:
return infile.read() return infile.read()
silent_f2p = mute(True)(fields2pelican) silent_f2p = mute(True)(fields2pelican)
test_post = filter(lambda p: p[0].startswith("Code in List"), self.posts) test_post = filter(
lambda p: p[0].startswith("Code in List"), self.posts)
with temporary_folder() as temp: with temporary_folder() as temp:
md = [r(f) for f in silent_f2p(test_post, 'markdown', temp)][0] md = [r(f) for f in silent_f2p(test_post, 'markdown', temp)][0]
sample_line = re.search(r'- This is a code sample', md).group(0) sample_line = re.search(r'- This is a code sample', md).group(0)
@ -247,6 +257,7 @@ class TestWordpressXmlImporter(unittest.TestCase):
class TestBuildHeader(unittest.TestCase): class TestBuildHeader(unittest.TestCase):
def test_build_header(self): def test_build_header(self):
header = build_header('test', None, None, None, None, None) header = build_header('test', None, None, None, None, None)
self.assertEqual(header, 'test\n####\n\n') self.assertEqual(header, 'test\n####\n\n')
@ -285,31 +296,31 @@ class TestBuildHeader(unittest.TestCase):
self.assertEqual(build_header(*header_data), expected_docutils) self.assertEqual(build_header(*header_data), expected_docutils)
self.assertEqual(build_markdown_header(*header_data), expected_md) self.assertEqual(build_markdown_header(*header_data), expected_md)
def test_build_header_with_east_asian_characters(self): def test_build_header_with_east_asian_characters(self):
header = build_header('これは広い幅の文字だけで構成されたタイトルです', header = build_header('これは広い幅の文字だけで構成されたタイトルです',
None, None, None, None, None) None, None, None, None, None)
self.assertEqual(header, self.assertEqual(header,
'これは広い幅の文字だけで構成されたタイトルです\n' + 'これは広い幅の文字だけで構成されたタイトルです\n' +
'##############################################\n\n') '##############################################\n\n')
def test_galleries_added_to_header(self): def test_galleries_added_to_header(self):
header = build_header('test', None, None, None, None, header = build_header('test', None, None, None, None,
None, attachments=['output/test1', 'output/test2']) None, attachments=['output/test1', 'output/test2'])
self.assertEqual(header, 'test\n####\n' + ':attachments: output/test1, ' self.assertEqual(header, 'test\n####\n' + ':attachments: output/test1, '
+ 'output/test2\n\n') + 'output/test2\n\n')
def test_galleries_added_to_markdown_header(self): def test_galleries_added_to_markdown_header(self):
header = build_markdown_header('test', None, None, None, None, None, header = build_markdown_header('test', None, None, None, None, None,
attachments=['output/test1', 'output/test2']) attachments=['output/test1', 'output/test2'])
self.assertEqual(header, 'Title: test\n' + 'Attachments: output/test1, ' self.assertEqual(header, 'Title: test\n' + 'Attachments: output/test1, '
+ 'output/test2\n\n') + 'output/test2\n\n')
@unittest.skipUnless(BeautifulSoup, 'Needs BeautifulSoup module') @unittest.skipUnless(BeautifulSoup, 'Needs BeautifulSoup module')
@unittest.skipUnless(LXML, 'Needs lxml module') @unittest.skipUnless(LXML, 'Needs lxml module')
class TestWordpressXMLAttachements(unittest.TestCase): class TestWordpressXMLAttachements(unittest.TestCase):
def setUp(self): def setUp(self):
self.old_locale = locale.setlocale(locale.LC_ALL) self.old_locale = locale.setlocale(locale.LC_ALL)
locale.setlocale(locale.LC_ALL, str('C')) locale.setlocale(locale.LC_ALL, str('C'))
@ -326,14 +337,19 @@ class TestWordpressXMLAttachements(unittest.TestCase):
self.assertTrue(self.attachments) self.assertTrue(self.attachments)
for post in self.attachments.keys(): for post in self.attachments.keys():
if post is None: if post is None:
self.assertTrue(self.attachments[post][0] == 'https://upload.wikimedia.org/wikipedia/commons/thumb/2/2c/Pelican_lakes_entrance02.jpg/240px-Pelican_lakes_entrance02.jpg') self.assertTrue(self.attachments[post][
0] == 'https://upload.wikimedia.org/wikipedia/commons/thumb/2/2c/Pelican_lakes_entrance02.jpg/240px-Pelican_lakes_entrance02.jpg')
elif post == 'with-excerpt': elif post == 'with-excerpt':
self.assertTrue(self.attachments[post][0] == 'http://thisurlisinvalid.notarealdomain/not_an_image.jpg') self.assertTrue(self.attachments[post][
self.assertTrue(self.attachments[post][1] == 'http://en.wikipedia.org/wiki/File:Pelikan_Walvis_Bay.jpg') 0] == 'http://thisurlisinvalid.notarealdomain/not_an_image.jpg')
self.assertTrue(self.attachments[post][
1] == 'http://en.wikipedia.org/wiki/File:Pelikan_Walvis_Bay.jpg')
elif post == 'with-tags': elif post == 'with-tags':
self.assertTrue(self.attachments[post][0] == 'http://thisurlisinvalid.notarealdomain') self.assertTrue(
self.attachments[post][0] == 'http://thisurlisinvalid.notarealdomain')
else: else:
self.fail('all attachments should match to a filename or None, {}'.format(post)) self.fail(
'all attachments should match to a filename or None, {}'.format(post))
def test_download_attachments(self): def test_download_attachments(self):
real_file = os.path.join(CUR_DIR, 'content/article.rst') real_file = os.path.join(CUR_DIR, 'content/article.rst')
@ -344,4 +360,5 @@ class TestWordpressXMLAttachements(unittest.TestCase):
locations = list(silent_da(temp, [good_url, bad_url])) locations = list(silent_da(temp, [good_url, bad_url]))
self.assertEqual(1, len(locations)) self.assertEqual(1, len(locations))
directory = locations[0] directory = locations[0]
self.assertTrue(directory.endswith(os.path.join('content', 'article.rst')), directory) self.assertTrue(
directory.endswith(os.path.join('content', 'article.rst')), directory)

View file

@ -13,7 +13,9 @@ from jinja2.utils import generate_lorem_ipsum
TEST_CONTENT = str(generate_lorem_ipsum(n=1)) TEST_CONTENT = str(generate_lorem_ipsum(n=1))
TEST_SUMMARY = generate_lorem_ipsum(n=1, html=False) TEST_SUMMARY = generate_lorem_ipsum(n=1, html=False)
class TestPage(unittest.TestCase): class TestPage(unittest.TestCase):
def setUp(self): def setUp(self):
super(TestPage, self).setUp() super(TestPage, self).setUp()
self.old_locale = locale.setlocale(locale.LC_ALL) self.old_locale = locale.setlocale(locale.LC_ALL)
@ -49,7 +51,8 @@ class TestPage(unittest.TestCase):
) )
self.page_kwargs['metadata']['author'] = Author('Blogger', settings) self.page_kwargs['metadata']['author'] = Author('Blogger', settings)
object_list = [Article(**self.page_kwargs), Article(**self.page_kwargs)] object_list = [
Article(**self.page_kwargs), Article(**self.page_kwargs)]
paginator = Paginator('foobar.foo', object_list, settings) paginator = Paginator('foobar.foo', object_list, settings)
page = paginator.page(1) page = paginator.page(1)
self.assertEqual(page.save_as, 'foobar.foo') self.assertEqual(page.save_as, 'foobar.foo')

View file

@ -17,7 +17,7 @@ from pelican.tests.support import LoggedTestCase, mute, locale_available, unitte
CURRENT_DIR = os.path.dirname(os.path.abspath(__file__)) CURRENT_DIR = os.path.dirname(os.path.abspath(__file__))
SAMPLES_PATH = os.path.abspath(os.path.join( SAMPLES_PATH = os.path.abspath(os.path.join(
CURRENT_DIR, os.pardir, os.pardir, 'samples')) CURRENT_DIR, os.pardir, os.pardir, 'samples'))
OUTPUT_PATH = os.path.abspath(os.path.join(CURRENT_DIR, 'output')) OUTPUT_PATH = os.path.abspath(os.path.join(CURRENT_DIR, 'output'))
INPUT_PATH = os.path.join(SAMPLES_PATH, "content") INPUT_PATH = os.path.join(SAMPLES_PATH, "content")
@ -27,13 +27,13 @@ SAMPLE_FR_CONFIG = os.path.join(SAMPLES_PATH, "pelican.conf_FR.py")
def recursiveDiff(dcmp): def recursiveDiff(dcmp):
diff = { diff = {
'diff_files': [os.path.join(dcmp.right, f) 'diff_files': [os.path.join(dcmp.right, f)
for f in dcmp.diff_files], for f in dcmp.diff_files],
'left_only': [os.path.join(dcmp.right, f) 'left_only': [os.path.join(dcmp.right, f)
for f in dcmp.left_only], for f in dcmp.left_only],
'right_only': [os.path.join(dcmp.right, f) 'right_only': [os.path.join(dcmp.right, f)
for f in dcmp.right_only], for f in dcmp.right_only],
} }
for sub_dcmp in dcmp.subdirs.values(): for sub_dcmp in dcmp.subdirs.values():
for k, v in recursiveDiff(sub_dcmp).items(): for k, v in recursiveDiff(sub_dcmp).items():
diff[k] += v diff[k] += v
@ -60,9 +60,11 @@ class TestPelican(LoggedTestCase):
def assertDirsEqual(self, left_path, right_path): def assertDirsEqual(self, left_path, right_path):
out, err = subprocess.Popen( out, err = subprocess.Popen(
['git', 'diff', '--no-ext-diff', '--exit-code', '-w', left_path, right_path], ['git', 'diff', '--no-ext-diff', '--exit-code',
'-w', left_path, right_path],
env={str('PAGER'): str('')}, stdout=subprocess.PIPE, stderr=subprocess.PIPE env={str('PAGER'): str('')}, stdout=subprocess.PIPE, stderr=subprocess.PIPE
).communicate() ).communicate()
def ignorable_git_crlf_errors(line): def ignorable_git_crlf_errors(line):
# Work around for running tests on Windows # Work around for running tests on Windows
for msg in [ for msg in [
@ -86,9 +88,9 @@ class TestPelican(LoggedTestCase):
generator_classes = pelican.get_generator_classes() generator_classes = pelican.get_generator_classes()
self.assertTrue(generator_classes[-1] is StaticGenerator, self.assertTrue(generator_classes[-1] is StaticGenerator,
"StaticGenerator must be the last generator, but it isn't!") "StaticGenerator must be the last generator, but it isn't!")
self.assertIsInstance(generator_classes, collections.Sequence, self.assertIsInstance(generator_classes, collections.Sequence,
"get_generator_classes() must return a Sequence to preserve order") "get_generator_classes() must return a Sequence to preserve order")
def test_basic_generation_works(self): def test_basic_generation_works(self):
# when running pelican without settings, it should pick up the default # when running pelican without settings, it should pick up the default
@ -98,10 +100,11 @@ class TestPelican(LoggedTestCase):
'OUTPUT_PATH': self.temp_path, 'OUTPUT_PATH': self.temp_path,
'CACHE_PATH': self.temp_cache, 'CACHE_PATH': self.temp_cache,
'LOCALE': locale.normalize('en_US'), 'LOCALE': locale.normalize('en_US'),
}) })
pelican = Pelican(settings=settings) pelican = Pelican(settings=settings)
mute(True)(pelican.run)() mute(True)(pelican.run)()
self.assertDirsEqual(self.temp_path, os.path.join(OUTPUT_PATH, 'basic')) self.assertDirsEqual(
self.temp_path, os.path.join(OUTPUT_PATH, 'basic'))
self.assertLogCountEqual( self.assertLogCountEqual(
count=3, count=3,
msg="Unable to find.*skipping url replacement", msg="Unable to find.*skipping url replacement",
@ -114,10 +117,11 @@ class TestPelican(LoggedTestCase):
'OUTPUT_PATH': self.temp_path, 'OUTPUT_PATH': self.temp_path,
'CACHE_PATH': self.temp_cache, 'CACHE_PATH': self.temp_cache,
'LOCALE': locale.normalize('en_US'), 'LOCALE': locale.normalize('en_US'),
}) })
pelican = Pelican(settings=settings) pelican = Pelican(settings=settings)
mute(True)(pelican.run)() mute(True)(pelican.run)()
self.assertDirsEqual(self.temp_path, os.path.join(OUTPUT_PATH, 'custom')) self.assertDirsEqual(
self.temp_path, os.path.join(OUTPUT_PATH, 'custom'))
@unittest.skipUnless(locale_available('fr_FR.UTF-8') or @unittest.skipUnless(locale_available('fr_FR.UTF-8') or
locale_available('French'), 'French locale needed') locale_available('French'), 'French locale needed')
@ -133,10 +137,11 @@ class TestPelican(LoggedTestCase):
'OUTPUT_PATH': self.temp_path, 'OUTPUT_PATH': self.temp_path,
'CACHE_PATH': self.temp_cache, 'CACHE_PATH': self.temp_cache,
'LOCALE': our_locale, 'LOCALE': our_locale,
}) })
pelican = Pelican(settings=settings) pelican = Pelican(settings=settings)
mute(True)(pelican.run)() mute(True)(pelican.run)()
self.assertDirsEqual(self.temp_path, os.path.join(OUTPUT_PATH, 'custom_locale')) self.assertDirsEqual(
self.temp_path, os.path.join(OUTPUT_PATH, 'custom_locale'))
def test_theme_static_paths_copy(self): def test_theme_static_paths_copy(self):
# the same thing with a specified set of settings should work # the same thing with a specified set of settings should work
@ -147,7 +152,7 @@ class TestPelican(LoggedTestCase):
'THEME_STATIC_PATHS': [os.path.join(SAMPLES_PATH, 'very'), 'THEME_STATIC_PATHS': [os.path.join(SAMPLES_PATH, 'very'),
os.path.join(SAMPLES_PATH, 'kinda'), os.path.join(SAMPLES_PATH, 'kinda'),
os.path.join(SAMPLES_PATH, 'theme_standard')] os.path.join(SAMPLES_PATH, 'theme_standard')]
}) })
pelican = Pelican(settings=settings) pelican = Pelican(settings=settings)
mute(True)(pelican.run)() mute(True)(pelican.run)()
theme_output = os.path.join(self.temp_path, 'theme') theme_output = os.path.join(self.temp_path, 'theme')
@ -166,7 +171,7 @@ class TestPelican(LoggedTestCase):
'OUTPUT_PATH': self.temp_path, 'OUTPUT_PATH': self.temp_path,
'CACHE_PATH': self.temp_cache, 'CACHE_PATH': self.temp_cache,
'THEME_STATIC_PATHS': [os.path.join(SAMPLES_PATH, 'theme_standard')] 'THEME_STATIC_PATHS': [os.path.join(SAMPLES_PATH, 'theme_standard')]
}) })
pelican = Pelican(settings=settings) pelican = Pelican(settings=settings)
mute(True)(pelican.run)() mute(True)(pelican.run)()
@ -184,9 +189,9 @@ class TestPelican(LoggedTestCase):
'WRITE_SELECTED': [ 'WRITE_SELECTED': [
os.path.join(self.temp_path, 'oh-yeah.html'), os.path.join(self.temp_path, 'oh-yeah.html'),
os.path.join(self.temp_path, 'categories.html'), os.path.join(self.temp_path, 'categories.html'),
], ],
'LOCALE': locale.normalize('en_US'), 'LOCALE': locale.normalize('en_US'),
}) })
pelican = Pelican(settings=settings) pelican = Pelican(settings=settings)
logger = logging.getLogger() logger = logging.getLogger()
orig_level = logger.getEffectiveLevel() orig_level = logger.getEffectiveLevel()

View file

@ -32,19 +32,21 @@ class ReaderTest(unittest.TestCase):
'Expected %s to have value %s, but was %s' % (key, value, real_value)) 'Expected %s to have value %s, but was %s' % (key, value, real_value))
else: else:
self.fail( self.fail(
'Expected %s to have value %s, but was not in Dict' % (key, value)) 'Expected %s to have value %s, but was not in Dict' % (key, value))
class TestAssertDictHasSubset(ReaderTest): class TestAssertDictHasSubset(ReaderTest):
def setUp(self): def setUp(self):
self.dictionary = { self.dictionary = {
'key-a' : 'val-a', 'key-a': 'val-a',
'key-b' : 'val-b'} 'key-b': 'val-b'}
def tearDown(self): def tearDown(self):
self.dictionary = None self.dictionary = None
def test_subset(self): def test_subset(self):
self.assertDictHasSubset(self.dictionary, {'key-a':'val-a'}) self.assertDictHasSubset(self.dictionary, {'key-a': 'val-a'})
def test_equal(self): def test_equal(self):
self.assertDictHasSubset(self.dictionary, self.dictionary) self.assertDictHasSubset(self.dictionary, self.dictionary)
@ -54,18 +56,19 @@ class TestAssertDictHasSubset(ReaderTest):
AssertionError, AssertionError,
'Expected.*key-c.*to have value.*val-c.*but was not in Dict', 'Expected.*key-c.*to have value.*val-c.*but was not in Dict',
self.assertDictHasSubset, self.assertDictHasSubset,
self.dictionary, self.dictionary,
{'key-c':'val-c'} {'key-c': 'val-c'}
) )
def test_fail_wrong_val(self): def test_fail_wrong_val(self):
self.assertRaisesRegexp( self.assertRaisesRegexp(
AssertionError, AssertionError,
'Expected .*key-a.* to have value .*val-b.* but was .*val-a.*', 'Expected .*key-a.* to have value .*val-b.* but was .*val-a.*',
self.assertDictHasSubset, self.assertDictHasSubset,
self.dictionary, self.dictionary,
{'key-a':'val-b'} {'key-a': 'val-b'}
) )
class DefaultReaderTest(ReaderTest): class DefaultReaderTest(ReaderTest):
@ -153,17 +156,17 @@ class RstReaderTest(ReaderTest):
'(?P<date>\d{4}-\d{2}-\d{2})' '(?P<date>\d{4}-\d{2}-\d{2})'
'_(?P<Slug>.*)' '_(?P<Slug>.*)'
'#(?P<MyMeta>.*)-(?P<author>.*)' '#(?P<MyMeta>.*)-(?P<author>.*)'
), ),
EXTRA_PATH_METADATA={ EXTRA_PATH_METADATA={
input_with_metadata: { input_with_metadata: {
'key-1a': 'value-1a', 'key-1a': 'value-1a',
'key-1b': 'value-1b' 'key-1b': 'value-1b'
}
} }
) }
)
expected_metadata = { expected_metadata = {
'category': 'yeah', 'category': 'yeah',
'author' : 'Alexis Métaireau', 'author': 'Alexis Métaireau',
'title': 'Rst with filename metadata', 'title': 'Rst with filename metadata',
'date': SafeDatetime(2012, 11, 29), 'date': SafeDatetime(2012, 11, 29),
'slug': 'rst_w_filename_meta', 'slug': 'rst_w_filename_meta',
@ -180,21 +183,21 @@ class RstReaderTest(ReaderTest):
EXTRA_PATH_METADATA={ EXTRA_PATH_METADATA={
input_file_path_without_metadata: { input_file_path_without_metadata: {
'author': 'Charlès Overwrite'} 'author': 'Charlès Overwrite'}
} }
) )
expected_without_metadata = { expected_without_metadata = {
'category' : 'misc', 'category': 'misc',
'author' : 'Charlès Overwrite', 'author': 'Charlès Overwrite',
'title' : 'Article title', 'title': 'Article title',
'reader' : 'rst', 'reader': 'rst',
} }
self.assertDictHasSubset( self.assertDictHasSubset(
page_without_metadata.metadata, page_without_metadata.metadata,
expected_without_metadata) expected_without_metadata)
def test_article_extra_path_metadata_dont_overwrite(self): def test_article_extra_path_metadata_dont_overwrite(self):
#EXTRA_PATH_METADATA['author'] should get ignored # EXTRA_PATH_METADATA['author'] should get ignored
#since we don't overwrite already set values # since we don't overwrite already set values
input_file_path = '2012-11-29_rst_w_filename_meta#foo-bar.rst' input_file_path = '2012-11-29_rst_w_filename_meta#foo-bar.rst'
page = self.read_file( page = self.read_file(
path=input_file_path, path=input_file_path,
@ -206,11 +209,11 @@ class RstReaderTest(ReaderTest):
input_file_path: { input_file_path: {
'author': 'Charlès Overwrite', 'author': 'Charlès Overwrite',
'key-1b': 'value-1b'} 'key-1b': 'value-1b'}
} }
) )
expected = { expected = {
'category': 'yeah', 'category': 'yeah',
'author' : 'Alexis Métaireau', 'author': 'Alexis Métaireau',
'title': 'Rst with filename metadata', 'title': 'Rst with filename metadata',
'date': SafeDatetime(2012, 11, 29), 'date': SafeDatetime(2012, 11, 29),
'slug': 'rst_w_filename_meta', 'slug': 'rst_w_filename_meta',
@ -273,7 +276,7 @@ class RstReaderTest(ReaderTest):
# typogrify should be able to ignore user specified tags, # typogrify should be able to ignore user specified tags,
# but tries to be clever with widont extension # but tries to be clever with widont extension
page = self.read_file(path='article.rst', TYPOGRIFY=True, page = self.read_file(path='article.rst', TYPOGRIFY=True,
TYPOGRIFY_IGNORE_TAGS = ['p']) TYPOGRIFY_IGNORE_TAGS=['p'])
expected = ('<p>THIS is some content. With some stuff to&nbsp;' expected = ('<p>THIS is some content. With some stuff to&nbsp;'
'&quot;typogrify&quot;...</p>\n<p>Now with added ' '&quot;typogrify&quot;...</p>\n<p>Now with added '
'support for <abbr title="three letter acronym">' 'support for <abbr title="three letter acronym">'
@ -284,7 +287,7 @@ class RstReaderTest(ReaderTest):
# typogrify should ignore code blocks by default because # typogrify should ignore code blocks by default because
# code blocks are composed inside the pre tag # code blocks are composed inside the pre tag
page = self.read_file(path='article_with_code_block.rst', page = self.read_file(path='article_with_code_block.rst',
TYPOGRIFY=True) TYPOGRIFY=True)
expected = ('<p>An article with some&nbsp;code</p>\n' expected = ('<p>An article with some&nbsp;code</p>\n'
'<div class="highlight"><pre><span class="n">x</span>' '<div class="highlight"><pre><span class="n">x</span>'
@ -298,7 +301,7 @@ class RstReaderTest(ReaderTest):
# instruct typogrify to also ignore blockquotes # instruct typogrify to also ignore blockquotes
page = self.read_file(path='article_with_code_block.rst', page = self.read_file(path='article_with_code_block.rst',
TYPOGRIFY=True, TYPOGRIFY_IGNORE_TAGS = ['blockquote']) TYPOGRIFY=True, TYPOGRIFY_IGNORE_TAGS=['blockquote'])
expected = ('<p>An article with some&nbsp;code</p>\n' expected = ('<p>An article with some&nbsp;code</p>\n'
'<div class="highlight"><pre><span class="n">x</span>' '<div class="highlight"><pre><span class="n">x</span>'
@ -339,6 +342,7 @@ class RstReaderTest(ReaderTest):
self.assertDictHasSubset(page.metadata, expected) self.assertDictHasSubset(page.metadata, expected)
@unittest.skipUnless(readers.Markdown, "markdown isn't installed") @unittest.skipUnless(readers.Markdown, "markdown isn't installed")
class MdReaderTest(ReaderTest): class MdReaderTest(ReaderTest):
@ -502,6 +506,7 @@ class MdReaderTest(ReaderTest):
class HTMLReaderTest(ReaderTest): class HTMLReaderTest(ReaderTest):
def test_article_with_comments(self): def test_article_with_comments(self):
page = self.read_file(path='article_with_comments.html') page = self.read_file(path='article_with_comments.html')

View file

@ -9,8 +9,10 @@ except ImportError:
Mock = False Mock = False
from pelican.tests.support import unittest from pelican.tests.support import unittest
@unittest.skipUnless(Mock, 'Needs Mock module') @unittest.skipUnless(Mock, 'Needs Mock module')
class Test_abbr_role(unittest.TestCase): class Test_abbr_role(unittest.TestCase):
def call_it(self, text): def call_it(self, text):
from pelican.rstdirectives import abbr_role from pelican.rstdirectives import abbr_role
rawtext = text rawtext = text

View file

@ -12,10 +12,12 @@ from pelican.tests.support import unittest
class TestSettingsConfiguration(unittest.TestCase): class TestSettingsConfiguration(unittest.TestCase):
"""Provided a file, it should read it, replace the default values, """Provided a file, it should read it, replace the default values,
append new values to the settings (if any), and apply basic settings append new values to the settings (if any), and apply basic settings
optimizations. optimizations.
""" """
def setUp(self): def setUp(self):
self.old_locale = locale.setlocale(locale.LC_ALL) self.old_locale = locale.setlocale(locale.LC_ALL)
locale.setlocale(locale.LC_ALL, str('C')) locale.setlocale(locale.LC_ALL, str('C'))
@ -29,12 +31,12 @@ class TestSettingsConfiguration(unittest.TestCase):
def test_overwrite_existing_settings(self): def test_overwrite_existing_settings(self):
self.assertEqual(self.settings.get('SITENAME'), "Alexis' log") self.assertEqual(self.settings.get('SITENAME'), "Alexis' log")
self.assertEqual(self.settings.get('SITEURL'), self.assertEqual(self.settings.get('SITEURL'),
'http://blog.notmyidea.org') 'http://blog.notmyidea.org')
def test_keep_default_settings(self): def test_keep_default_settings(self):
# Keep default settings if not defined. # Keep default settings if not defined.
self.assertEqual(self.settings.get('DEFAULT_CATEGORY'), self.assertEqual(self.settings.get('DEFAULT_CATEGORY'),
DEFAULT_CONFIG['DEFAULT_CATEGORY']) DEFAULT_CONFIG['DEFAULT_CATEGORY'])
def test_dont_copy_small_keys(self): def test_dont_copy_small_keys(self):
# Do not copy keys not in caps. # Do not copy keys not in caps.
@ -70,27 +72,27 @@ class TestSettingsConfiguration(unittest.TestCase):
def test_static_path_settings_safety(self): def test_static_path_settings_safety(self):
# Disallow static paths from being strings # Disallow static paths from being strings
settings = {'STATIC_PATHS': 'foo/bar', settings = {'STATIC_PATHS': 'foo/bar',
'THEME_STATIC_PATHS': 'bar/baz', 'THEME_STATIC_PATHS': 'bar/baz',
# These 4 settings are required to run configure_settings # These 4 settings are required to run configure_settings
'PATH': '.', 'PATH': '.',
'THEME': DEFAULT_THEME, 'THEME': DEFAULT_THEME,
'SITEURL': 'http://blog.notmyidea.org/', 'SITEURL': 'http://blog.notmyidea.org/',
'LOCALE': '', 'LOCALE': '',
} }
configure_settings(settings) configure_settings(settings)
self.assertEqual(settings['STATIC_PATHS'], self.assertEqual(settings['STATIC_PATHS'],
DEFAULT_CONFIG['STATIC_PATHS']) DEFAULT_CONFIG['STATIC_PATHS'])
self.assertEqual(settings['THEME_STATIC_PATHS'], self.assertEqual(settings['THEME_STATIC_PATHS'],
DEFAULT_CONFIG['THEME_STATIC_PATHS']) DEFAULT_CONFIG['THEME_STATIC_PATHS'])
def test_configure_settings(self): def test_configure_settings(self):
# Manipulations to settings should be applied correctly. # Manipulations to settings should be applied correctly.
settings = { settings = {
'SITEURL': 'http://blog.notmyidea.org/', 'SITEURL': 'http://blog.notmyidea.org/',
'LOCALE': '', 'LOCALE': '',
'PATH': os.curdir, 'PATH': os.curdir,
'THEME': DEFAULT_THEME, 'THEME': DEFAULT_THEME,
} }
configure_settings(settings) configure_settings(settings)
# SITEURL should not have a trailing slash # SITEURL should not have a trailing slash

View file

@ -4,7 +4,9 @@ from __future__ import unicode_literals
from pelican.urlwrappers import URLWrapper, Tag, Category from pelican.urlwrappers import URLWrapper, Tag, Category
from pelican.tests.support import unittest from pelican.tests.support import unittest
class TestURLWrapper(unittest.TestCase): class TestURLWrapper(unittest.TestCase):
def test_ordering(self): def test_ordering(self):
# URLWrappers are sorted by name # URLWrappers are sorted by name
wrapper_a = URLWrapper(name='first', settings={}) wrapper_a = URLWrapper(name='first', settings={})

View file

@ -72,7 +72,7 @@ class TestUtils(LoggedTestCase):
'2012-11-22T22:11:10Z': date_hour_sec_z, '2012-11-22T22:11:10Z': date_hour_sec_z,
'2012-11-22T22:11:10-0500': date_hour_sec_est, '2012-11-22T22:11:10-0500': date_hour_sec_est,
'2012-11-22T22:11:10.123Z': date_hour_sec_frac_z, '2012-11-22T22:11:10.123Z': date_hour_sec_frac_z,
} }
# examples from http://www.w3.org/TR/NOTE-datetime # examples from http://www.w3.org/TR/NOTE-datetime
iso_8601_date = utils.SafeDatetime(year=1997, month=7, day=16) iso_8601_date = utils.SafeDatetime(year=1997, month=7, day=16)
@ -95,7 +95,6 @@ class TestUtils(LoggedTestCase):
# invalid ones # invalid ones
invalid_dates = ['2010-110-12', 'yay'] invalid_dates = ['2010-110-12', 'yay']
for value, expected in dates.items(): for value, expected in dates.items():
self.assertEqual(utils.get_date(value), expected, value) self.assertEqual(utils.get_date(value), expected, value)
@ -290,7 +289,8 @@ class TestUtils(LoggedTestCase):
self.assertEqual(utils.strftime(d, '%d/%m/%Y'), '29/08/2012') self.assertEqual(utils.strftime(d, '%d/%m/%Y'), '29/08/2012')
# RFC 3339 # RFC 3339
self.assertEqual(utils.strftime(d, '%Y-%m-%dT%H:%M:%SZ'),'2012-08-29T00:00:00Z') self.assertEqual(
utils.strftime(d, '%Y-%m-%dT%H:%M:%SZ'), '2012-08-29T00:00:00Z')
# % escaped # % escaped
self.assertEqual(utils.strftime(d, '%d%%%m%%%y'), '29%08%12') self.assertEqual(utils.strftime(d, '%d%%%m%%%y'), '29%08%12')
@ -316,7 +316,6 @@ class TestUtils(LoggedTestCase):
d = utils.SafeDatetime(2012, 8, 9) d = utils.SafeDatetime(2012, 8, 9)
self.assertEqual(utils.strftime(d, '%-d/%-m/%y'), '9/8/12') self.assertEqual(utils.strftime(d, '%-d/%-m/%y'), '9/8/12')
# test the output of utils.strftime in a different locale # test the output of utils.strftime in a different locale
# Turkish locale # Turkish locale
@unittest.skipUnless(locale_available('tr_TR.UTF-8') or @unittest.skipUnless(locale_available('tr_TR.UTF-8') or
@ -340,16 +339,15 @@ class TestUtils(LoggedTestCase):
# with text # with text
self.assertEqual(utils.strftime(d, 'Yayınlanma tarihi: %A, %d %B %Y'), self.assertEqual(utils.strftime(d, 'Yayınlanma tarihi: %A, %d %B %Y'),
'Yayınlanma tarihi: Çarşamba, 29 Ağustos 2012') 'Yayınlanma tarihi: Çarşamba, 29 Ağustos 2012')
# non-ascii format candidate (someone might pass it... for some reason) # non-ascii format candidate (someone might pass it... for some reason)
self.assertEqual(utils.strftime(d, '%Y yılında %üretim artışı'), self.assertEqual(utils.strftime(d, '%Y yılında %üretim artışı'),
'2012 yılında %üretim artışı') '2012 yılında %üretim artışı')
# restore locale back # restore locale back
locale.setlocale(locale.LC_ALL, old_locale) locale.setlocale(locale.LC_ALL, old_locale)
# test the output of utils.strftime in a different locale # test the output of utils.strftime in a different locale
# French locale # French locale
@unittest.skipUnless(locale_available('fr_FR.UTF-8') or @unittest.skipUnless(locale_available('fr_FR.UTF-8') or
@ -374,23 +372,26 @@ class TestUtils(LoggedTestCase):
# with text # with text
self.assertEqual(utils.strftime(d, 'Écrit le %d %B %Y'), self.assertEqual(utils.strftime(d, 'Écrit le %d %B %Y'),
'Écrit le 29 août 2012') 'Écrit le 29 août 2012')
# non-ascii format candidate (someone might pass it... for some reason) # non-ascii format candidate (someone might pass it... for some reason)
self.assertEqual(utils.strftime(d, '%écrits en %Y'), self.assertEqual(utils.strftime(d, '%écrits en %Y'),
'%écrits en 2012') '%écrits en 2012')
# restore locale back # restore locale back
locale.setlocale(locale.LC_ALL, old_locale) locale.setlocale(locale.LC_ALL, old_locale)
def test_maybe_pluralize(self): def test_maybe_pluralize(self):
self.assertEqual(utils.maybe_pluralize(0, 'Article', 'Articles'), '0 Articles') self.assertEqual(
self.assertEqual(utils.maybe_pluralize(1, 'Article', 'Articles'), '1 Article') utils.maybe_pluralize(0, 'Article', 'Articles'), '0 Articles')
self.assertEqual(utils.maybe_pluralize(2, 'Article', 'Articles'), '2 Articles') self.assertEqual(
utils.maybe_pluralize(1, 'Article', 'Articles'), '1 Article')
self.assertEqual(
utils.maybe_pluralize(2, 'Article', 'Articles'), '2 Articles')
class TestCopy(unittest.TestCase): class TestCopy(unittest.TestCase):
'''Tests the copy utility''' '''Tests the copy utility'''
def setUp(self): def setUp(self):
@ -475,6 +476,7 @@ class TestCopy(unittest.TestCase):
class TestDateFormatter(unittest.TestCase): class TestDateFormatter(unittest.TestCase):
'''Tests that the output of DateFormatter jinja filter is same as '''Tests that the output of DateFormatter jinja filter is same as
utils.strftime''' utils.strftime'''
@ -491,35 +493,36 @@ class TestDateFormatter(unittest.TestCase):
template_file.write('date = {{ date|strftime("%A, %d %B %Y") }}') template_file.write('date = {{ date|strftime("%A, %d %B %Y") }}')
self.date = utils.SafeDatetime(2012, 8, 29) self.date = utils.SafeDatetime(2012, 8, 29)
def tearDown(self): def tearDown(self):
shutil.rmtree(self.temp_content) shutil.rmtree(self.temp_content)
shutil.rmtree(self.temp_output) shutil.rmtree(self.temp_output)
# reset locale to default # reset locale to default
locale.setlocale(locale.LC_ALL, '') locale.setlocale(locale.LC_ALL, '')
@unittest.skipUnless(locale_available('fr_FR.UTF-8') or @unittest.skipUnless(locale_available('fr_FR.UTF-8') or
locale_available('French'), locale_available('French'),
'French locale needed') 'French locale needed')
def test_french_strftime(self): def test_french_strftime(self):
# This test tries to reproduce an issue that occurred with python3.3 under macos10 only # This test tries to reproduce an issue that occurred with python3.3
# under macos10 only
if platform == 'win32': if platform == 'win32':
locale.setlocale(locale.LC_ALL, str('French')) locale.setlocale(locale.LC_ALL, str('French'))
else: else:
locale.setlocale(locale.LC_ALL, str('fr_FR.UTF-8')) locale.setlocale(locale.LC_ALL, str('fr_FR.UTF-8'))
date = utils.SafeDatetime(2014,8,14) date = utils.SafeDatetime(2014, 8, 14)
# we compare the lower() dates since macos10 returns "Jeudi" for %A whereas linux reports "jeudi" # we compare the lower() dates since macos10 returns "Jeudi" for %A
self.assertEqual( u'jeudi, 14 août 2014', utils.strftime(date, date_format="%A, %d %B %Y").lower() ) # whereas linux reports "jeudi"
self.assertEqual(u'jeudi, 14 août 2014', utils.strftime(
date, date_format="%A, %d %B %Y").lower())
df = utils.DateFormatter() df = utils.DateFormatter()
self.assertEqual( u'jeudi, 14 août 2014', df(date, date_format="%A, %d %B %Y").lower() ) self.assertEqual(
u'jeudi, 14 août 2014', df(date, date_format="%A, %d %B %Y").lower())
# Let us now set the global locale to C: # Let us now set the global locale to C:
locale.setlocale(locale.LC_ALL, str('C')) locale.setlocale(locale.LC_ALL, str('C'))
# DateFormatter should still work as expected since it is the whole point of DateFormatter # DateFormatter should still work as expected since it is the whole point of DateFormatter
# (This is where pre-2014/4/15 code fails on macos10) # (This is where pre-2014/4/15 code fails on macos10)
df_date = df(date, date_format="%A, %d %B %Y").lower() df_date = df(date, date_format="%A, %d %B %Y").lower()
self.assertEqual( u'jeudi, 14 août 2014', df_date ) self.assertEqual(u'jeudi, 14 août 2014', df_date)
@unittest.skipUnless(locale_available('fr_FR.UTF-8') or @unittest.skipUnless(locale_available('fr_FR.UTF-8') or
locale_available('French'), locale_available('French'),
@ -530,8 +533,8 @@ class TestDateFormatter(unittest.TestCase):
else: else:
locale_string = 'fr_FR.UTF-8' locale_string = 'fr_FR.UTF-8'
settings = read_settings( settings = read_settings(
override = {'LOCALE': locale_string, override={'LOCALE': locale_string,
'TEMPLATE_PAGES': {'template/source.html': 'TEMPLATE_PAGES': {'template/source.html':
'generated/file.html'}}) 'generated/file.html'}})
generator = TemplatePagesGenerator( generator = TemplatePagesGenerator(
@ -543,7 +546,7 @@ class TestDateFormatter(unittest.TestCase):
generator.generate_output(writer) generator.generate_output(writer)
output_path = os.path.join( output_path = os.path.join(
self.temp_output, 'generated', 'file.html') self.temp_output, 'generated', 'file.html')
# output file has been generated # output file has been generated
self.assertTrue(os.path.exists(output_path)) self.assertTrue(os.path.exists(output_path))
@ -553,7 +556,6 @@ class TestDateFormatter(unittest.TestCase):
self.assertEqual(output_file, self.assertEqual(output_file,
utils.strftime(self.date, 'date = %A, %d %B %Y')) utils.strftime(self.date, 'date = %A, %d %B %Y'))
@unittest.skipUnless(locale_available('tr_TR.UTF-8') or @unittest.skipUnless(locale_available('tr_TR.UTF-8') or
locale_available('Turkish'), locale_available('Turkish'),
'Turkish locale needed') 'Turkish locale needed')
@ -563,9 +565,9 @@ class TestDateFormatter(unittest.TestCase):
else: else:
locale_string = 'tr_TR.UTF-8' locale_string = 'tr_TR.UTF-8'
settings = read_settings( settings = read_settings(
override = {'LOCALE': locale_string, override={'LOCALE': locale_string,
'TEMPLATE_PAGES': {'template/source.html': 'TEMPLATE_PAGES': {'template/source.html':
'generated/file.html'}}) 'generated/file.html'}})
generator = TemplatePagesGenerator( generator = TemplatePagesGenerator(
{'date': self.date}, settings, {'date': self.date}, settings,
@ -576,7 +578,7 @@ class TestDateFormatter(unittest.TestCase):
generator.generate_output(writer) generator.generate_output(writer)
output_path = os.path.join( output_path = os.path.join(
self.temp_output, 'generated', 'file.html') self.temp_output, 'generated', 'file.html')
# output file has been generated # output file has been generated
self.assertTrue(os.path.exists(output_path)) self.assertTrue(os.path.exists(output_path))

View file

@ -14,6 +14,7 @@ logger = logging.getLogger(__name__)
@python_2_unicode_compatible @python_2_unicode_compatible
@functools.total_ordering @functools.total_ordering
class URLWrapper(object): class URLWrapper(object):
def __init__(self, name, settings): def __init__(self, name, settings):
self.settings = settings self.settings = settings
self._name = name self._name = name
@ -105,7 +106,7 @@ class URLWrapper(object):
return value.format(**self.as_dict()) return value.format(**self.as_dict())
page_name = property(functools.partial(_from_settings, key='URL', page_name = property(functools.partial(_from_settings, key='URL',
get_page_name=True)) 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'))
@ -115,6 +116,7 @@ class Category(URLWrapper):
class Tag(URLWrapper): class Tag(URLWrapper):
def __init__(self, name, *args, **kwargs): def __init__(self, name, *args, **kwargs):
super(Tag, self).__init__(name.strip(), *args, **kwargs) super(Tag, self).__init__(name.strip(), *args, **kwargs)

View file

@ -92,6 +92,7 @@ def strftime(date, date_format):
class SafeDatetime(datetime.datetime): class SafeDatetime(datetime.datetime):
'''Subclass of datetime that works with utf-8 format strings on PY2''' '''Subclass of datetime that works with utf-8 format strings on PY2'''
def strftime(self, fmt, safe=True): def strftime(self, fmt, safe=True):
@ -103,6 +104,7 @@ class SafeDatetime(datetime.datetime):
class DateFormatter(object): class DateFormatter(object):
'''A date formatter object used as a jinja filter '''A date formatter object used as a jinja filter
Uses the `strftime` implementation and makes sure jinja uses the locale Uses the `strftime` implementation and makes sure jinja uses the locale
@ -145,12 +147,14 @@ def python_2_unicode_compatible(klass):
class memoized(object): class memoized(object):
"""Function decorator to cache return values. """Function decorator to cache return values.
If called later with the same arguments, the cached value is returned If called later with the same arguments, the cached value is returned
(not reevaluated). (not reevaluated).
""" """
def __init__(self, func): def __init__(self, func):
self.func = func self.func = func
self.cache = {} self.cache = {}
@ -201,7 +205,7 @@ def deprecated_attribute(old, new, since=None, remove=None, doc=None):
message.append('. Use {} instead.'.format(new)) message.append('. Use {} instead.'.format(new))
logger.warning(''.join(message)) logger.warning(''.join(message))
logger.debug(''.join( logger.debug(''.join(
six.text_type(x) for x in traceback.format_stack())) six.text_type(x) for x in traceback.format_stack()))
def fget(self): def fget(self):
_warn() _warn()
@ -224,7 +228,7 @@ def get_date(string):
""" """
string = re.sub(' +', ' ', string) string = re.sub(' +', ' ', string)
default = SafeDatetime.now().replace(hour=0, minute=0, default = SafeDatetime.now().replace(hour=0, minute=0,
second=0, microsecond=0) second=0, microsecond=0)
try: try:
return dateutil.parser.parse(string, default=default) return dateutil.parser.parse(string, default=default)
except (TypeError, ValueError): except (TypeError, ValueError):
@ -319,12 +323,12 @@ def copy(source, destination, ignores=None):
for src_dir, subdirs, others in os.walk(source_): for src_dir, subdirs, others in os.walk(source_):
dst_dir = os.path.join(destination_, dst_dir = os.path.join(destination_,
os.path.relpath(src_dir, source_)) os.path.relpath(src_dir, source_))
subdirs[:] = (s for s in subdirs if not any(fnmatch.fnmatch(s, i) subdirs[:] = (s for s in subdirs if not any(fnmatch.fnmatch(s, i)
for i in ignores)) for i in ignores))
others[:] = (o for o in others if not any(fnmatch.fnmatch(o, i) others[:] = (o for o in others if not any(fnmatch.fnmatch(o, i)
for i in ignores)) for i in ignores))
if not os.path.isdir(dst_dir): if not os.path.isdir(dst_dir):
logger.info('Creating directory %s', dst_dir) logger.info('Creating directory %s', dst_dir)
@ -341,6 +345,7 @@ def copy(source, destination, ignores=None):
logger.warning('Skipped copy %s (not a file or directory) to %s', logger.warning('Skipped copy %s (not a file or directory) to %s',
src_path, dst_path) src_path, dst_path)
def clean_output_dir(path, retention): def clean_output_dir(path, retention):
"""Remove all files from output directory except those in retention list""" """Remove all files from output directory except those in retention list"""
@ -367,7 +372,7 @@ def clean_output_dir(path, retention):
logger.debug("Deleted directory %s", file) logger.debug("Deleted directory %s", file)
except Exception as e: except Exception as e:
logger.error("Unable to delete directory %s; %s", logger.error("Unable to delete directory %s; %s",
file, e) file, e)
elif os.path.isfile(file) or os.path.islink(file): elif os.path.isfile(file) or os.path.islink(file):
try: try:
os.remove(file) os.remove(file)
@ -509,9 +514,9 @@ def process_translations(content_list, order_by=None):
items = list(items) items = list(items)
# items with `translation` metadata will be used as translations… # items with `translation` metadata will be used as translations…
default_lang_items = list(filter( default_lang_items = list(filter(
lambda i: i.metadata.get('translation', 'false').lower() lambda i: i.metadata.get('translation', 'false').lower()
== 'false', == 'false',
items)) items))
# …unless all items with that slug are translations # …unless all items with that slug are translations
if not default_lang_items: if not default_lang_items:
default_lang_items = items default_lang_items = items
@ -522,13 +527,13 @@ def process_translations(content_list, order_by=None):
len_ = len(lang_items) len_ = len(lang_items)
if len_ > 1: if len_ > 1:
logger.warning('There are %s variants of "%s" with lang %s', logger.warning('There are %s variants of "%s" with lang %s',
len_, slug, lang) len_, slug, lang)
for x in lang_items: for x in lang_items:
logger.warning('\t%s', x.source_path) logger.warning('\t%s', x.source_path)
# find items with default language # find items with default language
default_lang_items = list(filter(attrgetter('in_default_lang'), default_lang_items = list(filter(attrgetter('in_default_lang'),
default_lang_items)) default_lang_items))
# if there is no article with default language, take an other one # if there is no article with default language, take an other one
if not default_lang_items: if not default_lang_items:
@ -536,10 +541,10 @@ def process_translations(content_list, order_by=None):
if not slug: if not slug:
logger.warning( logger.warning(
'empty slug for %s. ' 'empty slug for %s. '
'You can fix this by adding a title or a slug to your ' 'You can fix this by adding a title or a slug to your '
'content', 'content',
default_lang_items[0].source_path) default_lang_items[0].source_path)
index.extend(default_lang_items) index.extend(default_lang_items)
translations.extend([x for x in items if x not in default_lang_items]) translations.extend([x for x in items if x not in default_lang_items])
for a in items: for a in items:
@ -568,10 +573,10 @@ def process_translations(content_list, order_by=None):
reverse=order_reversed) reverse=order_reversed)
except AttributeError: except AttributeError:
logger.warning('There is no "%s" attribute in the item ' logger.warning('There is no "%s" attribute in the item '
'metadata. Defaulting to slug order.', order_by) 'metadata. Defaulting to slug order.', order_by)
else: else:
logger.warning('Invalid *_ORDER_BY setting (%s).' logger.warning('Invalid *_ORDER_BY setting (%s).'
'Valid options are strings and functions.', order_by) 'Valid options are strings and functions.', order_by)
return index, translations return index, translations
@ -590,7 +595,7 @@ def folder_watcher(path, extensions, ignores=[]):
for f in files: for f in files:
if (f.endswith(tuple(extensions)) and if (f.endswith(tuple(extensions)) and
not any(fnmatch.fnmatch(f, ignore) for ignore in ignores)): not any(fnmatch.fnmatch(f, ignore) for ignore in ignores)):
try: try:
yield os.stat(os.path.join(root, f)).st_mtime yield os.stat(os.path.join(root, f)).st_mtime
except OSError as e: except OSError as e:

View file

@ -119,10 +119,10 @@ class Writer(object):
feed.write(fp, 'utf-8') feed.write(fp, 'utf-8')
logger.info('Writing %s', complete_path) logger.info('Writing %s', complete_path)
signals.feed_written.send(complete_path, context=context, feed=feed) signals.feed_written.send(
complete_path, context=context, feed=feed)
return feed return feed
def write_file(self, name, template, context, relative_urls=False, def write_file(self, name, template, context, relative_urls=False,
paginated=None, override_output=False, **kwargs): paginated=None, override_output=False, **kwargs):
"""Render the template and write the file. """Render the template and write the file.
@ -140,8 +140,8 @@ class Writer(object):
""" """
if name is False or name == "" or\ if name is False or name == "" or\
not is_selected_for_writing(self.settings,\ not is_selected_for_writing(self.settings,
os.path.join(self.output_path, name)): os.path.join(self.output_path, name)):
return return
elif not name: elif not name:
# other stuff, just return for now # other stuff, just return for now
@ -169,7 +169,8 @@ class Writer(object):
def _get_localcontext(context, name, kwargs, relative_urls): def _get_localcontext(context, name, kwargs, relative_urls):
localcontext = context.copy() localcontext = context.copy()
localcontext['localsiteurl'] = localcontext.get('localsiteurl', None) localcontext['localsiteurl'] = localcontext.get(
'localsiteurl', None)
if relative_urls: if relative_urls:
relative_url = path_to_url(get_relative_path(name)) relative_url = path_to_url(get_relative_path(name))
localcontext['SITEURL'] = relative_url localcontext['SITEURL'] = relative_url
@ -201,11 +202,13 @@ class Writer(object):
'%s_previous_page' % key: previous_page, '%s_previous_page' % key: previous_page,
'%s_next_page' % key: next_page}) '%s_next_page' % key: next_page})
localcontext = _get_localcontext(context, page.save_as, paginated_kwargs, relative_urls) localcontext = _get_localcontext(
context, page.save_as, paginated_kwargs, relative_urls)
_write_file(template, localcontext, self.output_path, _write_file(template, localcontext, self.output_path,
page.save_as, override_output) page.save_as, override_output)
else: else:
# no pagination # no pagination
localcontext = _get_localcontext(context, name, kwargs, relative_urls) localcontext = _get_localcontext(
context, name, kwargs, relative_urls)
_write_file(template, localcontext, self.output_path, name, _write_file(template, localcontext, self.output_path, name,
override_output) override_output)