1
0
Fork 0
forked from github/pelican

Merge pull request #667 from wking/page-path

Consolidate Path.filename and and StaticContent.filepath as `source_path`.
This commit is contained in:
Bruno Binet 2013-01-18 07:30:03 -08:00
commit 6233f5a409
12 changed files with 213 additions and 130 deletions

View file

@ -15,7 +15,7 @@ from sys import platform, stdin
from pelican.settings import _DEFAULT_CONFIG
from pelican.utils import (slugify, truncate_html_words, memoized,
python_2_unicode_compatible)
python_2_unicode_compatible, deprecated_attribute)
from pelican import signals
import pelican.utils
@ -31,8 +31,12 @@ class Page(object):
mandatory_properties = ('title',)
default_template = 'page'
@deprecated_attribute(old='filename', new='source_path', since=(3, 2, 0))
def filename():
return None
def __init__(self, content, metadata=None, settings=None,
filename=None, context=None):
source_path=None, context=None):
# init parameters
if not metadata:
metadata = {}
@ -75,8 +79,8 @@ class Page(object):
if not hasattr(self, 'slug') and hasattr(self, 'title'):
self.slug = slugify(self.title)
if filename:
self.filename = filename
if source_path:
self.source_path = source_path
# manage the date format
if not hasattr(self, 'date_format'):
@ -160,8 +164,8 @@ class Page(object):
if value.startswith('/'):
value = value[1:]
else:
# relative to the filename of this content
value = self.get_relative_filename(
# relative to the source path of this content
value = self.get_relative_source_path(
os.path.join(self.relative_dir, value)
)
@ -215,24 +219,25 @@ class Page(object):
else:
return self.default_template
def get_relative_filename(self, filename=None):
def get_relative_source_path(self, source_path=None):
"""Return the relative path (from the content path) to the given
filename.
source_path.
If no filename is specified, use the filename of this content object.
If no source path is specified, use the source path of this
content object.
"""
if not filename:
filename = self.filename
if not source_path:
source_path = self.source_path
return os.path.relpath(
os.path.abspath(os.path.join(self.settings['PATH'], filename)),
os.path.abspath(os.path.join(self.settings['PATH'], source_path)),
os.path.abspath(self.settings['PATH'])
)
@property
def relative_dir(self):
return os.path.dirname(os.path.relpath(
os.path.abspath(self.filename),
os.path.abspath(self.source_path),
os.path.abspath(self.settings['PATH']))
)
@ -300,16 +305,20 @@ class Author(URLWrapper):
@python_2_unicode_compatible
class StaticContent(object):
@deprecated_attribute(old='filepath', new='source_path', since=(3, 2, 0))
def filepath():
return None
def __init__(self, src, dst=None, settings=None):
if not settings:
settings = copy.deepcopy(_DEFAULT_CONFIG)
self.src = src
self.url = dst or src
self.filepath = os.path.join(settings['PATH'], src)
self.source_path = os.path.join(settings['PATH'], src)
self.save_as = os.path.join(settings['OUTPUT_PATH'], self.url)
def __str__(self):
return self.filepath
return self.source_path
def is_valid_content(content, f):

View file

@ -108,8 +108,8 @@ class Generator(object):
files.append(os.sep.join((root, f)))
return files
def add_filename(self, content):
location = os.path.relpath(os.path.abspath(content.filename),
def add_source_path(self, content):
location = os.path.relpath(os.path.abspath(content.source_path),
os.path.abspath(self.path))
self.context['filenames'][location] = content
@ -352,11 +352,11 @@ class ArticlesGenerator(Generator):
signals.article_generate_context.send(self, metadata=metadata)
article = Article(content, metadata, settings=self.settings,
filename=f, context=self.context)
source_path=f, context=self.context)
if not is_valid_content(article, f):
continue
self.add_filename(article)
self.add_source_path(article)
if article.status == "published":
if hasattr(article, 'tags'):
@ -455,11 +455,11 @@ class PagesGenerator(Generator):
continue
signals.pages_generate_context.send(self, metadata=metadata)
page = Page(content, metadata, settings=self.settings,
filename=f, context=self.context)
source_path=f, context=self.context)
if not is_valid_content(page, f):
continue
self.add_filename(page)
self.add_source_path(page)
if page.status == "published":
all_pages.append(page)
@ -520,8 +520,8 @@ class StaticGenerator(Generator):
# copy all StaticContent files
for sc in self.staticfiles:
mkdir_p(os.path.dirname(sc.save_as))
shutil.copy(sc.filepath, sc.save_as)
logger.info('copying %s to %s' % (sc.filepath, sc.save_as))
shutil.copy(sc.source_path, sc.save_as)
logger.info('copying {} to {}'.format(sc.source_path, sc.save_as))
class PdfGenerator(Generator):
@ -544,11 +544,11 @@ class PdfGenerator(Generator):
raise Exception("unable to find rst2pdf")
def _create_pdf(self, obj, output_path):
if obj.filename.endswith(".rst"):
if obj.source_path.endswith('.rst'):
filename = obj.slug + ".pdf"
output_pdf = os.path.join(output_path, filename)
# print "Generating pdf for", obj.filename, " in ", output_pdf
with open(obj.filename) as f:
# print('Generating pdf for', obj.source_path, 'in', output_pdf)
with open(obj.source_path) as f:
self.pdfcreator.createPdf(text=f.read(), output=output_pdf)
logger.info(' [ok] writing %s' % output_pdf)
@ -578,9 +578,9 @@ class SourceFileGenerator(Generator):
self.output_extension = self.settings['OUTPUT_SOURCES_EXTENSION']
def _create_source(self, obj, output_path):
filename = os.path.splitext(obj.save_as)[0]
dest = os.path.join(output_path, filename + self.output_extension)
copy('', obj.filename, dest)
output_path = os.path.splitext(obj.save_as)[0]
dest = os.path.join(output_path, output_path + self.output_extension)
copy('', obj.source_path, dest)
def generate_output(self, writer=None):
logger.info(' Generating source files...')

