mirror of
https://github.com/getpelican/pelican.git
synced 2025-10-15 20:28:56 +02:00
Merge remote-tracking branch 'origin/master' into fix-functional-tests
Conflicts: tests/support.py
This commit is contained in:
commit
fe9388a7a4
47 changed files with 1711 additions and 184 deletions
|
|
@ -5,11 +5,13 @@ import time
|
|||
import logging
|
||||
import argparse
|
||||
|
||||
from pelican import signals
|
||||
|
||||
from pelican.generators import (ArticlesGenerator, PagesGenerator,
|
||||
StaticGenerator, PdfGenerator, LessCSSGenerator)
|
||||
from pelican.log import init
|
||||
from pelican.settings import read_settings, _DEFAULT_CONFIG
|
||||
from pelican.utils import clean_output_dir, files_changed
|
||||
from pelican.utils import clean_output_dir, files_changed, file_changed
|
||||
from pelican.writers import Writer
|
||||
|
||||
__major__ = 3
|
||||
|
|
@ -22,7 +24,7 @@ logger = logging.getLogger(__name__)
|
|||
|
||||
class Pelican(object):
|
||||
def __init__(self, settings=None, path=None, theme=None, output_path=None,
|
||||
markup=None, delete_outputdir=False):
|
||||
markup=None, delete_outputdir=False, plugin_path=None):
|
||||
"""Read the settings, and performs some checks on the environment
|
||||
before doing anything else.
|
||||
"""
|
||||
|
|
@ -58,6 +60,20 @@ class Pelican(object):
|
|||
else:
|
||||
raise Exception("Impossible to find the theme %s" % theme)
|
||||
|
||||
self.init_plugins()
|
||||
signals.initialized.send(self)
|
||||
|
||||
def init_plugins(self):
|
||||
self.plugins = self.settings['PLUGINS']
|
||||
for plugin in self.plugins:
|
||||
# if it's a string, then import it
|
||||
if isinstance(plugin, basestring):
|
||||
logger.debug("Loading plugin `{0}' ...".format(plugin))
|
||||
plugin = __import__(plugin, globals(), locals(), 'module')
|
||||
|
||||
logger.debug("Registering plugin `{0}' ...".format(plugin.__name__))
|
||||
plugin.register()
|
||||
|
||||
def _handle_deprecation(self):
|
||||
|
||||
if self.settings.get('CLEAN_URLS', False):
|
||||
|
|
@ -126,15 +142,20 @@ class Pelican(object):
|
|||
|
||||
writer = self.get_writer()
|
||||
|
||||
# pass the assets environment to the generators
|
||||
if self.settings['WEBASSETS']:
|
||||
generators[1].env.assets_environment = generators[0].assets_env
|
||||
generators[2].env.assets_environment = generators[0].assets_env
|
||||
|
||||
for p in generators:
|
||||
if hasattr(p, 'generate_output'):
|
||||
p.generate_output(writer)
|
||||
|
||||
def get_generator_classes(self):
|
||||
generators = [ArticlesGenerator, PagesGenerator, StaticGenerator]
|
||||
generators = [StaticGenerator, ArticlesGenerator, PagesGenerator]
|
||||
if self.settings['PDF_GENERATOR']:
|
||||
generators.append(PdfGenerator)
|
||||
if self.settings['LESS_GENERATOR']: # can be True or PATH to lessc
|
||||
if self.settings['LESS_GENERATOR']: # can be True or PATH to lessc
|
||||
generators.append(LessCSSGenerator)
|
||||
return generators
|
||||
|
||||
|
|
@ -192,11 +213,7 @@ def parse_arguments():
|
|||
return parser.parse_args()
|
||||
|
||||
|
||||
def main():
|
||||
args = parse_arguments()
|
||||
init(args.verbosity)
|
||||
# Split the markup languages only if some have been given. Otherwise,
|
||||
# populate the variable with None.
|
||||
def get_instance(args):
|
||||
markup = [a.strip().lower() for a in args.markup.split(',')]\
|
||||
if args.markup else None
|
||||
|
||||
|
|
@ -208,9 +225,18 @@ def main():
|
|||
module = __import__(module)
|
||||
cls = getattr(module, cls_name)
|
||||
|
||||
return cls(settings, args.path, args.theme, args.output, markup,
|
||||
args.delete_outputdir)
|
||||
|
||||
|
||||
def main():
|
||||
args = parse_arguments()
|
||||
init(args.verbosity)
|
||||
# Split the markup languages only if some have been given. Otherwise,
|
||||
# populate the variable with None.
|
||||
pelican = get_instance(args)
|
||||
|
||||
try:
|
||||
pelican = cls(settings, args.path, args.theme, args.output, markup,
|
||||
args.delete_outputdir)
|
||||
if args.autoreload:
|
||||
while True:
|
||||
try:
|
||||
|
|
@ -222,6 +248,14 @@ def main():
|
|||
if files_changed(pelican.path, pelican.markup) or \
|
||||
files_changed(pelican.theme, ['']):
|
||||
pelican.run()
|
||||
|
||||
# reload also if settings.py changed
|
||||
if file_changed(args.settings):
|
||||
logger.info('%s changed, re-generating' %
|
||||
args.settings)
|
||||
pelican = get_instance(args)
|
||||
pelican.run()
|
||||
|
||||
time.sleep(.5) # sleep to avoid cpu load
|
||||
except KeyboardInterrupt:
|
||||
break
|
||||
|
|
|
|||
|
|
@ -139,7 +139,9 @@ class Page(object):
|
|||
if hasattr(self, '_summary'):
|
||||
return self._summary
|
||||
else:
|
||||
return truncate_html_words(self.content, 50)
|
||||
if self.settings['SUMMARY_MAX_LENGTH']:
|
||||
return truncate_html_words(self.content, self.settings['SUMMARY_MAX_LENGTH'])
|
||||
return self.content
|
||||
|
||||
def _set_summary(self, summary):
|
||||
"""Dummy function"""
|
||||
|
|
|
|||
|
|
@ -17,6 +17,7 @@ from jinja2.exceptions import TemplateNotFound
|
|||
from pelican.contents import Article, Page, Category, is_valid_content
|
||||
from pelican.readers import read_file
|
||||
from pelican.utils import copy, process_translations, open
|
||||
from pelican import signals
|
||||
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
|
@ -42,7 +43,7 @@ class Generator(object):
|
|||
|
||||
simple_loader = FileSystemLoader(os.path.join(theme_path,
|
||||
"themes", "simple", "templates"))
|
||||
self._env = Environment(
|
||||
self.env = Environment(
|
||||
loader=ChoiceLoader([
|
||||
FileSystemLoader(self._templates_path),
|
||||
simple_loader, # implicit inheritance
|
||||
|
|
@ -51,11 +52,11 @@ class Generator(object):
|
|||
extensions=self.settings.get('JINJA_EXTENSIONS', []),
|
||||
)
|
||||
|
||||
logger.debug('template list: {0}'.format(self._env.list_templates()))
|
||||
logger.debug('template list: {0}'.format(self.env.list_templates()))
|
||||
|
||||
# get custom Jinja filters from user settings
|
||||
custom_filters = self.settings.get('JINJA_FILTERS', {})
|
||||
self._env.filters.update(custom_filters)
|
||||
self.env.filters.update(custom_filters)
|
||||
|
||||
def get_template(self, name):
|
||||
"""Return the template by name.
|
||||
|
|
@ -64,7 +65,7 @@ class Generator(object):
|
|||
"""
|
||||
if name not in self._templates:
|
||||
try:
|
||||
self._templates[name] = self._env.get_template(name + '.html')
|
||||
self._templates[name] = self.env.get_template(name + '.html')
|
||||
except TemplateNotFound:
|
||||
raise Exception('[templates] unable to load %s.html from %s' \
|
||||
% (name, self._templates_path))
|
||||
|
|
@ -118,6 +119,7 @@ class ArticlesGenerator(Generator):
|
|||
self.authors = defaultdict(list)
|
||||
super(ArticlesGenerator, self).__init__(*args, **kwargs)
|
||||
self.drafts = []
|
||||
signals.article_generator_init.send(self)
|
||||
|
||||
def generate_feeds(self, writer):
|
||||
"""Generate the feeds from the current context, and output files."""
|
||||
|
|
@ -245,7 +247,9 @@ class ArticlesGenerator(Generator):
|
|||
def generate_context(self):
|
||||
"""change the context"""
|
||||
|
||||
article_path = os.path.join(self.path, self.settings['ARTICLE_DIR'])
|
||||
article_path = os.path.normpath( # we have to remove trailing slashes
|
||||
os.path.join(self.path, self.settings['ARTICLE_DIR'])
|
||||
)
|
||||
all_articles = []
|
||||
for f in self.get_files(
|
||||
article_path,
|
||||
|
|
@ -259,8 +263,8 @@ class ArticlesGenerator(Generator):
|
|||
# if no category is set, use the name of the path as a category
|
||||
if 'category' not in metadata:
|
||||
|
||||
if os.path.dirname(f) == article_path:
|
||||
category = self.settings['DEFAULT_CATEGORY']
|
||||
if os.path.dirname(f) == article_path: # if the article is not in a subdirectory
|
||||
category = self.settings['DEFAULT_CATEGORY']
|
||||
else:
|
||||
category = os.path.basename(os.path.dirname(f))\
|
||||
.decode('utf-8')
|
||||
|
|
@ -276,6 +280,7 @@ class ArticlesGenerator(Generator):
|
|||
metadata['date'] = datetime.datetime(
|
||||
*self.settings['DEFAULT_DATE'])
|
||||
|
||||
signals.article_generate_context.send(self, metadata=metadata)
|
||||
article = Article(content, metadata, settings=self.settings,
|
||||
filename=f)
|
||||
if not is_valid_content(article, f):
|
||||
|
|
@ -288,6 +293,10 @@ class ArticlesGenerator(Generator):
|
|||
all_articles.append(article)
|
||||
elif article.status == "draft":
|
||||
self.drafts.append(article)
|
||||
else:
|
||||
logger.warning(u"Unknown status %s for file %s, skipping it." %
|
||||
(repr(unicode.encode(article.status, 'utf-8')),
|
||||
repr(f)))
|
||||
|
||||
self.articles, self.translations = process_translations(all_articles)
|
||||
|
||||
|
|
@ -352,10 +361,13 @@ class PagesGenerator(Generator):
|
|||
|
||||
def __init__(self, *args, **kwargs):
|
||||
self.pages = []
|
||||
self.hidden_pages = []
|
||||
self.hidden_translations = []
|
||||
super(PagesGenerator, self).__init__(*args, **kwargs)
|
||||
|
||||
def generate_context(self):
|
||||
all_pages = []
|
||||
hidden_pages = []
|
||||
for f in self.get_files(
|
||||
os.path.join(self.path, self.settings['PAGE_DIR']),
|
||||
exclude=self.settings['PAGE_EXCLUDES']):
|
||||
|
|
@ -368,15 +380,25 @@ class PagesGenerator(Generator):
|
|||
filename=f)
|
||||
if not is_valid_content(page, f):
|
||||
continue
|
||||
all_pages.append(page)
|
||||
if page.status == "published":
|
||||
all_pages.append(page)
|
||||
elif page.status == "hidden":
|
||||
hidden_pages.append(page)
|
||||
else:
|
||||
logger.warning(u"Unknown status %s for file %s, skipping it." %
|
||||
(repr(unicode.encode(page.status, 'utf-8')),
|
||||
repr(f)))
|
||||
|
||||
|
||||
self.pages, self.translations = process_translations(all_pages)
|
||||
self.hidden_pages, self.hidden_translations = process_translations(hidden_pages)
|
||||
|
||||
self._update_context(('pages', ))
|
||||
self.context['PAGES'] = self.pages
|
||||
|
||||
def generate_output(self, writer):
|
||||
for page in chain(self.translations, self.pages):
|
||||
for page in chain(self.translations, self.pages,
|
||||
self.hidden_translations, self.hidden_pages):
|
||||
writer.write_file(page.save_as, self.get_template('page'),
|
||||
self.context, page=page,
|
||||
relative_urls=self.settings.get('RELATIVE_URLS'))
|
||||
|
|
@ -393,7 +415,23 @@ class StaticGenerator(Generator):
|
|||
copy(path, source, os.path.join(output_path, destination),
|
||||
final_path, overwrite=True)
|
||||
|
||||
def generate_context(self):
|
||||
|
||||
if self.settings['WEBASSETS']:
|
||||
from webassets import Environment as AssetsEnvironment
|
||||
|
||||
# Define the assets environment that will be passed to the
|
||||
# generators. The StaticGenerator must then be run first to have
|
||||
# the assets in the output_path before generating the templates.
|
||||
assets_url = self.settings['SITEURL'] + '/theme/'
|
||||
assets_src = os.path.join(self.output_path, 'theme')
|
||||
self.assets_env = AssetsEnvironment(assets_src, assets_url)
|
||||
|
||||
if logging.getLevelName(logger.getEffectiveLevel()) == "DEBUG":
|
||||
self.assets_env.debug = True
|
||||
|
||||
def generate_output(self, writer):
|
||||
|
||||
self._copy_paths(self.settings['STATIC_PATHS'], self.path,
|
||||
'static', self.output_path)
|
||||
self._copy_paths(self.settings['THEME_STATIC_PATHS'], self.theme,
|
||||
|
|
|
|||
0
pelican/plugins/__init__.py
Normal file
0
pelican/plugins/__init__.py
Normal file
85
pelican/plugins/github_activity.py
Normal file
85
pelican/plugins/github_activity.py
Normal file
|
|
@ -0,0 +1,85 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
Copyright (c) Marco Milanesi <kpanic@gnufunk.org>
|
||||
|
||||
A plugin to list your Github Activity
|
||||
To enable it set in your pelican config file the GITHUB_ACTIVITY_FEED
|
||||
parameter pointing to your github activity feed.
|
||||
|
||||
for example my personal activity feed is:
|
||||
|
||||
https://github.com/kpanic.atom
|
||||
|
||||
in your template just write a for in jinja2 syntax against the
|
||||
github_activity variable.
|
||||
|
||||
i.e.
|
||||
|
||||
<div class="social">
|
||||
<h2>Github Activity</h2>
|
||||
<ul>
|
||||
|
||||
{% for entry in github_activity %}
|
||||
<li><b>{{ entry[0] }}</b><br /> {{ entry[1] }}</li>
|
||||
{% endfor %}
|
||||
</ul>
|
||||
</div><!-- /.github_activity -->
|
||||
|
||||
github_activity is a list containing a list. The first element is the title
|
||||
and the second element is the raw html from github
|
||||
"""
|
||||
|
||||
from pelican import signals
|
||||
|
||||
|
||||
class GitHubActivity():
|
||||
"""
|
||||
A class created to fetch github activity with feedparser
|
||||
"""
|
||||
def __init__(self, generator):
|
||||
try:
|
||||
import feedparser
|
||||
self.activities = feedparser.parse(
|
||||
generator.settings['GITHUB_ACTIVITY_FEED'])
|
||||
except ImportError:
|
||||
raise Exception("Unable to find feedparser")
|
||||
|
||||
def fetch(self):
|
||||
"""
|
||||
returns a list of html snippets fetched from github actitivy feed
|
||||
"""
|
||||
|
||||
entries = []
|
||||
for activity in self.activities['entries']:
|
||||
entries.append(
|
||||
[element for element in [activity['title'],
|
||||
activity['content'][0]['value']]])
|
||||
|
||||
return entries
|
||||
|
||||
|
||||
def fetch_github_activity(gen, metadata):
|
||||
"""
|
||||
registered handler for the github activity plugin
|
||||
it puts in generator.context the html needed to be displayed on a
|
||||
template
|
||||
"""
|
||||
|
||||
if 'GITHUB_ACTIVITY_FEED' in gen.settings.keys():
|
||||
gen.context['github_activity'] = gen.plugin_instance.fetch()
|
||||
|
||||
|
||||
def feed_parser_initialization(generator):
|
||||
"""
|
||||
Initialization of feed parser
|
||||
"""
|
||||
|
||||
generator.plugin_instance = GitHubActivity(generator)
|
||||
|
||||
|
||||
def register():
|
||||
"""
|
||||
Plugin registration
|
||||
"""
|
||||
signals.article_generator_init.connect(feed_parser_initialization)
|
||||
signals.article_generate_context.connect(fetch_github_activity)
|
||||
23
pelican/plugins/global_license.py
Normal file
23
pelican/plugins/global_license.py
Normal file
|
|
@ -0,0 +1,23 @@
|
|||
from pelican import signals
|
||||
|
||||
"""
|
||||
License plugin for Pelican
|
||||
==========================
|
||||
|
||||
Simply add license variable in article's context, which contain
|
||||
the license text.
|
||||
|
||||
Settings:
|
||||
---------
|
||||
|
||||
Add LICENSE to your settings file to define default license.
|
||||
|
||||
"""
|
||||
|
||||
def add_license(generator, metadata):
|
||||
if 'license' not in metadata.keys()\
|
||||
and 'LICENSE' in generator.settings.keys():
|
||||
metadata['license'] = generator.settings['LICENSE']
|
||||
|
||||
def register():
|
||||
signals.article_generate_context.connect(add_license)
|
||||
40
pelican/plugins/gravatar.py
Normal file
40
pelican/plugins/gravatar.py
Normal file
|
|
@ -0,0 +1,40 @@
|
|||
import hashlib
|
||||
|
||||
from pelican import signals
|
||||
"""
|
||||
Gravatar plugin for Pelican
|
||||
===========================
|
||||
|
||||
Simply add author_gravatar variable in article's context, which contains
|
||||
the gravatar url.
|
||||
|
||||
Settings:
|
||||
---------
|
||||
|
||||
Add AUTHOR_EMAIL to your settings file to define default author email.
|
||||
|
||||
Article metadata:
|
||||
------------------
|
||||
|
||||
:email: article's author email
|
||||
|
||||
If one of them are defined, the author_gravatar variable is added to
|
||||
article's context.
|
||||
"""
|
||||
|
||||
def add_gravatar(generator, metadata):
|
||||
|
||||
#first check email
|
||||
if 'email' not in metadata.keys()\
|
||||
and 'AUTHOR_EMAIL' in generator.settings.keys():
|
||||
metadata['email'] = generator.settings['AUTHOR_EMAIL']
|
||||
|
||||
#then add gravatar url
|
||||
if 'email' in metadata.keys():
|
||||
gravatar_url = "http://www.gravatar.com/avatar/" + \
|
||||
hashlib.md5(metadata['email'].lower()).hexdigest()
|
||||
metadata["author_gravatar"] = gravatar_url
|
||||
|
||||
|
||||
def register():
|
||||
signals.article_generate_context.connect(add_gravatar)
|
||||
63
pelican/plugins/html_rst_directive.py
Normal file
63
pelican/plugins/html_rst_directive.py
Normal file
|
|
@ -0,0 +1,63 @@
|
|||
from docutils import nodes
|
||||
from docutils.parsers.rst import directives, Directive
|
||||
from pelican import log
|
||||
|
||||
"""
|
||||
HTML tags for reStructuredText
|
||||
==============================
|
||||
|
||||
Directives
|
||||
----------
|
||||
|
||||
.. html::
|
||||
|
||||
(HTML code)
|
||||
|
||||
|
||||
Example
|
||||
-------
|
||||
|
||||
A search engine:
|
||||
|
||||
.. html::
|
||||
<form action="http://seeks.fr/search" method="GET">
|
||||
<input type="text" value="Pelican v2" title="Search" maxlength="2048" name="q" autocomplete="on" />
|
||||
<input type="hidden" name="lang" value="en" />
|
||||
<input type="submit" value="Seeks !" id="search_button" />
|
||||
</form>
|
||||
|
||||
|
||||
A contact form:
|
||||
|
||||
.. html::
|
||||
|
||||
<form method="GET" action="mailto:some email">
|
||||
<p>
|
||||
<input type="text" placeholder="Subject" name="subject">
|
||||
<br />
|
||||
<textarea name="body" placeholder="Message">
|
||||
</textarea>
|
||||
<br />
|
||||
<input type="reset"><input type="submit">
|
||||
</p>
|
||||
</form>
|
||||
|
||||
"""
|
||||
|
||||
|
||||
class RawHtml(Directive):
|
||||
required_arguments = 0
|
||||
optional_arguments = 0
|
||||
final_argument_whitespace = True
|
||||
has_content = True
|
||||
|
||||
def run(self):
|
||||
html = u' '.join(self.content)
|
||||
node = nodes.raw('', html, format='html')
|
||||
return [node]
|
||||
|
||||
|
||||
|
||||
def register():
|
||||
directives.register_directive('html', RawHtml)
|
||||
|
||||
7
pelican/plugins/initialized.py
Normal file
7
pelican/plugins/initialized.py
Normal file
|
|
@ -0,0 +1,7 @@
|
|||
from pelican import signals
|
||||
|
||||
def test(sender):
|
||||
print "%s initialized !!" % sender
|
||||
|
||||
def register():
|
||||
signals.initialized.connect(test)
|
||||
|
|
@ -36,3 +36,61 @@ class Pygments(Directive):
|
|||
|
||||
directives.register_directive('code-block', Pygments)
|
||||
directives.register_directive('sourcecode', Pygments)
|
||||
|
||||
|
||||
class YouTube(Directive):
|
||||
""" Embed YouTube video in posts.
|
||||
|
||||
Courtesy of Brian Hsu: https://gist.github.com/1422773
|
||||
|
||||
VIDEO_ID is required, with / height are optional integer,
|
||||
and align could be left / center / right.
|
||||
|
||||
Usage:
|
||||
.. youtube:: VIDEO_ID
|
||||
:width: 640
|
||||
:height: 480
|
||||
:align: center
|
||||
"""
|
||||
|
||||
def align(argument):
|
||||
"""Conversion function for the "align" option."""
|
||||
return directives.choice(argument, ('left', 'center', 'right'))
|
||||
|
||||
required_arguments = 1
|
||||
optional_arguments = 2
|
||||
option_spec = {
|
||||
'width': directives.positive_int,
|
||||
'height': directives.positive_int,
|
||||
'align': align
|
||||
}
|
||||
|
||||
final_argument_whitespace = False
|
||||
has_content = False
|
||||
|
||||
def run(self):
|
||||
videoID = self.arguments[0].strip()
|
||||
width = 420
|
||||
height = 315
|
||||
align = 'left'
|
||||
|
||||
if 'width' in self.options:
|
||||
width = self.options['width']
|
||||
|
||||
if 'height' in self.options:
|
||||
height = self.options['height']
|
||||
|
||||
if 'align' in self.options:
|
||||
align = self.options['align']
|
||||
|
||||
url = 'http://www.youtube.com/embed/%s' % videoID
|
||||
div_block = '<div class="youtube" align="%s">' % align
|
||||
embed_block = '<iframe width="%s" height="%s" src="%s" '\
|
||||
'frameborder="0"></iframe>' % (width, height, url)
|
||||
|
||||
return [
|
||||
nodes.raw('', div_block, format='html'),
|
||||
nodes.raw('', embed_block, format='html'),
|
||||
nodes.raw('', '</div>', format='html')]
|
||||
|
||||
directives.register_directive('youtube', YouTube)
|
||||
|
|
|
|||
|
|
@ -68,6 +68,9 @@ _DEFAULT_CONFIG = {'PATH': '.',
|
|||
'ARTICLE_PERMALINK_STRUCTURE': '',
|
||||
'TYPOGRIFY': False,
|
||||
'LESS_GENERATOR': False,
|
||||
'SUMMARY_MAX_LENGTH': 50,
|
||||
'WEBASSETS': False,
|
||||
'PLUGINS': [],
|
||||
}
|
||||
|
||||
|
||||
|
|
@ -150,4 +153,12 @@ def configure_settings(settings, default_settings=None, filename=None):
|
|||
"http://docs.notmyidea.org/alexis/pelican/settings.html#timezone "
|
||||
"for more information")
|
||||
|
||||
if 'WEBASSETS' in settings and settings['WEBASSETS'] is not False:
|
||||
try:
|
||||
from webassets.ext.jinja2 import AssetsExtension
|
||||
settings['JINJA_EXTENSIONS'].append(AssetsExtension)
|
||||
except ImportError:
|
||||
logger.warn("You must install the webassets module to use WEBASSETS.")
|
||||
settings['WEBASSETS'] = False
|
||||
|
||||
return settings
|
||||
|
|
|
|||
5
pelican/signals.py
Normal file
5
pelican/signals.py
Normal file
|
|
@ -0,0 +1,5 @@
|
|||
from blinker import signal
|
||||
|
||||
initialized = signal('pelican_initialized')
|
||||
article_generate_context = signal('article_generate_context')
|
||||
article_generator_init = signal('article_generator_init')
|
||||
|
|
@ -312,6 +312,7 @@ img.left, figure.left {float: right; margin: 0 0 2em 2em;}
|
|||
.social a[type$='atom+xml'], .social a[type$='rss+xml'] {background-image: url('../images/icons/rss.png');}
|
||||
.social a[href*='twitter.com'] {background-image: url('../images/icons/twitter.png');}
|
||||
.social a[href*='linkedin.com'] {background-image: url('../images/icons/linkedin.png');}
|
||||
.social a[href*='gitorious.org'] {background-image: url('../images/icons/gitorious.org');}
|
||||
|
||||
/*
|
||||
About
|
||||
|
|
|
|||
BIN
pelican/themes/notmyidea/static/images/icons/gitorious.png
Normal file
BIN
pelican/themes/notmyidea/static/images/icons/gitorious.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 3.6 KiB |
|
|
@ -29,7 +29,8 @@
|
|||
{% endif %}
|
||||
<li><article class="hentry">
|
||||
<header>
|
||||
<h1><a href="{{ SITEURL }}/{{ article.url }}" rel="bookmark" title="Permalink to {{ article.title}}">{{ article.title }}</a></h1>
|
||||
<h1><a href="{{ SITEURL }}/{{ article.url }}" rel="bookmark"
|
||||
title="Permalink to {{ article.title|striptags }}">{{ article.title }}</a></h1>
|
||||
</header>
|
||||
|
||||
<div class="entry-content">
|
||||
|
|
@ -40,12 +41,12 @@
|
|||
</div><!-- /.entry-content -->
|
||||
</article></li>
|
||||
{% endif %}
|
||||
{% if loop.last and (articles_page.has_previous()
|
||||
or not articles_page.has_previous() and loop.length > 1) %}
|
||||
{% include 'pagination.html' %}
|
||||
{% endif %}
|
||||
{% if loop.last %}
|
||||
</ol><!-- /#posts-list -->
|
||||
{% if loop.last and (articles_page.has_previous()
|
||||
or not articles_page.has_previous() and loop.length > 1) %}
|
||||
{% include 'pagination.html' %}
|
||||
{% endif %}
|
||||
</section><!-- /#content -->
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
|
|
|
|||
14
pelican/themes/simple/templates/gosquared.html
Normal file
14
pelican/themes/simple/templates/gosquared.html
Normal file
|
|
@ -0,0 +1,14 @@
|
|||
{% if GOSQUARED_SITENAME %}
|
||||
<script type="text/javascript">
|
||||
var GoSquared={};
|
||||
GoSquared.acct = "{{ GOSQUARED_SITENAME }}";
|
||||
(function(w){
|
||||
function gs(){
|
||||
w._gstc_lt=+(new Date); var d=document;
|
||||
var g = d.createElement("script"); g.type = "text/javascript"; g.async = true; g.src = "//d1l6p2sc9645hc.cloudfront.net/tracker.js";
|
||||
var s = d.getElementsByTagName("script")[0]; s.parentNode.insertBefore(g, s);
|
||||
}
|
||||
w.addEventListener?w.addEventListener("load",gs,false):w.attachEvent("onload",gs);
|
||||
})(window);
|
||||
</script>
|
||||
{% endif %}
|
||||
|
|
@ -25,8 +25,14 @@ def wp2fields(xml):
|
|||
items = soup.rss.channel.findAll('item')
|
||||
|
||||
for item in items:
|
||||
|
||||
if item.fetch('wp:status')[0].contents[0] == "publish":
|
||||
title = item.title.contents[0]
|
||||
|
||||
try:
|
||||
title = item.title.contents[0]
|
||||
except IndexError:
|
||||
continue
|
||||
|
||||
content = item.fetch('content:encoded')[0].contents[0]
|
||||
filename = item.fetch('wp:post_name')[0].contents[0]
|
||||
|
||||
|
|
@ -197,7 +203,7 @@ def build_markdown_header(title, date, author, categories, tags):
|
|||
header += '\n'
|
||||
return header
|
||||
|
||||
def fields2pelican(fields, out_markup, output_path, dircat=False):
|
||||
def fields2pelican(fields, out_markup, output_path, dircat=False, strip_raw=False):
|
||||
for title, content, filename, date, author, categories, tags, in_markup in fields:
|
||||
if (in_markup == "markdown") or (out_markup == "markdown") :
|
||||
ext = '.md'
|
||||
|
|
@ -230,22 +236,26 @@ def fields2pelican(fields, out_markup, output_path, dircat=False):
|
|||
paragraphs = [u'<p>{}</p>'.format(p) for p in paragraphs]
|
||||
new_content = ''.join(paragraphs)
|
||||
|
||||
fp.write(content)
|
||||
fp.write(new_content)
|
||||
|
||||
cmd = 'pandoc --normalize --reference-links --from=html --to={0} -o "{1}" "{2}"'.format(
|
||||
out_markup, out_filename, html_filename)
|
||||
|
||||
parse_raw = '--parse-raw' if not strip_raw else ''
|
||||
cmd = ('pandoc --normalize --reference-links {0} --from=html'
|
||||
' --to={1} -o "{2}" "{3}"').format(
|
||||
parse_raw, out_markup, out_filename, html_filename)
|
||||
|
||||
try:
|
||||
rc = subprocess.call(cmd, shell=True)
|
||||
if rc < 0:
|
||||
print("Child was terminated by signal %d" % -rc)
|
||||
exit()
|
||||
error = "Child was terminated by signal %d" % -rc
|
||||
exit(error)
|
||||
|
||||
elif rc > 0:
|
||||
print("Please, check your Pandoc installation.")
|
||||
exit()
|
||||
error = "Please, check your Pandoc installation."
|
||||
exit(error)
|
||||
except OSError, e:
|
||||
print("Pandoc execution failed: %s" % e)
|
||||
exit()
|
||||
error = "Pandoc execution failed: %s" % e
|
||||
exit(error)
|
||||
|
||||
os.remove(html_filename)
|
||||
|
||||
|
|
@ -279,6 +289,10 @@ def main():
|
|||
help='Output markup format (supports rst & markdown)')
|
||||
parser.add_argument('--dir-cat', action='store_true', dest='dircat',
|
||||
help='Put files in directories with categories name')
|
||||
parser.add_argument('--strip-raw', action='store_true', dest='strip_raw',
|
||||
help="Strip raw HTML code that can't be converted to "
|
||||
"markup such as flash embeds or iframes (wordpress import only)")
|
||||
|
||||
args = parser.parse_args()
|
||||
|
||||
input_type = None
|
||||
|
|
@ -289,15 +303,15 @@ def main():
|
|||
elif args.feed:
|
||||
input_type = 'feed'
|
||||
else:
|
||||
print("You must provide either --wpfile, --dotclear or --feed options")
|
||||
exit()
|
||||
error = "You must provide either --wpfile, --dotclear or --feed options"
|
||||
exit(error)
|
||||
|
||||
if not os.path.exists(args.output):
|
||||
try:
|
||||
os.mkdir(args.output)
|
||||
except OSError:
|
||||
print("Unable to create the output folder: " + args.output)
|
||||
exit()
|
||||
error = "Unable to create the output folder: " + args.output
|
||||
exit(error)
|
||||
|
||||
if input_type == 'wordpress':
|
||||
fields = wp2fields(args.input)
|
||||
|
|
@ -306,4 +320,6 @@ def main():
|
|||
elif input_type == 'feed':
|
||||
fields = feed2fields(args.input)
|
||||
|
||||
fields2pelican(fields, args.markup, args.output, dircat=args.dircat or False)
|
||||
fields2pelican(fields, args.markup, args.output,
|
||||
dircat=args.dircat or False,
|
||||
strip_raw=args.strip_raw or False)
|
||||
|
|
|
|||
|
|
@ -48,9 +48,11 @@ def main():
|
|||
|
||||
|
||||
parser.add_argument('-i', '--install', dest='to_install', nargs='+', metavar="theme path",
|
||||
help='The themes to install ')
|
||||
help='The themes to install')
|
||||
parser.add_argument('-r', '--remove', dest='to_remove', nargs='+', metavar="theme name",
|
||||
help='The themes to remove')
|
||||
parser.add_argument('-U', '--upgrade', dest='to_upgrade', nargs='+',
|
||||
metavar="theme path", help='The themes to upgrade')
|
||||
parser.add_argument('-s', '--symlink', dest='to_symlink', nargs='+', metavar="theme path",
|
||||
help="Same as `--install', but create a symbolic link instead of copying the theme. Useful for theme development")
|
||||
parser.add_argument('-c', '--clean', dest='clean', action="store_true",
|
||||
|
|
@ -62,6 +64,9 @@ def main():
|
|||
|
||||
|
||||
args = parser.parse_args()
|
||||
|
||||
to_install = args.to_install or args.to_upgrade
|
||||
to_sym = args.to_symlink or args.clean
|
||||
|
||||
|
||||
if args.action:
|
||||
|
|
@ -69,8 +74,7 @@ def main():
|
|||
list_themes(args.verbose)
|
||||
elif args.action is 'path':
|
||||
print(_THEMES_PATH)
|
||||
elif args.to_install or args.to_remove or args.to_symlink or args.clean:
|
||||
|
||||
elif to_install or args.to_remove or to_sym:
|
||||
if args.to_remove:
|
||||
if args.verbose:
|
||||
print('Removing themes...')
|
||||
|
|
@ -85,6 +89,13 @@ def main():
|
|||
for i in args.to_install:
|
||||
install(i, v=args.verbose)
|
||||
|
||||
if args.to_upgrade:
|
||||
if args.verbose:
|
||||
print('Upgrading themes...')
|
||||
|
||||
for i in args.to_upgrade:
|
||||
install(i, v=args.verbose, u=True)
|
||||
|
||||
if args.to_symlink:
|
||||
if args.verbose:
|
||||
print('Linking themes...')
|
||||
|
|
@ -149,17 +160,21 @@ def remove(theme_name, v=False):
|
|||
err(target + ' : no such file or directory')
|
||||
|
||||
|
||||
def install(path, v=False):
|
||||
def install(path, v=False, u=False):
|
||||
"""Installs a theme"""
|
||||
if not os.path.exists(path):
|
||||
err(path + ' : no such file or directory')
|
||||
elif not os.path.isdir(path):
|
||||
err(path + ' : no a directory')
|
||||
err(path + ' : not a directory')
|
||||
else:
|
||||
theme_name = os.path.basename(os.path.normpath(path))
|
||||
theme_path = os.path.join(_THEMES_PATH, theme_name)
|
||||
if os.path.exists(theme_path):
|
||||
exists = os.path.exists(theme_path)
|
||||
if exists and not u:
|
||||
err(path + ' : already exists')
|
||||
elif exists and u:
|
||||
remove(theme_name, v)
|
||||
install(path, v)
|
||||
else:
|
||||
if v:
|
||||
print("Copying `{p}' to `{t}' ...".format(p=path, t=theme_path))
|
||||
|
|
@ -174,7 +189,7 @@ def symlink(path, v=False):
|
|||
if not os.path.exists(path):
|
||||
err(path + ' : no such file or directory')
|
||||
elif not os.path.isdir(path):
|
||||
err(path + ' : no a directory')
|
||||
err(path + ' : not a directory')
|
||||
else:
|
||||
theme_name = os.path.basename(os.path.normpath(path))
|
||||
theme_path = os.path.join(_THEMES_PATH, theme_name)
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@ import re
|
|||
import pytz
|
||||
import shutil
|
||||
import logging
|
||||
from collections import defaultdict
|
||||
|
||||
from codecs import open as _open
|
||||
from datetime import datetime
|
||||
|
|
@ -91,10 +92,22 @@ def clean_output_dir(path):
|
|||
"""Remove all the files from the output directory"""
|
||||
|
||||
# remove all the existing content from the output folder
|
||||
try:
|
||||
shutil.rmtree(path)
|
||||
except Exception:
|
||||
pass
|
||||
for filename in os.listdir(path):
|
||||
file = os.path.join(path, filename)
|
||||
if os.path.isdir(file):
|
||||
try:
|
||||
shutil.rmtree(file)
|
||||
logger.debug("Deleted directory %s" % file)
|
||||
except Exception, e:
|
||||
logger.error("Unable to delete directory %s; %e" % file, e)
|
||||
elif os.path.isfile(file) or os.path.islink(file):
|
||||
try:
|
||||
os.remove(file)
|
||||
logger.debug("Deleted file/link %s" % file)
|
||||
except Exception, e:
|
||||
logger.error("Unable to delete file %s; %e" % file, e)
|
||||
else:
|
||||
logger.error("Unable to delete %s, file type unknown" % file)
|
||||
|
||||
|
||||
def get_relative_path(filename):
|
||||
|
|
@ -221,9 +234,9 @@ def files_changed(path, extensions):
|
|||
"""Return the last time files have been modified"""
|
||||
for root, dirs, files in os.walk(path):
|
||||
dirs[:] = [x for x in dirs if x[0] != '.']
|
||||
for file in files:
|
||||
if any(file.endswith(ext) for ext in extensions):
|
||||
yield os.stat(os.path.join(root, file)).st_mtime
|
||||
for f in files:
|
||||
if any(f.endswith(ext) for ext in extensions):
|
||||
yield os.stat(os.path.join(root, f)).st_mtime
|
||||
|
||||
global LAST_MTIME
|
||||
mtime = max(file_times(path))
|
||||
|
|
@ -233,6 +246,21 @@ def files_changed(path, extensions):
|
|||
return False
|
||||
|
||||
|
||||
FILENAMES_MTIMES = defaultdict(int)
|
||||
|
||||
|
||||
def file_changed(filename):
|
||||
mtime = os.stat(filename).st_mtime
|
||||
if FILENAMES_MTIMES[filename] == 0:
|
||||
FILENAMES_MTIMES[filename] = mtime
|
||||
return False
|
||||
else:
|
||||
if mtime > FILENAMES_MTIMES[filename]:
|
||||
FILENAMES_MTIMES[filename] = mtime
|
||||
return True
|
||||
return False
|
||||
|
||||
|
||||
def set_date_tzinfo(d, tz_name=None):
|
||||
""" Date without tzinfo shoudbe utc.
|
||||
This function set the right tz to date that aren't utc and don't have
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue