aboutsummaryrefslogtreecommitdiffstats
path: root/nikola/nikola.py
diff options
context:
space:
mode:
Diffstat (limited to 'nikola/nikola.py')
-rw-r--r--nikola/nikola.py1479
1 files changed, 134 insertions, 1345 deletions
diff --git a/nikola/nikola.py b/nikola/nikola.py
index aa43398..8b69d02 100644
--- a/nikola/nikola.py
+++ b/nikola/nikola.py
@@ -1,34 +1,35 @@
# -*- coding: utf-8 -*-
-import codecs
from collections import defaultdict
from copy import copy
-import datetime
import glob
-import json
import os
-from StringIO import StringIO
import sys
-import tempfile
-import urllib2
import urlparse
-from doit.tools import PythonInteractiveAction
import lxml.html
-from pygments import highlight
-from pygments.lexers import get_lexer_for_filename, TextLexer
-from pygments.formatters import HtmlFormatter
-try:
- import webassets
-except ImportError:
- webassets = None
+from yapsy.PluginManager import PluginManager
+
+if os.getenv('DEBUG'):
+ import logging
+ logging.basicConfig(level=logging.DEBUG)
+else:
+ import logging
+ logging.basicConfig(level=logging.ERROR)
from post import Post
import utils
+from plugin_categories import (
+ Command,
+ LateTask,
+ PageCompiler,
+ Task,
+ TemplateSystem,
+)
config_changed = utils.config_changed
-__all__ = ['Nikola', 'nikola_main']
+__all__ = ['Nikola']
class Nikola(object):
@@ -68,6 +69,7 @@ class Nikola(object):
'FILTERS': {},
'USE_BUNDLES': True,
'TAG_PAGES_ARE_INDEXES': False,
+ 'THEME': 'default',
'post_compilers': {
"rest": ['.txt', '.rst'],
"markdown": ['.md', '.mdown', '.markdown'],
@@ -75,29 +77,48 @@ class Nikola(object):
},
}
self.config.update(config)
- if not self.config['TRANSLATIONS']:
- self.config['TRANSLATIONS']={
- self.config['DEFAULT_LANG']: ''}
-
- if self.config['USE_BUNDLES'] and not webassets:
- self.config['USE_BUNDLES'] = False
+ self.config['TRANSLATIONS'] = self.config.get('TRANSLATIONS',
+ {self.config['DEFAULT_LANG']: ''})
- self.get_compile_html = utils.CompileHtmlGetter(
- self.config.pop('post_compilers'))
-
- self.GLOBAL_CONTEXT = self.config['GLOBAL_CONTEXT']
self.THEMES = utils.get_theme_chain(self.config['THEME'])
- self.templates_module = utils.get_template_module(
- utils.get_template_engine(self.THEMES), self.THEMES)
- self.template_deps = self.templates_module.template_deps
-
- self.theme_bundles = utils.get_theme_bundles(self.THEMES)
-
self.MESSAGES = utils.load_messages(self.THEMES,
self.config['TRANSLATIONS'])
- self.GLOBAL_CONTEXT['messages'] = self.MESSAGES
+ self.plugin_manager = PluginManager(categories_filter={
+ "Command": Command,
+ "Task": Task,
+ "LateTask": LateTask,
+ "TemplateSystem": TemplateSystem,
+ "PageCompiler": PageCompiler,
+ })
+ self.plugin_manager.setPluginInfoExtension('plugin')
+ self.plugin_manager.setPluginPlaces([
+ os.path.join(os.path.dirname(__file__), 'plugins'),
+ os.path.join(os.getcwd(), 'plugins'),
+ ])
+ self.plugin_manager.collectPlugins()
+
+ self.commands = {}
+ # Activate all command plugins
+ for pluginInfo in self.plugin_manager.getPluginsOfCategory("Command"):
+ self.plugin_manager.activatePluginByName(pluginInfo.name)
+ pluginInfo.plugin_object.set_site(self)
+ pluginInfo.plugin_object.short_help = pluginInfo.description
+ self.commands[pluginInfo.name] = pluginInfo.plugin_object
+
+ # Activate all task plugins
+ for pluginInfo in self.plugin_manager.getPluginsOfCategory("Task"):
+ self.plugin_manager.activatePluginByName(pluginInfo.name)
+ pluginInfo.plugin_object.set_site(self)
+
+ for pluginInfo in self.plugin_manager.getPluginsOfCategory("LateTask"):
+ self.plugin_manager.activatePluginByName(pluginInfo.name)
+ pluginInfo.plugin_object.set_site(self)
+
+ # set global_context for template rendering
+ self.GLOBAL_CONTEXT = self.config.get('GLOBAL_CONTEXT', {})
+ self.GLOBAL_CONTEXT['messages'] = self.MESSAGES
self.GLOBAL_CONTEXT['_link'] = self.link
self.GLOBAL_CONTEXT['rel_link'] = self.rel_link
self.GLOBAL_CONTEXT['abs_link'] = self.abs_link
@@ -108,19 +129,74 @@ class Nikola(object):
'INDEX_DISPLAY_POST_COUNT']
self.GLOBAL_CONTEXT['use_bundles'] = self.config['USE_BUNDLES']
- self.DEPS_CONTEXT = {}
- for k, v in self.GLOBAL_CONTEXT.items():
- if isinstance(v, (str, unicode, int, float, dict)):
- self.DEPS_CONTEXT[k] = v
+ # Load template plugin
+ template_sys_name = utils.get_template_engine(self.THEMES)
+ pi = self.plugin_manager.getPluginByName(
+ template_sys_name, "TemplateSystem")
+ if pi is None:
+ sys.stderr.write("Error loading %s template system plugin\n"
+ % template_sys_name)
+ sys.exit(1)
+ self.template_system = pi.plugin_object
+ self.template_system.set_directories(
+ [os.path.join(utils.get_theme_path(name), "templates")
+ for name in self.THEMES])
+
+ # Load compiler plugins
+ self.compilers = {}
+ self.inverse_compilers = {}
+
+ for pluginInfo in self.plugin_manager.getPluginsOfCategory(
+ "PageCompiler"):
+ self.compilers[pluginInfo.name] = \
+ pluginInfo.plugin_object.compile_html
+
+ def get_compiler(self, source_name):
+ """Get the correct compiler for a post from `conf.post_compilers`
+
+ To make things easier for users, the mapping in conf.py is
+ compiler->[extensions], although this is less convenient for us. The
+ majority of this function is reversing that dictionary and error
+ checking.
+ """
+ ext = os.path.splitext(source_name)[1]
+ try:
+ compile_html = self.inverse_compilers[ext]
+ except KeyError:
+ # Find the correct compiler for this files extension
+ langs = [lang for lang, exts in
+ self.config['post_compilers'].items()
+ if ext in exts]
+ if len(langs) != 1:
+ if len(set(langs)) > 1:
+ exit("Your file extension->compiler definition is"
+ "ambiguous.\nPlease remove one of the file extensions"
+ "from 'post_compilers' in conf.py\n(The error is in"
+ "one of %s)" % ', '.join(langs))
+ elif len(langs) > 1:
+ langs = langs[:1]
+ else:
+ exit("post_compilers in conf.py does not tell me how to "
+ "handle '%s' extensions." % ext)
+
+ lang = langs[0]
+ compile_html = self.compilers[lang]
+ self.inverse_compilers[ext] = compile_html
+
+ return compile_html
def render_template(self, template_name, output_name, context):
- data = self.templates_module.render_template(
- template_name, None, context, self.GLOBAL_CONTEXT)
+ local_context = {}
+ local_context["template_name"] = template_name
+ local_context.update(self.config['GLOBAL_CONTEXT'])
+ local_context.update(context)
+ data = self.template_system.render_template(
+ template_name, None, local_context)
assert output_name.startswith(self.config["OUTPUT_FOLDER"])
url_part = output_name[len(self.config["OUTPUT_FOLDER"]) + 1:]
- #this to support windows paths
+ # This is to support windows paths
url_part = "/".join(url_part.split(os.sep))
src = urlparse.urljoin(self.config["BLOG_URL"], url_part)
@@ -289,130 +365,39 @@ class Nikola(object):
return exists
def gen_tasks(self):
+ task_dep = []
+ for pluginInfo in self.plugin_manager.getPluginsOfCategory("Task"):
+ for task in pluginInfo.plugin_object.gen_tasks():
+ yield task
+ if pluginInfo.plugin_object.is_default:
+ task_dep.append(pluginInfo.plugin_object.name)
- yield self.task_serve(output_folder=self.config['OUTPUT_FOLDER'])
- yield self.task_install_theme()
- yield self.task_bootswatch_theme()
- yield self.gen_task_new_post(self.config['post_pages'])
- yield self.gen_task_new_page(self.config['post_pages'])
- yield self.gen_task_copy_assets(themes=self.THEMES,
- output_folder=self.config['OUTPUT_FOLDER'],
- filters=self.config['FILTERS']
- )
- if webassets:
- yield self.gen_task_build_bundles(theme_bundles=self.theme_bundles,
- output_folder=self.config['OUTPUT_FOLDER'],
- filters=self.config['FILTERS']
- )
- yield self.gen_task_deploy(commands=self.config['DEPLOY_COMMANDS'])
- yield self.gen_task_sitemap(blog_url=self.config['BLOG_URL'],
- output_folder=self.config['OUTPUT_FOLDER']
- )
- yield self.gen_task_render_pages(
- translations=self.config['TRANSLATIONS'],
- post_pages=self.config['post_pages'],
- filters=self.config['FILTERS'])
- yield self.gen_task_render_sources(
- translations=self.config['TRANSLATIONS'],
- default_lang=self.config['DEFAULT_LANG'],
- output_folder=self.config['OUTPUT_FOLDER'],
- post_pages=self.config['post_pages'])
- yield self.gen_task_render_posts(
- translations=self.config['TRANSLATIONS'],
- default_lang=self.config['DEFAULT_LANG'],
- timeline=self.timeline
- )
- yield self.gen_task_render_indexes(
- translations=self.config['TRANSLATIONS'],
- messages=self.MESSAGES,
- output_folder=self.config['OUTPUT_FOLDER'],
- index_display_post_count=self.config['INDEX_DISPLAY_POST_COUNT'],
- index_teasers=self.config['INDEX_TEASERS'],
- filters=self.config['FILTERS'],
- )
- yield self.gen_task_render_archive(
- translations=self.config['TRANSLATIONS'],
- messages=self.MESSAGES,
- output_folder=self.config['OUTPUT_FOLDER'],
- filters=self.config['FILTERS'],
- )
- yield self.gen_task_render_tags(
- translations=self.config['TRANSLATIONS'],
- messages=self.MESSAGES,
- blog_title=self.config['BLOG_TITLE'],
- blog_url=self.config['BLOG_URL'],
- blog_description=self.config['BLOG_DESCRIPTION'],
- output_folder=self.config['OUTPUT_FOLDER'],
- filters=self.config['FILTERS'],
- tag_pages_are_indexes=self.config['TAG_PAGES_ARE_INDEXES'],
- index_display_post_count=self.config['INDEX_DISPLAY_POST_COUNT'],
- index_teasers=self.config['INDEX_TEASERS'],
- )
- yield self.gen_task_render_rss(
- translations=self.config['TRANSLATIONS'],
- blog_title=self.config['BLOG_TITLE'],
- blog_url=self.config['BLOG_URL'],
- blog_description=self.config['BLOG_DESCRIPTION'],
- output_folder=self.config['OUTPUT_FOLDER'])
- yield self.gen_task_render_galleries(
- max_image_size=self.config['MAX_IMAGE_SIZE'],
- thumbnail_size=self.config['THUMBNAIL_SIZE'],
- default_lang=self.config['DEFAULT_LANG'],
- output_folder=self.config['OUTPUT_FOLDER'],
- use_filename_as_title=self.config['USE_FILENAME_AS_TITLE'],
- blog_description=self.config['BLOG_DESCRIPTION']
- )
- yield self.gen_task_render_listings(
- listings_folder=self.config['LISTINGS_FOLDER'],
- default_lang=self.config['DEFAULT_LANG'],
- output_folder=self.config['OUTPUT_FOLDER'])
- yield self.gen_task_redirect(
- redirections=self.config['REDIRECTIONS'],
- output_folder=self.config['OUTPUT_FOLDER'])
- yield self.gen_task_copy_files(
- output_folder=self.config['OUTPUT_FOLDER'],
- files_folders=self.config['FILES_FOLDERS'],
- filters=self.config['FILTERS'])
-
- task_dep = [
- 'render_listings',
- 'render_archive',
- 'render_galleries',
- 'render_indexes',
- 'render_pages',
- 'render_posts',
- 'render_rss',
- 'render_sources',
- 'render_tags',
- 'copy_assets',
- 'copy_files',
- 'sitemap',
- 'redirect'
- ]
-
- if webassets:
- task_dep.append( 'build_bundles' )
+ for pluginInfo in self.plugin_manager.getPluginsOfCategory("LateTask"):
+ for task in pluginInfo.plugin_object.gen_tasks():
+ yield task
+ if pluginInfo.plugin_object.is_default:
+ task_dep.append(pluginInfo.plugin_object.name)
yield {
'name': 'all',
'actions': None,
'clean': True,
'task_dep': task_dep
- }
+ }
def scan_posts(self):
"""Scan all the posts."""
if not self._scanned:
print "Scanning posts ",
targets = set([])
- for wildcard, destination, _, use_in_feeds in self.config['post_pages']:
+ for wildcard, destination, _, use_in_feeds in \
+ self.config['post_pages']:
print ".",
for base_path in glob.glob(wildcard):
post = Post(base_path, destination, use_in_feeds,
self.config['TRANSLATIONS'],
self.config['DEFAULT_LANG'],
self.config['BLOG_URL'],
- self.get_compile_html(base_path),
self.MESSAGES)
for lang, langpath in self.config['TRANSLATIONS'].items():
dest = (destination, langpath, post.pagenames[lang])
@@ -448,7 +433,8 @@ class Nikola(object):
post_name = os.path.splitext(post)[0]
context = {}
post = self.global_data[post_name]
- deps = post.deps(lang) + self.template_deps(template_name)
+ deps = post.deps(lang) + \
+ self.template_system.template_deps(template_name)
context['post'] = post
context['lang'] = lang
context['title'] = post.title(lang)
@@ -468,6 +454,7 @@ class Nikola(object):
deps_dict['NEXT_LINK'] = [post.next_post.permalink(lang)]
deps_dict['OUTPUT_FOLDER'] = self.config['OUTPUT_FOLDER']
deps_dict['TRANSLATIONS'] = self.config['TRANSLATIONS']
+ deps_dict['global'] = self.config['GLOBAL_CONTEXT']
task = {
'name': output_name.encode('utf-8'),
@@ -481,187 +468,11 @@ class Nikola(object):
yield utils.apply_filters(task, filters)
- def gen_task_render_pages(self, **kw):
- """Build final pages from metadata and HTML fragments.
-
- Required keyword arguments:
-
- translations
- post_pages
- """
- self.scan_posts()
- flag = False
- for lang in kw["translations"]:
- for wildcard, destination, template_name, _ in kw["post_pages"]:
- for task in self.generic_page_renderer(lang,
- wildcard, template_name, destination, kw["filters"]):
- # TODO: enable or remove
- #task['uptodate'] = task.get('uptodate', []) +\
- #[config_changed(kw)]
- task['basename'] = 'render_pages'
- flag = True
- yield task
- if flag == False: # No page rendered, yield a dummy task
- yield {
- 'basename': 'render_pages',
- 'name': 'None',
- 'uptodate': [True],
- 'actions': [],
- }
-
- def gen_task_render_sources(self, **kw):
- """Publish the rst sources because why not?
-
- Required keyword arguments:
-
- translations
- default_lang
- post_pages
- output_folder
- """
- self.scan_posts()
- flag = False
- for lang in kw["translations"]:
- # TODO: timeline is global
- for post in self.timeline:
- output_name = os.path.join(kw['output_folder'],
- post.destination_path(lang, post.source_ext()))
- source = post.source_path
- if lang != kw["default_lang"]:
- source_lang = source + '.' + lang
- if os.path.exists(source_lang):
- source = source_lang
- yield {
- 'basename': 'render_sources',
- 'name': output_name.encode('utf8'),
- 'file_dep': [source],
- 'targets': [output_name],
- 'actions': [(utils.copy_file, (source, output_name))],
- 'clean': True,
- 'uptodate': [config_changed(kw)],
- }
- if flag == False: # No page rendered, yield a dummy task
- yield {
- 'basename': 'render_sources',
- 'name': 'None',
- 'uptodate': [True],
- 'actions': [],
- }
-
- def gen_task_render_posts(self, **kw):
- """Build HTML fragments from metadata and reSt.
-
- Required keyword arguments:
-
- translations
- default_lang
- timeline
- """
- self.scan_posts()
- flag = False
- for lang in kw["translations"]:
- # TODO: timeline is global, get rid of it
- deps_dict = copy(kw)
- deps_dict.pop('timeline')
- for post in kw['timeline']:
- source = post.source_path
- dest = post.base_path
- if lang != kw["default_lang"]:
- dest += '.' + lang
- source_lang = source + '.' + lang
- if os.path.exists(source_lang):
- source = source_lang
- flag = True
- yield {
- 'basename': 'render_posts',
- 'name': dest.encode('utf-8'),
- 'file_dep': post.fragment_deps(lang),
- 'targets': [dest],
- 'actions': [(post.compile_html, [source, dest])],
- 'clean': True,
- 'uptodate': [config_changed(deps_dict)],
- }
- if flag == False: # Return a dummy task
- yield {
- 'basename': 'render_posts',
- 'name': 'None',
- 'uptodate': [True],
- 'actions': [],
- }
-
- def gen_task_render_indexes(self, **kw):
- """Render post-per-page indexes.
- The default is 10.
-
- Required keyword arguments:
-
- translations
- output_folder
- index_display_post_count
- index_teasers
- """
- self.scan_posts()
- template_name = "index.tmpl"
- # TODO: timeline is global, get rid of it
- posts = [x for x in self.timeline if x.use_in_feeds]
- # Split in smaller lists
- lists = []
- while posts:
- lists.append(posts[:kw["index_display_post_count"]])
- posts = posts[kw["index_display_post_count"]:]
- num_pages = len(lists)
- if not lists:
- yield {
- 'basename': 'render_indexes',
- 'actions': [],
- }
- for lang in kw["translations"]:
- for i, post_list in enumerate(lists):
- context = {}
- if self.config.get("INDEXES_TITLE", ""):
- indexes_title = self.config['INDEXES_TITLE']
- else:
- indexes_title = self.config["BLOG_TITLE"]
- if not i:
- output_name = "index.html"
- context["title"] = indexes_title
- else:
- output_name = "index-%s.html" % i
- if self.config.get("INDEXES_PAGES", ""):
- indexes_pages = self.config["INDEXES_PAGES"] % i
- else:
- indexes_pages = " (" + kw["messages"][lang]["old posts page %d"] % i + ")"
- context["title"] = indexes_title + indexes_pages
- context["prevlink"] = None
- context["nextlink"] = None
- context['index_teasers'] = kw['index_teasers']
- if i > 1:
- context["prevlink"] = "index-%s.html" % (i - 1)
- if i == 1:
- context["prevlink"] = "index.html"
- if i < num_pages - 1:
- context["nextlink"] = "index-%s.html" % (i + 1)
- context["permalink"] = self.link("index", i, lang)
- output_name = os.path.join(
- kw['output_folder'], self.path("index", i, lang))
- for task in self.generic_post_list_renderer(
- lang,
- post_list,
- output_name,
- template_name,
- kw['filters'],
- context,
- ):
- task['uptodate'] = task.get('updtodate', []) +\
- [config_changed(kw)]
- task['basename'] = 'render_indexes'
- yield task
-
def generic_post_list_renderer(self, lang, posts,
output_name, template_name, filters, extra_context):
"""Renders pages with lists of posts."""
- deps = self.template_deps(template_name)
+ deps = self.template_system.template_deps(template_name)
for post in posts:
deps += post.deps(lang)
context = {}
@@ -675,6 +486,7 @@ class Nikola(object):
deps_context = copy(context)
deps_context["posts"] = [(p.titles[lang], p.permalink(lang))
for p in posts]
+ deps_context["global"] = self.config['GLOBAL_CONTEXT']
task = {
'name': output_name.encode('utf8'),
'targets': [output_name],
@@ -686,1026 +498,3 @@ class Nikola(object):
}
yield utils.apply_filters(task, filters)
-
- def gen_task_render_archive(self, **kw):
- """Render the post archives.
-
- Required keyword arguments:
-
- translations
- messages
- output_folder
- """
- # TODO add next/prev links for years
- template_name = "list.tmpl"
- # TODO: posts_per_year is global, kill it
- for year, posts in self.posts_per_year.items():
- for lang in kw["translations"]:
- output_name = os.path.join(
- kw['output_folder'], self.path("archive", year, lang))
- post_list = [self.global_data[post] for post in posts]
- post_list.sort(cmp=lambda a, b: cmp(a.date, b.date))
- post_list.reverse()
- context = {}
- context["lang"] = lang
- context["items"] = [("[%s] %s" %
- (post.date, post.title(lang)), post.permalink(lang))
- for post in post_list]
- context["permalink"] = self.link("archive", year, lang)
- context["title"] = kw["messages"][lang]["Posts for year %s"]\
- % year
- for task in self.generic_post_list_renderer(
- lang,
- post_list,
- output_name,
- template_name,
- kw['filters'],
- context,
- ):
- task['uptodate'] = task.get('updtodate', []) +\
- [config_changed(kw)]
- yield task
-
- # And global "all your years" page
- years = self.posts_per_year.keys()
- years.sort(reverse=True)
- template_name = "list.tmpl"
- kw['years'] = years
- for lang in kw["translations"]:
- context = {}
- output_name = os.path.join(
- kw['output_folder'], self.path("archive", None, lang))
- context["title"] = kw["messages"][lang]["Archive"]
- context["items"] = [(year, self.link("archive", year, lang))
- for year in years]
- context["permalink"] = self.link("archive", None, lang)
- for task in self.generic_post_list_renderer(
- lang,
- [],
- output_name,
- template_name,
- kw['filters'],
- context,
- ):
- task['uptodate'] = task.get('updtodate', []) +\
- [config_changed(kw)]
- task['basename'] = 'render_archive'
- yield task
-
- def gen_task_render_tags(self, **kw):
- """Render the tag pages.
-
- Required keyword arguments:
-
- translations
- messages
- blog_title
- blog_url
- blog_description
- output_folder
- tag_pages_are_indexes
- index_display_post_count
- index_teasers
- """
- if not self.posts_per_tag:
- yield {
- 'basename': 'render_tags',
- 'actions': [],
- }
- return
- def page_name(tagname, i, lang):
- """Given tag, n, returns a page name."""
- name = self.path("tag", tag, lang)
- if i:
- name = name.replace('.html', '-%s.html' % i)
- return name
-
- for tag, posts in self.posts_per_tag.items():
- post_list = [self.global_data[post] for post in posts]
- post_list.sort(cmp=lambda a, b: cmp(a.date, b.date))
- post_list.reverse()
- for lang in kw["translations"]:
- #Render RSS
- output_name = os.path.join(kw['output_folder'],
- self.path("tag_rss", tag, lang))
- deps = []
- post_list = [self.global_data[post] for post in posts
- if self.global_data[post].use_in_feeds]
- post_list.sort(cmp=lambda a, b: cmp(a.date, b.date))
- post_list.reverse()
- for post in post_list:
- deps += post.deps(lang)
- yield {
- 'name': output_name.encode('utf8'),
- 'file_dep': deps,
- 'targets': [output_name],
- 'actions': [(utils.generic_rss_renderer,
- (lang, "%s (%s)" % (kw["blog_title"], tag),
- kw["blog_url"], kw["blog_description"],
- post_list, output_name))],
- 'clean': True,
- 'uptodate': [config_changed(kw)],
- 'basename': 'render_tags'
- }
-
- # Render HTML
- if kw['tag_pages_are_indexes']:
- # We render a sort of index page collection using only
- # this tag's posts.
-
- # FIXME: deduplicate this with render_indexes
- template_name = "index.tmpl"
- # Split in smaller lists
- lists = []
- while post_list:
- lists.append(post_list[:kw["index_display_post_count"]])
- post_list = post_list[kw["index_display_post_count"]:]
- num_pages = len(lists)
- for i, post_list in enumerate(lists):
- context = {}
- # On a tag page, the feeds are the tag's feeds, plus the site's
- rss_link = \
- """<link rel="alternate" type="application/rss+xml" """\
- """type="application/rss+xml" title="RSS for tag """\
- """%s (%s)" href="%s">""" % \
- (tag, lang, self.link("tag_rss", tag, lang))
- context ['rss_link'] = rss_link
- output_name = os.path.join(kw['output_folder'],
- page_name(tag, i, lang))
- context["title"] = kw["messages"][lang][u"Posts about %s:"]\
- % tag
- context["prevlink"] = None
- context["nextlink"] = None
- context['index_teasers'] = kw['index_teasers']
- if i > 1:
- context["prevlink"] = os.path.basename(page_name(tag, i - 1, lang))
- if i == 1:
- context["prevlink"] = os.path.basename(page_name(tag, 0, lang))
- if i < num_pages - 1:
- context["nextlink"] = os.path.basename(page_name(tag, i + 1, lang))
- context["permalink"] = self.link("tag", tag, lang)
- context["tag"] = tag
- for task in self.generic_post_list_renderer(
- lang,
- post_list,
- output_name,
- template_name,
- kw['filters'],
- context,
- ):
- task['uptodate'] = task.get('updtodate', []) +\
- [config_changed(kw)]
- task['basename'] = 'render_tags'
- yield task
- else:
- # We render a single flat link list with this tag's posts
- template_name = "tag.tmpl"
- output_name = os.path.join(kw['output_folder'],
- self.path("tag", tag, lang))
- context = {}
- context["lang"] = lang
- context["title"] = kw["messages"][lang][u"Posts about %s:"]\
- % tag
- context["items"] = [("[%s] %s" % (post.date, post.title(lang)),
- post.permalink(lang)) for post in post_list]
- context["permalink"] = self.link("tag", tag, lang)
- context["tag"] = tag
- for task in self.generic_post_list_renderer(
- lang,
- post_list,
- output_name,
- template_name,
- kw['filters'],
- context,
- ):
- task['uptodate'] = task.get('updtodate', []) +\
- [config_changed(kw)]
- task['basename'] = 'render_tags'
- yield task
-
- # And global "all your tags" page
- tags = self.posts_per_tag.keys()
- tags.sort()
- template_name = "tags.tmpl"
- kw['tags'] = tags
- for lang in kw["translations"]:
- output_name = os.path.join(
- kw['output_folder'], self.path('tag_index', None, lang))
- context = {}
- context["title"] = kw["messages"][lang][u"Tags"]
- context["items"] = [(tag, self.link("tag", tag, lang))
- for tag in tags]
- context["permalink"] = self.link("tag_index", None, lang)
- for task in self.generic_post_list_renderer(
- lang,
- [],
- output_name,
- template_name,
- kw['filters'],
- context,
- ):
- task['uptodate'] = task.get('updtodate', []) +\
- [config_changed(kw)]
- yield task
-
- def gen_task_render_rss(self, **kw):
- """Generate RSS feeds.
-
- Required keyword arguments:
-
- translations
- blog_title
- blog_url
- blog_description
- output_folder
- """
-
- self.scan_posts()
- # TODO: timeline is global, kill it
- for lang in kw["translations"]:
- output_name = os.path.join(kw['output_folder'],
- self.path("rss", None, lang))
- deps = []
- posts = [x for x in self.timeline if x.use_in_feeds][:10]
- for post in posts:
- deps += post.deps(lang)
- yield {
- 'basename': 'render_rss',
- 'name': output_name,
- 'file_dep': deps,
- 'targets': [output_name],
- 'actions': [(utils.generic_rss_renderer,
- (lang, kw["blog_title"], kw["blog_url"],
- kw["blog_description"], posts, output_name))],
- 'clean': True,
- 'uptodate': [config_changed(kw)],
- }
-
- def gen_task_render_listings(self, **kw):
- """
- Required keyword arguments:
-
- listings_folder
- output_folder
- default_lang
- """
-
- # Things to ignore in listings
- ignored_extensions = (".pyc",)
-
- def render_listing(in_name, out_name):
- with open(in_name, 'r') as fd:
- try:
- lexer = get_lexer_for_filename(in_name)
- except:
- lexer = TextLexer()
- code = highlight(fd.read(), lexer,
- HtmlFormatter(cssclass='code',
- linenos="table",
- nowrap=False,
- lineanchors=utils.slugify(f),
- anchorlinenos=True))
- title = os.path.basename(in_name)
- crumbs = out_name.split(os.sep)[1:-1] + [title]
- # TODO: write this in human
- paths = ['/'.join(['..'] * (len(crumbs) - 2 - i)) for i in range(len(crumbs[:-2]))] + ['.', '#']
- context = {
- 'code': code,
- 'title': title,
- 'crumbs': zip(paths, crumbs),
- 'lang': kw['default_lang'],
- 'description': title,
- }
- self.render_template('listing.tmpl', out_name, context)
- flag = True
- template_deps = self.template_deps('listing.tmpl')
- for root, dirs, files in os.walk(kw['listings_folder']):
- # Render all files
- for f in files:
- ext = os.path.splitext(f)[-1]
- if ext in ignored_extensions:
- continue
- flag = False
- in_name = os.path.join(root, f)
- out_name = os.path.join(
- kw['output_folder'],
- root,
- f) + '.html'
- yield {
- 'basename': 'render_listings',
- 'name': out_name.encode('utf8'),
- 'file_dep': template_deps + [in_name],
- 'targets': [out_name],
- 'actions': [(render_listing, [in_name, out_name])],
- }
- if flag:
- yield {
- 'basename': 'render_listings',
- 'actions': [],
- }
-
- def gen_task_render_galleries(self, **kw):
- """Render image galleries.
-
- Required keyword arguments:
-
- image_size
- thumbnail_size,
- default_lang,
- output_folder,
- use_filename_as_title
- """
-
- # FIXME: lots of work is done even when images don't change,
- # which should be moved into the task.
- # Also, this is getting complex enough to be refactored into a file.
-
- template_name = "gallery.tmpl"
-
- gallery_list = glob.glob("galleries/*")
- # Fail quick if we don't have galleries, so we don't
- # require PIL
- Image = None
- if not gallery_list:
- yield {
- 'basename': 'render_galleries',
- 'actions': [],
- }
- return
- try:
- import Image as _Image
- import ExifTags
- Image = _Image
- except ImportError:
- try:
- from PIL import Image as _Image, ExifTags
- Image = _Image
- except ImportError:
- pass
- if Image:
- def _resize_image(src, dst, max_size):
- im = Image.open(src)
- w, h = im.size
- if w > max_size or h > max_size:
- size = max_size, max_size
- try:
- exif = im._getexif()
- except Exception:
- exif = None
- if exif is not None:
- for tag, value in exif.items():
- decoded = ExifTags.TAGS.get(tag, tag)
-
- if decoded == 'Orientation':
- if value == 3:
- im = im.rotate(180)
- elif value == 6:
- im = im.rotate(270)
- elif value == 8:
- im = im.rotate(90)
-
- break
-
- im.thumbnail(size, Image.ANTIALIAS)
- im.save(dst)
-
- else:
- utils.copy_file(src, dst)
-
- def create_thumb(src, dst):
- return _resize_image(src, dst, kw['thumbnail_size'])
-
- def create_resized_image(src, dst):
- return _resize_image(src, dst, kw['max_image_size'])
-
- dates = {}
- def image_date(src):
- if src not in dates:
- im = Image.open(src)
- try:
- exif = im._getexif()
- except Exception:
- exif = None
- if exif is not None:
- for tag, value in exif.items():
- decoded = ExifTags.TAGS.get(tag, tag)
- if decoded == 'DateTimeOriginal':
- try:
- dates[src] = datetime.datetime.strptime(value, r'%Y:%m:%d %H:%M:%S')
- break
- except ValueError: #invalid EXIF date
- pass
- if src not in dates:
- dates[src] = datetime.datetime.fromtimestamp(os.stat(src).st_mtime)
- return dates[src]
-
- else:
- create_thumb = utils.copy_file
- create_resized_image = utils.copy_file
-
- # gallery_path is "gallery/name"
- for gallery_path in gallery_list:
- # gallery_name is "name"
- gallery_name = os.path.basename(gallery_path)
- # output_gallery is "output/GALLERY_PATH/name"
- output_gallery = os.path.dirname(os.path.join(kw["output_folder"],
- self.path("gallery", gallery_name, None)))
- if not os.path.isdir(output_gallery):
- yield {
- 'basename': 'render_galleries',
- 'name': output_gallery,
- 'actions': [(os.makedirs, (output_gallery,))],
- 'targets': [output_gallery],
- 'clean': True,
- 'uptodate': [config_changed(kw)],
- }
- # image_list contains "gallery/name/image_name.jpg"
- image_list = glob.glob(gallery_path + "/*jpg") +\
- glob.glob(gallery_path + "/*JPG") +\
- glob.glob(gallery_path + "/*PNG") +\
- glob.glob(gallery_path + "/*png")
-
- # Filter ignore images
- try:
- def add_gallery_path(index):
- return "{0}/{1}".format(gallery_path, index)
-
- exclude_path = os.path.join(gallery_path, "exclude.meta")
- try:
- f = open(exclude_path, 'r')
- excluded_image_name_list = f.read().split()
- except IOError:
- excluded_image_name_list = []
-
- excluded_image_list = map(add_gallery_path,
- excluded_image_name_list)
- image_set = set(image_list) - set(excluded_image_list)
- image_list = list(image_set)
- except IOError:
- pass
-
- image_list = [x for x in image_list if "thumbnail" not in x]
- # Sort by date
- image_list.sort(cmp=lambda a,b: cmp(image_date(a), image_date(b)))
- image_name_list = [os.path.basename(x) for x in image_list]
-
- thumbs = []
- # Do thumbnails and copy originals
- for img, img_name in zip(image_list, image_name_list):
- # img is "galleries/name/image_name.jpg"
- # img_name is "image_name.jpg"
- # fname, ext are "image_name", ".jpg"
- fname, ext = os.path.splitext(img_name)
- # thumb_path is
- # "output/GALLERY_PATH/name/image_name.thumbnail.jpg"
- thumb_path = os.path.join(output_gallery,
- fname + ".thumbnail" + ext)
- # thumb_path is "output/GALLERY_PATH/name/image_name.jpg"
- orig_dest_path = os.path.join(output_gallery, img_name)
- thumbs.append(os.path.basename(thumb_path))
- yield {
- 'basename': 'render_galleries',
- 'name': thumb_path,
- 'file_dep': [img],
- 'targets': [thumb_path],
- 'actions': [
- (create_thumb, (img, thumb_path))
- ],
- 'clean': True,
- 'uptodate': [config_changed(kw)],
- }
- yield {
- 'basename': 'render_galleries',
- 'name': orig_dest_path,
- 'file_dep': [img],
- 'targets': [orig_dest_path],
- 'actions': [
- (create_resized_image, (img, orig_dest_path))
- ],
- 'clean': True,
- 'uptodate': [config_changed(kw)],
- }
-
- # Remove excluded images
- if excluded_image_name_list:
- for img, img_name in zip(excluded_image_list,
- excluded_image_name_list):
- # img_name is "image_name.jpg"
- # fname, ext are "image_name", ".jpg"
- fname, ext = os.path.splitext(img_name)
- excluded_thumb_dest_path = os.path.join(output_gallery,
- fname + ".thumbnail" + ext)
- excluded_dest_path = os.path.join(output_gallery, img_name)
- yield {
- 'basename': 'render_galleries',
- 'name': excluded_thumb_dest_path,
- 'file_dep': [exclude_path],
- #'targets': [excluded_thumb_dest_path],
- 'actions': [
- (utils.remove_file, (excluded_thumb_dest_path,))
- ],
- 'clean': True,
- 'uptodate': [config_changed(kw)],
- }
- yield {
- 'basename': 'render_galleries',
- 'name': excluded_dest_path,
- 'file_dep': [exclude_path],
- #'targets': [excluded_dest_path],
- 'actions': [
- (utils.remove_file, (excluded_dest_path,))
- ],
- 'clean': True,
- 'uptodate': [config_changed(kw)],
- }
-
- output_name = os.path.join(output_gallery, "index.html")
- context = {}
- context["lang"] = kw["default_lang"]
- context["title"] = os.path.basename(gallery_path)
- context["description"] = kw["blog_description"]
- if kw['use_filename_as_title']:
- img_titles = ['title="%s"' % utils.unslugify(fn[:-4])
- for fn in image_name_list]
- else:
- img_titles = [''] * len(image_name_list)
- context["images"] = zip(image_name_list, thumbs, img_titles)
- context["permalink"] = self.link("gallery", gallery_name, None)
-
- # Use galleries/name/index.txt to generate a blurb for
- # the gallery, if it exists
- index_path = os.path.join(gallery_path, "index.txt")
- index_dst_path = os.path.join(gallery_path, "index.html")
- if os.path.exists(index_path):
- compile_html = self.get_compile_html(index_path)
- yield {
- 'basename': 'render_galleries',
- 'name': index_dst_path.encode('utf-8'),
- 'file_dep': [index_path],
- 'targets': [index_dst_path],
- 'actions': [(compile_html,
- [index_path, index_dst_path])],
- 'clean': True,
- 'uptodate': [config_changed(kw)],
- }
-
- file_dep = self.template_deps(template_name) + image_list
-
- def render_gallery(output_name, context, index_dst_path):
- if os.path.exists(index_dst_path):
- with codecs.open(index_dst_path, "rb", "utf8") as fd:
- context['text'] = fd.read()
- file_dep.append(index_dst_path)
- else:
- context['text'] = ''
- self.render_template(template_name, output_name, context)
-
- yield {
- 'basename': 'render_galleries',
- 'name': gallery_path,
- 'file_dep': file_dep,
- 'targets': [output_name],
- 'actions': [(render_gallery,
- (output_name, context, index_dst_path))],
- 'clean': True,
- 'uptodate': [config_changed(kw)],
- }
-
- @staticmethod
- def gen_task_redirect(**kw):
- """Generate redirections.
-
- Required keyword arguments:
-
- redirections
- output_folder
- """
-
- def create_redirect(src, dst):
- with codecs.open(src, "wb+", "utf8") as fd:
- fd.write(('<head>' +
- '<meta HTTP-EQUIV="REFRESH" content="0; url=%s">' +
- '</head>') % dst)
-
- if not kw['redirections']:
- # If there are no redirections, still needs to create a
- # dummy action so dependencies don't fail
- yield {
- 'basename': 'redirect',
- 'name': 'None',
- 'uptodate': [True],
- 'actions': [],
- }
- else:
- for src, dst in kw["redirections"]:
- src_path = os.path.join(kw["output_folder"], src)
- yield {
- 'basename': 'redirect',
- 'name': src_path,
- 'targets': [src_path],
- 'actions': [(create_redirect, (src_path, dst))],
- 'clean': True,
- 'uptodate': [config_changed(kw)],
- }
-
- @staticmethod
- def gen_task_copy_files(**kw):
- """Copy static files into the output folder.
-
- required keyword arguments:
-
- output_folder
- files_folders
- """
-
- flag = False
- for src in kw['files_folders']:
- dst = kw['output_folder']
- filters = kw['filters']
- real_dst = os.path.join(dst, kw['files_folders'][src])
- for task in utils.copy_tree(src, real_dst, link_cutoff=dst):
- flag = True
- task['basename'] = 'copy_files'
- task['uptodate'] = task.get('uptodate', []) +\
- [config_changed(kw)]
- yield utils.apply_filters(task, filters)
- if not flag:
- yield {
- 'basename': 'copy_files',
- 'actions': (),
- }
-
- @staticmethod
- def gen_task_copy_assets(**kw):
- """Create tasks to copy the assets of the whole theme chain.
-
- If a file is present on two themes, use the version
- from the "youngest" theme.
-
- Required keyword arguments:
-
- themes
- output_folder
-
- """
- tasks = {}
- for theme_name in kw['themes']:
- src = os.path.join(utils.get_theme_path(theme_name), 'assets')
- dst = os.path.join(kw['output_folder'], 'assets')
- for task in utils.copy_tree(src, dst):
- if task['name'] in tasks:
- continue
- tasks[task['name']] = task
- task['uptodate'] = task.get('uptodate', []) + \
- [config_changed(kw)]
- task['basename'] = 'copy_assets'
- yield utils.apply_filters(task, kw['filters'])
-
- @staticmethod
- def gen_task_build_bundles(**kw):
- """Create tasks to build bundles from theme assets.
-
- theme_bundles
- output_folder
- filters
- """
-
- def build_bundle(output, inputs):
- env = webassets.Environment(
- os.path.join(kw['output_folder'], os.path.dirname(output)),
- os.path.dirname(output))
- bundle = webassets.Bundle(*inputs,
- output=os.path.basename(output))
- env.register(output, bundle)
- # This generates the file
- env[output].urls()
-
- flag = False
- for name, files in kw['theme_bundles'].items():
- output_path = os.path.join(kw['output_folder'], name)
- dname = os.path.dirname(name)
- file_dep = [os.path.join('output', dname, fname)
- for fname in files]
- task = {
- 'task_dep': ['copy_assets', 'copy_files'],
- 'file_dep': file_dep,
- 'name': name,
- 'actions': [(build_bundle, (name, files))],
- 'targets': [os.path.join(kw['output_folder'], name)],
- 'basename': 'build_bundles',
- 'uptodate': [config_changed(kw)]
- }
- flag = True
- yield utils.apply_filters(task, kw['filters'])
- if flag == False: # No page rendered, yield a dummy task
- yield {
- 'basename': 'build_bundles',
- 'name': 'None',
- 'uptodate': [True],
- 'actions': [],
- }
-
-
- @staticmethod
- def new_post(post_pages, is_post=True):
- # Guess where we should put this
- for path, _, _, use_in_rss in post_pages:
- if use_in_rss == is_post:
- break
- else:
- path = post_pages[0][0]
-
- print "Creating New Post"
- print "-----------------\n"
- title = raw_input("Enter title: ").decode(sys.stdin.encoding)
- slug = utils.slugify(title)
- data = u'\n'.join([
- title,
- slug,
- datetime.datetime.now().strftime('%Y/%m/%d %H:%M:%S')
- ])
- output_path = os.path.dirname(path)
- meta_path = os.path.join(output_path, slug + ".meta")
- pattern = os.path.basename(path)
- if pattern.startswith("*."):
- suffix = pattern[1:]
- else:
- suffix = ".txt"
- txt_path = os.path.join(output_path, slug + suffix)
-
- if os.path.isfile(meta_path) or os.path.isfile(txt_path):
- print "The title already exists!"
- exit()
-
- with codecs.open(meta_path, "wb+", "utf8") as fd:
- fd.write(data)
- with codecs.open(txt_path, "wb+", "utf8") as fd:
- fd.write(u"Write your post here.")
- print "Your post's metadata is at: ", meta_path
- print "Your post's text is at: ", txt_path
-
- @classmethod
- def new_page(cls):
- cls.new_post(False)
-
- @classmethod
- def gen_task_new_post(cls, post_pages):
- """Create a new post (interactive)."""
- yield {
- "basename": "new_post",
- "actions": [PythonInteractiveAction(cls.new_post, (post_pages,))],
- }
-
- @classmethod
- def gen_task_new_page(cls, post_pages):
- """Create a new post (interactive)."""
- yield {
- "basename": "new_page",
- "actions": [PythonInteractiveAction(cls.new_post,
- (post_pages, False,))],
- }
-
- @staticmethod
- def gen_task_deploy(**kw):
- """Deploy site.
-
- Required keyword arguments:
-
- commands
-
- """
- yield {
- "basename": "deploy",
- "actions": kw['commands'],
- "verbosity": 2,
- }
-
- @staticmethod
- def gen_task_sitemap(**kw):
- """Generate Google sitemap.
-
- Required keyword arguments:
-
- blog_url
- output_folder
- """
-
- output_path = os.path.abspath(kw['output_folder'])
- sitemap_path = os.path.join(output_path, "sitemap.xml.gz")
-
- def sitemap():
- # Generate config
- config_data = """<?xml version="1.0" encoding="UTF-8"?>
- <site
- base_url="%s"
- store_into="%s"
- verbose="1" >
- <directory path="%s" url="%s" />
- <filter action="drop" type="wildcard" pattern="*~" />
- <filter action="drop" type="regexp" pattern="/\.[^/]*" />
- </site>""" % (
- kw["blog_url"],
- sitemap_path,
- output_path,
- kw["blog_url"],
- )
- config_file = tempfile.NamedTemporaryFile(delete=False)
- config_file.write(config_data)
- config_file.close()
-
- # Generate sitemap
- import sitemap_gen as smap
- sitemap = smap.CreateSitemapFromFile(config_file.name, True)
- if not sitemap:
- smap.output.Log('Configuration file errors -- exiting.', 0)
- else:
- sitemap.Generate()
- smap.output.Log('Number of errors: %d' %
- smap.output.num_errors, 1)
- smap.output.Log('Number of warnings: %d' %
- smap.output.num_warns, 1)
- os.unlink(config_file.name)
-
- yield {
- "basename": "sitemap",
- "task_dep": [
- "render_archive",
- "render_indexes",
- "render_pages",
- "render_posts",
- "render_rss",
- "render_sources",
- "render_tags"],
- "targets": [sitemap_path],
- "actions": [(sitemap,)],
- "uptodate": [config_changed(kw)],
- "clean": True,
- }
-
- @staticmethod
- def task_serve(**kw):
- """
- Start test server. (doit serve [--address 127.0.0.1] [--port 8000])
- By default, the server runs on port 8000 on the IP address 127.0.0.1.
-
- required keyword arguments:
-
- output_folder
- """
-
- def serve(address, port):
- from BaseHTTPServer import HTTPServer
- from SimpleHTTPServer import SimpleHTTPRequestHandler
-
- class OurHTTPRequestHandler(SimpleHTTPRequestHandler):
- extensions_map = dict(SimpleHTTPRequestHandler.extensions_map)
- extensions_map[""] = "text/plain"
-
- os.chdir(kw['output_folder'])
-
- httpd = HTTPServer((address, port), OurHTTPRequestHandler)
- sa = httpd.socket.getsockname()
- print "Serving HTTP on", sa[0], "port", sa[1], "..."
- httpd.serve_forever()
-
- yield {
- "basename": 'serve',
- "actions": [(serve,)],
- "verbosity": 2,
- "params": [{'short': 'a',
- 'name': 'address',
- 'long': 'address',
- 'type': str,
- 'default': '127.0.0.1',
- 'help': 'Bind address (default: 127.0.0.1)'},
- {'short': 'p',
- 'name': 'port',
- 'long': 'port',
- 'type': int,
- 'default': 8000,
- 'help': 'Port number (default: 8000)'}],
- }
-
- @staticmethod
- def task_install_theme():
- """Install theme. (doit install_theme -n themename [-u URL]|[-l])."""
-
- def install_theme(name, url, listing):
- if name is None and not listing:
- print "This command needs either the -n or the -l option."
- return False
- data = urllib2.urlopen(url).read()
- data = json.loads(data)
- if listing:
- print "Themes:"
- print "-------"
- for theme in sorted(data.keys()):
- print theme
- return True
- else:
- if name in data:
- if os.path.isfile("themes"):
- raise IOError("'themes' isn't a directory!")
- elif not os.path.isdir("themes"):
- try:
- os.makedirs("themes")
- except:
- raise OSError("mkdir 'theme' error!")
- print 'Downloading: %s' % data[name]
- zip_file = StringIO()
- zip_file.write(urllib2.urlopen(data[name]).read())
- print 'Extracting: %s into themes' % name
- utils.extract_all(zip_file)
- else:
- print "Can't find theme %s" % name
- return False
-
- yield {
- "basename": 'install_theme',
- "actions": [(install_theme,)],
- "verbosity": 2,
- "params": [
- {
- 'short': 'u',
- 'name': 'url',
- 'long': 'url',
- 'type': str,
- 'default': 'http://nikola.ralsina.com.ar/themes/index.json',
- 'help': 'URL for theme collection.'
- },
- {
- 'short': 'l',
- 'name': 'listing',
- 'long': 'list',
- 'type': bool,
- 'default': False,
- 'help': 'List available themes.'
- },
- {
- 'short': 'n',
- 'name': 'name',
- 'long': 'name',
- 'type': str,
- 'default': None,
- 'help': 'Name of theme to install.'
- }],
- }
-
- @staticmethod
- def task_bootswatch_theme():
- """Given a swatch name and a parent theme, creates a custom theme."""
- def bootswatch_theme(name, parent, swatch):
- print "Creating %s theme from %s and %s" % (name, swatch, parent)
- try:
- os.makedirs(os.path.join('themes', name, 'assets', 'css'))
- except:
- pass
- for fname in ('bootstrap.min.css', 'bootstrap.css'):
- url = 'http://bootswatch.com/%s/%s' % (swatch, fname)
- print "Downloading: ", url
- data = urllib2.urlopen(url).read()
- with open(os.path.join(
- 'themes', name, 'assets', 'css', fname), 'wb+') as output:
- output.write(data)
-
- with open(os.path.join('themes', name, 'parent'), 'wb+') as output:
- output.write(parent)
- print 'Theme created. Change the THEME setting to "%s" to use it.'\
- % name
-
- yield {
- "basename": 'bootswatch_theme',
- "actions": [(bootswatch_theme,)],
- "verbosity": 2,
- "params": [
- {
- 'short': 'p',
- 'name': 'parent',
- 'long': 'parent',
- 'type': str,
- 'default': 'site',
- 'help': 'Name of parent theme.'
- },
- {
- 'short': 's',
- 'name': 'swatch',
- 'long': 'swatch',
- 'type': str,
- 'default': 'slate',
- 'help': 'Name of the swatch from bootswatch.com'
- },
- {
- 'short': 'n',
- 'name': 'name',
- 'long': 'name',
- 'type': str,
- 'default': 'custom',
- 'help': 'Name of the new theme'
- }
- ],
- }
-
-
-def nikola_main():
- print "Starting doit..."
- os.system("doit -f %s" % __file__)