View file

@ -109,20 +109,20 @@ class RstReader(Reader):
output[name] = self.process_metadata(name, value)
return output
def _get_publisher(self, filename):
def _get_publisher(self, source_path):
extra_params = {'initial_header_level': '2'}
pub = docutils.core.Publisher(
destination_class=docutils.io.StringOutput)
pub.set_components('standalone', 'restructuredtext', 'html')
pub.writer.translator_class = PelicanHTMLTranslator
pub.process_programmatic_settings(None, extra_params, None)
pub.set_source(source_path=filename)
pub.set_source(source_path=source_path)
pub.publish()
return pub
def read(self, filename):
def read(self, source_path):
"""Parses restructured text"""
pub = self._get_publisher(filename)
pub = self._get_publisher(source_path)
parts = pub.writer.parts
content = parts.get('body')
@ -151,9 +151,9 @@ class MarkdownReader(Reader):
output[name] = self.process_metadata(name, value[0])
return output
def read(self, filename):
def read(self, source_path):
"""Parse content and metadata of markdown files"""
text = pelican_open(filename)
text = pelican_open(source_path)
md = Markdown(extensions=set(self.extensions + ['meta']))
content = md.convert(text)
@ -165,9 +165,9 @@ class HtmlReader(Reader):
file_extensions = ['html', 'htm']
_re = re.compile('\<\!\-\-\#\s?[A-z0-9_-]*\s?\:s?[A-z0-9\s_-]*\s?\-\-\>')
def read(self, filename):
def read(self, source_path):
"""Parse content and metadata of (x)HTML files"""
with open(filename) as content:
with open(source_path) as content:
metadata = {'title': 'unnamed'}
for i in self._re.findall(content):
key = i.split(':')[0][5:].strip()
@ -183,10 +183,10 @@ class AsciiDocReader(Reader):
file_extensions = ['asc']
default_options = ["--no-header-footer", "-a newline=\\n"]
def read(self, filename):
def read(self, source_path):
"""Parse content and metadata of asciidoc files"""
from cStringIO import StringIO
text = StringIO(pelican_open(filename))
text = StringIO(pelican_open(source_path))
content = StringIO()
ad = AsciiDocAPI()
@ -216,14 +216,14 @@ for cls in Reader.__subclasses__():
_EXTENSIONS[ext] = cls
def read_file(filename, fmt=None, settings=None):
def read_file(path, fmt=None, settings=None):
"""Return a reader object using the given format."""
base, ext = os.path.splitext(os.path.basename(filename))
base, ext = os.path.splitext(os.path.basename(path))
if not fmt:
fmt = ext[1:]
if fmt not in _EXTENSIONS:
raise TypeError('Pelican does not know how to parse %s' % filename)
raise TypeError('Pelican does not know how to parse {}'.format(path))
reader = _EXTENSIONS[fmt](settings)
settings_key = '%s_EXTENSIONS' % fmt.upper()
@ -234,7 +234,7 @@ def read_file(filename, fmt=None, settings=None):
if not reader.enabled:
raise ValueError("Missing dependencies for %s" % fmt)
content, metadata = reader.read(filename)
content, metadata = reader.read(path)
# eventually filter the content with typogrify if asked so
if settings and settings.get('TYPOGRIFY'):
@ -242,9 +242,9 @@ def read_file(filename, fmt=None, settings=None):
content = typogrify(content)
metadata['title'] = typogrify(metadata['title'])
filename_metadata = settings and settings.get('FILENAME_METADATA')
if filename_metadata:
match = re.match(filename_metadata, base)
file_metadata = settings and settings.get('FILENAME_METADATA')
if file_metadata:
match = re.match(file_metadata, base)
if match:
# .items() for py3k compat.
for k, v in match.groupdict().items():

View file

@ -84,15 +84,15 @@ _DEFAULT_CONFIG = {'PATH': '.',
}
def read_settings(filename=None, override=None):
if filename:
local_settings = get_settings_from_file(filename)
def read_settings(path=None, override=None):
if path:
local_settings = get_settings_from_file(path)
# Make the paths relative to the settings file
for p in ['PATH', 'OUTPUT_PATH', 'THEME']:
if p in local_settings and local_settings[p] is not None \
and not isabs(local_settings[p]):
absp = os.path.abspath(os.path.normpath(os.path.join(
os.path.dirname(filename), local_settings[p])))
os.path.dirname(path), local_settings[p])))
if p != 'THEME' or os.path.exists(absp):
local_settings[p] = absp
else:
@ -116,14 +116,14 @@ def get_settings_from_module(module=None, default_settings=_DEFAULT_CONFIG):
return context
def get_settings_from_file(filename, default_settings=_DEFAULT_CONFIG):
def get_settings_from_file(path, default_settings=_DEFAULT_CONFIG):
"""
Load settings from a file path, returning a dict.
"""
name = os.path.basename(filename).rpartition(".")[0]
module = imp.load_source(name, filename)
name = os.path.basename(path).rpartition('.')[0]
module = imp.load_source(name, path)
return get_settings_from_module(module, default_settings=default_settings)

View file

@ -6,6 +6,7 @@ import os
import re
import pytz
import shutil
import traceback
import logging
import errno
import locale
@ -122,6 +123,48 @@ class memoized(object):
'''Support instance methods.'''
return partial(self.__call__, obj)
def deprecated_attribute(old, new, since=None, remove=None, doc=None):
"""Attribute deprecation decorator for gentle upgrades
For example:
class MyClass (object):
@deprecated_attribute(
old='abc', new='xyz', since=(3, 2, 0), remove=(4, 1, 3))
def abc(): return None
def __init__(self):
xyz = 5
Note that the decorator needs a dummy method to attach to, but the
content of the dummy method is ignored.
"""
def _warn():
version = '.'.join(six.text_type(x) for x in since)
message = ['{} has been deprecated since {}'.format(old, version)]
if remove:
version = '.'.join(six.text_type(x) for x in remove)
message.append(
' and will be removed by version {}'.format(version))
message.append('. Use {} instead.'.format(new))
logger.warning(''.join(message))
logger.debug(''.join(
six.text_type(x) for x in traceback.format_stack()))
def fget(self):
_warn()
return getattr(self, new)
def fset(self, value):
_warn()
setattr(self, new, value)
def decorator(dummy):
return property(fget=fget, fset=fset, doc=doc)
return decorator
def get_date(string):
"""Return a datetime object from a string.
@ -141,9 +184,9 @@ def get_date(string):
raise ValueError("'%s' is not a valid date" % string)
def pelican_open(filename):
def pelican_open(path):
"""Open a file and return it's content"""
return open(filename, encoding='utf-8').read()
return open(path, encoding='utf-8').read()
def slugify(value):
@ -245,9 +288,9 @@ def clean_output_dir(path):
logger.error("Unable to delete %s, file type unknown" % file)
def get_relative_path(filename):
"""Return the relative path from the given filename to the root path."""
nslashes = filename.count('/')
def get_relative_path(path):
"""Return the relative path from the given path to the root path."""
nslashes = path.count('/')
if nslashes == 0:
return '.'
else:
@ -344,15 +387,16 @@ def process_translations(content_list):
if len_ > 1:
logger.warning('there are %s variants of "%s"' % (len_, slug))
for x in default_lang_items:
logger.warning(' %s' % x.filename)
logger.warning(' {}'.format(x.source_path))
elif len_ == 0:
default_lang_items = items[:1]
if not slug:
msg = 'empty slug for %r. ' % default_lang_items[0].filename\
+ 'You can fix this by adding a title or a slug to your '\
+ 'content'
logger.warning(msg)
logger.warning((
'empty slug for {!r}. '
'You can fix this by adding a title or a slug to your '
'content'
).format(default_lang_items[0].source_path))
index.extend(default_lang_items)
translations.extend([x for x in items if x not in default_lang_items])
for a in items:
@ -388,14 +432,14 @@ def files_changed(path, extensions):
FILENAMES_MTIMES = defaultdict(int)
def file_changed(filename):
mtime = os.stat(filename).st_mtime
if FILENAMES_MTIMES[filename] == 0:
FILENAMES_MTIMES[filename] = mtime
def file_changed(path):
mtime = os.stat(path).st_mtime
if FILENAMES_MTIMES[path] == 0:
FILENAMES_MTIMES[path] = mtime
return False
else:
if mtime > FILENAMES_MTIMES[filename]:
FILENAMES_MTIMES[filename] = mtime
if mtime > FILENAMES_MTIMES[path]:
FILENAMES_MTIMES[path] = mtime
return True
return False

View file

@ -46,23 +46,23 @@ class Writer(object):
pubdate=set_date_tzinfo(item.date,
self.settings.get('TIMEZONE', None)))
def write_feed(self, elements, context, filename=None, feed_type='atom'):
def write_feed(self, elements, context, path=None, feed_type='atom'):
"""Generate a feed with the list of articles provided
Return the feed. If no output_path or filename is specified, just
Return the feed. If no path or output_path is specified, just
return the feed object.
:param elements: the articles to put on the feed.
:param context: the context to get the feed metadata.
:param filename: the filename to output.
:param path: the path to output.
:param feed_type: the feed type to use (atom or rss)
"""
old_locale = locale.setlocale(locale.LC_ALL)
locale.setlocale(locale.LC_ALL, str('C'))
try:
self.site_url = context.get('SITEURL', get_relative_path(filename))
self.site_url = context.get('SITEURL', get_relative_path(path))
self.feed_domain = context.get('FEED_DOMAIN')
self.feed_url = '%s/%s' % (self.feed_domain, filename)
self.feed_url = '{}/{}'.format(self.feed_domain, path)
feed = self._create_new_feed(feed_type, context)
@ -72,8 +72,8 @@ class Writer(object):
for i in range(max_items):
self._add_item_to_the_feed(feed, elements[i])
if filename:
complete_path = os.path.join(self.output_path, filename)
if path:
complete_path = os.path.join(self.output_path, path)
try:
os.makedirs(os.path.dirname(complete_path))
except Exception:
@ -114,14 +114,14 @@ class Writer(object):
output = template.render(localcontext)
finally:
locale.setlocale(locale.LC_ALL, old_locale)
filename = os.sep.join((output_path, name))
path = os.path.join(output_path, name)
try:
os.makedirs(os.path.dirname(filename))
os.makedirs(os.path.dirname(path))
except Exception:
pass
with open(filename, 'w', encoding='utf-8') as f:
with open(path, 'w', encoding='utf-8') as f:
f.write(output)
logger.info('writing %s' % filename)
logger.info('writing {}'.format(path))
localcontext = context.copy()
if relative_urls: