diff --git a/docs/importer.rst b/docs/importer.rst index 0147f900..ccf3ffe2 100644 --- a/docs/importer.rst +++ b/docs/importer.rst @@ -39,7 +39,7 @@ Usage """"" | pelican-import [-h] [--wpfile] [--dotclear] [--feed] [-o OUTPUT] -| [--dir-cat] +| [-m MARKUP][--dir-cat] | input Optional arguments @@ -51,6 +51,7 @@ Optional arguments --feed Feed to parse -o OUTPUT, --output OUTPUT Output path + -m MARKUP Output markup --dir-cat Put files in directories with categories name Examples diff --git a/docs/index.rst b/docs/index.rst index 1389f132..6ad22670 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -23,6 +23,7 @@ Pelican currently supports: * Publication of articles in multiple languages * Atom/RSS feeds * Code syntax highlighting +* Compilation of less css (optional) * Import from WordPress, Dotclear, or RSS feeds * Integration with external tools: Twitter, Google Analytics, etc. (optional) diff --git a/docs/settings.rst b/docs/settings.rst index 427795c2..85e9f0c3 100644 --- a/docs/settings.rst +++ b/docs/settings.rst @@ -21,71 +21,81 @@ Here is a list of settings for Pelican: Basic settings ============== -================================================ ===================================================== -Setting name (default value) What does it do? -================================================ ===================================================== -`AUTHOR` Default author (put your name) -`DATE_FORMATS` (``{}``) If you do manage multiple languages, you can - set the date formatting here. See "Date format and locales" - section below for details. -`DEFAULT_CATEGORY` (``'misc'``) The default category to fall back on. -`DEFAULT_DATE_FORMAT` (``'%a %d %B %Y'``) The default date format you want to use. -`DISPLAY_PAGES_ON_MENU` (``True``) Whether to display pages on the menu of the - template. Templates may or not honor this - setting. -`FALLBACK_ON_FS_DATE` (``True``) If True, Pelican will use the file system - timestamp information (mtime) if it can't get - date information from the metadata. -`JINJA_EXTENSIONS` (``[]``) A list of any Jinja2 extensions you want to use. -`DELETE_OUTPUT_DIRECTORY` (``False``) Delete the output directory as well as - the generated files. -`LOCALE` (''[#]_) Change the locale. A list of locales can be provided - here or a single string representing one locale. - When providing a list, all the locales will be tried - until one works. -`MARKUP` (``('rst', 'md')``) A list of available markup languages you want - to use. For the moment, the only available values - are `rst` and `md`. -`MD_EXTENSIONS` (``['codehilite','extra']``) A list of the extensions that the Markdown processor - will use. Refer to the extensions chapter in the - Python-Markdown documentation for a complete list of - supported extensions. -`OUTPUT_PATH` (``'output/'``) Where to output the generated files. -`PATH` (``None``) Path to look at for input files. -`PAGE_DIR` (``'pages'``) Directory to look at for pages. -`PAGE_EXCLUDES` (``()``) A list of directories to exclude when looking for pages. -`ARTICLE_DIR` (``''``) Directory to look at for articles. -`ARTICLE_EXCLUDES`: (``('pages',)``) A list of directories to exclude when looking for articles. -`PDF_GENERATOR` (``False``) Set to True if you want to have PDF versions - of your documents. You will need to install - `rst2pdf`. -`RELATIVE_URLS` (``True``) Defines whether Pelican should use relative URLs or - not. -`SITENAME` (``'A Pelican Blog'``) Your site name -`SITEURL` Base URL of your website. Not defined by default, - which means the base URL is assumed to be "/" with a - root-relative URL structure. If `SITEURL` is specified - explicitly, there should be no trailing slash at the end, - and URLs will be generated with an absolute URL structure - (including the domain). If you want to use relative URLs - instead of root-relative or absolute URLs, you should - instead use the `RELATIVE_URL` setting. -`STATIC_PATHS` (``['images']``) The static paths you want to have accessible - on the output path "static". By default, - Pelican will copy the 'images' folder to the - output folder. -`TIMEZONE` The timezone used in the date information, to - generate Atom and RSS feeds. See the "timezone" - section below for more info. -`TYPOGRIFY` (``False``) If set to true, some - additional transformations will be done on the - generated HTML, using the `Typogrify - `_ - library -================================================ ===================================================== +===================================================================== ===================================================================== +Setting name (default value) What does it do? +===================================================================== ===================================================================== +`AUTHOR` Default author (put your name) +`DATE_FORMATS` (``{}``) If you do manage multiple languages, you can + set the date formatting here. See "Date format and locales" + section below for details. +`DEFAULT_CATEGORY` (``'misc'``) The default category to fall back on. +`DEFAULT_DATE_FORMAT` (``'%a %d %B %Y'``) The default date format you want to use. +`DISPLAY_PAGES_ON_MENU` (``True``) Whether to display pages on the menu of the + template. Templates may or not honor this + setting. +`FALLBACK_ON_FS_DATE` (``True``) If True, Pelican will use the file system + timestamp information (mtime) if it can't get + date information from the metadata. +`JINJA_EXTENSIONS` (``[]``) A list of any Jinja2 extensions you want to use. +`DELETE_OUTPUT_DIRECTORY` (``False``) Delete the output directory as well as + the generated files. +`LOCALE` (''[#]_) Change the locale. A list of locales can be provided + here or a single string representing one locale. + When providing a list, all the locales will be tried + until one works. +`MARKUP` (``('rst', 'md')``) A list of available markup languages you want + to use. For the moment, the only available values + are `rst` and `md`. +`MD_EXTENSIONS` (``['codehilite','extra']``) A list of the extensions that the Markdown processor + will use. Refer to the extensions chapter in the + Python-Markdown documentation for a complete list of + supported extensions. +`OUTPUT_PATH` (``'output/'``) Where to output the generated files. +`PATH` (``None``) Path to look at for input files. +`PAGE_DIR` (``'pages'``) Directory to look at for pages. +`PAGE_EXCLUDES` (``()``) A list of directories to exclude when looking for pages. +`ARTICLE_DIR` (``''``) Directory to look at for articles. +`ARTICLE_EXCLUDES`: (``('pages',)``) A list of directories to exclude when looking for articles. +`PDF_GENERATOR` (``False``) Set to True if you want to have PDF versions + of your documents. You will need to install + `rst2pdf`. +`RELATIVE_URLS` (``True``) Defines whether Pelican should use relative URLs or + not. +`SITENAME` (``'A Pelican Blog'``) Your site name +`SITEURL` Base URL of your website. Not defined by default, + which means the base URL is assumed to be "/" with a + root-relative URL structure. If `SITEURL` is specified + explicitly, there should be no trailing slash at the end, + and URLs will be generated with an absolute URL structure + (including the domain). If you want to use relative URLs + instead of root-relative or absolute URLs, you should + instead use the `RELATIVE_URL` setting. +`STATIC_PATHS` (``['images']``) The static paths you want to have accessible + on the output path "static". By default, + Pelican will copy the 'images' folder to the + output folder. +`TIMEZONE` The timezone used in the date information, to + generate Atom and RSS feeds. See the "timezone" + section below for more info. +`TYPOGRIFY` (``False``) If set to true, some + additional transformations will be done on the + generated HTML, using the `Typogrify + `_ + library +`LESS_GENERATOR` (``FALSE``) Set to True or complete path to `lessc` (if not + found in system PATH) to enable compiling less + css files. Requires installation of `less css`_. +`DIRECT_TEMPLATES` (``('index', 'tags', 'categories', 'archives')``) List of templates that are used directly to render + content. Typically direct templates are used to generate + index pages for collections of content e.g. tags and + category index pages. +`PAGINATED_DIRECT_TEMPLATES` (``('index',)``) Provides the direct templates that should be paginated. +===================================================================== ===================================================================== .. [#] Default is the system locale. +.. _less css: http://lesscss.org/ + URL settings ------------ @@ -96,14 +106,15 @@ your articles in a location such as '{slug}/index.html' and link to them as '{slug}' for clean URLs. These settings give you the flexibility to place your articles and pages anywhere you want. -Note: If you specify a datetime directive, it will be substituted using the -input files' date metadata attribute. If the date is not specified for a -particular file, Pelican will rely on the file's mtime timestamp. +.. note:: + If you specify a datetime directive, it will be substituted using the + input files' date metadata attribute. If the date is not specified for a + particular file, Pelican will rely on the file's mtime timestamp. Check the Python datetime documentation at http://bit.ly/cNcJUC for more information. -Also, you can use other file metadata attributes as well: +Also, you can use other file metadata attributes as well: * slug * date @@ -111,7 +122,7 @@ Also, you can use other file metadata attributes as well: * author * category -Example usage: +Example usage: * ARTICLE_URL = 'posts/{date:%Y}/{date:%b}/{date:%d}/{slug}/' * ARTICLE_SAVE_AS = 'posts/{date:%Y}/{date:%b}/{date:%d}/{slug}/index.html' @@ -140,8 +151,15 @@ Setting name (default value) what does it do? `CATEGORY_SAVE_AS` ('category/{name}.html') The location to save a category. `TAG_URL` ('tag/{name}.html') The URL to use for a tag. `TAG_SAVE_AS` ('tag/{name}.html') The location to save the tag page. +`_SAVE_AS` The location to save content generated from direct + templates. Where is the + upper case template name. ================================================ ===================================================== +.. note:: + + When any of `*_SAVE_AS` is set to False, files will not be created. + Timezone -------- @@ -330,7 +348,7 @@ Setting name (default value) What does it do? ================================================ ===================================================== `REVERSE_ARCHIVE_ORDER` (``False``) Reverse the archives list order. (True: orders by date in descending order, with newer articles first.) -`REVERSE_CATEGORY_ORDER` (``False``) Reverse the category order. (True: lists by reverse +`REVERSE_CATEGORY_ORDER` (``False``) Reverse the category order. (True: lists by reverse alphabetical order; default lists alphabetically.) ================================================ ===================================================== diff --git a/pelican/__init__.py b/pelican/__init__.py index 59aef653..6b3d12fb 100644 --- a/pelican/__init__.py +++ b/pelican/__init__.py @@ -6,7 +6,7 @@ import logging import argparse from pelican.generators import (ArticlesGenerator, PagesGenerator, - StaticGenerator, PdfGenerator) + 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 @@ -134,6 +134,8 @@ class Pelican(object): generators = [ArticlesGenerator, PagesGenerator, StaticGenerator] if self.settings['PDF_GENERATOR']: generators.append(PdfGenerator) + if self.settings['LESS_GENERATOR']: # can be True or PATH to lessc + generators.append(LessCSSGenerator) return generators def get_writer(self): diff --git a/pelican/contents.py b/pelican/contents.py index 593822a9..f5f3a1dc 100644 --- a/pelican/contents.py +++ b/pelican/contents.py @@ -183,7 +183,12 @@ class URLWrapper(object): def _from_settings(self, key): setting = "%s_%s" % (self.__class__.__name__.upper(), key) - return unicode(self.settings[setting]).format(**self.as_dict()) + value = self.settings[setting] + if not isinstance(value, basestring): + logger.warning(u'%s is set to %s' % (setting, value)) + return value + else: + return unicode(value).format(**self.as_dict()) url = property(functools.partial(_from_settings, key='URL')) save_as = property(functools.partial(_from_settings, key='SAVE_AS')) diff --git a/pelican/generators.py b/pelican/generators.py index 664eb9fb..ede948a4 100644 --- a/pelican/generators.py +++ b/pelican/generators.py @@ -4,6 +4,7 @@ import math import random import logging import datetime +import subprocess from collections import defaultdict from functools import partial @@ -162,31 +163,32 @@ class ArticlesGenerator(Generator): writer.write_feed(items, self.context, self.settings['TRANSLATION_FEED'] % lang) - def generate_pages(self, writer): - """Generate the pages on the disk""" - - write = partial(writer.write_file, - relative_urls=self.settings.get('RELATIVE_URLS')) - - # to minimize the number of relative path stuff modification - # in writer, articles pass first + def generate_articles(self, write): + """Generate the articles.""" article_template = self.get_template('article') for article in chain(self.translations, self.articles): write(article.save_as, article_template, self.context, article=article, category=article.category) + def generate_direct_templates(self, write): + """Generate direct templates pages""" PAGINATED_TEMPLATES = self.settings.get('PAGINATED_DIRECT_TEMPLATES') for template in self.settings.get('DIRECT_TEMPLATES'): paginated = {} if template in PAGINATED_TEMPLATES: paginated = {'articles': self.articles, 'dates': self.dates} + save_as = self.settings.get("%s_SAVE_AS" % template.upper(), + '%s.html' % template) + if not save_as: + continue - write('%s.html' % template, self.get_template(template), + write(save_as, self.get_template(template), self.context, blog=True, paginated=paginated, page_name=template) - # and subfolders after that + def generate_tags(self, write): + """Generate Tags pages.""" tag_template = self.get_template('tag') for tag, articles in self.tags.items(): articles.sort(key=attrgetter('date'), reverse=True) @@ -196,6 +198,8 @@ class ArticlesGenerator(Generator): paginated={'articles': articles, 'dates': dates}, page_name=u'tag/%s' % tag) + def generate_categories(self, write): + """Generate category pages.""" category_template = self.get_template('category') for cat, articles in self.categories: dates = [article for article in self.dates if article in articles] @@ -204,6 +208,8 @@ class ArticlesGenerator(Generator): paginated={'articles': articles, 'dates': dates}, page_name=u'category/%s' % cat) + def generate_authors(self, write): + """Generate Author pages.""" author_template = self.get_template('author') for aut, articles in self.authors: dates = [article for article in self.dates if article in articles] @@ -212,10 +218,30 @@ class ArticlesGenerator(Generator): paginated={'articles': articles, 'dates': dates}, page_name=u'author/%s' % aut) + def generate_drafts(self, write): + """Generate drafts pages.""" + article_template = self.get_template('article') for article in self.drafts: write('drafts/%s.html' % article.slug, article_template, self.context, article=article, category=article.category) + def generate_pages(self, writer): + """Generate the pages on the disk""" + write = partial(writer.write_file, + relative_urls=self.settings.get('RELATIVE_URLS')) + + # to minimize the number of relative path stuff modification + # in writer, articles pass first + self.generate_articles(write) + self.generate_direct_templates(write) + + + # and subfolders after that + self.generate_tags(write) + self.generate_categories(write) + self.generate_authors(write) + self.generate_drafts(write) + def generate_context(self): """change the context""" @@ -416,3 +442,50 @@ class PdfGenerator(Generator): for page in self.context['pages']: self._create_pdf(page, pdf_path) + + +class LessCSSGenerator(Generator): + """Compile less css files.""" + + def _compile(self, less_file, source_dir, dest_dir): + base = os.path.relpath(less_file, source_dir) + target = os.path.splitext( + os.path.join(dest_dir, base))[0] + '.css' + target_dir = os.path.dirname(target) + + if not os.path.exists(target_dir): + try: + os.makedirs(target_dir) + except OSError: + logger.error("Couldn't create the less css output folder in " + + target_dir) + + subprocess.call([self._lessc, less_file, target]) + logger.info(u' [ok] compiled %s' % base) + + def generate_output(self, writer=None): + logger.info(u' Compiling less css') + + # store out compiler here, so it won't be evaulted on each run of + # _compile + lg = self.settings['LESS_GENERATOR'] + self._lessc = lg if isinstance(lg, basestring) else 'lessc' + + # walk static paths + for static_path in self.settings['STATIC_PATHS']: + for f in self.get_files( + os.path.join(self.path, static_path), + extensions=['less']): + + self._compile(f, self.path, self.output_path) + + # walk theme static paths + theme_output_path = os.path.join(self.output_path, 'theme') + + for static_path in self.settings['THEME_STATIC_PATHS']: + theme_static_path = os.path.join(self.theme, static_path) + for f in self.get_files( + theme_static_path, + extensions=['less']): + + self._compile(f, theme_static_path, theme_output_path) diff --git a/pelican/readers.py b/pelican/readers.py index 917d8614..83565918 100644 --- a/pelican/readers.py +++ b/pelican/readers.py @@ -65,7 +65,7 @@ def render_node_to_html(document, node): class RstReader(Reader): enabled = bool(docutils) - extension = "rst" + file_extensions = ['rst'] def _parse_metadata(self, document): """Return the dict containing document metadata""" @@ -111,7 +111,7 @@ class RstReader(Reader): class MarkdownReader(Reader): enabled = bool(Markdown) - extension = "md" + file_extensions = ['md', 'markdown', 'mkd'] extensions = ['codehilite', 'extra'] def read(self, filename): @@ -128,7 +128,7 @@ class MarkdownReader(Reader): class HtmlReader(Reader): - extension = "html" + file_extensions = ['html', 'htm'] _re = re.compile('\<\!\-\-\#\s?[A-z0-9_-]*\s?\:s?[A-z0-9\s_-]*\s?\-\-\>') def read(self, filename): @@ -144,7 +144,11 @@ class HtmlReader(Reader): return content, metadata -_EXTENSIONS = dict((cls.extension, cls) for cls in Reader.__subclasses__()) +_EXTENSIONS = {} + +for cls in Reader.__subclasses__(): + for ext in cls.file_extensions: + _EXTENSIONS[ext] = cls def read_file(filename, fmt=None, settings=None): @@ -170,5 +174,6 @@ def read_file(filename, fmt=None, settings=None): if settings and settings['TYPOGRIFY']: from typogrify import Typogrify content = Typogrify.typogrify(content) + metadata['title'] = Typogrify.typogrify(metadata['title']) return content, metadata diff --git a/pelican/settings.py b/pelican/settings.py index 7a30e56e..4da66989 100644 --- a/pelican/settings.py +++ b/pelican/settings.py @@ -43,8 +43,8 @@ _DEFAULT_CONFIG = {'PATH': '.', 'PAGE_SAVE_AS': 'pages/{slug}.html', 'PAGE_LANG_URL': 'pages/{slug}-{lang}.html', 'PAGE_LANG_SAVE_AS': 'pages/{slug}-{lang}.html', - 'CATEGORY_URL': 'category/{name}.html', - 'CATEGORY_SAVE_AS': 'category/{name}.html', + 'CATEGORY_URL': 'category/{slug}.html', + 'CATEGORY_SAVE_AS': 'category/{slug}.html', 'TAG_URL': 'tag/{slug}.html', 'TAG_SAVE_AS': 'tag/{slug}.html', 'AUTHOR_URL': u'author/{slug}.html', @@ -67,6 +67,7 @@ _DEFAULT_CONFIG = {'PATH': '.', 'DEFAULT_STATUS': 'published', 'ARTICLE_PERMALINK_STRUCTURE': '', 'TYPOGRIFY': False, + 'LESS_GENERATOR': False, } diff --git a/pelican/themes/notmyidea/static/css/wide.css b/pelican/themes/notmyidea/static/css/wide.css index 3376f4c7..88fd59ce 100644 --- a/pelican/themes/notmyidea/static/css/wide.css +++ b/pelican/themes/notmyidea/static/css/wide.css @@ -4,13 +4,17 @@ body { font:1.3em/1.3 "Hoefler Text","Georgia",Georgia,serif,sans-serif; } -.body, #banner nav, #banner nav ul, #about, #featured, #content{ - width: inherit; +.post-info{ + display: none; } #banner nav { + display: none; -moz-border-radius: 0px; - margin-bottom: 0px; + margin-bottom: 20px; + overflow: hidden; + font-size: 1em; + background: #F5F4EF; } #banner nav ul{ @@ -19,10 +23,11 @@ body { #banner nav li{ float: right; + color: #000; } -#banner nav li:first-child a { - -moz-border-radius: 0px; +#banner nav li a { + color: #000; } #banner h1 { diff --git a/pelican/themes/notmyidea/static/images/icons/facebook.png b/pelican/themes/notmyidea/static/images/icons/facebook.png new file mode 100644 index 00000000..a7914b49 Binary files /dev/null and b/pelican/themes/notmyidea/static/images/icons/facebook.png differ diff --git a/pelican/themes/notmyidea/templates/archives.html b/pelican/themes/notmyidea/templates/archives.html index f7f1c400..f6784942 100644 --- a/pelican/themes/notmyidea/templates/archives.html +++ b/pelican/themes/notmyidea/templates/archives.html @@ -6,7 +6,7 @@
{% for article in dates %}
{{ article.locale_date }}
-
{{ article.title }}
+
{{ article.title }}
{% endfor %}
diff --git a/pelican/themes/notmyidea/templates/article.html b/pelican/themes/notmyidea/templates/article.html index 6615b63a..fc7e5893 100644 --- a/pelican/themes/notmyidea/templates/article.html +++ b/pelican/themes/notmyidea/templates/article.html @@ -1,30 +1,34 @@ {% extends "base.html" %} -{% block title %}{{ article.title }}{% endblock %} -{% block content %} -
-
-

{{ article.title - }}

{% include 'twitter.html' %}
-
- {% include 'article_infos.html' %} - {{ article.content }} -
- {% if DISQUS_SITENAME %} -
-

Comments !

-
- -
- {% endif %} +{% block title %}{{ article.title|striptags }}{% endblock %} +{% block content %} +
+ +
+ {% include 'article_infos.html' %} + {{ article.content }} +
+ {% if DISQUS_SITENAME %} +
+

Comments !

+
+ +
+ {% endif %} + +
{% endblock %} diff --git a/pelican/themes/notmyidea/templates/categories.html b/pelican/themes/notmyidea/templates/categories.html index e4d9d0a7..e29be0ca 100644 --- a/pelican/themes/notmyidea/templates/categories.html +++ b/pelican/themes/notmyidea/templates/categories.html @@ -2,7 +2,7 @@ {% block content %} {% endblock %} diff --git a/pelican/themes/simple/templates/archives.html b/pelican/themes/simple/templates/archives.html index 6c9db183..050f2686 100644 --- a/pelican/themes/simple/templates/archives.html +++ b/pelican/themes/simple/templates/archives.html @@ -5,7 +5,7 @@
{% for article in dates %}
{{ article.locale_date }}
-
{{ article.title }}
+
{{ article.title }}
{% endfor %}
{% endblock %} diff --git a/pelican/themes/simple/templates/article.html b/pelican/themes/simple/templates/article.html index d6c96a13..16c34266 100644 --- a/pelican/themes/simple/templates/article.html +++ b/pelican/themes/simple/templates/article.html @@ -1,19 +1,23 @@ {% extends "base.html" %} -{% block content %} -
-

{{ article.title }}

-
- - {{ article.locale_date }} - - {% if article.author %} -
- By {{ article.author }} -
- {% endif %} -
-
- {{ article.content }} -
+{% block content %} +
+
+

+ {{ article.title }}

+
+
+ + {{ article.locale_date }} + + {% if article.author %} +
+ By {{ article.author }} +
+ {% endif %} +
+
+ {{ article.content }} +
{% endblock %} diff --git a/pelican/tools/pelican_quickstart.py b/pelican/tools/pelican_quickstart.py index 8459870a..cfd9bb4c 100755 --- a/pelican/tools/pelican_quickstart.py +++ b/pelican/tools/pelican_quickstart.py @@ -7,94 +7,9 @@ import argparse from pelican import __version__ -TEMPLATES = { - 'Makefile' : ''' -PELICAN=$pelican -PELICANOPTS=$pelicanopts +_TEMPLATES_DIR = os.path.join(os.path.dirname(os.path.abspath(__file__)), \ + "templates") -BASEDIR=$$(PWD) -INPUTDIR=$$(BASEDIR)/src -OUTPUTDIR=$$(BASEDIR)/output -CONFFILE=$$(BASEDIR)/pelican.conf.py - -FTP_HOST=$ftp_host -FTP_USER=$ftp_user -FTP_TARGET_DIR=$ftp_target_dir - -SSH_HOST=$ssh_host -SSH_USER=$ssh_user -SSH_TARGET_DIR=$ssh_target_dir - -DROPBOX_DIR=$dropbox_dir - -help: -\t@echo 'Makefile for a pelican Web site ' -\t@echo ' ' -\t@echo 'Usage: ' -\t@echo ' make html (re)generate the web site ' -\t@echo ' make clean remove the generated files ' -\t@echo ' ftp_upload upload the web site using FTP ' -\t@echo ' ssh_upload upload the web site using SSH ' -\t@echo ' dropbox_upload upload the web site using Dropbox ' -\t@echo ' ' - - -html: clean $$(OUTPUTDIR)/index.html -\t@echo 'Done' - -$$(OUTPUTDIR)/%.html: -\t$$(PELICAN) $$(INPUTDIR) -o $$(OUTPUTDIR) -s $$(CONFFILE) $$(PELICANOPTS) - -clean: -\trm -fr $$(OUTPUTDIR) -\tmkdir $$(OUTPUTDIR) - -dropbox_upload: $$(OUTPUTDIR)/index.html -\tcp -r $$(OUTPUTDIR)/* $$(DROPBOX_DIR) - -rsync_upload: $$(OUTPUTDIR)/index.html -\trsync --delete -rvz -e ssh $(OUTPUTDIR)/* $(SSH_USER)@$(SSH_HOST):$(SSH_TARGET_DIR) - -ssh_upload: $$(OUTPUTDIR)/index.html -\tscp -r $$(OUTPUTDIR)/* $$(SSH_USER)@$$(SSH_HOST):$$(SSH_TARGET_DIR) - -ftp_upload: $$(OUTPUTDIR)/index.html -\tlftp ftp://$$(FTP_USER)@$$(FTP_HOST) -e "mirror -R $$(OUTPUTDIR) $$(FTP_TARGET_DIR) ; quit" - -github: $$(OUTPUTDIR)/index.html -\tghp-import $$(OUTPUTDIR) -\tgit push origin gh-pages - -.PHONY: html help clean ftp_upload ssh_upload dropbox_upload github -''', - - 'pelican.conf.py': '''#!/usr/bin/env python -# -*- coding: utf-8 -*- # - -AUTHOR = u"$author" -SITENAME = u"$sitename" -SITEURL = '/' - -TIMEZONE = 'Europe/Paris' - -DEFAULT_LANG='$lang' - -# Blogroll -LINKS = ( - ('Pelican', 'http://docs.notmyidea.org/alexis/pelican/'), - ('Python.org', 'http://python.org'), - ('Jinja2', 'http://jinja.pocoo.org'), - ('You can modify those links in your config file', '#') - ) - -# Social widget -SOCIAL = ( - ('You can add links in your config file', '#'), - ) - -DEFAULT_PAGINATION = $default_pagination -''' -} CONF = { 'pelican' : 'pelican', @@ -112,6 +27,20 @@ CONF = { } +def get_template(name): + template = os.path.join(_TEMPLATES_DIR, "{0}.in".format(name)) + + if not os.path.isfile(template): + raise RuntimeError("Cannot open {0}".format(template)) + + with open(template, 'r') as fd: + line = fd.readline() + while line: + yield line + line = fd.readline() + fd.close() + + def ask(question, answer=str, default=None, l=None): if answer == str: r = '' @@ -246,20 +175,22 @@ Please answer the following questions so this script can generate the files need except OSError, e: print('Error: {0}'.format(e)) - conf = string.Template(TEMPLATES['pelican.conf.py']) try: with open(os.path.join(CONF['basedir'], 'pelican.conf.py'), 'w') as fd: - fd.write(conf.safe_substitute(CONF)) + for line in get_template('pelican.conf.py'): + template = string.Template(line) + fd.write(template.safe_substitute(CONF)) fd.close() except OSError, e: print('Error: {0}'.format(e)) if mkfile: - Makefile = string.Template(TEMPLATES['Makefile']) try: with open(os.path.join(CONF['basedir'], 'Makefile'), 'w') as fd: - fd.write(Makefile.safe_substitute(CONF)) + for line in get_template('Makefile'): + template = string.Template(line) + fd.write(template.safe_substitute(CONF)) fd.close() except OSError, e: print('Error: {0}'.format(e)) diff --git a/pelican/tools/templates/Makefile.in b/pelican/tools/templates/Makefile.in new file mode 100644 index 00000000..998d8c97 --- /dev/null +++ b/pelican/tools/templates/Makefile.in @@ -0,0 +1,58 @@ +PELICAN=$pelican +PELICANOPTS=$pelicanopts + +BASEDIR=$$(PWD) +INPUTDIR=$$(BASEDIR)/src +OUTPUTDIR=$$(BASEDIR)/output +CONFFILE=$$(BASEDIR)/pelican.conf.py + +FTP_HOST=$ftp_host +FTP_USER=$ftp_user +FTP_TARGET_DIR=$ftp_target_dir + +SSH_HOST=$ssh_host +SSH_USER=$ssh_user +SSH_TARGET_DIR=$ssh_target_dir + +DROPBOX_DIR=$dropbox_dir + +help: + @echo 'Makefile for a pelican Web site ' + @echo ' ' + @echo 'Usage: ' + @echo ' make html (re)generate the web site ' + @echo ' make clean remove the generated files ' + @echo ' ftp_upload upload the web site using FTP ' + @echo ' ssh_upload upload the web site using SSH ' + @echo ' dropbox_upload upload the web site using Dropbox ' + @echo ' rsync_upload upload the web site using rsync/ssh' + @echo ' ' + + +html: clean $$(OUTPUTDIR)/index.html + @echo 'Done' + +$$(OUTPUTDIR)/%.html: + $$(PELICAN) $$(INPUTDIR) -o $$(OUTPUTDIR) -s $$(CONFFILE) $$(PELICANOPTS) + +clean: + rm -fr $$(OUTPUTDIR) + mkdir $$(OUTPUTDIR) + +dropbox_upload: $$(OUTPUTDIR)/index.html + cp -r $$(OUTPUTDIR)/* $$(DROPBOX_DIR) + +ssh_upload: $$(OUTPUTDIR)/index.html + scp -r $$(OUTPUTDIR)/* $$(SSH_USER)@$$(SSH_HOST):$$(SSH_TARGET_DIR) + +rsync_upload: $$(OUTPUTDIR)/index.html + rsync -e ssh -P -rvz --delete $(OUTPUTDIR)/* $(SSH_USER)@$(SSH_HOST):$(SSH_TARGET_DIR) + +ftp_upload: $$(OUTPUTDIR)/index.html + lftp ftp://$$(FTP_USER)@$$(FTP_HOST) -e "mirror -R $$(OUTPUTDIR) $$(FTP_TARGET_DIR) ; quit" + +github: $$(OUTPUTDIR)/index.html + ghp-import $$(OUTPUTDIR) + git push origin gh-pages + +.PHONY: html help clean ftp_upload ssh_upload rsync_upload dropbox_upload github diff --git a/pelican/tools/templates/pelican.conf.py.in b/pelican/tools/templates/pelican.conf.py.in new file mode 100644 index 00000000..ee56048e --- /dev/null +++ b/pelican/tools/templates/pelican.conf.py.in @@ -0,0 +1,25 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- # + +AUTHOR = u"$author" +SITENAME = u"$sitename" +SITEURL = '/' + +TIMEZONE = 'Europe/Paris' + +DEFAULT_LANG='$lang' + +# Blogroll +LINKS = ( + ('Pelican', 'http://docs.notmyidea.org/alexis/pelican/'), + ('Python.org', 'http://python.org'), + ('Jinja2', 'http://jinja.pocoo.org'), + ('You can modify those links in your config file', '#') + ) + +# Social widget +SOCIAL = ( + ('You can add links in your config file', '#'), + ) + +DEFAULT_PAGINATION = $default_pagination diff --git a/pelican/utils.py b/pelican/utils.py index 18730e6c..d4e34842 100644 --- a/pelican/utils.py +++ b/pelican/utils.py @@ -8,6 +8,7 @@ import logging from codecs import open as _open from datetime import datetime from itertools import groupby +from jinja2 import Markup from operator import attrgetter logger = logging.getLogger(__name__) @@ -44,6 +45,7 @@ def slugify(value): Took from django sources. """ + value = Markup(value).striptags() if type(value) == unicode: import unicodedata value = unicodedata.normalize('NFKD', value).encode('ascii', 'ignore') diff --git a/pelican/writers.py b/pelican/writers.py index 4dd04a2a..75971ee9 100644 --- a/pelican/writers.py +++ b/pelican/writers.py @@ -8,8 +8,8 @@ import logging from codecs import open from functools import partial - from feedgenerator import Atom1Feed, Rss201rev2Feed +from jinja2 import Markup from pelican.paginator import Paginator from pelican.utils import get_relative_path, set_date_tzinfo @@ -25,8 +25,9 @@ class Writer(object): def _create_new_feed(self, feed_type, context): feed_class = Rss201rev2Feed if feed_type == 'rss' else Atom1Feed + sitename = Markup(context['SITENAME']).striptags() feed = feed_class( - title=context['SITENAME'], + title=sitename, link=(self.site_url + '/'), feed_url=self.feed_url, description=context.get('SITESUBTITLE', '')) @@ -34,8 +35,9 @@ class Writer(object): def _add_item_to_the_feed(self, feed, item): + title = Markup(item.title).striptags() feed.add_item( - title=item.title, + title=title, link='%s/%s' % (self.site_url, item.url), unique_id='tag:%s,%s:%s' % (self.site_url.replace('http://', ''), item.date.date(), item.url), @@ -99,6 +101,12 @@ class Writer(object): :param **kwargs: additional variables to pass to the templates """ + if name is False: + return + elif not name: + # other stuff, just return for now + return + def _write_file(template, localcontext, output_path, name): """Render the template write the file.""" old_locale = locale.setlocale(locale.LC_ALL) diff --git a/tests/content/article_with_md_extension.md b/tests/content/article_with_md_extension.md new file mode 100644 index 00000000..11aa22a2 --- /dev/null +++ b/tests/content/article_with_md_extension.md @@ -0,0 +1,10 @@ +title: Test md File +category: test + +Test Markdown File Header +========================= + +Used for pelican test +--------------------- + +The quick brown fox jumped over the lazy dog's back. diff --git a/tests/content/article_with_mkd_extension.mkd b/tests/content/article_with_mkd_extension.mkd new file mode 100644 index 00000000..c946cb87 --- /dev/null +++ b/tests/content/article_with_mkd_extension.mkd @@ -0,0 +1,10 @@ +title: Test mkd File +category: test + +Test Markdown File Header +========================= + +Used for pelican test +--------------------- + +This is another markdown test file. Uses the mkd extension. diff --git a/tests/support.py b/tests/support.py index 4eb07ec4..f2b4a075 100644 --- a/tests/support.py +++ b/tests/support.py @@ -4,6 +4,8 @@ __all__ = [ 'unittest', ] +import os +import subprocess from contextlib import contextmanager from tempfile import mkdtemp from shutil import rmtree @@ -35,3 +37,21 @@ def get_article(title, slug, content, lang, extra_metadata=None): if extra_metadata is not None: metadata.update(extra_metadata) return Article(content, metadata=metadata) + + +def skipIfNoExecutable(executable, valid_exit_code=1): + """Tries to run an executable to make sure it's in the path, Skips the tests + if not found. + """ + + # calling with no params the command should exit with 1 + with open(os.devnull, 'w') as fnull: + try: + res = subprocess.call(executable, stdout=fnull, stderr=fnull) + except OSError: + res = None + + if res != valid_exit_code: + return unittest.skip('{0} compiler not found'.format(executable)) + + return lambda func: func diff --git a/tests/test_generators.py b/tests/test_generators.py index dd27d97f..e62551fa 100644 --- a/tests/test_generators.py +++ b/tests/test_generators.py @@ -2,13 +2,15 @@ from mock import MagicMock import os +import re -from pelican.generators import ArticlesGenerator +from pelican.generators import ArticlesGenerator, LessCSSGenerator from pelican.settings import _DEFAULT_CONFIG -from .support import unittest +from .support import unittest, temporary_folder, skipIfNoExecutable CUR_DIR = os.path.dirname(__file__) + class TestArticlesGenerator(unittest.TestCase): def test_generate_feeds(self): @@ -49,4 +51,91 @@ class TestArticlesGenerator(unittest.TestCase): categories = [cat.name for cat, _ in generator.categories] # assert that the categories are ordered as expected self.assertEquals( - categories, ['Default', 'TestCategory', 'Yeah', 'yeah']) + categories, ['Default', 'TestCategory', 'Yeah', 'test', + 'yeah']) + + def test_direct_templates_save_as_default(self): + + settings = _DEFAULT_CONFIG.copy() + settings['DIRECT_TEMPLATES'] = ['archives'] + generator = ArticlesGenerator(settings.copy(), settings, None, + _DEFAULT_CONFIG['THEME'], None, + _DEFAULT_CONFIG['MARKUP']) + write = MagicMock() + generator.generate_direct_templates(write) + write.assert_called_with("archives.html", + generator.get_template("archives"), settings, + blog=True, paginated={}, page_name='archives') + + def test_direct_templates_save_as_modified(self): + + settings = _DEFAULT_CONFIG.copy() + settings['DIRECT_TEMPLATES'] = ['archives'] + settings['ARCHIVES_SAVE_AS'] = 'archives/index.html' + generator = ArticlesGenerator(settings, settings, None, + _DEFAULT_CONFIG['THEME'], None, + _DEFAULT_CONFIG['MARKUP']) + write = MagicMock() + generator.generate_direct_templates(write) + write.assert_called_with("archives/index.html", + generator.get_template("archives"), settings, + blog=True, paginated={}, page_name='archives') + + def test_direct_templates_save_as_false(self): + + settings = _DEFAULT_CONFIG.copy() + settings['DIRECT_TEMPLATES'] = ['archives'] + settings['ARCHIVES_SAVE_AS'] = 'archives/index.html' + generator = ArticlesGenerator(settings, settings, None, + _DEFAULT_CONFIG['THEME'], None, + _DEFAULT_CONFIG['MARKUP']) + write = MagicMock() + generator.generate_direct_templates(write) + write.assert_called_count == 0 + + +class TestLessCSSGenerator(unittest.TestCase): + + LESS_CONTENT = """ + @color: #4D926F; + + #header { + color: @color; + } + h2 { + color: @color; + } + """ + + @skipIfNoExecutable('lessc') + def test_less_compiler(self): + + settings = _DEFAULT_CONFIG.copy() + settings['STATIC_PATHS'] = ['static'] + settings['LESS_GENERATOR'] = True + + # we'll nest here for py < 2.7 compat + with temporary_folder() as temp_content: + with temporary_folder() as temp_output: + generator = LessCSSGenerator(None, settings, temp_content, + _DEFAULT_CONFIG['THEME'], temp_output, None) + + # create a dummy less file + less_dir = os.path.join(temp_content, 'static', 'css') + less_filename = os.path.join(less_dir, 'test.less') + + less_output = os.path.join(temp_output, 'static', 'css', + 'test.css') + + os.makedirs(less_dir) + with open(less_filename, 'w') as less_file: + less_file.write(self.LESS_CONTENT) + + generator.generate_output() + + # we have the file ? + self.assertTrue(os.path.exists(less_output)) + + # was it compiled ? + self.assertIsNotNone(re.search(r'^\s+color:\s*#4D926F;$', + open(less_output).read(), re.MULTILINE | re.IGNORECASE)) diff --git a/tests/test_readers.py b/tests/test_readers.py index 7b9316b5..de2e9c32 100644 --- a/tests/test_readers.py +++ b/tests/test_readers.py @@ -61,3 +61,25 @@ class RstReaderTest(unittest.TestCase): self.assertEqual(content, expected) except ImportError: return unittest.skip('need the typogrify distribution') + +class MdReaderTest(unittest.TestCase): + + def test_article_with_md_extention(self): + # test to ensure the md extension is being processed by the correct reader + reader = readers.MarkdownReader({}) + content, metadata = reader.read(_filename('article_with_md_extension.md')) + expected = "

Test Markdown File Header

\n"\ + "

Used for pelican test

\n"\ + "

The quick brown fox jumped over the lazy dog's back.

" + + self.assertEqual(content, expected) + + def test_article_with_mkd_extension(self): + # test to ensure the mkd extension is being processed by the correct reader + reader = readers.MarkdownReader({}) + content, metadata = reader.read(_filename('article_with_mkd_extension.mkd')) + expected = "

Test Markdown File Header

\n"\ + "

Used for pelican test

\n"\ + "

This is another markdown test file. Uses the mkd extension.

" + + self.assertEqual(content, expected)