aboutsummaryrefslogtreecommitdiffstats
path: root/nikola
diff options
context:
space:
mode:
authorLibravatarAgustin Henze <tin@sluc.org.ar>2012-12-12 19:58:42 -0300
committerLibravatarAgustin Henze <tin@sluc.org.ar>2012-12-12 19:58:42 -0300
commitca1f5a392261a7c6b82b5ac1015427605909d8c9 (patch)
treef91146c9340c6c78e84aaf6b92053386397e2069 /nikola
Imported Upstream version 4.0.3upstream/4.0.3
Diffstat (limited to 'nikola')
-rw-r--r--nikola/PyRSS2Gen.py444
-rw-r--r--nikola/__init__.py0
-rw-r--r--nikola/data/samplesite/README.txt31
-rwxr-xr-xnikola/data/samplesite/conf.py274
-rwxr-xr-xnikola/data/samplesite/conf.py.in268
-rwxr-xr-xnikola/data/samplesite/dodo.py21
-rw-r--r--nikola/data/samplesite/files/assets/css/custom.css0
-rw-r--r--nikola/data/samplesite/files/favicon.icobin0 -> 432254 bytes
-rw-r--r--nikola/data/samplesite/galleries/demo/exclude.meta1
-rw-r--r--nikola/data/samplesite/galleries/demo/index.txt2
-rw-r--r--nikola/data/samplesite/galleries/demo/tesla2_lg.jpgbin0 -> 20846 bytes
-rw-r--r--nikola/data/samplesite/galleries/demo/tesla4_lg.jpgbin0 -> 29282 bytes
-rw-r--r--nikola/data/samplesite/galleries/demo/tesla_conducts_lg.jpgbin0 -> 18755 bytes
-rw-r--r--nikola/data/samplesite/galleries/demo/tesla_lightning1_lg.jpgbin0 -> 40194 bytes
-rw-r--r--nikola/data/samplesite/galleries/demo/tesla_lightning2_lg.jpgbin0 -> 36091 bytes
-rw-r--r--nikola/data/samplesite/galleries/demo/tesla_tower1_lg.jpgbin0 -> 17188 bytes
-rw-r--r--nikola/data/samplesite/listings/hello.py10
-rw-r--r--nikola/data/samplesite/posts/1.meta5
-rw-r--r--nikola/data/samplesite/posts/1.txt13
-rw-r--r--nikola/data/samplesite/stories/1.meta4
-rw-r--r--nikola/data/samplesite/stories/1.txt4
-rw-r--r--nikola/data/samplesite/stories/configsample.meta4
-rwxr-xr-xnikola/data/samplesite/stories/configsample.txt221
-rw-r--r--nikola/data/samplesite/stories/manual.meta4
-rw-r--r--nikola/data/samplesite/stories/manual.txt808
-rw-r--r--nikola/data/samplesite/stories/quickref.meta4
-rw-r--r--nikola/data/samplesite/stories/quickref.txt1341
-rw-r--r--nikola/data/samplesite/stories/quickstart.meta4
-rw-r--r--nikola/data/samplesite/stories/quickstart.txt406
-rw-r--r--nikola/data/samplesite/stories/theming.meta3
-rw-r--r--nikola/data/samplesite/stories/theming.txt236
-rw-r--r--nikola/data/themes/default/assets/css/bootstrap-responsive.css1040
-rw-r--r--nikola/data/themes/default/assets/css/bootstrap.css5624
-rw-r--r--nikola/data/themes/default/assets/css/code.css62
-rw-r--r--nikola/data/themes/default/assets/css/colorbox.css85
-rw-r--r--nikola/data/themes/default/assets/css/images/border.pngbin0 -> 112 bytes
-rw-r--r--nikola/data/themes/default/assets/css/images/controls.pngbin0 -> 2893 bytes
-rw-r--r--nikola/data/themes/default/assets/css/images/ie6/borderBottomCenter.pngbin0 -> 111 bytes
-rw-r--r--nikola/data/themes/default/assets/css/images/ie6/borderBottomLeft.pngbin0 -> 215 bytes
-rw-r--r--nikola/data/themes/default/assets/css/images/ie6/borderBottomRight.pngbin0 -> 217 bytes
-rw-r--r--nikola/data/themes/default/assets/css/images/ie6/borderMiddleLeft.pngbin0 -> 108 bytes
-rw-r--r--nikola/data/themes/default/assets/css/images/ie6/borderMiddleRight.pngbin0 -> 108 bytes
-rw-r--r--nikola/data/themes/default/assets/css/images/ie6/borderTopCenter.pngbin0 -> 111 bytes
-rw-r--r--nikola/data/themes/default/assets/css/images/ie6/borderTopLeft.pngbin0 -> 216 bytes
-rw-r--r--nikola/data/themes/default/assets/css/images/ie6/borderTopRight.pngbin0 -> 214 bytes
-rw-r--r--nikola/data/themes/default/assets/css/images/loading.gifbin0 -> 9427 bytes
-rw-r--r--nikola/data/themes/default/assets/css/images/loading_background.pngbin0 -> 157 bytes
-rw-r--r--nikola/data/themes/default/assets/css/images/overlay.pngbin0 -> 182 bytes
-rw-r--r--nikola/data/themes/default/assets/css/rst.css315
-rw-r--r--nikola/data/themes/default/assets/css/theme.css26
-rw-r--r--nikola/data/themes/default/assets/img/glyphicons-halflings-white.pngbin0 -> 8777 bytes
-rw-r--r--nikola/data/themes/default/assets/img/glyphicons-halflings.pngbin0 -> 12799 bytes
-rw-r--r--nikola/data/themes/default/assets/js/bootstrap.js2027
-rw-r--r--nikola/data/themes/default/bundles2
-rw-r--r--nikola/data/themes/default/engine1
-rw-r--r--nikola/data/themes/default/messages/de.py19
-rw-r--r--nikola/data/themes/default/messages/en.py25
-rw-r--r--nikola/data/themes/default/messages/es.py21
-rw-r--r--nikola/data/themes/default/messages/fr.py17
-rw-r--r--nikola/data/themes/default/messages/gr.py20
-rw-r--r--nikola/data/themes/default/messages/it.py20
-rw-r--r--nikola/data/themes/default/messages/ru.py17
-rw-r--r--nikola/data/themes/default/templates/base.tmpl103
-rw-r--r--nikola/data/themes/default/templates/gallery.tmpl17
-rw-r--r--nikola/data/themes/default/templates/index.tmpl36
-rw-r--r--nikola/data/themes/default/templates/list.tmpl14
-rw-r--r--nikola/data/themes/default/templates/listing.tmpl10
-rw-r--r--nikola/data/themes/default/templates/post.tmpl51
-rw-r--r--nikola/data/themes/default/templates/story.tmpl8
-rw-r--r--nikola/data/themes/default/templates/tag.tmpl7
-rw-r--r--nikola/data/themes/default/templates/tags.tmpl14
-rw-r--r--nikola/data/themes/jinja-default/README3
-rw-r--r--nikola/data/themes/jinja-default/engine1
-rw-r--r--nikola/data/themes/jinja-default/parent1
-rw-r--r--nikola/data/themes/jinja-default/templates/base.tmpl102
-rw-r--r--nikola/data/themes/jinja-default/templates/gallery.tmpl17
-rw-r--r--nikola/data/themes/jinja-default/templates/index.tmpl36
-rw-r--r--nikola/data/themes/jinja-default/templates/list.tmpl13
-rw-r--r--nikola/data/themes/jinja-default/templates/listing.tmpl9
-rw-r--r--nikola/data/themes/jinja-default/templates/post.tmpl50
-rw-r--r--nikola/data/themes/jinja-default/templates/story.tmpl7
-rw-r--r--nikola/data/themes/jinja-default/templates/tag.tmpl6
-rw-r--r--nikola/data/themes/jinja-default/templates/tags.tmpl13
-rw-r--r--nikola/data/themes/site/README32
-rw-r--r--nikola/data/themes/site/assets/css/theme.css27
-rw-r--r--nikola/data/themes/site/engine1
-rw-r--r--nikola/data/themes/site/parent1
-rw-r--r--nikola/data/themes/site/templates/base.tmpl104
-rw-r--r--nikola/data/themes/site/templates/post.tmpl55
-rw-r--r--nikola/filters.py29
-rw-r--r--nikola/jinja_templates.py37
-rw-r--r--nikola/mako_templates.py65
-rw-r--r--nikola/md.py29
-rw-r--r--nikola/nikola.py1711
-rw-r--r--nikola/post.py180
-rw-r--r--nikola/pygments_code_block_directive.py401
-rw-r--r--nikola/rest.py78
-rwxr-xr-xnikola/sitemap_gen.py2240
-rw-r--r--nikola/utils.py466
-rw-r--r--nikola/wordpress.py134
-rw-r--r--nikola/youtube.py33
101 files changed, 19570 insertions, 0 deletions
diff --git a/nikola/PyRSS2Gen.py b/nikola/PyRSS2Gen.py
new file mode 100644
index 0000000..6c4bda3
--- /dev/null
+++ b/nikola/PyRSS2Gen.py
@@ -0,0 +1,444 @@
+"""PyRSS2Gen - A Python library for generating RSS 2.0 feeds."""
+
+__name__ = "PyRSS2Gen"
+__version__ = (1, 0, 0)
+__author__ = "Andrew Dalke <dalke@dalkescientific.com>"
+
+_generator_name = __name__ + "-" + ".".join(map(str, __version__))
+
+import datetime
+try:
+ import cStringIO
+ StringIO = cStringIO
+except ImportError:
+ import StringIO
+
+# Could make this the base class; will need to add 'publish'
+class WriteXmlMixin:
+ def write_xml(self, outfile, encoding = "iso-8859-1"):
+ from xml.sax import saxutils
+ handler = saxutils.XMLGenerator(outfile, encoding)
+ handler.startDocument()
+ self.publish(handler)
+ handler.endDocument()
+
+ def to_xml(self, encoding = "iso-8859-1"):
+ f = StringIO.StringIO()
+ self.write_xml(f, encoding)
+ return f.getvalue()
+
+
+def _element(handler, name, obj, d = {}):
+ if isinstance(obj, basestring) or obj is None:
+ # special-case handling to make the API easier
+ # to use for the common case.
+ handler.startElement(name, d)
+ if obj is not None:
+ handler.characters(obj)
+ handler.endElement(name)
+ else:
+ # It better know how to emit the correct XML.
+ obj.publish(handler)
+
+def _opt_element(handler, name, obj):
+ if obj is None:
+ return
+ _element(handler, name, obj)
+
+
+def _format_date(dt):
+ """convert a datetime into an RFC 822 formatted date
+
+ Input date must be in GMT.
+ """
+ # Looks like:
+ # Sat, 07 Sep 2002 00:00:01 GMT
+ # Can't use strftime because that's locale dependent
+ #
+ # Isn't there a standard way to do this for Python? The
+ # rfc822 and email.Utils modules assume a timestamp. The
+ # following is based on the rfc822 module.
+ return "%s, %02d %s %04d %02d:%02d:%02d GMT" % (
+ ["Mon", "Tue", "Wed", "Thu", "Fri", "Sat", "Sun"][dt.weekday()],
+ dt.day,
+ ["Jan", "Feb", "Mar", "Apr", "May", "Jun",
+ "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"][dt.month-1],
+ dt.year, dt.hour, dt.minute, dt.second)
+
+
+##
+# A couple simple wrapper objects for the fields which
+# take a simple value other than a string.
+class IntElement:
+ """implements the 'publish' API for integers
+
+ Takes the tag name and the integer value to publish.
+
+ (Could be used for anything which uses str() to be published
+ to text for XML.)
+ """
+ element_attrs = {}
+ def __init__(self, name, val):
+ self.name = name
+ self.val = val
+ def publish(self, handler):
+ handler.startElement(self.name, self.element_attrs)
+ handler.characters(str(self.val))
+ handler.endElement(self.name)
+
+class DateElement:
+ """implements the 'publish' API for a datetime.datetime
+
+ Takes the tag name and the datetime to publish.
+
+ Converts the datetime to RFC 2822 timestamp (4-digit year).
+ """
+ def __init__(self, name, dt):
+ self.name = name
+ self.dt = dt
+ def publish(self, handler):
+ _element(handler, self.name, _format_date(self.dt))
+####
+
+class Category:
+ """Publish a category element"""
+ def __init__(self, category, domain = None):
+ self.category = category
+ self.domain = domain
+ def publish(self, handler):
+ d = {}
+ if self.domain is not None:
+ d["domain"] = self.domain
+ _element(handler, "category", self.category, d)
+
+class Cloud:
+ """Publish a cloud"""
+ def __init__(self, domain, port, path,
+ registerProcedure, protocol):
+ self.domain = domain
+ self.port = port
+ self.path = path
+ self.registerProcedure = registerProcedure
+ self.protocol = protocol
+ def publish(self, handler):
+ _element(handler, "cloud", None, {
+ "domain": self.domain,
+ "port": str(self.port),
+ "path": self.path,
+ "registerProcedure": self.registerProcedure,
+ "protocol": self.protocol})
+
+class Image:
+ """Publish a channel Image"""
+ element_attrs = {}
+ def __init__(self, url, title, link,
+ width = None, height = None, description = None):
+ self.url = url
+ self.title = title
+ self.link = link
+ self.width = width
+ self.height = height
+ self.description = description
+
+ def publish(self, handler):
+ handler.startElement("image", self.element_attrs)
+
+ _element(handler, "url", self.url)
+ _element(handler, "title", self.title)
+ _element(handler, "link", self.link)
+
+ width = self.width
+ if isinstance(width, int):
+ width = IntElement("width", width)
+ _opt_element(handler, "width", width)
+
+ height = self.height
+ if isinstance(height, int):
+ height = IntElement("height", height)
+ _opt_element(handler, "height", height)
+
+ _opt_element(handler, "description", self.description)
+
+ handler.endElement("image")
+
+class Guid:
+ """Publish a guid
+
+ Defaults to being a permalink, which is the assumption if it's
+ omitted. Hence strings are always permalinks.
+ """
+ def __init__(self, guid, isPermaLink = 1):
+ self.guid = guid
+ self.isPermaLink = isPermaLink
+ def publish(self, handler):
+ d = {}
+ if self.isPermaLink:
+ d["isPermaLink"] = "true"
+ else:
+ d["isPermaLink"] = "false"
+ _element(handler, "guid", self.guid, d)
+
+class TextInput:
+ """Publish a textInput
+
+ Apparently this is rarely used.
+ """
+ element_attrs = {}
+ def __init__(self, title, description, name, link):
+ self.title = title
+ self.description = description
+ self.name = name
+ self.link = link
+
+ def publish(self, handler):
+ handler.startElement("textInput", self.element_attrs)
+ _element(handler, "title", self.title)
+ _element(handler, "description", self.description)
+ _element(handler, "name", self.name)
+ _element(handler, "link", self.link)
+ handler.endElement("textInput")
+
+
+class Enclosure:
+ """Publish an enclosure"""
+ def __init__(self, url, length, type):
+ self.url = url
+ self.length = length
+ self.type = type
+ def publish(self, handler):
+ _element(handler, "enclosure", None,
+ {"url": self.url,
+ "length": str(self.length),
+ "type": self.type,
+ })
+
+class Source:
+ """Publish the item's original source, used by aggregators"""
+ def __init__(self, name, url):
+ self.name = name
+ self.url = url
+ def publish(self, handler):
+ _element(handler, "source", self.name, {"url": self.url})
+
+class SkipHours:
+ """Publish the skipHours
+
+ This takes a list of hours, as integers.
+ """
+ element_attrs = {}
+ def __init__(self, hours):
+ self.hours = hours
+ def publish(self, handler):
+ if self.hours:
+ handler.startElement("skipHours", self.element_attrs)
+ for hour in self.hours:
+ _element(handler, "hour", str(hour))
+ handler.endElement("skipHours")
+
+class SkipDays:
+ """Publish the skipDays
+
+ This takes a list of days as strings.
+ """
+ element_attrs = {}
+ def __init__(self, days):
+ self.days = days
+ def publish(self, handler):
+ if self.days:
+ handler.startElement("skipDays", self.element_attrs)
+ for day in self.days:
+ _element(handler, "day", day)
+ handler.endElement("skipDays")
+
+class RSS2(WriteXmlMixin):
+ """The main RSS class.
+
+ Stores the channel attributes, with the "category" elements under
+ ".categories" and the RSS items under ".items".
+ """
+
+ rss_attrs = {"version": "2.0"}
+ element_attrs = {}
+ def __init__(self,
+ title,
+ link,
+ description,
+
+ language = None,
+ copyright = None,
+ managingEditor = None,
+ webMaster = None,
+ pubDate = None, # a datetime, *in* *GMT*
+ lastBuildDate = None, # a datetime
+
+ categories = None, # list of strings or Category
+ generator = _generator_name,
+ docs = "http://blogs.law.harvard.edu/tech/rss",
+ cloud = None, # a Cloud
+ ttl = None, # integer number of minutes
+
+ image = None, # an Image
+ rating = None, # a string; I don't know how it's used
+ textInput = None, # a TextInput
+ skipHours = None, # a SkipHours with a list of integers
+ skipDays = None, # a SkipDays with a list of strings
+
+ items = None, # list of RSSItems
+ ):
+ self.title = title
+ self.link = link
+ self.description = description
+ self.language = language
+ self.copyright = copyright
+ self.managingEditor = managingEditor
+
+ self.webMaster = webMaster
+ self.pubDate = pubDate
+ self.lastBuildDate = lastBuildDate
+
+ if categories is None:
+ categories = []
+ self.categories = categories
+ self.generator = generator
+ self.docs = docs
+ self.cloud = cloud
+ self.ttl = ttl
+ self.image = image
+ self.rating = rating
+ self.textInput = textInput
+ self.skipHours = skipHours
+ self.skipDays = skipDays
+
+ if items is None:
+ items = []
+ self.items = items
+
+ def publish(self, handler):
+ handler.startElement("rss", self.rss_attrs)
+ handler.startElement("channel", self.element_attrs)
+ _element(handler, "title", self.title)
+ _element(handler, "link", self.link)
+ _element(handler, "description", self.description)
+
+ self.publish_extensions(handler)
+
+ _opt_element(handler, "language", self.language)
+ _opt_element(handler, "copyright", self.copyright)
+ _opt_element(handler, "managingEditor", self.managingEditor)
+ _opt_element(handler, "webMaster", self.webMaster)
+
+ pubDate = self.pubDate
+ if isinstance(pubDate, datetime.datetime):
+ pubDate = DateElement("pubDate", pubDate)
+ _opt_element(handler, "pubDate", pubDate)
+
+ lastBuildDate = self.lastBuildDate
+ if isinstance(lastBuildDate, datetime.datetime):
+ lastBuildDate = DateElement("lastBuildDate", lastBuildDate)
+ _opt_element(handler, "lastBuildDate", lastBuildDate)
+
+ for category in self.categories:
+ if isinstance(category, basestring):
+ category = Category(category)
+ category.publish(handler)
+
+ _opt_element(handler, "generator", self.generator)
+ _opt_element(handler, "docs", self.docs)
+
+ if self.cloud is not None:
+ self.cloud.publish(handler)
+
+ ttl = self.ttl
+ if isinstance(self.ttl, int):
+ ttl = IntElement("ttl", ttl)
+ _opt_element(handler, "tt", ttl)
+
+ if self.image is not None:
+ self.image.publish(handler)
+
+ _opt_element(handler, "rating", self.rating)
+ if self.textInput is not None:
+ self.textInput.publish(handler)
+ if self.skipHours is not None:
+ self.skipHours.publish(handler)
+ if self.skipDays is not None:
+ self.skipDays.publish(handler)
+
+ for item in self.items:
+ item.publish(handler)
+
+ handler.endElement("channel")
+ handler.endElement("rss")
+
+ def publish_extensions(self, handler):
+ # Derived classes can hook into this to insert
+ # output after the three required fields.
+ pass
+
+
+
+class RSSItem(WriteXmlMixin):
+ """Publish an RSS Item"""
+ element_attrs = {}
+ def __init__(self,
+ title = None, # string
+ link = None, # url as string
+ description = None, # string
+ author = None, # email address as string
+ categories = None, # list of string or Category
+ comments = None, # url as string
+ enclosure = None, # an Enclosure
+ guid = None, # a unique string
+ pubDate = None, # a datetime
+ source = None, # a Source
+ ):
+
+ if title is None and description is None:
+ raise TypeError(
+ "must define at least one of 'title' or 'description'")
+ self.title = title
+ self.link = link
+ self.description = description
+ self.author = author
+ if categories is None:
+ categories = []
+ self.categories = categories
+ self.comments = comments
+ self.enclosure = enclosure
+ self.guid = guid
+ self.pubDate = pubDate
+ self.source = source
+ # It sure does get tedious typing these names three times...
+
+ def publish(self, handler):
+ handler.startElement("item", self.element_attrs)
+ _opt_element(handler, "title", self.title)
+ _opt_element(handler, "link", self.link)
+ self.publish_extensions(handler)
+ _opt_element(handler, "description", self.description)
+ _opt_element(handler, "author", self.author)
+
+ for category in self.categories:
+ if isinstance(category, basestring):
+ category = Category(category)
+ category.publish(handler)
+
+ _opt_element(handler, "comments", self.comments)
+ if self.enclosure is not None:
+ self.enclosure.publish(handler)
+ _opt_element(handler, "guid", self.guid)
+
+ pubDate = self.pubDate
+ if isinstance(pubDate, datetime.datetime):
+ pubDate = DateElement("pubDate", pubDate)
+ _opt_element(handler, "pubDate", pubDate)
+
+ if self.source is not None:
+ self.source.publish(handler)
+
+ handler.endElement("item")
+
+ def publish_extensions(self, handler):
+ # Derived classes can hook into this to insert
+ # output after the title and link elements
+ pass
diff --git a/nikola/__init__.py b/nikola/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/nikola/__init__.py
diff --git a/nikola/data/samplesite/README.txt b/nikola/data/samplesite/README.txt
new file mode 100644
index 0000000..ca94b34
--- /dev/null
+++ b/nikola/data/samplesite/README.txt
@@ -0,0 +1,31 @@
+How To make This Work
+---------------------
+
+The full manual is in stories/manual.txt, but here is the very short version:
+
+1. Install docutils (http://docutils.sourceforge.net)
+2. Install Mako (http://makotemplates.org)
+3. Install doit (http://python-doit.sourceforge.net)
+4. Install PIL (http://www.pythonware.com/products/pil/)
+5. Install Pygments (http://pygments.org/)
+6. Install unidecode (http://pypi.python.org/pypi/Unidecode/)
+7. Install lxml (http://lxml.de/)
+
+To build or update the demo site run this command in the nikola's folder::
+
+ doit
+
+To see it::
+
+ doit serve -p 8000
+
+And point your browser to http://localhost:8000
+
+Notes on Requirements
+---------------------
+
+If you don't have PIL, then image galleries will be inefficient because Nikola
+will not generate thumbnails. Alternatively, you may install pillow instead of
+PIL.
+
+If you don't have pygments, the code-block directive will not highlight syntax.
diff --git a/nikola/data/samplesite/conf.py b/nikola/data/samplesite/conf.py
new file mode 100755
index 0000000..4389f03
--- /dev/null
+++ b/nikola/data/samplesite/conf.py
@@ -0,0 +1,274 @@
+# -*- coding: utf-8 -*-
+
+import os
+
+########################################
+# Configuration, please edit
+########################################
+
+# Data about this site
+BLOG_AUTHOR = "Your Name"
+BLOG_TITLE = "Demo Site"
+BLOG_URL = "http://nikola.ralsina.com.ar"
+BLOG_EMAIL = "joe@demo.site"
+BLOG_DESCRIPTION = "This is a demo site for Nikola."
+
+# post_pages contains (wildcard, destination, template, use_in_feed) tuples.
+#
+# The wildcard is used to generate a list of reSt source files
+# (whatever/thing.txt).
+# That fragment must have an associated metadata file (whatever/thing.meta),
+# and opcionally translated files (example for spanish, with code "es"):
+# whatever/thing.txt.es and whatever/thing.meta.es
+#
+# From those files, a set of HTML fragment files will be generated:
+# cache/whatever/thing.html (and maybe cache/whatever/thing.html.es)
+#
+# These files are combinated with the template to produce rendered
+# pages, which will be placed at
+# output / TRANSLATIONS[lang] / destination / pagename.html
+#
+# where "pagename" is specified in the metadata file.
+#
+# if use_in_feed is True, then those posts will be added to the site's
+# rss feeds.
+#
+
+post_pages = (
+ ("posts/*.txt", "posts", "post.tmpl", True),
+ ("stories/*.txt", "stories", "story.tmpl", False),
+)
+
+# One or more folders containing files to be copied as-is into the output.
+# The format is a dictionary of "source" "relative destination".
+# Default is:
+# FILES_FOLDERS = {'files': '' }
+# Which means copy 'files' into 'output'
+
+# A mapping of languages to file-extensions that represent that language.
+# Feel free to add or delete extensions to any list, but don't add any new
+# compilers unless you write the interface for it yourself.
+#
+# 'rest' is reStructuredText
+# 'markdown' is MarkDown
+# 'html' assumes the file is html and just copies it
+#post_compilers = {
+# "rest": ('.txt', '.rst'),
+# "markdown": ('.md', '.mdown', '.markdown')
+# "html": ('.html', '.htm')
+# }
+
+# Nikola is multilingual!
+#
+# Currently supported languages are:
+# English -> en
+# Greek -> gr
+# German -> de
+# French -> fr
+# Russian -> ru
+# Spanish -> es
+# Italian -> it
+#
+# If you want to use Nikola with a non-supported language you have to provide
+# a module containing the necessary translations
+# (p.e. look at the modules at: ./nikola/data/themes/default/messages/fr.py).
+# If a specific post is not translated to a language, then the version
+# in the default language will be shown instead.
+
+# What is the default language?
+DEFAULT_LANG = "en"
+
+# What other languages do you have?
+# The format is {"translationcode" : "path/to/translation" }
+# the path will be used as a prefix for the generated pages location
+TRANSLATIONS = {
+ DEFAULT_LANG: "",
+ #"gr": "./gr",
+ #"de": "./de",
+ #"fr": "./fr",
+ #"ru": "./ru",
+ #"es": "./es",
+ }
+
+# Paths for different autogenerated bits. These are combined with the
+# translation paths.
+
+# Final locations are:
+# output / TRANSLATION[lang] / TAG_PATH / index.html (list of tags)
+# output / TRANSLATION[lang] / TAG_PATH / tag.html (list of posts for a tag)
+# output / TRANSLATION[lang] / TAG_PATH / tag.xml (RSS feed for a tag)
+TAG_PATH = "categories"
+
+# If TAG_PAGES_ARE_INDEXES is set to True, each tag's page will contain
+# the posts themselves. If set to False, it will be just a list of links.
+TAG_PAGES_ARE_INDEXES = True
+
+# Final location is output / TRANSLATION[lang] / INDEX_PATH / index-*.html
+INDEX_PATH = ""
+# Final locations for the archives are:
+# output / TRANSLATION[lang] / ARCHIVE_PATH / ARCHIVE_FILENAME
+# output / TRANSLATION[lang] / ARCHIVE_PATH / YEAR / index.html
+ARCHIVE_PATH = ""
+ARCHIVE_FILENAME = "archive.html"
+# Final locations are:
+# output / TRANSLATION[lang] / RSS_PATH / rss.xml
+RSS_PATH = ""
+
+# Slug the Tag URL easier for users to type, special characters are
+# often removed or replaced as well.
+SLUG_TAG_PATH = True
+
+# A list of redirection tuples, [("foo/from.html", "/bar/to.html")].
+#
+# A HTML file will be created in output/foo/from.html that redirects
+# to the "/bar/to.html" URL. notice that the "from" side MUST be a
+# relative URL.
+#
+# If you don't need any of these, just set to []
+
+REDIRECTIONS = []
+
+# Commands to execute to deploy. Can be anything, for example,
+# you may use rsync:
+# "rsync -rav output/* joe@my.site:/srv/www/site"
+# And then do a backup, or ping pingomatic.
+# To do manual deployment, set it to []
+DEPLOY_COMMANDS = []
+
+# Where the output site should be located
+# If you don't use an absolute path, it will be considered as relative
+# to the location of conf.py
+
+OUTPUT_FOLDER = 'output'
+
+# Filters to apply to the output.
+# A directory where the keys are either: a file extensions, or
+# a tuple of file extensions.
+#
+# And the value is a list of commands to be applied in order.
+#
+# Each command must be either:
+#
+# A string containing a '%s' which will
+# be replaced with a filename. The command *must* produce output
+# in place.
+#
+# Or:
+#
+# A python callable, which will be called with the filename as
+# argument.
+#
+# By default, there are no filters.
+FILTERS = {
+# ".jpg": ["jpegoptim --strip-all -m75 -v %s"],
+}
+
+##############################################################################
+# Image Gallery Options
+##############################################################################
+
+# Galleries are folders in galleries/
+# Final location of galleries will be output / GALLERY_PATH / gallery_name
+GALLERY_PATH = "galleries"
+THUMBNAIL_SIZE = 180
+MAX_IMAGE_SIZE = 1280
+USE_FILENAME_AS_TITLE = True
+
+##############################################################################
+# HTML fragments and diverse things that are used by the templates
+##############################################################################
+
+# Data about post-per-page indexes
+INDEXES_TITLE = "" # If this is empty, the default is BLOG_TITLE
+INDEXES_PAGES = "" # If this is empty, the default is 'old posts page %d' translated
+
+# Name of the theme to use. Themes are located in themes/theme_name
+THEME = 'site'
+
+# Show only teasers in the index pages? Defaults to False.
+# INDEX_TEASERS = False
+
+# A HTML fragment describing the license, for the sidebar.
+# I recomment using the Creative Commons' wizard:
+# http://creativecommons.org/choose/
+LICENSE = """
+<a rel="license" href="http://creativecommons.org/licenses/by-nc-sa/2.5/ar/">
+<img alt="Creative Commons License BY-NC-SA"
+style="border-width:0; margin-bottom:12px;"
+src="http://i.creativecommons.org/l/by-nc-sa/2.5/ar/88x31.png"></a>"""
+
+# A small copyright notice for the page footer (in HTML)
+CONTENT_FOOTER = u'Contents &copy; 2012 Example Joe'
+
+# To enable comments via Disqus, you need to create a forum at
+# http://disqus.com, and set DISQUS_FORUM to the short name you selected.
+# If you want to disable comments, set it to False.
+DISQUS_FORUM = "nikolademo"
+
+# Enable Addthis social buttons?
+# Defaults to true
+# ADD_THIS_BUTTONS = True
+
+# Modify the number of Post per Index Page
+# Defaults to 10
+# INDEX_DISPLAY_POST_COUNT = 10
+
+# RSS_LINK is a HTML fragment to link the RSS or Atom feeds. If set to None,
+# the base.tmpl will use the feed Nikola generates. However, you may want to
+# change it for a feedburner feed or something else.
+RSS_LINK = None
+
+# A search form to search this site, for the sidebar. You can use a google
+# custom search (http://www.google.com/cse/)
+# Or a duckduckgo search: https://duckduckgo.com/search_box.html
+# This example should work for pretty much any site we generate.
+SEARCH_FORM = ""
+# This search form is better for the "site" theme where it
+# appears on the navigation bar
+#SEARCH_FORM = """
+#<!-- Custom search -->
+#<form method="get" id="search" action="http://duckduckgo.com/" """\
+#"""class="navbar-form pull-left">
+#<input type="hidden" name="sites" value="%s"/>
+#<input type="hidden" name="k8" value="#444444"/>
+#<input type="hidden" name="k9" value="#D51920"/>
+#<input type="hidden" name="kt" value="h"/>
+#<input type="text" name="q" maxlength="255" """\
+#"""placeholder="Search&hellip;" class="span2" style="margin-top: 4px;"/>
+#<input type="submit" value="DuckDuckGo Search" style="visibility: hidden;" />
+#</form>
+#<!-- End of custom search -->
+#""" % BLOG_URL
+
+# Google analytics or whatever else you use. Added to the bottom of <body>
+# in the default template (base.tmpl).
+ANALYTICS = """
+ """
+
+# Put in global_context things you want available on all your templates.
+# It can be anything, data, functions, modules, etc.
+GLOBAL_CONTEXT = {
+ 'analytics': ANALYTICS,
+ 'blog_author': BLOG_AUTHOR,
+ 'blog_title': BLOG_TITLE,
+ 'blog_url': BLOG_URL,
+ 'blog_desc': BLOG_DESCRIPTION,
+ 'translations': TRANSLATIONS,
+ 'license': LICENSE,
+ 'search_form': SEARCH_FORM,
+ 'disqus_forum': DISQUS_FORUM,
+ 'content_footer': CONTENT_FOOTER,
+ 'rss_path': RSS_PATH,
+ 'rss_link': RSS_LINK,
+ # Locale-dependent links for the sidebar
+ # You should provide a key-value pair for each used language.
+ 'sidebar_links': {
+ DEFAULT_LANG: (
+ ('/' + os.path.join(ARCHIVE_PATH, ARCHIVE_FILENAME), 'Archives'),
+ ('/categories/index.html', 'Tags'),
+ ('/stories/about-nikola.html', 'About Nikola'),
+ ('/stories/handbook.html', 'The Nikola Handbook'),
+ ('http://nikola.ralsina.com.ar', 'Powered by Nikola!'),
+ ),
+ }
+ }
diff --git a/nikola/data/samplesite/conf.py.in b/nikola/data/samplesite/conf.py.in
new file mode 100755
index 0000000..8794565
--- /dev/null
+++ b/nikola/data/samplesite/conf.py.in
@@ -0,0 +1,268 @@
+# -*- coding: utf-8 -*-
+
+import os
+
+########################################
+# Configuration, please edit
+########################################
+
+# Data about this site
+BLOG_AUTHOR = "${BLOG_AUTHOR}"
+BLOG_TITLE = "${BLOG_TITLE}"
+BLOG_URL = "${BLOG_URL}"
+BLOG_EMAIL = "${BLOG_EMAIL}"
+BLOG_DESCRIPTION = "${BLOG_DESCRIPTION}"
+
+
+# post_pages contains (wildcard, destination, template, use_in_feed) tuples.
+#
+# The wildcard is used to generate a list of reSt source files
+# (whatever/thing.txt).
+# That fragment must have an associated metadata file (whatever/thing.meta),
+# and opcionally translated files (example for spanish, with code "es"):
+# whatever/thing.txt.es and whatever/thing.meta.es
+#
+# From those files, a set of HTML fragment files will be generated:
+# cache/whatever/thing.html (and maybe cache/whatever/thing.html.es)
+#
+# These files are combinated with the template to produce rendered
+# pages, which will be placed at
+# output / TRANSLATIONS[lang] / destination / pagename.html
+#
+# where "pagename" is specified in the metadata file.
+#
+# if use_in_feed is True, then those posts will be added to the site's
+# rss feeds.
+#
+
+post_pages = ${POST_PAGES}
+
+# One or more folders containing files to be copied as-is into the output.
+# The format is a dictionary of "source" "relative destination".
+# Default is:
+# FILES_FOLDERS = {'files': '' }
+# Which means copy 'files' into 'output'
+
+# A mapping of languages to file-extensions that represent that language.
+# Feel free to add or delete extensions to any list, but don't add any new
+# compilers unless you write the interface for it yourself.
+#
+# 'rest' is reStructuredTextq
+# 'markdown' is MarkDown
+# 'html' assumes the file is html and just copies it
+post_compilers = ${POST_COMPILERS}
+
+# Nikola is multilingual!
+#
+# Currently supported languages are:
+# English -> en
+# Greek -> gr
+# German -> de
+# French -> fr
+# Russian -> ru
+# Spanish -> es
+# Italian -> it
+#
+# If you want to use Nikola with a non-supported language you have to provide
+# a module containing the necessary translations
+# (p.e. look at the modules at: ./nikola/data/themes/default/messages/fr.py).
+# If a specific post is not translated to a language, then the version
+# in the default language will be shown instead.
+
+# What is the default language?
+DEFAULT_LANG = "${DEFAULT_LANG}"
+
+# What other languages do you have?
+# The format is {"translationcode" : "path/to/translation" }
+# the path will be used as a prefix for the generated pages location
+TRANSLATIONS = {
+ "${DEFAULT_LANG}": "",
+ #"gr": "./gr",
+ #"de": "./de",
+ #"fr": "./fr",
+ #"ru": "./ru",
+ #"es": "./es",
+ }
+
+# Paths for different autogenerated bits. These are combined with the
+# translation paths.
+
+# Final locations are:
+# output / TRANSLATION[lang] / TAG_PATH / index.html (list of tags)
+# output / TRANSLATION[lang] / TAG_PATH / tag.html (list of posts for a tag)
+# output / TRANSLATION[lang] / TAG_PATH / tag.xml (RSS feed for a tag)
+TAG_PATH = "categories"
+
+# If TAG_PAGES_ARE_INDEXES is set to True, each tag's page will contain
+# the posts themselves. If set to False, it will be just a list of links.
+TAG_PAGES_ARE_INDEXES = True
+
+# Final location is output / TRANSLATION[lang] / INDEX_PATH / index-*.html
+INDEX_PATH = ""
+# Final locations for the archives are:
+# output / TRANSLATION[lang] / ARCHIVE_PATH / ARCHIVE_FILENAME
+# output / TRANSLATION[lang] / ARCHIVE_PATH / YEAR / index.html
+ARCHIVE_PATH = ""
+ARCHIVE_FILENAME = "archive.html"
+# Final locations are:
+# output / TRANSLATION[lang] / RSS_PATH / rss.xml
+RSS_PATH = ""
+
+# Slug the Tag URL easier for users to type, special characters are
+# often removed or replaced as well.
+SLUG_TAG_PATH = True
+
+# A list of redirection tuples, [("foo/from.html", "/bar/to.html")].
+#
+# A HTML file will be created in output/foo/from.html that redirects
+# to the "/bar/to.html" URL. notice that the "from" side MUST be a
+# relative URL.
+#
+# If you don't need any of these, just set to []
+
+REDIRECTIONS = []
+
+# Commands to execute to deploy. Can be anything, for example,
+# you may use rsync:
+# "rsync -rav output/* joe@my.site:/srv/www/site"
+# And then do a backup, or ping pingomatic.
+# To do manual deployment, set it to []
+DEPLOY_COMMANDS = []
+
+# Where the output site should be located
+# If you don't use an absolute path, it will be considered as relative
+# to the location of conf.py
+
+OUTPUT_FOLDER = 'output'
+
+# Filters to apply to the output.
+# A directory where the keys are either: a file extensions, or
+# a tuple of file extensions.
+#
+# And the value is a list of commands to be applied in order.
+#
+# Each command must be either:
+#
+# A string containing a '%s' which will
+# be replaced with a filename. The command *must* produce output
+# in place.
+#
+# Or:
+#
+# A python callable, which will be called with the filename as
+# argument.
+#
+# By default, there are no filters.
+FILTERS = {
+# ".jpg": ["jpegoptim --strip-all -m75 -v %s"],
+}
+
+##############################################################################
+# Image Gallery Options
+##############################################################################
+
+# Galleries are folders in galleries/
+# Final location of galleries will be output / GALLERY_PATH / gallery_name
+GALLERY_PATH = "galleries"
+THUMBNAIL_SIZE = 180
+MAX_IMAGE_SIZE = 1280
+USE_FILENAME_AS_TITLE = True
+
+##############################################################################
+# HTML fragments and diverse things that are used by the templates
+##############################################################################
+
+# Data about post-per-page indexes
+INDEXES_TITLE = "" # If this is empty, the default is BLOG_TITLE
+INDEXES_PAGES = "" # If this is empty, the default is 'old posts page %d' translated
+
+# Name of the theme to use. Themes are located in themes/theme_name
+THEME = 'site'
+
+# Show only teasers in the index pages? Defaults to False.
+# INDEX_TEASERS = False
+
+# A HTML fragment describing the license, for the sidebar.
+# I recomment using the Creative Commons' wizard:
+# http://creativecommons.org/choose/
+LICENSE = """
+<a rel="license" href="http://creativecommons.org/licenses/by-nc-sa/2.5/ar/">
+<img alt="Creative Commons License BY-NC-SA"
+style="border-width:0; margin-bottom:12px;"
+src="http://i.creativecommons.org/l/by-nc-sa/2.5/ar/88x31.png"></a>"""
+
+# A small copyright notice for the page footer (in HTML)
+CONTENT_FOOTER = u'Contents &copy; 2012 <a href="${BLOG_EMAIL}">${BLOG_AUTHOR}</a>'
+
+# To enable comments via Disqus, you need to create a forum at
+# http://disqus.com, and set DISQUS_FORUM to the short name you selected.
+# If you want to disable comments, set it to False.
+DISQUS_FORUM = "nikolademo"
+
+# Enable Addthis social buttons?
+# Defaults to true
+# ADD_THIS_BUTTONS = True
+
+# Modify the number of Post per Index Page
+# Defaults to 10
+# INDEX_DISPLAY_POST_COUNT = 10
+
+# RSS_LINK is a HTML fragment to link the RSS or Atom feeds. If set to None,
+# the base.tmpl will use the feed Nikola generates. However, you may want to
+# change it for a feedburner feed or something else.
+RSS_LINK = None
+
+# A search form to search this site, for the sidebar. You can use a google
+# custom search (http://www.google.com/cse/)
+# Or a duckduckgo search: https://duckduckgo.com/search_box.html
+# This example should work for pretty much any site we generate.
+SEARCH_FORM = ""
+# This search form is better for the "site" theme where it
+# appears on the navigation bar
+#SEARCH_FORM = """
+#<!-- Custom search -->
+#<form method="get" id="search" action="http://duckduckgo.com/" """\
+#"""class="navbar-form pull-left">
+#<input type="hidden" name="sites" value="%s"/>
+#<input type="hidden" name="k8" value="#444444"/>
+#<input type="hidden" name="k9" value="#D51920"/>
+#<input type="hidden" name="kt" value="h"/>
+#<input type="text" name="q" maxlength="255" """\
+#"""placeholder="Search&hellip;" class="span2" style="margin-top: 4px;"/>
+#<input type="submit" value="DuckDuckGo Search" style="visibility: hidden;" />
+#</form>
+#<!-- End of custom search -->
+#""" % BLOG_URL
+
+# Google analytics or whatever else you use. Added to the bottom of <body>
+# in the default template (base.tmpl).
+ANALYTICS = """
+ """
+
+# Put in global_context things you want available on all your templates.
+# It can be anything, data, functions, modules, etc.
+GLOBAL_CONTEXT = {
+ 'analytics': ANALYTICS,
+ 'blog_author': BLOG_AUTHOR,
+ 'blog_title': BLOG_TITLE,
+ 'blog_url': BLOG_URL,
+ 'blog_desc': BLOG_DESCRIPTION,
+ 'translations': TRANSLATIONS,
+ 'license': LICENSE,
+ 'search_form': SEARCH_FORM,
+ 'disqus_forum': DISQUS_FORUM,
+ 'content_footer': CONTENT_FOOTER,
+ 'rss_path': RSS_PATH,
+ 'rss_link': RSS_LINK,
+ # Locale-dependent links for the sidebar
+ # You should provide a key-value pair for each used language.
+ 'sidebar_links': {
+ DEFAULT_LANG: (
+ ('/' + os.path.join(ARCHIVE_PATH, ARCHIVE_FILENAME), 'Archives'),
+ ('/categories/index.html', 'Tags'),
+ ('/stories/about-nikola.html', 'About Nikola'),
+ ('/stories/handbook.html', 'The Nikola Handbook'),
+ ('http://nikola.ralsina.com.ar', 'Powered by Nikola!'),
+ ),
+ }
+ }
diff --git a/nikola/data/samplesite/dodo.py b/nikola/data/samplesite/dodo.py
new file mode 100755
index 0000000..1be7663
--- /dev/null
+++ b/nikola/data/samplesite/dodo.py
@@ -0,0 +1,21 @@
+#!/usr/bin/env python
+# -*- coding: utf-8 -*-
+
+# Please don't edit this file unless you really know what you are doing.
+# The configuration is now in conf.py
+
+from doit.reporter import ExecutedOnlyReporter
+
+from nikola.nikola import Nikola
+
+import conf
+
+DOIT_CONFIG = {
+ 'reporter': ExecutedOnlyReporter,
+ 'default_tasks': ['render_site'],
+}
+SITE = Nikola(**conf.__dict__)
+
+
+def task_render_site():
+ return SITE.gen_tasks()
diff --git a/nikola/data/samplesite/files/assets/css/custom.css b/nikola/data/samplesite/files/assets/css/custom.css
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/nikola/data/samplesite/files/assets/css/custom.css
diff --git a/nikola/data/samplesite/files/favicon.ico b/nikola/data/samplesite/files/favicon.ico
new file mode 100644
index 0000000..c4efbcc
--- /dev/null
+++ b/nikola/data/samplesite/files/favicon.ico
Binary files differ
diff --git a/nikola/data/samplesite/galleries/demo/exclude.meta b/nikola/data/samplesite/galleries/demo/exclude.meta
new file mode 100644
index 0000000..967e566
--- /dev/null
+++ b/nikola/data/samplesite/galleries/demo/exclude.meta
@@ -0,0 +1 @@
+tesla2_lg.jpg
diff --git a/nikola/data/samplesite/galleries/demo/index.txt b/nikola/data/samplesite/galleries/demo/index.txt
new file mode 100644
index 0000000..d3d5a44
--- /dev/null
+++ b/nikola/data/samplesite/galleries/demo/index.txt
@@ -0,0 +1,2 @@
+Some public domain pictures of Nikola Tesla
+(copied from `here <http://kerryr.net/pioneers/gallery/tesla.htm>`_) \ No newline at end of file
diff --git a/nikola/data/samplesite/galleries/demo/tesla2_lg.jpg b/nikola/data/samplesite/galleries/demo/tesla2_lg.jpg
new file mode 100644
index 0000000..8be0531
--- /dev/null
+++ b/nikola/data/samplesite/galleries/demo/tesla2_lg.jpg
Binary files differ
diff --git a/nikola/data/samplesite/galleries/demo/tesla4_lg.jpg b/nikola/data/samplesite/galleries/demo/tesla4_lg.jpg
new file mode 100644
index 0000000..e350491
--- /dev/null
+++ b/nikola/data/samplesite/galleries/demo/tesla4_lg.jpg
Binary files differ
diff --git a/nikola/data/samplesite/galleries/demo/tesla_conducts_lg.jpg b/nikola/data/samplesite/galleries/demo/tesla_conducts_lg.jpg
new file mode 100644
index 0000000..7549d09
--- /dev/null
+++ b/nikola/data/samplesite/galleries/demo/tesla_conducts_lg.jpg
Binary files differ
diff --git a/nikola/data/samplesite/galleries/demo/tesla_lightning1_lg.jpg b/nikola/data/samplesite/galleries/demo/tesla_lightning1_lg.jpg
new file mode 100644
index 0000000..7e4a6a0
--- /dev/null
+++ b/nikola/data/samplesite/galleries/demo/tesla_lightning1_lg.jpg
Binary files differ
diff --git a/nikola/data/samplesite/galleries/demo/tesla_lightning2_lg.jpg b/nikola/data/samplesite/galleries/demo/tesla_lightning2_lg.jpg
new file mode 100644
index 0000000..730b4de
--- /dev/null
+++ b/nikola/data/samplesite/galleries/demo/tesla_lightning2_lg.jpg
Binary files differ
diff --git a/nikola/data/samplesite/galleries/demo/tesla_tower1_lg.jpg b/nikola/data/samplesite/galleries/demo/tesla_tower1_lg.jpg
new file mode 100644
index 0000000..1b9edcb
--- /dev/null
+++ b/nikola/data/samplesite/galleries/demo/tesla_tower1_lg.jpg
Binary files differ
diff --git a/nikola/data/samplesite/listings/hello.py b/nikola/data/samplesite/listings/hello.py
new file mode 100644
index 0000000..695c212
--- /dev/null
+++ b/nikola/data/samplesite/listings/hello.py
@@ -0,0 +1,10 @@
+#!/usr/bin/python
+
+import sys
+
+
+def hello(name='world'):
+ print "hello", name
+
+if __name__ == "__main__":
+ hello(*sys.argv[1:])
diff --git a/nikola/data/samplesite/posts/1.meta b/nikola/data/samplesite/posts/1.meta
new file mode 100644
index 0000000..3bc104a
--- /dev/null
+++ b/nikola/data/samplesite/posts/1.meta
@@ -0,0 +1,5 @@
+Welcome to Nikola
+welcome-to-nikola
+2012/03/30 23:00
+nikola, python, demo, blog
+http://nikola.ralsina.com.ar
diff --git a/nikola/data/samplesite/posts/1.txt b/nikola/data/samplesite/posts/1.txt
new file mode 100644
index 0000000..4e583db
--- /dev/null
+++ b/nikola/data/samplesite/posts/1.txt
@@ -0,0 +1,13 @@
+.. figure:: http://farm1.staticflickr.com/138/352972944_4f9d568680.jpg
+ :target: http://farm1.staticflickr.com/138/352972944_4f9d568680_z.jpg?zz=1
+ :class: thumbnail
+ :alt: Nikola Tesla Corner by nicwest, on Flickr
+
+If you can see this in a web browser, it means you have managed to install Nikola,
+and build a site using it. Congratulations!
+
+* You can read the manual `here </stories/handbook.html>`__
+* You can learn more about Nikola at http://nikola.ralsina.com.ar
+* You can see a demo photo gallery `here </galleries/demo/>`__
+
+Send feedback to ralsina@netmanagers.com.ar!
diff --git a/nikola/data/samplesite/stories/1.meta b/nikola/data/samplesite/stories/1.meta
new file mode 100644
index 0000000..e19bf10
--- /dev/null
+++ b/nikola/data/samplesite/stories/1.meta
@@ -0,0 +1,4 @@
+Nikola: it generates static
+about-nikola
+2012/03/30 23:00
+
diff --git a/nikola/data/samplesite/stories/1.txt b/nikola/data/samplesite/stories/1.txt
new file mode 100644
index 0000000..68b4008
--- /dev/null
+++ b/nikola/data/samplesite/stories/1.txt
@@ -0,0 +1,4 @@
+Hope you enjoy this software!
+
+* Home page at http://nikola.ralsina.com.ar
+* Author's blog (and reason why Nikola exists): http://lateral.netmanagers.com.ar
diff --git a/nikola/data/samplesite/stories/configsample.meta b/nikola/data/samplesite/stories/configsample.meta
new file mode 100644
index 0000000..530d05f
--- /dev/null
+++ b/nikola/data/samplesite/stories/configsample.meta
@@ -0,0 +1,4 @@
+Sample Nikola Config File
+sampleconfig
+2012/03/30 23:00
+
diff --git a/nikola/data/samplesite/stories/configsample.txt b/nikola/data/samplesite/stories/configsample.txt
new file mode 100755
index 0000000..89d296e
--- /dev/null
+++ b/nikola/data/samplesite/stories/configsample.txt
@@ -0,0 +1,221 @@
+.. code-block:: python
+
+ #!/usr/bin/env python
+ # -*- coding: utf-8 -*-
+
+ ########################################
+ # Configuration, please edit
+ ########################################
+
+ # post_pages contains (wildcard, destination, template, use_in_feed) tuples.
+ #
+ # The wildcard is used to generate a list of reSt source files (whatever/thing.txt)
+ # That fragment must have an associated metadata file (whatever/thing.meta),
+ # and opcionally translated files (example for spanish, with code "es"):
+ # whatever/thing.txt.es and whatever/thing.meta.es
+ #
+ # From those files, a set of HTML fragment files will be generated:
+ # whatever/thing.html (and maybe whatever/thing.html.es)
+ #
+ # These files are combinated with the template to produce rendered
+ # pages, which will be placed at
+ # output / TRANSLATIONS[lang] / destination / pagename.html
+ #
+ # where "pagename" is specified in the metadata file.
+ #
+ # if use_in_feed is True, then those posts will be added to the site's
+ # rss feeds.
+ #
+
+ post_pages = (
+ ("posts/*.txt", "weblog/posts", "post.tmpl", True),
+ ("stories/*.txt", "stories", "post.tmpl", False),
+ )
+
+ # What is the default language?
+
+ DEFAULT_LANG = "en"
+
+ # What languages do you have?
+ # If a specific post is not translated to a language, then the version
+ # in the default language will be shown instead.
+ # The format is {"translationcode" : "path/to/translation" }
+ # the path will be used as a prefix for the generated pages location
+
+ TRANSLATIONS = {
+ "en": "",
+ "es": "tr/es",
+ }
+
+ # Paths for different autogenerated bits. These are combined with the translation
+ # paths.
+
+ # Final locations are:
+ # output / TRANSLATION[lang] / TAG_PATH / index.html (list of tags)
+ # output / TRANSLATION[lang] / TAG_PATH / tag.html (list of posts for a tag)
+ # output / TRANSLATION[lang] / TAG_PATH / tag.xml (RSS feed for a tag)
+ TAG_PATH = "categories"
+ # Final location is output / TRANSLATION[lang] / INDEX_PATH / index-*.html
+ INDEX_PATH = "weblog"
+ # Final locations for the archives are:
+ # output / TRANSLATION[lang] / ARCHIVE_PATH / archive.html
+ # output / TRANSLATION[lang] / ARCHIVE_PATH / YEAR / index.html
+ ARCHIVE_PATH = "weblog"
+ # Final locations are:
+ # output / TRANSLATION[lang] / RSS_PATH / rss.xml
+ RSS_PATH = "weblog"
+
+ # A list of redirection tuples, [("foo/from.html", "/bar/to.html")].
+ #
+ # A HTML file will be created in output/foo/from.html that redirects
+ # to the "/bar/to.html" URL. notice that the "from" side MUST be a
+ # relative URL.
+ #
+ # If you don't need any of these, just set to []
+
+ REDIRECTIONS = [("index.html", "/weblog/index.html")]
+
+ # Commands to execute to deploy. Can be anything, for example,
+ # you may use rsync:
+ # "rsync -rav output/* joe@my.site:/srv/www/site"
+ # And then do a backup, or ping pingomatic.
+ # To do manual deployment, set it to []
+ DEPLOY_COMMANDS = [
+ r'rsync -rav --delete output/* ralsina@lateral.netmanagers.com.ar:/srv/www/lateral',
+ r'rdiff-backup output ~/bartleblog-backup',
+ r"links -dump 'http://www.twingly.com/ping2?url=lateral.netmanagers.com.ar' > /dev/null",
+ r"links -dump 'http://pingomatic.com/ping/?title=Lateral+Opinion&blogurl=http%%3A%%2F%%2Flateral.netmanagers.com.ar&rssurl=http%%3A%%2F%%2F&chk_weblogscom=on&chk_blogs=on&chk_technorati=on&chk_feedburner=on&chk_syndic8=on&chk_newsgator=on&chk_myyahoo=on&chk_pubsubcom=on&chk_blogdigger=on&chk_blogrolling=on&chk_blogstreet=on&chk_moreover=on&chk_weblogalot=on&chk_icerocket=on&chk_newsisfree=on&chk_topicexchange=on&chk_google=on&chk_tailrank=on&chk_bloglines=on&chk_aiderss=on&chk_skygrid=on&chk_bitacoras=on&chk_collecta=on' > /dev/null",
+ r'rsync -rav ~/bartleblog-backup/* ralsina@netmanagers.com.ar:bartleblog-backup',
+ ]
+
+ ##############################################################################
+ # Image Gallery Options
+ ##############################################################################
+
+ # Galleries are folders in galleries/
+ # Final location of galleries will be output / GALLERY_PATH / gallery_name
+ GALLERY_PATH = "galleries"
+ THUMBNAIL_SIZE = 256
+
+
+ ##############################################################################
+ # HTML fragments and diverse things that are used by the templates
+ ##############################################################################
+
+ # Data about this site
+ BLOG_TITLE = "Lateral Opinion"
+ BLOG_URL = "http://lateral.netmanagers.com.ar"
+ BLOG_EMAIL = "ralsina@kde.org"
+ BLOG_DESCRIPTION = "I write free software. I have an opinion on almost "\
+ "everything. I write quickly. A weblog was inevitable."
+
+ # A HTML fragment describing the license, for the sidebar.
+ # I recomment using Creative Commons' wizard: http://creativecommons.org/choose/
+ LICENSE = """
+ <a rel="license" href="http://creativecommons.org/licenses/by-nc-sa/2.5/ar/">
+ <img alt="Creative Commons License" style="border-width:0; margin-bottom:12px;"
+ src="http://i.creativecommons.org/l/by-nc-sa/2.5/ar/88x31.png"></a>"""
+
+ # A small copyright notice for the page footer
+ CONTENT_FOOTER = u"Contents © 2000-2012 Roberto Alsina <ralsina@kde.org>"
+
+ # To enable comments via Disqus, you need to create a forum at http://disqus.com,
+ # and set DISQUS_FORUM to the short name you selected.
+ # If you want to disable comments, set it to False.
+ DISQUS_FORUM = "lateralopinion"
+
+ # RSS_LINK is a HTML fragment to link the RSS or Atom feeds. If set to None,
+ # the base.tmpl will use the feed Nikola generates. However, you may want to
+ # change it for a feedburner feed or something else.
+
+ #RSS_LINK = """
+ #<link rel="alternate" type="application/rss+xml" title="RSS" href="http://feeds.feedburner.com/LateralOpinion">
+ #<link rel="alternate" type="application/rss+xml" title="RSS en Espanol" href="http://feeds.feedburner.com/LateralOpinionEsp">
+ #"""
+ RSS_LINK = None
+
+ # A search form to search this site, for the sidebar. You can use a google
+ # custom search (http://www.google.com/cse/)
+ # Or a duckduckgo search: https://duckduckgo.com/search_box.html
+ # This example should work for pretty much any site we generate.
+ SEARCH_FORM = """
+ <!-- Custom search -->
+ <form method="get" id="search" action="http://duckduckgo.com/">
+ <input type="hidden" name="sites" value="%s"/>
+ <input type="hidden" name="k8" value="#444444"/>
+ <input type="hidden" name="k9" value="#D51920"/>
+ <input type="hidden" name="kt" value="h"/>
+ <input type="text" name="q" maxlength="255" placeholder="Search&hellip;" class="span2" style="margin-top: 4px;"/>
+ <input type="submit" value="DuckDuckGo Search" style="visibility: hidden;" />
+ </form>
+ <!-- End of custom search -->
+ """ % BLOG_URL
+
+ # Google analytics or whatever else you use. Added to the bottom of <body>
+ # in the default template (base.tmpl).
+ ANALYTICS = """
+ <!-- Start of StatCounter Code -->
+ <script type="text/javascript">
+ sc_project=1436219;
+ sc_invisible=1;
+ sc_partition=13;
+ sc_security="b91cd70a";
+ </script>
+ <script type="text/javascript" src="http://www.statcounter.com/counter/counter.js"></script>
+ <noscript>
+ <div class="statcounter">
+ <a title="free hit counters" href="http://www.statcounter.com/" target="_blank"><img class="statcounter" src="http://c14.statcounter.com/1436219/0/b91cd70a/1/" alt="free hit counters" ></a>
+ </div>
+ </noscript>
+ <!-- End of StatCounter Code -->
+ <!-- Start of Google Analytics -->
+ <script src="http://www.google-analytics.com/urchin.js" type="text/javascript">
+ </script>
+ <script type="text/javascript">
+ _uacct = "UA-1639287-1";
+ urchinTracker();
+ </script>
+ <!-- End of Google Analytics -->
+ """
+
+ # Put in global_context things you want available on all your templates.
+ # It can be anything, data, functions, modules, etc.
+ GLOBAL_CONTEXT = {
+ 'analytics': ANALYTICS,
+ 'blog_title': BLOG_TITLE,
+ 'blog_url': BLOG_URL,
+ 'translations': TRANSLATIONS,
+ 'license': LICENSE,
+ 'search_form': SEARCH_FORM,
+ 'disqus_forum': DISQUS_FORUM,
+ 'content_footer': CONTENT_FOOTER,
+ 'rss_path': RSS_PATH,
+ 'rss_link': RSS_LINK,
+ # Locale-dependent links for the sidebar
+ 'sidebar_links': {
+ 'en': (
+ ('/archive.html', 'Archives'),
+ ('/categories/index.html', 'Tags'),
+ ('http://nikola.ralsina.com.ar', 'Powered by Nikola!'),
+ ),
+ 'es': (
+ ('/archive.html', 'Archivos'),
+ ('/categories/index.html', 'Tags'),
+ ('http://nikola.ralsina.com.ar', 'Powered by Nikola!'),
+ )
+ }
+ }
+
+ # Sorry, this is magic. It just is.
+ execfile("nikola/nikola.py")
+ if __name__ == "__main__":
+ nikola_main()
+ # End of magic.
+
+ # To disable tasks, just delete them. For example, if you really
+ # do not want to do a sitemap:
+ # del(task_sitemap)
+
+ # You can also replace the provided tasks with your own by redefining them
+ # below this point. For a list of current tasks, run "doit list", and for
+ # help on their syntax, refer to the doit handbook at http://python-doit.sf.net
diff --git a/nikola/data/samplesite/stories/manual.meta b/nikola/data/samplesite/stories/manual.meta
new file mode 100644
index 0000000..b5e43ec
--- /dev/null
+++ b/nikola/data/samplesite/stories/manual.meta
@@ -0,0 +1,4 @@
+The Nikola Handbook
+handbook
+2012/03/30 23:00
+
diff --git a/nikola/data/samplesite/stories/manual.txt b/nikola/data/samplesite/stories/manual.txt
new file mode 100644
index 0000000..f8804e6
--- /dev/null
+++ b/nikola/data/samplesite/stories/manual.txt
@@ -0,0 +1,808 @@
+The Nikola Handbook
+===================
+
+:Version: 2.1+svn
+:Author: Roberto Alsina <ralsina@netmanagers.com.ar>
+
+.. class:: alert alert-info pull-right
+
+.. contents::
+
+
+All You Need to Know
+--------------------
+
+After you have Nikola installed:
+
+Create a site:
+ ``nikola init mysite``
+
+Create a post:
+ ``doit new_post``
+
+Edit the post:
+ The filename should be in the output of the previous command.
+
+Build the site:
+ ``doit``
+
+Start the test server:
+ ``doit serve``
+
+See the site:
+ http://127.0.0.1:8000
+
+That should get you going. If you want to know more, this manual will always be here
+for you.
+
+DON'T READ THIS MANUAL. IF YOU NEED TO READ IT I FAILED, JUST USE THE THING.
+
+On the other hand, if anything about Nikola is not as obvious as it should be, by all
+means tell me about it :-)
+
+What's Nikola and what can you do with it?
+------------------------------------------
+
+Nikola is a static website and blog generator. The very short explanation is
+that it takes some texts you wrote, and uses them to create a folder full
+of HTML files. If you upload that folder to a server, you will have a
+rather full-featured website, done with little effort.
+
+It's original goal is to create blogs, but it supports most kind of sites, and
+can be used as a CMS, as long as what you present to the user is your own content
+instead of something the user generates.
+
+Nikola can do:
+
+* A blog (`example <http://lateral.netmanagers.com.ar>`__)
+* Your company's site
+* Your personal site
+* A software project's site (`example <http://nikola.ralsina.com.ar>`__)
+* A book's site
+
+Since Nikola-based sites don't run any code on the server, there is no way to process
+user input in forms.
+
+Nikola can't do:
+
+* Twitter
+* Facebook
+* An Issue tracker
+* Anything with forms, really (except for comments_!)
+
+Keep in mind that "static" doesn't mean **boring**. You can have animations, slides
+or whatever fancy CSS/HTML5 thingie you like. It only means all that HTML is
+generated already before being uploaded. On the other hand, Nikola sites will
+tend to be content-heavy. What Nikola is good at is at putting what you write
+out there.
+
+Getting Help
+------------
+
+* Feel free to contact me at ralsina@netmanagers.com.ar for questions about Nikola.
+* You can file bugs at `the issue tracker <http://code.google.com/p/nikola-generator/issues/list>`__
+* You can discuss Nikola at the `nikola-discuss google group <http://groups.google.com/group/nikola-discuss>`_
+* You can subscribe to `the Nikola Blog <http://nikola.ralsina.com.ar/blog>`_
+* You can follow `Nikola on Twitter <https://twitter.com/#!/nikolagenerator>`_
+
+Why Static?
+-----------
+
+Most "modern" websites are *dynamic* in the sense that the contents of the site
+live in a database, and are converted into presentation-ready HTML only when a
+user wants to see the page. That's great. However, it presents some minor issues
+that static site generators try to solve.
+
+In a static site, the whole site, every page, *everything*, is created before
+the first user even sees it and uploaded to the server as a simple folder full
+of HTML files (and images, CSS, etc).
+
+So, let's see some reasons for using static sites:
+
+Security
+ Dynamic sites are prone to experience security issues. The solution for that
+ is constant vigilance, keeping the software behind the site updated, and
+ plain old good luck. The stack of software used to provide a static site,
+ like those Nikola generates, is much smaller (Just a webserver).
+
+ A smaller software stack implies less security risk.
+
+Obsolescense
+ If you create a site using (for example) Wordpress, what happens when Wordpress
+ releases a new version? You have to update your Wordpress. That is not optional,
+ because of security and support issues. If I release a new version of Nikola, and
+ you don't update, *nothing* happens. You can continue to use the version you
+ have now forever, no problems.
+
+ Also, in the longer term, the very foundations of dynamic sites shift. Can you
+ still deploy a blog software based on Django 0.96? What happens when your
+ host stops supporting the php version you rely on? And so on.
+
+ You may say those are long term issues, or that they won't matter for years. Well,
+ I believe things should work forever, or as close to it as we can make them.
+ Nikola's static output and its input files will work as long as you can install
+ a Python > 2.5 (soon 3.x) in a Linux, Windows, or Mac and can find a server
+ that sends files over HTTP. That's probably 10 or 15 years at least.
+
+ Also, static sites are easily handled by the Internet Archive.
+
+Cost and Performance
+ On dynamic sites, every time a reader wants a page, a whole lot of database
+ queries are made. Then a whole pile of code chews that data, and HTML is
+ produced, which is sent to the user. All that requires CPU and memory.
+
+ On a static site, the highly optimized HTTP server reads the file from disk
+ (or, if it's a popular file, from disk cache), and sends it to the user. You could
+ probably serve a bazillion (technical term) pageviews from a phone using
+ static sites.
+
+Lockin
+ On server-side blog platforms, sometimes you can't export your own data, or
+ it's in strange formats you can't use in other services. I have switched
+ blogging platforms from Advogato to PyCs to two homebrewed systems, to Nikola,
+ and have never lost a file, a URL, or a comment. That's because I have *always*
+ had my own data in a format of my choice.
+
+ With Nikola, you own your files, and you can do anything with them.
+
+Features
+--------
+
+Nikola has a very defined featureset: it has every feature I needed for my own sites.
+Hopefully, it will be enough for others, and anyway, I am open to suggestions.
+
+If you want to create a blog or a site, Nikola provides:
+
+* Front page (and older posts pages)
+* RSS Feeds
+* Pages and feeds for each tag you used
+* Custom search
+* Full yearly archives
+* Custom output paths for generated pages
+* Easy page template customization
+* Static pages (not part of the blog)
+* Internationalization support (my own blog is English/Spanish)
+* Google sitemap generation
+* Custom deployment (if it's a command, you can use it)
+* A (very) basic look and feel you can customize, and is even text-mode friendly
+* The input format is light markup (`reStructuredText <quickstart.html>`_ or
+ `Markdown <http://daringfireball.net/projects/markdown/>`_)
+* Easy-to-create image galleries
+
+Also:
+
+* A preview webserver
+* "Live" re-rendering while you edit
+* "Smart" builds: only what changed gets rebuilt (usually in 1 or 2 seconds)
+* Very easy to extend with minimal Python knowledge.
+
+Installing Nikola
+-----------------
+
+This is currently lacking on detail. Considering the niche Nikola is aimed at,
+I suspect that's not a problem yet. So, when I say "get", the specific details
+of how to "get" something for your specific operating system are left to you.
+
+The short version is: ``pip install https://github.com/ralsina/nikola/zipball/master``
+
+Longer version:
+
+#. Get python, if you don't have it.
+#. Get `doit <http://python-doit.sf.net>`_
+#. Get `docutils <http://docutils.sf.net>`_
+#. Get `Mako <http://makotemplates.org>`_
+#. Get `PIL <http://www.pythonware.com/products/pil/>`_
+#. Get `Pygments <http://pygments.org/>`_
+#. Get `unidecode <http://pypi.python.org/pypi/Unidecode/>`_
+#. Get `lxml <http://lxml.de/>`_
+
+Any non-prehistorical version of the above should work, and if you are in Linux
+you can try to use your distribution's packages if they exist, but the newer the better.
+
+Then get Nikola itself (<http://nikola.ralsina.com.ar/>), unzip it, and
+run ``python setup.py install``.
+
+After that, run ``nikola init sitename`` and that will create a folder called
+``sitename`` containing a functional demo site.
+
+Getting Started
+---------------
+
+To create posts and pages in Nikola, you write them in restructured text or Markdown, light
+markups that are later converted to HTML (I may add support for textile or other
+markups later). There is a great `quick tutorial to learn restructured text. <quickstart.html>`_
+
+First, let's see how you "build" your site. Nikola comes with a minimal site to get you started.
+
+The tool used to do builds is called `doit <http://python-doit.sf.net>`_, and it rebuilds the
+files that are not up to date, so your site always reflects your latest content. To do our
+first build, just run "doit"::
+
+ $ doit
+ Parsing metadata
+ . render_posts:stories/manual.html
+ . render_posts:posts/1.html
+ . render_posts:stories/1.html
+ . render_archive:output/2012/index.html
+ . render_archive:output/archive.html
+ . render_indexes:output/index.html
+ . render_pages:output/posts/welcome-to-nikola.html
+ . render_pages:output/stories/about-nikola.html
+ . render_pages:output/stories/handbook.html
+ . render_rss:output/rss.xml
+ . render_sources:output/stories/about-nikola.txt
+ :
+ :
+ :
+
+Nikola will print a line for every output file it generates. If we do it again, that
+will be much much shorter::
+
+ $ doit
+ Parsing metadata
+ . sitemap
+
+That is because `doit <http://python-doit.sf.net>`_ is smart enough not to generate
+all the pages again, unless you changed something that the page requires. So, if you change
+the text of a post, or its title, that post page, and all index pages where it is mentioned,
+will be recreated. If you change the post page template, then all the post pages will be rebuilt.
+
+Nikola is a series of doit *tasks*, and you can see them by doing ``doit list``::
+
+ $ doit list
+ Scanning posts . . done!
+ copy_assets Create tasks to copy the assets of the whole theme chain.
+ copy_files Copy static files into the output folder.
+ deploy Deploy site.
+ new_page Create a new post (interactive).
+ new_post Create a new post (interactive).
+ redirect Generate redirections.
+ render_archive Render the post archives.
+ render_galleries Render image galleries.
+ render_indexes Render 10-post-per-page indexes.
+ render_pages Build final pages from metadata and HTML fragments.
+ render_posts Build HTML fragments from metadata and reSt.
+ render_rss Generate RSS feeds.
+ render_site Render the post archives.
+ render_sources Publish the rst sources because why not?
+ render_tags Render the tag pages.
+ serve Start test server. (Usage: doit serve [--address 127.0.0.1] [--port 8000])
+ sitemap Generate Google sitemap.
+
+You can make Nikola redo everything by calling ``doit clean``, you can make it do just a specific
+part of the site using task names, for example ``doit render_pages``, and even individual files like
+``doit render_indexes:output/index.html``
+
+The ``serve`` task is special, in that instead of generating a file it starts a web server so
+you can see the site you are creating::
+
+ $ doit serve
+ Parsing metadata
+ . serve
+ Serving HTTP on 127.0.0.1 port 8000 ...
+
+After you do this, you can point your web browser to http://localhost:8000 and you should see
+the sample site. This is useful as a "preview" of your work. You can combine add ``auto`` and do
+``doit auto serve`` which makes doit automatically regenerate your pages as needed, and
+it's a live preview!
+
+By default, the ``serve`` task runs the web server on port 8000 on the IP address 127.0.0.1.
+You can pass in an IP address and port number explicity using ``-a IP_ADDRESS``
+(short version of ``--address``) or ``-p PORT_NUMBER`` (short version of ``--port``)
+Example usage::
+
+ $ doit serve --address 0.0.0.0 --port 8080
+ Parsing metadata
+ . serve
+ Serving HTTP on 0.0.0.0 port 8080 ...
+
+The ``deploy`` task is discussed in the Deployment_ section.
+
+Creating a Blog Post
+--------------------
+
+A post consists of two files, a metadata file (``post-title.meta``) and a
+file containing the contents written in `restructured text <http://docutils.sf.net>`_
+(``post-title.txt``), markdown or HTML. Which input type is used is guessed using
+the ``post_compilers`` option in ``conf.py`` but by default, the extensions
+supported are:
+
+.txt .rst
+ Restructured Text
+
+.md .markdown .mdown
+ Markdown
+
+.htm .html
+ HTML
+
+The default configuration expects them to be placed in ``posts`` but that can be
+changed (see below, the post_pages option)
+
+You can just create them in ``posts`` or use a little helper task provided by Nikola::
+
+ $ doit new_post
+ Parsing metadata
+ . new_post
+ Creating New Post
+ -----------------
+
+ Enter title: How to Make Money
+ Your post's metadata is at: posts/how-to-make-money.meta
+ Your post's text is at: posts/how-to-make-money.txt
+
+The format for the ``.meta`` file is as follows::
+
+ How to Make Money
+ how-to-make-money
+ 2012/04/09 13:59
+
+The first line is the title. The second one is the pagename. Since often titles will have
+characters that look bad on URLs, it's generated as a "clean" version of the title.
+The third line is the post's date, and is set to "now".
+
+You can add three more optional lines. A fourth line that is a list of tags
+separated with commas (spaces around the commas are ignored)::
+
+ programming, python, fame, fortune
+
+And a fifth line that's a URL for an original source of the post.
+
+And a sixth line that's the page description.
+
+If you are writing a multilingual site, you can also create a per-language
+metadata file. This one can have two lines:
+
+1) The translated title for the post or page
+2) A translated version of the pagename
+
+You can edit these files with your favourite text editor, and once you are happy
+with the contents, generate the pages as explained in `Getting Started`_
+
+Currently supported languages are
+
+* English
+* Spanish
+* French
+* German
+* Russian
+* Greek.
+
+If you wish to add support for more languages, check out the instructions
+at the `theming guide <http://nikola.ralsina.com.ar/theming.html>`.
+
+The post page is generated using the ``post.tmpl`` template, which you can use
+to customize the output.
+
+The place where the post will be placed by ``new_post`` is based on the ``post_pages``
+configuration option::
+
+ # post_pages contains (wildcard, destination, template, use_in_feed) tuples.
+ #
+ # The wildcard is used to generate a list of reSt source files (whatever/thing.txt)
+ # That fragment must have an associated metadata file (whatever/thing.meta),
+ # and opcionally translated files (example for spanish, with code "es"):
+ # whatever/thing.txt.es and whatever/thing.meta.es
+ #
+ # From those files, a set of HTML fragment files will be generated:
+ # cache/whatever/thing.html (and maybe cache/whatever/thing.html.es)
+ #
+ # These files are combinated with the template to produce rendered
+ # pages, which will be placed at
+ # output / TRANSLATIONS[lang] / destination / pagename.html
+ #
+ # where "pagename" is specified in the metadata file.
+ #
+ # if use_in_feed is True, then those posts will be added to the site's
+ # rss feeds.
+ #
+ post_pages = (
+ ("posts/*.txt", "posts", "post.tmpl", True),
+ ("stories/*.txt", "stories", "story.tmpl", False),
+ )
+
+It will use the first location that has the last parameter set to True, or the last
+one in the list if all of them have it set to False.
+
+Alternatively, you can not have a meta file and embed the metadata in the post itself.
+
+In restructured text::
+
+ .. tags: test,demo
+ .. slug: demo-test
+ .. date: 2012/04/09 13:59
+ .. link: http://foo.bar/baz
+
+In Markdown:
+ <!--
+ .. tags: test,demo
+ .. slug: demo-test
+ .. date: 2012/04/09 13:59
+ .. link: http://foo.bar/baz
+ -->
+
+Teasers
+~~~~~~~
+
+If for any reason you want to provide feeds that only display the beginning of
+your post, you only need to add a "magical comment" in your post.
+
+In restructuredtext::
+
+ .. TEASER_END
+
+In Markdown::
+
+ <!-- TEASER_END -->
+
+Additionally, if you want also the "index" pages to show only the teasers, you can
+set the variable ``INDEX_TEASERS`` to ``True`` in ``conf.py``.
+
+Drafts
+~~~~~~
+
+If you add a "draft" tag to a post, then it will not be shown in indexes and feeds.
+It *will* be compiled, and if you deploy it it *will* be made available, so use
+with care.
+
+
+Creating a Page
+---------------
+
+Pages are the same as posts, except that:
+
+* They are not added to the front page
+* They don't appear on the RSS feed
+* They use the ``story.tmpl`` template instead of ``post.tmpl`` by default
+
+The default configuration expects the page's metadata and text files to be on the
+``stories`` folder, but that can be changed (see post_pages option above).
+
+You can create the page's files manually or use the helper ``new_page`` that works exactly like
+the ``new_post`` described above, except it will place the files in the folder that
+has ``use_in_feed`` set to False.
+
+Redirections
+------------
+
+If you need a page to be available in more than one place, you can define redirections
+in your ``conf.py``::
+
+ # A list of redirection tuples, [("foo/from.html", "/bar/to.html")].
+ #
+ # A HTML file will be created in output/foo/from.html that redirects
+ # to the "/bar/to.html" URL. notice that the "from" side MUST be a
+ # relative URL.
+ #
+ # If you don't need any of these, just set to []
+
+ REDIRECTIONS = [("index.html", "/weblog/index.html")]
+
+It's better if you can do these using your web server's configuration, but if
+you can't, this will work.
+
+Configuration
+-------------
+
+The configuration file is called ``conf.py`` and can be used to customize a lot of
+what Nikola does. Its syntax is python, but if you don't know the language, it
+still should not be terribly hard to grasp.
+
+The default ``conf.py`` you get with Nikola should be fairly complete, and is quite
+commented, but just in case, here is a full,
+`customized example configuration <sampleconfig.html>`_ (the one I use for
+`my site <http://lateral.netmanagers.com.ar>`_)
+
+Adding Files
+------------
+
+Any files you want to be in ``output/`` but are not generated by Nikola (for example,
+``favicon.ico``, just put it in ``files/``. Everything there is copied into
+``output`` by the ``copy_files`` task. Remember that you can't have files that collide
+with files Nikola generates (it will give an error).
+
+.. admonition:: Important
+
+ Don't put any files manually in ``output/``. Ever. Really. Maybe someday Nikola
+ will just wipe ``output/`` and then you will be sorry. So, please don't do that.
+
+If you want to copy more than one folder of static files into ``output`` you can
+change the FILES_FOLDERS option::
+
+ # One or more folders containing files to be copied as-is into the output.
+ # The format is a dictionary of "source" "relative destination".
+ # Default is:
+ # FILES_FOLDERS = {'files': '' }
+ # Which means copy 'files' into 'output'
+
+Post Processing Filters
+-----------------------
+
+You can apply post processing to the files in your site, in order to optimize them
+or change them in arbitrary ways. For example, you may want to compress all CSS
+and JS files using yui-compressor.
+
+To do that, you can use the provided helper adding this in your ``config.py``::
+
+ from nikola import filters
+
+ FILTERS = {
+ ".css": [filters.yui_compressor],
+ ".js": [filters.yui_compressor],
+ }
+
+Where ``filters.yui_compressor`` is a helper function provided by Nikola. You can
+replace that with strings describing command lines, or arbitrary python functions.
+
+If there's any specific thing you expect to be generally useful as a filter, contact
+me and I will add it to the filters library so that more people use it.
+
+Customizing Your Site
+---------------------
+
+There are lots of things you can do to persoalize your website, but let's see the easy ones!
+
+Basics
+ You can assume this needs to be changed::
+
+ # Data about this site
+ BLOG_TITLE = "Demo Site"
+ BLOG_URL = "http://nikola.ralsina.com.ar"
+ BLOG_EMAIL = "joe@demo.site"
+ BLOG_DESCRIPTION = "This is a demo site for Nikola."
+
+CSS tweaking
+ The default configuration includes a file, ``themes/default/assets/css/custom.css``
+ which is empty. Put your CSS there, for minimal disruption of the provided CSS files.
+
+ If you feel tempted to touch other files in assets, you probably will be better off
+ with a `custom theme <theming.html>`_.
+
+Template tweaking
+ If you really want to change the pages radically, you will want to do a
+ `custom theme <theming.html>`_.
+
+
+Sidebar
+ ``LICENSE`` is a HTML snippet for things like a CC badge, or whatever you prefer.
+
+ The 'sidebar_links' option lets you define what links go in the right-hand
+ sidebar, so you can link to important pages, or to other sites.
+
+ The ``SEARCH_FORM`` option contains the HTML code for a search form based on
+ duckduckgo.com which should always work, but feel free to change it to
+ something else.
+
+Footer
+ ``CONTENT_FOOTER`` is displayed, small at the bottom of all pages, I use it for
+ the copyright notice.
+
+Analytics
+ This is probably a misleading name, but the ``ANALYTICS`` option lets you define
+ a HTML snippet that will be added at the bottom of body. The main usage is
+ a Google analytics snippet or something similar, but you can really put anything
+ there.
+
+Getting More Themes
+-------------------
+
+There are not so many themes for Nikola. On occasion, I port something I like, and make
+it available for download. Nikola has a builtin theme download/install mechanism, its
+``install_theme`` task::
+
+ $ doit install_theme -l
+ Scanning posts . . done!
+ . install_theme
+ Themes:
+ -------
+ blogtxt
+ readable
+
+ $ doit install_theme -n blogtxt
+ Scanning posts . . done!
+ . install_theme
+ Downloading: http://nikola.ralsina.com.ar/themes/blogtxt.zip
+ Extracting: blogtxt into themes
+
+And there you are, you now have themes/blogtxt installed. It's very rudimentary, but it
+should work in most cases.
+
+If you create a nice theme, please share it! You can post about it on
+`the nikola forum <http://groups.google.com/group/nikola-discuss>`_ and I will
+make it available for download.
+
+One other option is to tweak an existing theme using a different color scheme,
+typography and CSS in general. Nikola provides a ``bootswatch_theme`` option
+to create a custom theme by downloading free CSS files from http://bootswatch.com::
+
+ $ doit bootswatch_theme -n custom_theme -s spruce -p site
+ Scanning posts . . done!
+ . bootswatch_theme
+ Creating custom_theme theme from spruce and site
+ Downloading: http://bootswatch.com/spruce/bootstrap.min.css
+ Downloading: http://bootswatch.com/spruce/bootstrap.css
+ Theme created. Change the THEME setting to "custom_theme" to use it.
+
+You can even try what different swatches do on an existing site using
+their handy `bootswatchlet <http://news.bootswatch.com/post/29555952123/a-bookmarklet-for-bootswatch>`_
+
+Play with it, there's cool stuff there. This feature was suggested by
+`clodo <http://elgalpondebanquito.com.ar>`_.
+
+Deployment
+----------
+
+Nikola doesn't really have a concept of deployment. However, if you can specify your
+deployment procedure as a series of commands, you can put them in the ``DEPLOY_COMMANDS``
+option, and run them with ``doit deploy``.
+
+One caveat is that if any command has a % in it, you should double them.
+
+Here is an example, from my own site's deployment script::
+
+ DEPLOY_COMMANDS = [
+ 'rsync -rav --delete output/* ralsina@lateral.netmanagers.com.ar:/srv/www/lateral',
+ 'rdiff-backup output ~/bartleblog-backup',
+ "links -dump 'http://www.twingly.com/ping2?url=lateral.netmanagers.com.ar'",
+ 'rsync -rav ~/bartleblog-backup/* ralsina@netmanagers.com.ar:bartleblog-backup',
+ ]
+
+Other interesting ideas are using
+`git as a deployment mechanism <http://toroid.org/ams/git-website-howto>`_ (or any other VCS
+for that matter), using `lftp mirror <http://lftp.yar.ru/>`_ or unison, or dropbox, or
+Ubuntu One. Any way you can think of to copy files from one place to another is good enough.
+
+Comments
+--------
+
+While Nikola creates static sites, there is a minimum level of user interaction you
+are probably expecting: comments.
+
+The default templates contain support for `Disqus <http://disqus.com>`_. All you have
+to do is register a forum, put its short name in the ``DISQUS_FORUM`` option.
+
+Disqus is a good option because:
+
+1) It doesn't require any server-side software on your site
+2) They offer you a way to export your comments, so you can take
+ them with you if you need to.
+3) It's free.
+4) It's damn nice.
+
+.. admonition:: Important
+
+ In some cases, when you run the test site, you won't see the comments.
+ That can be fixed by adding the disqus_developer flag to the templates
+ but it's probably more trouble than it's worth.
+
+
+Image Galleries
+---------------
+
+To create an image gallery, all you have to do is add a folder inside ``galleries``,
+and put images there. Nikola will take care of creating thumbnails, index page, etc.
+
+If you click on images on a gallery, you should see a bigger image, thanks to
+the excellent `colorbox <http://www.jacklmoore.com/colorbox>`_
+
+The gallery pages are generated using the ``gallery.tmpl`` template, and you can
+customize it there (you could switch to another lightbox instead of colorbox, change
+its settings, change the layout, etc.).
+
+The ``conf.py`` options affecting gallery pages are these::
+
+ # Galleries are folders in galleries/
+ # Final location of galleries will be output / GALLERY_PATH / gallery_name
+ GALLERY_PATH = "galleries"
+ THUMBNAIL_SIZE = 180
+ MAX_IMAGE_SIZE = 1280
+ USE_FILENAME_AS_TITLE = True
+
+If you add a file in ``galleries/gallery_name/index.txt`` its contents will be
+converted to HTML and inserted above the images in the gallery page.
+
+If you add some image filenames in ``galleries/gallery_name/exclude.meta``, they
+will be excluded in the gallery page.
+
+If ``USE_FILENAME_AS_TITLE`` is True the filename (parsed as a readable string)
+is used as the photo caption. If the filename starts with a number, it will
+be stripped. For example ``03_an_amazing_sunrise.jpg`` will be render as *An amazing sunrise*.
+
+Here is a `demo gallery </galleries/demo>`_ of historic, public domain Nikola
+Tesla pictures taken from `this site <http://kerryr.net/pioneers/gallery/tesla.htm>`_.
+
+Optimizing Your Website
+-----------------------
+
+One of the main goals of Nikola is to make your site fast and light. So here are a few
+tips we have found when setting up Nikola with Apache. If you have more, or
+different ones, or about other webservers, please share!
+
+#. Use a speed testing tool. I used Yahoo's YSlow but you can use any of them, and
+ it's probably a good idea to use more than one.
+
+#. Enable compression in Apache::
+
+ AddOutputFilterByType DEFLATE text/html text/plain text/xml text/css
+
+#. If even after you did the previous step the CSS files are not sent compressed::
+
+ AddType text/css .css
+
+In the future we will be adding HTML/CSS/JS minimization and image recompression but
+that's not there yet, so you may want to use 3rd party tools to achieve that.
+
+Restructured Text Extensions
+----------------------------
+
+Nikola includes support for a few directives that are not part of docutils, but which
+we think are handy for website development.
+
+Youtube
+~~~~~~~
+
+To link to a youtube video, you need the id of the video. For example, if the
+URL of the video is http://www.youtube.com/watch?v=8N_tupPBtWQ what you need is
+**8N_tupPBtWQ**
+
+Once you have that, all you need to do is::
+
+ .. youtube:: 8N_tupPBtWQ
+
+code-block
+~~~~~~~~~~
+
+This is a somewhat complicated directive to display code nicely. You can just
+embed code like this::
+
+ .. code-block:: python
+
+ print "Hello World!"
+
+Or you can include the code from a file:
+
+ .. code-block:: python
+ :include: /foo/bar/baz.py
+
+listing
+~~~~~~~
+
+To use this, you have to put your source code files inside ``listings`` or whatever your
+``LISTINGS_FOLDER`` variable is set to. Assuming you have a ``foo.py`` inside that folder::
+
+ .. listing:: foo.py
+
+Will include the source code from ``foo.py`` and also create a ``listings/foo.py.html`` page
+and the listing will have a title linking to it.
+
+Advanced Code Options
+~~~~~~~~~~~~~~~~~~~~~
+
+Both code-block and listing support a number of options, including these:
+
+start-at
+ A string, the diplayed code will start when it finds this
+end-at
+ A string, the diplayed code will end when it finds this
+start-after
+ A string, the diplayed code will start in the line after this
+end-before
+ A string, the diplayed code will end in the line before this
+linenos
+ Display line numbers
+linenos_offset
+ Use the original file's line numbers (warning: broken)
+tab-width
+ Size of the tabs (default 4)
+
+License
+-------
+
+Nikola is released under the `GPL version 3 <http://www.gnu.org/licenses/gpl-3.0.html>`_ which
+is a free software license. Some components shipped along with Nikola, or required by it are
+released under other licenses.
+
+If you are not familiar with free software licensing: In general, you should be able to
+do pretty much anything you want, unless you modify Nikola. If you modify it, and share
+it with someone else, that someone else should get all your modifications under the same
+license you got it.
diff --git a/nikola/data/samplesite/stories/quickref.meta b/nikola/data/samplesite/stories/quickref.meta
new file mode 100644
index 0000000..f8b0659
--- /dev/null
+++ b/nikola/data/samplesite/stories/quickref.meta
@@ -0,0 +1,4 @@
+A reStructuredText Reference
+quickref
+2012/03/30 23:00
+
diff --git a/nikola/data/samplesite/stories/quickref.txt b/nikola/data/samplesite/stories/quickref.txt
new file mode 100644
index 0000000..13ebc9b
--- /dev/null
+++ b/nikola/data/samplesite/stories/quickref.txt
@@ -0,0 +1,1341 @@
+.. raw:: html
+
+ <div class="alert alert-info pull-right" style="margin-left: 2em;">
+ <h2><a name="contents">Contents</a></h2>
+
+ <ul>
+ <li><a href="#inline-markup">Inline Markup</a></li>
+ <li><a href="#escaping">Escaping with Backslashes</a></li>
+ <li><a href="#section-structure">Section Structure</a></li>
+ <li><a href="#paragraphs">Paragraphs</a></li>
+ <li><a href="#bullet-lists">Bullet Lists</a></li>
+ <li><a href="#enumerated-lists">Enumerated Lists</a></li>
+ <li><a href="#definition-lists">Definition Lists</a></li>
+ <li><a href="#field-lists">Field Lists</a></li>
+ <li><a href="#option-lists">Option Lists</a></li>
+ <li><a href="#literal-blocks">Literal Blocks</a></li>
+ <li><a href="#line-blocks">Line Blocks</a></li>
+ <li><a href="#block-quotes">Block Quotes</a></li>
+ <li><a href="#doctest-blocks">Doctest Blocks</a></li>
+ <li><a href="#tables">Tables</a></li>
+ <li><a href="#transitions">Transitions</a></li>
+ <li><a href="#explicit-markup">Explicit Markup</a>
+ <ul>
+ <li><a href="#footnotes">Footnotes</a></li>
+ <li><a href="#citations">Citations</a></li>
+ <li><a href="#hyperlink-targets">Hyperlink Targets</a>
+ <ul>
+ <li><a href="#external-hyperlink-targets">External Hyperlink Targets</a></li>
+ <li><a href="#internal-hyperlink-targets">Internal Hyperlink Targets</a></li>
+ <li><a href="#indirect-hyperlink-targets">Indirect Hyperlink Targets</a></li>
+ <li><a href="#implicit-hyperlink-targets">Implicit Hyperlink Targets</a></li>
+ </ul></li>
+ <li><a href="#directives">Directives</a></li>
+ <li><a href="#substitution-references-and-definitions">Substitution References and Definitions</a></li>
+ <li><a href="#comments">Comments</a></li>
+ </ul></li>
+ <li><a href="#getting-help">Getting Help</a></li>
+ </ul>
+ </div>
+
+
+ <h1>Quick <i>re</i><font size="+4"><tt>Structured</tt></font><i>Text</i></h1>
+
+ <!-- Caveat: if you're reading the HTML for the examples, -->
+ <!-- beware that it was hand-generated, not by Docutils/ReST. -->
+
+ <blockquote>
+ <p>Copyright: This document has been placed in the public domain.
+ </blockquote>
+
+
+ <p>The full details of the markup may be found on the
+ <a href="http://docutils.sourceforge.net/rst.html">reStructuredText</a>
+ page. This document is just intended as a reminder.
+
+ <p>Links that look like "(<a href="#details">details</a>)" point
+ into the HTML version of the full <a
+ href="../../ref/rst/restructuredtext.html">reStructuredText
+ specification</a> document. These are relative links; if they
+ don't work, please use the <a
+ href="http://docutils.sourceforge.net/docs/user/rst/quickref.html"
+ >master "Quick reStructuredText"</a> document.
+
+
+ <h2><a href="#contents" name="inline-markup" class="backref"
+ >Inline Markup</a></h2>
+
+ <p>(<a href="../../ref/rst/restructuredtext.html#inline-markup">details</a>)
+
+ <p>Inline markup allows words and phrases within text to have
+ character styles (like italics and boldface) and functionality
+ (like hyperlinks).
+
+ <p><table border="1" bgcolor="#ffffcc" cellpadding="3">
+ <thead>
+ <tr align="left" bgcolor="#99CCFF">
+ <th>Plain text
+ <th>Typical result
+ <th>Notes
+ </thead>
+ <tbody>
+ <tr valign="top">
+ <td nowrap><samp>*emphasis*</samp>
+ <td><em>emphasis</em>
+ <td>Normally rendered as italics.
+
+ <tr valign="top">
+ <td nowrap><samp>**strong&nbsp;emphasis**</samp>
+ <td><strong>strong emphasis</strong>
+ <td>Normally rendered as boldface.
+
+ <tr valign="top">
+ <td nowrap><samp>`interpreted&nbsp;text`</samp>
+ <td>(see note at right)
+ <td>The rendering and <em>meaning</em> of interpreted text is
+ domain- or application-dependent. It can be used for things
+ like index entries or explicit descriptive markup (like program
+ identifiers).
+
+ <tr valign="top">
+ <td nowrap><samp>``inline&nbsp;literal``</samp>
+ <td><code>inline&nbsp;literal</code>
+ <td>Normally rendered as monospaced text. Spaces should be
+ preserved, but line breaks will not be.
+
+ <tr valign="top">
+ <td nowrap><samp>reference_</samp>
+ <td><a href="#hyperlink-targets">reference</a>
+ <td>A simple, one-word hyperlink reference. See <a
+ href="#hyperlink-targets">Hyperlink Targets</a>.
+
+ <tr valign="top">
+ <td nowrap><samp>`phrase reference`_</samp>
+ <td><a href="#hyperlink-targets">phrase reference</a>
+ <td>A hyperlink reference with spaces or punctuation needs to be
+ quoted with backquotes. See <a
+ href="#hyperlink-targets">Hyperlink Targets</a>.
+
+ <tr valign="top">
+ <td nowrap><samp>anonymous__</samp>
+ <td><a href="#hyperlink-targets">anonymous</a>
+ <td>With two underscores instead of one, both simple and phrase
+ references may be anonymous (the reference text is not repeated
+ at the target). See <a
+ href="#hyperlink-targets">Hyperlink Targets</a>.
+
+ <tr valign="top">
+ <td nowrap><samp>_`inline internal target`</samp>
+ <td><a name="inline-internal-target">inline internal target</a>
+ <td>A crossreference target within text.
+ See <a href="#hyperlink-targets">Hyperlink Targets</a>.
+
+ <tr valign="top">
+ <td nowrap><samp>|substitution reference|</samp>
+ <td>(see note at right)
+ <td>The result is substituted in from the <a
+ href="#substitution-references-and-definitions">substitution
+ definition</a>. It could be text, an image, a hyperlink, or a
+ combination of these and others.
+
+ <tr valign="top">
+ <td nowrap><samp>footnote reference [1]_</samp>
+ <td>footnote reference <sup><a href="#footnotes">1</a></sup>
+ <td>See <a href="#footnotes">Footnotes</a>.
+
+ <tr valign="top">
+ <td nowrap><samp>citation reference [CIT2002]_</samp>
+ <td>citation reference <a href="#citations">[CIT2002]</a>
+ <td>See <a href="#citations">Citations</a>.
+
+ <tr valign="top">
+ <td nowrap><samp>http://docutils.sf.net/</samp>
+ <td><a href="http://docutils.sf.net/">http://docutils.sf.net/</a>
+ <td>A standalone hyperlink.
+
+ </table>
+
+ <p>Asterisk, backquote, vertical bar, and underscore are inline
+ delimiter characters. Asterisk, backquote, and vertical bar act
+ like quote marks; matching characters surround the marked-up word
+ or phrase, whitespace or other quoting is required outside them,
+ and there can't be whitespace just inside them. If you want to use
+ inline delimiter characters literally, <a href="#escaping">escape
+ (with backslash)</a> or quote them (with double backquotes; i.e.
+ use inline literals).
+
+ <p>In detail, the reStructuredText specification says that in
+ inline markup, the following rules apply to start-strings and
+ end-strings (inline markup delimiters):
+
+ <ol>
+ <li>The start-string must start a text block or be
+ immediately preceded by whitespace or any of&nbsp;
+ <samp>' " ( [ {</samp> or&nbsp;<samp>&lt;</samp>.
+ <li>The start-string must be immediately followed by non-whitespace.
+ <li>The end-string must be immediately preceded by non-whitespace.
+ <li>The end-string must end a text block (end of document or
+ followed by a blank line) or be immediately followed by whitespace
+ or any of&nbsp;<samp>' " . , : ; ! ? - ) ] } / \</samp>
+ or&nbsp;<samp>&gt;</samp>.
+ <li>If a start-string is immediately preceded by one of&nbsp;
+ <samp>' " ( [ {</samp> or&nbsp;<samp>&lt;</samp>, it must not be
+ immediately followed by the corresponding character from&nbsp;
+ <samp>' " ) ] }</samp> or&nbsp;<samp>&gt;</samp>.
+ <li>An end-string must be separated by at least one
+ character from the start-string.
+ <li>An <a href="#escaping">unescaped</a> backslash preceding a
+ start-string or end-string will disable markup recognition, except
+ for the end-string of inline literals.
+ </ol>
+
+ <p>Also remember that inline markup may not be nested (well,
+ except that inline literals can contain any of the other inline
+ markup delimiter characters, but that doesn't count because
+ nothing is processed).
+
+ <h2><a href="#contents" name="escaping" class="backref"
+ >Escaping with Backslashes</a></h2>
+
+ <p>(<a
+ href="../../ref/rst/restructuredtext.html#escaping-mechanism">details</a>)
+
+ <p>reStructuredText uses backslashes ("\") to override the special
+ meaning given to markup characters and get the literal characters
+ themselves. To get a literal backslash, use an escaped backslash
+ ("\\"). For example:
+
+ <p><table border="1" width="100%" bgcolor="#ffffcc" cellpadding="3">
+ <thead>
+ <tr align="left" bgcolor="#99CCFF">
+ <th width="50%">Raw reStructuredText
+ <th width="50%">Typical result
+ </thead>
+ <tbody>
+ <tr valign="top"><td>
+ <samp>*escape*&nbsp;``with``&nbsp;"\"</samp>
+ <td><em>escape</em> <samp>with</samp> ""
+ <tr valign="top"><td>
+ <samp>\*escape*&nbsp;\``with``&nbsp;"\\"</samp>
+ <td>*escape* ``with`` "\"
+ </table>
+
+ <p>In Python strings it will, of course, be necessary
+ to escape any backslash characters so that they actually
+ <em>reach</em> reStructuredText.
+ The simplest way to do this is to use raw strings:
+
+ <p><table border="1" width="100%" bgcolor="#ffffcc" cellpadding="3">
+ <thead>
+ <tr align="left" bgcolor="#99CCFF">
+ <th width="50%">Python string
+ <th width="50%">Typical result
+ </thead>
+ <tbody>
+ <tr valign="top"><td>
+ <samp>r"""\*escape*&nbsp;\`with`&nbsp;"\\""""</samp>
+ <td>*escape* `with` "\"
+ <tr valign="top"><td>
+ <samp>&nbsp;"""\\*escape*&nbsp;\\`with`&nbsp;"\\\\""""</samp>
+ <td>*escape* `with` "\"
+ <tr valign="top"><td>
+ <samp>&nbsp;"""\*escape*&nbsp;\`with`&nbsp;"\\""""</samp>
+ <td><em>escape</em> with ""
+ </table>
+
+ <h2><a href="#contents" name="section-structure" class="backref"
+ >Section Structure</a></h2>
+
+ <p>(<a href="../../ref/rst/restructuredtext.html#sections">details</a>)
+
+ <p><table border="1" width="100%" bgcolor="#ffffcc" cellpadding="3">
+ <thead>
+ <tr align="left" bgcolor="#99CCFF">
+ <th width="50%">Plain text
+ <th width="50%">Typical result
+ </thead>
+ <tbody>
+ <tr valign="top">
+ <td>
+ <samp>=====</samp>
+ <br><samp>Title</samp>
+ <br><samp>=====</samp>
+ <br><samp>Subtitle</samp>
+ <br><samp>--------</samp>
+ <br><samp>Titles&nbsp;are&nbsp;underlined&nbsp;(or&nbsp;over-</samp>
+ <br><samp>and&nbsp;underlined)&nbsp;with&nbsp;a&nbsp;printing</samp>
+ <br><samp>nonalphanumeric&nbsp;7-bit&nbsp;ASCII</samp>
+ <br><samp>character.&nbsp;Recommended&nbsp;choices</samp>
+ <br><samp>are&nbsp;"``=&nbsp;-&nbsp;`&nbsp;:&nbsp;'&nbsp;"&nbsp;~&nbsp;^&nbsp;_&nbsp;*&nbsp;+&nbsp;#&nbsp;&lt;&nbsp;&gt;``".</samp>
+ <br><samp>The&nbsp;underline/overline&nbsp;must&nbsp;be&nbsp;at</samp>
+ <br><samp>least&nbsp;as&nbsp;long&nbsp;as&nbsp;the&nbsp;title&nbsp;text.</samp>
+ <br><samp></samp>
+ <br><samp>A&nbsp;lone&nbsp;top-level&nbsp;(sub)section</samp>
+ <br><samp>is&nbsp;lifted&nbsp;up&nbsp;to&nbsp;be&nbsp;the&nbsp;document's</samp>
+ <br><samp>(sub)title.</samp>
+
+ <td>
+ <font size="+2"><strong>Title</strong></font>
+ <p><font size="+1"><strong>Subtitle</strong></font>
+ <p>Titles are underlined (or over-
+ and underlined) with a printing
+ nonalphanumeric 7-bit ASCII
+ character. Recommended choices
+ are "<samp>= - ` : ' " ~ ^ _ * + # &lt; &gt;</samp>".
+ The underline/overline must be at
+ least as long as the title text.
+ <p>A lone top-level (sub)section is
+ lifted up to be the document's
+ (sub)title.
+ </table>
+
+ <h2><a href="#contents" name="paragraphs" class="backref"
+ >Paragraphs</a></h2>
+
+ <p>(<a href="../../ref/rst/restructuredtext.html#paragraphs">details</a>)
+
+ <p><table border="1" width="100%" bgcolor="#ffffcc" cellpadding="3">
+ <thead>
+ <tr align="left" bgcolor="#99CCFF">
+ <th width="50%">Plain text
+ <th width="50%">Typical result
+ </thead>
+ <tbody>
+ <tr valign="top">
+ <td>
+ <p><samp>This&nbsp;is&nbsp;a&nbsp;paragraph.</samp>
+
+ <p><samp>Paragraphs&nbsp;line&nbsp;up&nbsp;at&nbsp;their&nbsp;left</samp>
+ <br><samp>edges,&nbsp;and&nbsp;are&nbsp;normally&nbsp;separated</samp>
+ <br><samp>by&nbsp;blank&nbsp;lines.</samp>
+
+ <td>
+ <p>This is a paragraph.
+
+ <p>Paragraphs line up at their left edges, and are normally
+ separated by blank lines.
+
+ </table>
+
+ <h2><a href="#contents" name="bullet-lists" class="backref"
+ >Bullet Lists</a></h2>
+
+ <p>(<a href="../../ref/rst/restructuredtext.html#bullet-lists">details</a>)
+
+ <p><table border="1" width="100%" bgcolor="#ffffcc" cellpadding="3">
+ <thead>
+ <tr align="left" bgcolor="#99CCFF">
+ <th width="50%">Plain text
+ <th width="50%">Typical result
+ </thead>
+ <tbody>
+ <tr valign="top">
+ <td>
+ <samp>Bullet&nbsp;lists:</samp>
+
+ <p><samp>-&nbsp;This&nbsp;is&nbsp;item&nbsp;1</samp>
+ <br><samp>-&nbsp;This&nbsp;is&nbsp;item&nbsp;2</samp>
+
+ <p><samp>-&nbsp;Bullets&nbsp;are&nbsp;"-",&nbsp;"*"&nbsp;or&nbsp;"+".</samp>
+ <br><samp>&nbsp;&nbsp;Continuing&nbsp;text&nbsp;must&nbsp;be&nbsp;aligned</samp>
+ <br><samp>&nbsp;&nbsp;after&nbsp;the&nbsp;bullet&nbsp;and&nbsp;whitespace.</samp>
+
+ <p><samp>Note&nbsp;that&nbsp;a&nbsp;blank&nbsp;line&nbsp;is&nbsp;required</samp>
+ <br><samp>before&nbsp;the&nbsp;first&nbsp;item&nbsp;and&nbsp;after&nbsp;the</samp>
+ <br><samp>last,&nbsp;but&nbsp;is&nbsp;optional&nbsp;between&nbsp;items.</samp>
+ <td>Bullet lists:
+ <ul>
+ <li>This is item 1
+ <li>This is item 2
+ <li>Bullets are "-", "*" or "+".
+ Continuing text must be aligned
+ after the bullet and whitespace.
+ </ul>
+ <p>Note that a blank line is required before the first
+ item and after the last, but is optional between items.
+ </table>
+
+ <h2><a href="#contents" name="enumerated-lists" class="backref"
+ >Enumerated Lists</a></h2>
+
+ <p>(<a href="../../ref/rst/restructuredtext.html#enumerated-lists">details</a>)
+
+ <p><table border="1" width="100%" bgcolor="#ffffcc" cellpadding="3">
+ <thead>
+ <tr align="left" bgcolor="#99CCFF">
+ <th width="50%">Plain text
+ <th width="50%">Typical result
+ </thead>
+ <tbody>
+ <tr valign="top">
+ <td>
+ <samp>Enumerated&nbsp;lists:</samp>
+
+ <p><samp>3.&nbsp;This&nbsp;is&nbsp;the&nbsp;first&nbsp;item</samp>
+ <br><samp>4.&nbsp;This&nbsp;is&nbsp;the&nbsp;second&nbsp;item</samp>
+ <br><samp>5.&nbsp;Enumerators&nbsp;are&nbsp;arabic&nbsp;numbers,</samp>
+ <br><samp>&nbsp;&nbsp;&nbsp;single&nbsp;letters,&nbsp;or&nbsp;roman&nbsp;numerals</samp>
+ <br><samp>6.&nbsp;List&nbsp;items&nbsp;should&nbsp;be&nbsp;sequentially</samp>
+ <br><samp>&nbsp;&nbsp;&nbsp;numbered,&nbsp;but&nbsp;need&nbsp;not&nbsp;start&nbsp;at&nbsp;1</samp>
+ <br><samp>&nbsp;&nbsp;&nbsp;(although&nbsp;not&nbsp;all&nbsp;formatters&nbsp;will</samp>
+ <br><samp>&nbsp;&nbsp;&nbsp;honour&nbsp;the&nbsp;first&nbsp;index).</samp>
+ <br><samp>#.&nbsp;This&nbsp;item&nbsp;is&nbsp;auto-enumerated</samp>
+ <td>Enumerated lists:
+ <ol type="1">
+ <li value="3">This is the first item
+ <li>This is the second item
+ <li>Enumerators are arabic numbers, single letters,
+ or roman numerals
+ <li>List items should be sequentially numbered,
+ but need not start at 1 (although not all
+ formatters will honour the first index).
+ <li>This item is auto-enumerated
+ </ol>
+ </table>
+
+ <h2><a href="#contents" name="definition-lists" class="backref"
+ >Definition Lists</a></h2>
+
+ <p>(<a href="../../ref/rst/restructuredtext.html#definition-lists">details</a>)
+
+ <p><table border="1" width="100%" bgcolor="#ffffcc" cellpadding="3">
+ <thead>
+ <tr align="left" bgcolor="#99CCFF">
+ <th width="50%">Plain text
+ <th width="50%">Typical result
+ </thead>
+ <tbody>
+ <tr valign="top">
+ <td>
+ <samp>Definition&nbsp;lists:</samp>
+ <br>
+ <br><samp>what</samp>
+ <br><samp>&nbsp;&nbsp;Definition&nbsp;lists&nbsp;associate&nbsp;a&nbsp;term&nbsp;with</samp>
+ <br><samp>&nbsp;&nbsp;a&nbsp;definition.</samp>
+ <br>
+ <br><samp>how</samp>
+ <br><samp>&nbsp;&nbsp;The&nbsp;term&nbsp;is&nbsp;a&nbsp;one-line&nbsp;phrase,&nbsp;and&nbsp;the</samp>
+ <br><samp>&nbsp;&nbsp;definition&nbsp;is&nbsp;one&nbsp;or&nbsp;more&nbsp;paragraphs&nbsp;or</samp>
+ <br><samp>&nbsp;&nbsp;body&nbsp;elements,&nbsp;indented&nbsp;relative&nbsp;to&nbsp;the</samp>
+ <br><samp>&nbsp;&nbsp;term.&nbsp;Blank&nbsp;lines&nbsp;are&nbsp;not&nbsp;allowed</samp>
+ <br><samp>&nbsp;&nbsp;between&nbsp;term&nbsp;and&nbsp;definition.</samp>
+ <td>Definition lists:
+ <dl>
+ <dt><strong>what</strong>
+ <dd>Definition lists associate a term with
+ a definition.
+
+ <dt><strong>how</strong>
+ <dd>The term is a one-line phrase, and the
+ definition is one or more paragraphs or
+ body elements, indented relative to the
+ term. Blank lines are not allowed
+ between term and definition.
+ </dl>
+ </table>
+
+ <h2><a href="#contents" name="field-lists" class="backref"
+ >Field Lists</a></h2>
+
+ <p>(<a href="../../ref/rst/restructuredtext.html#field-lists">details</a>)
+
+ <p><table border="1" width="100%" bgcolor="#ffffcc" cellpadding="3">
+ <thead>
+ <tr align="left" bgcolor="#99CCFF">
+ <th width="50%">Plain text
+ <th width="50%">Typical result
+ </thead>
+ <tbody>
+ <tr valign="top">
+ <td>
+ <samp>:Authors:</samp>
+ <br><samp>&nbsp;&nbsp;&nbsp;&nbsp;Tony&nbsp;J.&nbsp;(Tibs)&nbsp;Ibbs,</samp>
+ <br><samp>&nbsp;&nbsp;&nbsp;&nbsp;David&nbsp;Goodger</samp>
+
+ <p><samp>&nbsp;&nbsp;&nbsp;&nbsp;(and&nbsp;sundry&nbsp;other&nbsp;good-natured&nbsp;folks)</samp>
+
+ <p><samp>:Version:&nbsp;1.0&nbsp;of&nbsp;2001/08/08</samp>
+ <br><samp>:Dedication:&nbsp;To&nbsp;my&nbsp;father.</samp>
+ <td>
+ <table>
+ <tr valign="top">
+ <td><strong>Authors:</strong>
+ <td>Tony J. (Tibs) Ibbs,
+ David Goodger
+ <tr><td><td>(and sundry other good-natured folks)
+ <tr><td><strong>Version:</strong><td>1.0 of 2001/08/08
+ <tr><td><strong>Dedication:</strong><td>To my father.
+ </table>
+ </table>
+
+ <p>Field lists are used as part of an extension syntax, such as
+ options for <a href="#directives">directives</a>, or database-like
+ records meant for further processing. Field lists may also be
+ used as generic two-column table constructs in documents.
+
+ <h2><a href="#contents" name="option-lists" class="backref"
+ >Option Lists</a></h2>
+
+ <p>(<a href="../../ref/rst/restructuredtext.html#option-lists">details</a>)
+
+ <p><table border="1" width="100%" bgcolor="#ffffcc" cellpadding="3">
+ <thead>
+ <tr align="left" bgcolor="#99CCFF">
+ <th width="50%">Plain text
+ <th width="50%">Typical result
+ </thead>
+ <tbody>
+ <tr valign="top">
+ <td>
+ <p><samp>
+ -a&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;command-line&nbsp;option&nbsp;"a"
+ <br>-b&nbsp;file&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;options&nbsp;can&nbsp;have&nbsp;arguments
+ <br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;and&nbsp;long&nbsp;descriptions
+ <br>--long&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;options&nbsp;can&nbsp;be&nbsp;long&nbsp;also
+ <br>--input=file&nbsp;&nbsp;long&nbsp;options&nbsp;can&nbsp;also&nbsp;have
+ <br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;arguments
+ <br>/V&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;DOS/VMS-style&nbsp;options&nbsp;too
+ </samp>
+
+ <td>
+ <table border="0" width="100%">
+ <tbody valign="top">
+ <tr>
+ <td width="30%"><samp>-a</samp>
+ <td>command-line option "a"
+ <tr>
+ <td><samp>-b <i>file</i></samp>
+ <td>options can have arguments and long descriptions
+ <tr>
+ <td><samp>--long</samp>
+ <td>options can be long also
+ <tr>
+ <td><samp>--input=<i>file</i></samp>
+ <td>long options can also have arguments
+ <tr>
+ <td><samp>/V</samp>
+ <td>DOS/VMS-style options too
+ </table>
+ </table>
+
+ <p>There must be at least two spaces between the option and the
+ description.
+
+ <h2><a href="#contents" name="literal-blocks" class="backref"
+ >Literal Blocks</a></h2>
+
+ <p>(<a href="../../ref/rst/restructuredtext.html#literal-blocks">details</a>)
+
+ <p><table border="1" width="100%" bgcolor="#ffffcc" cellpadding="3">
+ <thead>
+ <tr align="left" bgcolor="#99CCFF">
+ <th width="50%">Plain text
+ <th width="50%">Typical result
+ </thead>
+ <tbody>
+ <tr valign="top">
+ <td>
+ <samp>A&nbsp;paragraph&nbsp;containing&nbsp;only&nbsp;two&nbsp;colons</samp>
+ <br><samp>indicates&nbsp;that&nbsp;the&nbsp;following&nbsp;indented</samp>
+ <br><samp>or&nbsp;quoted&nbsp;text&nbsp;is&nbsp;a&nbsp;literal&nbsp;block.</samp>
+ <br>
+ <br><samp>::</samp>
+ <br>
+ <br><samp>&nbsp;&nbsp;Whitespace,&nbsp;newlines,&nbsp;blank&nbsp;lines,&nbsp;and</samp>
+ <br><samp>&nbsp;&nbsp;all&nbsp;kinds&nbsp;of&nbsp;markup&nbsp;(like&nbsp;*this*&nbsp;or</samp>
+ <br><samp>&nbsp;&nbsp;\this)&nbsp;is&nbsp;preserved&nbsp;by&nbsp;literal&nbsp;blocks.</samp>
+ <br>
+ <br><samp>&nbsp;&nbsp;The&nbsp;paragraph&nbsp;containing&nbsp;only&nbsp;'::'</samp>
+ <br><samp>&nbsp;&nbsp;will&nbsp;be&nbsp;omitted&nbsp;from&nbsp;the&nbsp;result.</samp>
+ <br>
+ <br><samp>The&nbsp;``::``&nbsp;may&nbsp;be&nbsp;tacked&nbsp;onto&nbsp;the&nbsp;very</samp>
+ <br><samp>end&nbsp;of&nbsp;any&nbsp;paragraph.&nbsp;The&nbsp;``::``&nbsp;will&nbsp;be</samp>
+ <br><samp>omitted&nbsp;if&nbsp;it&nbsp;is&nbsp;preceded&nbsp;by&nbsp;whitespace.</samp>
+ <br><samp>The&nbsp;``::``&nbsp;will&nbsp;be&nbsp;converted&nbsp;to&nbsp;a&nbsp;single</samp>
+ <br><samp>colon&nbsp;if&nbsp;preceded&nbsp;by&nbsp;text,&nbsp;like&nbsp;this::</samp>
+ <br>
+ <br><samp>&nbsp;&nbsp;It's&nbsp;very&nbsp;convenient&nbsp;to&nbsp;use&nbsp;this&nbsp;form.</samp>
+ <br>
+ <br><samp>Literal&nbsp;blocks&nbsp;end&nbsp;when&nbsp;text&nbsp;returns&nbsp;to</samp>
+ <br><samp>the&nbsp;preceding&nbsp;paragraph's&nbsp;indentation.</samp>
+ <br><samp>This&nbsp;means&nbsp;that&nbsp;something&nbsp;like&nbsp;this</samp>
+ <br><samp>is&nbsp;possible::</samp>
+ <br>
+ <br><samp>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;We&nbsp;start&nbsp;here</samp>
+ <br><samp>&nbsp;&nbsp;&nbsp;&nbsp;and&nbsp;continue&nbsp;here</samp>
+ <br><samp>&nbsp;&nbsp;and&nbsp;end&nbsp;here.</samp>
+ <br>
+ <br><samp>Per-line&nbsp;quoting&nbsp;can&nbsp;also&nbsp;be&nbsp;used&nbsp;on</samp>
+ <br><samp>unindented&nbsp;literal&nbsp;blocks::</samp>
+ <br>
+ <br><samp>&gt;&nbsp;Useful&nbsp;for&nbsp;quotes&nbsp;from&nbsp;email&nbsp;and</samp>
+ <br><samp>&gt;&nbsp;for&nbsp;Haskell&nbsp;literate&nbsp;programming.</samp>
+
+ <td>
+ <p>A paragraph containing only two colons
+ indicates that the following indented or quoted
+ text is a literal block.
+
+ <pre>
+ Whitespace, newlines, blank lines, and
+ all kinds of markup (like *this* or
+ \this) is preserved by literal blocks.
+
+ The paragraph containing only '::'
+ will be omitted from the result.</pre>
+
+ <p>The <samp>::</samp> may be tacked onto the very
+ end of any paragraph. The <samp>::</samp> will be
+ omitted if it is preceded by whitespace.
+ The <samp>::</samp> will be converted to a single
+ colon if preceded by text, like this:
+
+ <pre>
+ It's very convenient to use this form.</pre>
+
+ <p>Literal blocks end when text returns to
+ the preceding paragraph's indentation.
+ This means that something like this is possible:
+
+ <pre>
+ We start here
+ and continue here
+ and end here.</pre>
+
+ <p>Per-line quoting can also be used on
+ unindented literal blocks:
+
+ <pre>
+ &gt; Useful for quotes from email and
+ &gt; for Haskell literate programming.</pre>
+ </table>
+
+ <h2><a href="#contents" name="line-blocks" class="backref"
+ >Line Blocks</a></h2>
+
+ <p>(<a href="../../ref/rst/restructuredtext.html#line-blocks">details</a>)
+
+ <p><table border="1" width="100%" bgcolor="#ffffcc" cellpadding="3">
+ <thead>
+ <tr align="left" bgcolor="#99CCFF">
+ <th width="50%">Plain text
+ <th width="50%">Typical result
+ </thead>
+ <tbody>
+ <tr valign="top">
+ <td>
+ <samp>|&nbsp;Line&nbsp;blocks&nbsp;are&nbsp;useful&nbsp;for&nbsp;addresses,</samp>
+ <br><samp>|&nbsp;verse,&nbsp;and&nbsp;adornment-free&nbsp;lists.</samp>
+ <br><samp>|</samp>
+ <br><samp>|&nbsp;Each&nbsp;new&nbsp;line&nbsp;begins&nbsp;with&nbsp;a</samp>
+ <br><samp>|&nbsp;vertical&nbsp;bar&nbsp;("|").</samp>
+ <br><samp>|&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;Line&nbsp;breaks&nbsp;and&nbsp;initial&nbsp;indents</samp>
+ <br><samp>|&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;are&nbsp;preserved.</samp>
+ <br><samp>|&nbsp;Continuation&nbsp;lines&nbsp;are&nbsp;wrapped</samp>
+ <br><samp>&nbsp;&nbsp;portions&nbsp;of&nbsp;long&nbsp;lines;&nbsp;they&nbsp;begin</samp>
+ <br><samp>&nbsp;&nbsp;with&nbsp;spaces&nbsp;in&nbsp;place&nbsp;of&nbsp;vertical&nbsp;bars.</samp>
+
+ <td>
+ <div class="line-block">
+ <div class="line">Line blocks are useful for addresses,</div>
+ <div class="line">verse, and adornment-free lists.</div>
+ <div class="line"><br /></div>
+ <div class="line">Each new line begins with a</div>
+ <div class="line">vertical bar ("|").</div>
+ <div class="line-block">
+ <div class="line">Line breaks and initial indents</div>
+ <div class="line">are preserved.</div>
+ </div>
+ <div class="line">Continuation lines are wrapped portions
+ of long lines; they begin
+ with spaces in place of vertical bars.</div>
+ </div>
+ </table>
+
+ <h2><a href="#contents" name="block-quotes" class="backref"
+ >Block Quotes</a></h2>
+
+ <p>(<a href="../../ref/rst/restructuredtext.html#block-quotes">details</a>)
+
+ <p><table border="1" width="100%" bgcolor="#ffffcc" cellpadding="3">
+ <thead>
+ <tr align="left" bgcolor="#99CCFF">
+ <th width="50%">Plain text
+ <th width="50%">Typical result
+ </thead>
+ <tbody>
+ <tr valign="top">
+ <td>
+ <samp>Block&nbsp;quotes&nbsp;are&nbsp;just:</samp>
+
+ <p><samp>&nbsp;&nbsp;&nbsp;&nbsp;Indented&nbsp;paragraphs,</samp>
+
+ <p><samp>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;and&nbsp;they&nbsp;may&nbsp;nest.</samp>
+ <td>
+ Block quotes are just:
+ <blockquote>
+ <p>Indented paragraphs,
+ <blockquote>
+ <p>and they may nest.
+ </blockquote>
+ </blockquote>
+ </table>
+
+ <p>Use <a href="#comments">empty comments</a> to separate indentation
+ contexts, such as block quotes and directive contents.</p>
+
+ <h2><a href="#contents" name="doctest-blocks" class="backref"
+ >Doctest Blocks</a></h2>
+
+ <p>(<a href="../../ref/rst/restructuredtext.html#doctest-blocks">details</a>)
+
+ <p><table border="1" width="100%" bgcolor="#ffffcc" cellpadding="3">
+ <thead>
+ <tr align="left" bgcolor="#99CCFF">
+ <th width="50%">Plain text
+ <th width="50%">Typical result
+ </thead>
+ <tbody>
+ <tr valign="top">
+ <td>
+ <p><samp>Doctest&nbsp;blocks&nbsp;are&nbsp;interactive
+ <br>Python&nbsp;sessions.&nbsp;They&nbsp;begin&nbsp;with
+ <br>"``&gt;&gt;&gt;``"&nbsp;and&nbsp;end&nbsp;with&nbsp;a&nbsp;blank&nbsp;line.</samp>
+
+ <p><samp>&gt;&gt;&gt;&nbsp;print&nbsp;"This&nbsp;is&nbsp;a&nbsp;doctest&nbsp;block."
+ <br>This&nbsp;is&nbsp;a&nbsp;doctest&nbsp;block.</samp>
+
+ <td>
+ <p>Doctest blocks are interactive
+ Python sessions. They begin with
+ "<samp>&gt;&gt;&gt;</samp>" and end with a blank line.
+
+ <p><samp>&gt;&gt;&gt;&nbsp;print&nbsp;"This&nbsp;is&nbsp;a&nbsp;doctest&nbsp;block."
+ <br>This&nbsp;is&nbsp;a&nbsp;doctest&nbsp;block.</samp>
+ </table>
+
+ <p>"The <a
+ href="http://www.python.org/doc/current/lib/module-doctest.html">doctest</a>
+ module searches a module's docstrings for text that looks like an
+ interactive Python session, then executes all such sessions to
+ verify they still work exactly as shown." (From the doctest docs.)
+
+ <h2><a href="#contents" name="tables" class="backref"
+ >Tables</a></h2>
+
+ <p>(<a href="../../ref/rst/restructuredtext.html#tables">details</a>)
+
+ <p>There are two syntaxes for tables in reStructuredText. Grid
+ tables are complete but cumbersome to create. Simple tables are
+ easy to create but limited (no row spans, etc.).</p>
+
+ <p><table border="1" width="100%" bgcolor="#ffffcc" cellpadding="3">
+ <thead>
+ <tr align="left" bgcolor="#99CCFF">
+ <th width="50%">Plain text
+ <th width="50%">Typical result
+ </thead>
+ <tbody>
+ <tr valign="top">
+ <td>
+ <p><samp>Grid table:</samp></p>
+
+ <p><samp>+------------+------------+-----------+</samp>
+ <br><samp>|&nbsp;Header&nbsp;1&nbsp;&nbsp;&nbsp;|&nbsp;Header&nbsp;2&nbsp;&nbsp;&nbsp;|&nbsp;Header&nbsp;3&nbsp;&nbsp;|</samp>
+ <br><samp>+============+============+===========+</samp>
+ <br><samp>|&nbsp;body&nbsp;row&nbsp;1&nbsp;|&nbsp;column&nbsp;2&nbsp;&nbsp;&nbsp;|&nbsp;column&nbsp;3&nbsp;&nbsp;|</samp>
+ <br><samp>+------------+------------+-----------+</samp>
+ <br><samp>|&nbsp;body&nbsp;row&nbsp;2&nbsp;|&nbsp;Cells&nbsp;may&nbsp;span&nbsp;columns.|</samp>
+ <br><samp>+------------+------------+-----------+</samp>
+ <br><samp>|&nbsp;body&nbsp;row&nbsp;3&nbsp;|&nbsp;Cells&nbsp;may&nbsp;&nbsp;|&nbsp;-&nbsp;Cells&nbsp;&nbsp;&nbsp;|</samp>
+ <br><samp>+------------+&nbsp;span&nbsp;rows.&nbsp;|&nbsp;-&nbsp;contain&nbsp;|</samp>
+ <br><samp>|&nbsp;body&nbsp;row&nbsp;4&nbsp;|&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;|&nbsp;-&nbsp;blocks.&nbsp;|</samp>
+ <br><samp>+------------+------------+-----------+</samp></p>
+ <td>
+ <p>Grid table:</p>
+ <table border="1">
+ <thead valign="bottom">
+ <tr>
+ <th>Header 1
+ <th>Header 2
+ <th>Header 3
+ </tr>
+ </thead>
+ <tbody valign="top">
+ <tr>
+ <td>body row 1
+ <td>column 2
+ <td>column 3
+ </tr>
+ <tr>
+ <td>body row 2
+ <td colspan="2">Cells may span columns.
+ </tr>
+ <tr>
+ <td>body row 3
+ <td rowspan="2">Cells may<br>span rows.
+ <td rowspan="2">
+ <ul>
+ <li>Cells
+ <li>contain
+ <li>blocks.
+ </ul>
+ </tr>
+ <tr>
+ <td>body row 4
+ </tr>
+ </table>
+ <tr valign="top">
+ <td>
+ <p><samp>Simple table:</samp></p>
+
+ <p><samp>=====&nbsp;&nbsp;=====&nbsp;&nbsp;======</samp>
+ <br><samp>&nbsp;&nbsp;&nbsp;Inputs&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;Output</samp>
+ <br><samp>------------&nbsp;&nbsp;------</samp>
+ <br><samp>&nbsp;&nbsp;A&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;B&nbsp;&nbsp;&nbsp;&nbsp;A&nbsp;or&nbsp;B</samp>
+ <br><samp>=====&nbsp;&nbsp;=====&nbsp;&nbsp;======</samp>
+ <br><samp>False&nbsp;&nbsp;False&nbsp;&nbsp;False</samp>
+ <br><samp>True&nbsp;&nbsp;&nbsp;False&nbsp;&nbsp;True</samp>
+ <br><samp>False&nbsp;&nbsp;True&nbsp;&nbsp;&nbsp;True</samp>
+ <br><samp>True&nbsp;&nbsp;&nbsp;True&nbsp;&nbsp;&nbsp;True</samp>
+ <br><samp>=====&nbsp;&nbsp;=====&nbsp;&nbsp;======</samp></p>
+
+ <td>
+ <p>Simple table:</p>
+ <table border="1">
+ <colgroup>
+ <col width="31%">
+ <col width="31%">
+ <col width="38%">
+ </colgroup>
+ <thead valign="bottom">
+ <tr>
+ <th colspan="2">Inputs
+ <th>Output
+ <tr>
+ <th>A
+ <th>B
+ <th>A or B
+ <tbody valign="top">
+ <tr>
+ <td>False
+ <td>False
+ <td>False
+ <tr>
+ <td>True
+ <td>False
+ <td>True
+ <tr>
+ <td>False
+ <td>True
+ <td>True
+ <tr>
+ <td>True
+ <td>True
+ <td>True
+ </table>
+
+ </table>
+
+ <h2><a href="#contents" name="transitions" class="backref"
+ >Transitions</a></h2>
+
+ <p>(<a href="../../ref/rst/restructuredtext.html#transitions">details</a>)
+
+ <p><table border="1" width="100%" bgcolor="#ffffcc" cellpadding="3">
+ <thead>
+ <tr align="left" bgcolor="#99CCFF">
+ <th width="50%">Plain text
+ <th width="50%">Typical result
+ </thead>
+ <tbody>
+ <tr valign="top">
+ <td>
+ <p><samp>
+ A&nbsp;transition&nbsp;marker&nbsp;is&nbsp;a&nbsp;horizontal&nbsp;line
+ <br>of&nbsp;4&nbsp;or&nbsp;more&nbsp;repeated&nbsp;punctuation
+ <br>characters.</samp>
+
+ <p><samp>------------</samp>
+
+ <p><samp>A&nbsp;transition&nbsp;should&nbsp;not&nbsp;begin&nbsp;or&nbsp;end&nbsp;a
+ <br>section&nbsp;or&nbsp;document,&nbsp;nor&nbsp;should&nbsp;two
+ <br>transitions&nbsp;be&nbsp;immediately&nbsp;adjacent.</samp>
+
+ <td>
+ <p>A transition marker is a horizontal line
+ of 4 or more repeated punctuation
+ characters.</p>
+
+ <hr>
+
+ <p>A transition should not begin or end a
+ section or document, nor should two
+ transitions be immediately adjacent.
+ </table>
+
+ <p>Transitions are commonly seen in novels and short fiction, as a
+ gap spanning one or more lines, marking text divisions or
+ signaling changes in subject, time, point of view, or emphasis.
+
+ <h2><a href="#contents" name="explicit-markup" class="backref"
+ >Explicit Markup</a></h2>
+
+ <p>Explicit markup blocks are used for constructs which float
+ (footnotes), have no direct paper-document representation
+ (hyperlink targets, comments), or require specialized processing
+ (directives). They all begin with two periods and whitespace, the
+ "explicit markup start".
+
+ <h3><a href="#contents" name="footnotes" class="backref"
+ >Footnotes</a></h3>
+
+ <p>(<a href="../../ref/rst/restructuredtext.html#footnotes">details</a>)
+
+ <p><table border="1" width="100%" bgcolor="#ffffcc" cellpadding="3">
+ <thead>
+ <tr align="left" bgcolor="#99CCFF">
+ <th width="50%">Plain text
+ <th width="50%">Typical result
+ </thead>
+ <tbody>
+
+ <tr valign="top">
+ <td>
+ <samp>Footnote&nbsp;references,&nbsp;like&nbsp;[5]_.</samp>
+ <br><samp>Note&nbsp;that&nbsp;footnotes&nbsp;may&nbsp;get</samp>
+ <br><samp>rearranged,&nbsp;e.g.,&nbsp;to&nbsp;the&nbsp;bottom&nbsp;of</samp>
+ <br><samp>the&nbsp;"page".</samp>
+
+ <p><samp>..&nbsp;[5]&nbsp;A&nbsp;numerical&nbsp;footnote.&nbsp;Note</samp>
+ <br><samp>&nbsp;&nbsp;&nbsp;there's&nbsp;no&nbsp;colon&nbsp;after&nbsp;the&nbsp;``]``.</samp>
+
+ <td>
+ Footnote references, like <sup><a href="#5">5</a></sup>.
+ Note that footnotes may get rearranged, e.g., to the bottom of
+ the "page".
+
+ <p><table>
+ <tr><td colspan="2"><hr>
+ <!-- <tr><td colspan="2">Footnotes: -->
+ <tr><td><a name="5"><strong>[5]</strong></a><td> A numerical footnote.
+ Note there's no colon after the <samp>]</samp>.
+ </table>
+
+ <tr valign="top">
+ <td>
+ <samp>Autonumbered&nbsp;footnotes&nbsp;are</samp>
+ <br><samp>possible,&nbsp;like&nbsp;using&nbsp;[#]_&nbsp;and&nbsp;[#]_.</samp>
+ <p><samp>..&nbsp;[#]&nbsp;This&nbsp;is&nbsp;the&nbsp;first&nbsp;one.</samp>
+ <br><samp>..&nbsp;[#]&nbsp;This&nbsp;is&nbsp;the&nbsp;second&nbsp;one.</samp>
+
+ <p><samp>They&nbsp;may&nbsp;be&nbsp;assigned&nbsp;'autonumber</samp>
+ <br><samp>labels'&nbsp;-&nbsp;for&nbsp;instance,
+ <br>[#fourth]_&nbsp;and&nbsp;[#third]_.</samp>
+
+ <p><samp>..&nbsp;[#third]&nbsp;a.k.a.&nbsp;third_</samp>
+ <p><samp>..&nbsp;[#fourth]&nbsp;a.k.a.&nbsp;fourth_</samp>
+ <td>
+ Autonumbered footnotes are possible, like using <sup><a
+ href="#auto1">1</a></sup> and <sup><a href="#auto2">2</a></sup>.
+
+ <p>They may be assigned 'autonumber labels' - for instance,
+ <sup><a href="#fourth">4</a></sup> and <sup><a
+ href="#third">3</a></sup>.
+
+ <p><table>
+ <tr><td colspan="2"><hr>
+ <!-- <tr><td colspan="2">Footnotes: -->
+ <tr><td><a name="auto1"><strong>[1]</strong></a><td> This is the first one.
+ <tr><td><a name="auto2"><strong>[2]</strong></a><td> This is the second one.
+ <tr><td><a name="third"><strong>[3]</strong></a><td> a.k.a. <a href="#third">third</a>
+ <tr><td><a name="fourth"><strong>[4]</strong></a><td> a.k.a. <a href="#fourth">fourth</a>
+ </table>
+
+ <tr valign="top">
+ <td>
+ <samp>Auto-symbol&nbsp;footnotes&nbsp;are&nbsp;also</samp>
+ <br><samp>possible,&nbsp;like&nbsp;this:&nbsp;[*]_&nbsp;and&nbsp;[*]_.</samp>
+ <p><samp>..&nbsp;[*]&nbsp;This&nbsp;is&nbsp;the&nbsp;first&nbsp;one.</samp>
+ <br><samp>..&nbsp;[*]&nbsp;This&nbsp;is&nbsp;the&nbsp;second&nbsp;one.</samp>
+
+ <td>
+ Auto-symbol footnotes are also
+ possible, like this: <sup><a href="#symbol1">*</a></sup>
+ and <sup><a href="#symbol2">&dagger;</a></sup>.
+
+ <p><table>
+ <tr><td colspan="2"><hr>
+ <!-- <tr><td colspan="2">Footnotes: -->
+ <tr><td><a name="symbol1"><strong>[*]</strong></a><td> This is the first symbol footnote
+ <tr><td><a name="symbol2"><strong>[&dagger;]</strong></a><td> This is the second one.
+ </table>
+
+ </table>
+
+ <p>The numbering of auto-numbered footnotes is determined by the
+ order of the footnotes, not of the references. For auto-numbered
+ footnote references without autonumber labels
+ ("<samp>[#]_</samp>"), the references and footnotes must be in the
+ same relative order. Similarly for auto-symbol footnotes
+ ("<samp>[*]_</samp>").
+
+ <h3><a href="#contents" name="citations" class="backref"
+ >Citations</a></h3>
+
+ <p>(<a href="../../ref/rst/restructuredtext.html#citations">details</a>)
+
+ <p><table border="1" width="100%" bgcolor="#ffffcc" cellpadding="3">
+ <thead>
+ <tr align="left" bgcolor="#99CCFF">
+ <th width="50%">Plain text
+ <th width="50%">Typical result
+ </thead>
+ <tbody>
+
+ <tr valign="top">
+ <td>
+ <samp>Citation&nbsp;references,&nbsp;like&nbsp;[CIT2002]_.</samp>
+ <br><samp>Note&nbsp;that&nbsp;citations&nbsp;may&nbsp;get</samp>
+ <br><samp>rearranged,&nbsp;e.g.,&nbsp;to&nbsp;the&nbsp;bottom&nbsp;of</samp>
+ <br><samp>the&nbsp;"page".</samp>
+
+ <p><samp>..&nbsp;[CIT2002]&nbsp;A&nbsp;citation</samp>
+ <br><samp>&nbsp;&nbsp;&nbsp;(as&nbsp;often&nbsp;used&nbsp;in&nbsp;journals).</samp>
+
+ <p><samp>Citation&nbsp;labels&nbsp;contain&nbsp;alphanumerics,</samp>
+ <br><samp>underlines,&nbsp;hyphens&nbsp;and&nbsp;fullstops.</samp>
+ <br><samp>Case&nbsp;is&nbsp;not&nbsp;significant.</samp>
+
+ <p><samp>Given&nbsp;a&nbsp;citation&nbsp;like&nbsp;[this]_,&nbsp;one</samp>
+ <br><samp>can&nbsp;also&nbsp;refer&nbsp;to&nbsp;it&nbsp;like&nbsp;this_.</samp>
+
+ <p><samp>..&nbsp;[this]&nbsp;here.</samp>
+
+ <td>
+ Citation references, like <a href="#cit2002">[CIT2002]</a>.
+ Note that citations may get rearranged, e.g., to the bottom of
+ the "page".
+
+ <p>Citation labels contain alphanumerics, underlines, hyphens
+ and fullstops. Case is not significant.
+
+ <p>Given a citation like <a href="#this">[this]</a>, one
+ can also refer to it like <a href="#this">this</a>.
+
+ <p><table>
+ <tr><td colspan="2"><hr>
+ <!-- <tr><td colspan="2">Citations: -->
+ <tr><td><a name="cit2002"><strong>[CIT2002]</strong></a><td> A citation
+ (as often used in journals).
+ <tr><td><a name="this"><strong>[this]</strong></a><td> here.
+ </table>
+
+ </table>
+
+ <h3><a href="#contents" name="hyperlink-targets" class="backref"
+ >Hyperlink Targets</a></h3>
+
+ <p>(<a href="../../ref/rst/restructuredtext.html#hyperlink-targets">details</a>)
+
+ <h4><a href="#contents" name="external-hyperlink-targets" class="backref"
+ >External Hyperlink Targets</a></h4>
+
+ <p><table border="1" width="100%" bgcolor="#ffffcc" cellpadding="3">
+ <thead>
+ <tr align="left" bgcolor="#99CCFF">
+ <th width="50%">Plain text
+ <th width="50%">Typical result
+ </thead>
+ <tbody>
+
+ <tr valign="top">
+ <td rowspan="2">
+ <samp>External&nbsp;hyperlinks,&nbsp;like&nbsp;Python_.</samp>
+
+ <p><samp>..&nbsp;_Python:&nbsp;http://www.python.org/</samp>
+ <td>
+ <table width="100%">
+ <tr bgcolor="#99CCFF"><td><em>Fold-in form</em>
+ <tr><td>External hyperlinks, like
+ <a href="http://www.python.org/">Python</a>.
+ </table>
+ <tr valign="top">
+ <td>
+ <table width="100%">
+ <tr bgcolor="#99CCFF"><td><em>Call-out form</em>
+ <tr><td>External hyperlinks, like
+ <a href="#labPython"><i>Python</i></a>.
+
+ <p><table>
+ <tr><td colspan="2"><hr>
+ <tr><td><a name="labPython"><i>Python:</i></a>
+ <td> <a href="http://www.python.org/">http://www.python.org/</a>
+ </table>
+ </table>
+ </table>
+
+ <p>"<em>Fold-in</em>" is the representation typically used in HTML
+ documents (think of the indirect hyperlink being "folded in" like
+ ingredients into a cake), and "<em>call-out</em>" is more suitable for
+ printed documents, where the link needs to be presented explicitly, for
+ example as a footnote. You can force usage of the call-out form by
+ using the
+ "<a href="../../ref/rst/directives.html#target-notes">target-notes</a>"
+ directive.
+
+ <p>reStructuredText also provides for <b>embedded URIs</b> (<a
+ href="../../ref/rst/restructuredtext.html#embedded-uris">details</a>),
+ a convenience at the expense of readability. A hyperlink
+ reference may directly embed a target URI inline, within angle
+ brackets. The following is exactly equivalent to the example above:
+
+ <p><table border="1" width="100%" bgcolor="#ffffcc" cellpadding="3">
+ <thead>
+ <tr align="left" bgcolor="#99CCFF">
+ <th width="50%">Plain text
+ <th width="50%">Typical result
+ </thead>
+ <tbody>
+
+ <tr valign="top">
+ <td rowspan="2">
+ <samp>External&nbsp;hyperlinks,&nbsp;like&nbsp;`Python
+ <br>&lt;http://www.python.org/&gt;`_.</samp>
+ <td>External hyperlinks, like
+ <a href="http://www.python.org/">Python</a>.
+ </table>
+
+ <h4><a href="#contents" name="internal-hyperlink-targets" class="backref"
+ >Internal Hyperlink Targets</a></h4>
+
+ <p><table border="1" width="100%" bgcolor="#ffffcc" cellpadding="3">
+ <thead>
+ <tr align="left" bgcolor="#99CCFF">
+ <th width="50%">Plain text
+ <th width="50%">Typical result
+ </thead>
+ <tbody>
+
+ <tr valign="top">
+ <td rowspan="2"><samp>Internal&nbsp;crossreferences,&nbsp;like&nbsp;example_.</samp>
+
+ <p><samp>..&nbsp;_example:</samp>
+
+ <p><samp>This&nbsp;is&nbsp;an&nbsp;example&nbsp;crossreference&nbsp;target.</samp>
+ <td>
+ <table width="100%">
+ <tr bgcolor="#99CCFF"><td><em>Fold-in form</em>
+ <!-- Note that some browsers may not like an "a" tag that -->
+ <!-- does not have any content, so we could arbitrarily -->
+ <!-- use the first word as content - *or* just trust to -->
+ <!-- luck! -->
+ <tr><td>Internal crossreferences, like <a href="#example-foldin">example</a>
+ <p><a name="example-foldin">This</a> is an example
+ crossreference target.
+ </table>
+ <tr valign="top">
+ <td>
+ <table width="100%">
+ <tr><td bgcolor="#99CCFF"><em>Call-out form</em>
+ <tr><td>Internal crossreferences, like <a href="#example-callout">example</a>
+
+ <p><a name="example-callout"><i>example:</i></a>
+ <br>This is an example crossreference target.
+ </table>
+
+ </table>
+
+ <h4><a href="#contents" name="indirect-hyperlink-targets" class="backref"
+ >Indirect Hyperlink Targets</a></h4>
+
+ <p>(<a href="../../ref/rst/restructuredtext.html#indirect-hyperlink-targets">details</a>)
+
+ <p><table border="1" width="100%" bgcolor="#ffffcc" cellpadding="3">
+ <thead>
+ <tr align="left" bgcolor="#99CCFF">
+ <th width="50%">Plain text
+ <th width="50%">Typical result
+ </thead>
+ <tbody>
+
+ <tr valign="top">
+ <td>
+ <samp>Python_&nbsp;is&nbsp;`my&nbsp;favourite
+ <br>programming&nbsp;language`__.</samp>
+
+ <p><samp>..&nbsp;_Python:&nbsp;http://www.python.org/</samp>
+
+ <p><samp>__&nbsp;Python_</samp>
+
+ <td>
+ <p><a href="http://www.python.org/">Python</a> is
+ <a href="http://www.python.org/">my favourite
+ programming language</a>.
+
+ </table>
+
+ <p>The second hyperlink target (the line beginning with
+ "<samp>__</samp>") is both an indirect hyperlink target
+ (<i>indirectly</i> pointing at the Python website via the
+ "<samp>Python_</samp>" reference) and an <b>anonymous hyperlink
+ target</b>. In the text, a double-underscore suffix is used to
+ indicate an <b>anonymous hyperlink reference</b>. In an anonymous
+ hyperlink target, the reference text is not repeated. This is
+ useful for references with long text or throw-away references, but
+ the target should be kept close to the reference to prevent them
+ going out of sync.
+
+ <h4><a href="#contents" name="implicit-hyperlink-targets" class="backref"
+ >Implicit Hyperlink Targets</a></h4>
+
+ <p>(<a href="../../ref/rst/restructuredtext.html#implicit-hyperlink-targets">details</a>)
+
+ <p>Section titles, footnotes, and citations automatically generate
+ hyperlink targets (the title text or footnote/citation label is
+ used as the hyperlink name).
+
+ <p><table border="1" width="100%" bgcolor="#ffffcc" cellpadding="3">
+ <thead><tr align="left" bgcolor="#99CCFF">
+ <th width="50%">Plain text
+ <th width="50%">Typical result
+ </thead>
+ <tbody>
+
+ <tr valign="top">
+ <td>
+ <samp>Titles&nbsp;are&nbsp;targets,&nbsp;too</samp>
+ <br><samp>=======================</samp>
+ <br><samp>Implict&nbsp;references,&nbsp;like&nbsp;`Titles&nbsp;are</samp>
+ <br><samp>targets,&nbsp;too`_.</samp>
+ <td>
+ <font size="+2"><strong><a name="title">Titles are targets, too</a></strong></font>
+ <p>Implict references, like <a href="#title">Titles are
+ targets, too</a>.
+ </table>
+
+ <h3><a href="#contents" name="directives" class="backref"
+ >Directives</a></h3>
+
+ <p>(<a href="../../ref/rst/restructuredtext.html#directives">details</a>)
+
+ <p>Directives are a general-purpose extension mechanism, a way of
+ adding support for new constructs without adding new syntax. For
+ a description of all standard directives, see <a
+ href="../../ref/rst/directives.html" >reStructuredText
+ Directives</a>.
+
+ <p><table border="1" width="100%" bgcolor="#ffffcc" cellpadding="3">
+ <thead>
+ <tr align="left" bgcolor="#99CCFF">
+ <th width="50%">Plain text
+ <th width="50%">Typical result
+ </thead>
+ <tbody>
+ <tr valign="top">
+ <td><samp>For&nbsp;instance:</samp>
+
+ <p><samp>..&nbsp;image::&nbsp;images/ball1.gif</samp>
+
+ <td>
+ For instance:
+ <p><img src="images/ball1.gif" alt="ball1">
+ </table>
+
+ <h3><a href="#contents" name="substitution-references-and-definitions"
+ class="backref" >Substitution References and Definitions</a></h3>
+
+ <p>(<a href="../../ref/rst/restructuredtext.html#substitution-definitions">details</a>)
+
+ <p>Substitutions are like inline directives, allowing graphics and
+ arbitrary constructs within text.
+
+ <p><table border="1" width="100%" bgcolor="#ffffcc" cellpadding="3">
+ <thead>
+ <tr align="left" bgcolor="#99CCFF">
+ <th width="50%">Plain text
+ <th width="50%">Typical result
+ </thead>
+ <tbody>
+ <tr valign="top">
+ <td><samp>
+ The&nbsp;|biohazard|&nbsp;symbol&nbsp;must&nbsp;be
+ used&nbsp;on&nbsp;containers&nbsp;used&nbsp;to
+ dispose&nbsp;of&nbsp;medical&nbsp;waste.</samp>
+
+ <p><samp>
+ ..&nbsp;|biohazard|&nbsp;image::&nbsp;biohazard.png</samp>
+
+ <td>
+
+ <p>The <img src="images/biohazard.png" align="bottom" alt="biohazard"> symbol
+ must be used on containers used to dispose of medical waste.
+
+ </table>
+
+ <h3><a href="#contents" name="comments" class="backref"
+ >Comments</a></h3>
+
+ <p>(<a href="../../ref/rst/restructuredtext.html#comments">details</a>)
+
+ <p>Any text which begins with an explicit markup start but doesn't
+ use the syntax of any of the constructs above, is a comment.
+
+ <p><table border="1" width="100%" bgcolor="#ffffcc" cellpadding="3">
+ <thead>
+ <tr align="left" bgcolor="#99CCFF">
+ <th width="50%">Plain text
+ <th width="50%">Typical result
+ </thead>
+ <tbody>
+ <tr valign="top">
+ <td><samp>..&nbsp;This&nbsp;text&nbsp;will&nbsp;not&nbsp;be&nbsp;shown</samp>
+ <br><samp>&nbsp;&nbsp;&nbsp;(but,&nbsp;for&nbsp;instance,&nbsp;in&nbsp;HTML&nbsp;might&nbsp;be</samp>
+ <br><samp>&nbsp;&nbsp;&nbsp;rendered&nbsp;as&nbsp;an&nbsp;HTML&nbsp;comment)</samp>
+
+ <td>&nbsp;
+ <!-- This text will not be shown -->
+ <!-- (but, for instance in HTML might be -->
+ <!-- rendered as an HTML comment) -->
+
+ <tr valign="top">
+ <td>
+ <samp>An&nbsp;"empty&nbsp;comment"&nbsp;does&nbsp;not</samp>
+ <br><samp>consume&nbsp;following&nbsp;blocks.</samp>
+ <br><samp>(An&nbsp;empty&nbsp;comment&nbsp;is&nbsp;".."&nbsp;with</samp>
+ <br><samp>blank&nbsp;lines&nbsp;before&nbsp;and&nbsp;after.)</samp>
+ <p><samp>..</samp>
+ <p><samp>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;So&nbsp;this&nbsp;block&nbsp;is&nbsp;not&nbsp;"lost",</samp>
+ <br><samp>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;despite&nbsp;its&nbsp;indentation.</samp>
+ <td>
+ An "empty comment" does not
+ consume following blocks.
+ (An empty comment is ".." with
+ blank lines before and after.)
+ <blockquote>
+ So this block is not "lost",
+ despite its indentation.
+ </blockquote>
+ </table>
+
+ <h2><a href="#contents" name="getting-help" class="backref"
+ >Getting Help</a></h2>
+
+ <p>Users who have questions or need assistance with Docutils or
+ reStructuredText should <a
+ href="mailto:docutils-users@lists.sourceforge.net" >post a
+ message</a> to the <a
+ href="http://lists.sourceforge.net/lists/listinfo/docutils-users"
+ >Docutils-Users mailing list</a>. The <a
+ href="http://docutils.sourceforge.net/" >Docutils project web
+ site</a> has more information.
+
+ <p><hr>
+ <address>
+ <p>Authors:
+ <a href="http://www.tibsnjoan.co.uk/">Tibs</a>
+ (<a href="mailto:tibs@tibsnjoan.co.uk"><tt>tibs@tibsnjoan.co.uk</tt></a>)
+ and David Goodger
+ (<a href="mailto:goodger@python.org">goodger@python.org</a>)
+ </address>
+ <!-- Created: Fri Aug 03 09:11:57 GMT Daylight Time 2001 -->
diff --git a/nikola/data/samplesite/stories/quickstart.meta b/nikola/data/samplesite/stories/quickstart.meta
new file mode 100644
index 0000000..ec9e8b5
--- /dev/null
+++ b/nikola/data/samplesite/stories/quickstart.meta
@@ -0,0 +1,4 @@
+A reStructuredText Primer
+quickstart
+2012/03/30 23:00
+
diff --git a/nikola/data/samplesite/stories/quickstart.txt b/nikola/data/samplesite/stories/quickstart.txt
new file mode 100644
index 0000000..a74d8fa
--- /dev/null
+++ b/nikola/data/samplesite/stories/quickstart.txt
@@ -0,0 +1,406 @@
+A ReStructuredText Primer
+=========================
+
+:Author: Richard Jones
+:Version: $Revision: 5801 $
+:Copyright: This document has been placed in the public domain.
+
+.. class:: alert alert-info pull-left
+
+.. contents::
+
+
+The text below contains links that look like "(quickref__)". These
+are relative links that point to the `Quick reStructuredText`_ user
+reference. If these links don't work, please refer to the `master
+quick reference`_ document.
+
+__
+.. _Quick reStructuredText: quickref.html
+.. _master quick reference:
+ http://docutils.sourceforge.net/docs/user/rst/quickref.html
+
+.. Note:: This document is an informal introduction to
+ reStructuredText. The `What Next?`_ section below has links to
+ further resources, including a formal reference.
+
+
+Structure
+---------
+
+From the outset, let me say that "Structured Text" is probably a bit
+of a misnomer. It's more like "Relaxed Text" that uses certain
+consistent patterns. These patterns are interpreted by a HTML
+converter to produce "Very Structured Text" that can be used by a web
+browser.
+
+The most basic pattern recognised is a **paragraph** (quickref__).
+That's a chunk of text that is separated by blank lines (one is
+enough). Paragraphs must have the same indentation -- that is, line
+up at their left edge. Paragraphs that start indented will result in
+indented quote paragraphs. For example::
+
+ This is a paragraph. It's quite
+ short.
+
+ This paragraph will result in an indented block of
+ text, typically used for quoting other text.
+
+ This is another one.
+
+Results in:
+
+ This is a paragraph. It's quite
+ short.
+
+ This paragraph will result in an indented block of
+ text, typically used for quoting other text.
+
+ This is another one.
+
+__ quickref.html#paragraphs
+
+
+Text styles
+-----------
+
+(quickref__)
+
+__ quickref.html#inline-markup
+
+Inside paragraphs and other bodies of text, you may additionally mark
+text for *italics* with "``*italics*``" or **bold** with
+"``**bold**``". This is called "inline markup".
+
+If you want something to appear as a fixed-space literal, use
+"````double back-quotes````". Note that no further fiddling is done
+inside the double back-quotes -- so asterisks "``*``" etc. are left
+alone.
+
+If you find that you want to use one of the "special" characters in
+text, it will generally be OK -- reStructuredText is pretty smart.
+For example, this lone asterisk * is handled just fine, as is the
+asterisk in this equation: 5*6=30. If you actually
+want text \*surrounded by asterisks* to **not** be italicised, then
+you need to indicate that the asterisk is not special. You do this by
+placing a backslash just before it, like so "``\*``" (quickref__), or
+by enclosing it in double back-quotes (inline literals), like this::
+
+ ``*``
+
+__ quickref.html#escaping
+
+.. Tip:: Think of inline markup as a form of (parentheses) and use it
+ the same way: immediately before and after the text being marked
+ up. Inline markup by itself (surrounded by whitespace) or in the
+ middle of a word won't be recognized. See the `markup spec`__ for
+ full details.
+
+__ ../../ref/rst/restructuredtext.html#inline-markup
+
+
+Lists
+-----
+
+Lists of items come in three main flavours: **enumerated**,
+**bulleted** and **definitions**. In all list cases, you may have as
+many paragraphs, sublists, etc. as you want, as long as the left-hand
+side of the paragraph or whatever aligns with the first line of text
+in the list item.
+
+Lists must always start a new paragraph -- that is, they must appear
+after a blank line.
+
+**enumerated** lists (numbers, letters or roman numerals; quickref__)
+ __ quickref.html#enumerated-lists
+
+ Start a line off with a number or letter followed by a period ".",
+ right bracket ")" or surrounded by brackets "( )" -- whatever you're
+ comfortable with. All of the following forms are recognised::
+
+ 1. numbers
+
+ A. upper-case letters
+ and it goes over many lines
+
+ with two paragraphs and all!
+
+ a. lower-case letters
+
+ 3. with a sub-list starting at a different number
+ 4. make sure the numbers are in the correct sequence though!
+
+ I. upper-case roman numerals
+
+ i. lower-case roman numerals
+
+ (1) numbers again
+
+ 1) and again
+
+ Results in (note: the different enumerated list styles are not
+ always supported by every web browser, so you may not get the full
+ effect here):
+
+ 1. numbers
+
+ A. upper-case letters
+ and it goes over many lines
+
+ with two paragraphs and all!
+
+ a. lower-case letters
+
+ 3. with a sub-list starting at a different number
+ 4. make sure the numbers are in the correct sequence though!
+
+ I. upper-case roman numerals
+
+ i. lower-case roman numerals
+
+ (1) numbers again
+
+ 1) and again
+
+**bulleted** lists (quickref__)
+ __ quickref.html#bullet-lists
+
+ Just like enumerated lists, start the line off with a bullet point
+ character - either "-", "+" or "*"::
+
+ * a bullet point using "*"
+
+ - a sub-list using "-"
+
+ + yet another sub-list
+
+ - another item
+
+ Results in:
+
+ * a bullet point using "*"
+
+ - a sub-list using "-"
+
+ + yet another sub-list
+
+ - another item
+
+**definition** lists (quickref__)
+ __ quickref.html#definition-lists
+
+ Unlike the other two, the definition lists consist of a term, and
+ the definition of that term. The format of a definition list is::
+
+ what
+ Definition lists associate a term with a definition.
+
+ *how*
+ The term is a one-line phrase, and the definition is one or more
+ paragraphs or body elements, indented relative to the term.
+ Blank lines are not allowed between term and definition.
+
+ Results in:
+
+ what
+ Definition lists associate a term with a definition.
+
+ *how*
+ The term is a one-line phrase, and the definition is one or more
+ paragraphs or body elements, indented relative to the term.
+ Blank lines are not allowed between term and definition.
+
+
+Preformatting (code samples)
+----------------------------
+(quickref__)
+
+__ quickref.html#literal-blocks
+
+To just include a chunk of preformatted, never-to-be-fiddled-with
+text, finish the prior paragraph with "``::``". The preformatted
+block is finished when the text falls back to the same indentation
+level as a paragraph prior to the preformatted block. For example::
+
+ An example::
+
+ Whitespace, newlines, blank lines, and all kinds of markup
+ (like *this* or \this) is preserved by literal blocks.
+ Lookie here, I've dropped an indentation level
+ (but not far enough)
+
+ no more example
+
+Results in:
+
+ An example::
+
+ Whitespace, newlines, blank lines, and all kinds of markup
+ (like *this* or \this) is preserved by literal blocks.
+ Lookie here, I've dropped an indentation level
+ (but not far enough)
+
+ no more example
+
+Note that if a paragraph consists only of "``::``", then it's removed
+from the output::
+
+ ::
+
+ This is preformatted text, and the
+ last "::" paragraph is removed
+
+Results in:
+
+::
+
+ This is preformatted text, and the
+ last "::" paragraph is removed
+
+
+Sections
+--------
+
+(quickref__)
+
+__ quickref.html#section-structure
+
+To break longer text up into sections, you use **section headers**.
+These are a single line of text (one or more words) with adornment: an
+underline alone, or an underline and an overline together, in dashes
+"``-----``", equals "``======``", tildes "``~~~~~~``" or any of the
+non-alphanumeric characters ``= - ` : ' " ~ ^ _ * + # < >`` that you
+feel comfortable with. An underline-only adornment is distinct from
+an overline-and-underline adornment using the same character. The
+underline/overline must be at least as long as the title text. Be
+consistent, since all sections marked with the same adornment style
+are deemed to be at the same level::
+
+ Chapter 1 Title
+ ===============
+
+ Section 1.1 Title
+ -----------------
+
+ Subsection 1.1.1 Title
+ ~~~~~~~~~~~~~~~~~~~~~~
+
+ Section 1.2 Title
+ -----------------
+
+ Chapter 2 Title
+ ===============
+
+This results in the following structure, illustrated by simplified
+pseudo-XML::
+
+ <section>
+ <title>
+ Chapter 1 Title
+ <section>
+ <title>
+ Section 1.1 Title
+ <section>
+ <title>
+ Subsection 1.1.1 Title
+ <section>
+ <title>
+ Section 1.2 Title
+ <section>
+ <title>
+ Chapter 2 Title
+
+(Pseudo-XML uses indentation for nesting and has no end-tags. It's
+not possible to show actual processed output, as in the other
+examples, because sections cannot exist inside block quotes. For a
+concrete example, compare the section structure of this document's
+source text and processed output.)
+
+Note that section headers are available as link targets, just using
+their name. To link to the Lists_ heading, I write "``Lists_``". If
+the heading has a space in it like `text styles`_, we need to quote
+the heading "```text styles`_``".
+
+
+Document Title / Subtitle
+`````````````````````````
+
+The title of the whole document is distinct from section titles and
+may be formatted somewhat differently (e.g. the HTML writer by default
+shows it as a centered heading).
+
+To indicate the document title in reStructuredText, use a unique adornment
+style at the beginning of the document. To indicate the document subtitle,
+use another unique adornment style immediately after the document title. For
+example::
+
+ ================
+ Document Title
+ ================
+ ----------
+ Subtitle
+ ----------
+
+ Section Title
+ =============
+
+ ...
+
+Note that "Document Title" and "Section Title" above both use equals
+signs, but are distict and unrelated styles. The text of
+overline-and-underlined titles (but not underlined-only) may be inset
+for aesthetics.
+
+
+Images
+------
+
+(quickref__)
+
+__ quickref.html#directives
+
+To include an image in your document, you use the the ``image`` directive__.
+For example::
+
+ .. image:: images/biohazard.png
+
+results in:
+
+.. image:: images/biohazard.png
+
+The ``images/biohazard.png`` part indicates the filename of the image
+you wish to appear in the document. There's no restriction placed on
+the image (format, size etc). If the image is to appear in HTML and
+you wish to supply additional information, you may::
+
+ .. image:: images/biohazard.png
+ :height: 100
+ :width: 200
+ :scale: 50
+ :alt: alternate text
+
+See the full `image directive documentation`__ for more info.
+
+__ ../../ref/rst/directives.html
+__ ../../ref/rst/directives.html#images
+
+
+What Next?
+----------
+
+This primer introduces the most common features of reStructuredText,
+but there are a lot more to explore. The `Quick reStructuredText`_
+user reference is a good place to go next. For complete details, the
+`reStructuredText Markup Specification`_ is the place to go [#]_.
+
+Users who have questions or need assistance with Docutils or
+reStructuredText should post a message to the Docutils-users_ mailing
+list.
+
+.. [#] If that relative link doesn't work, try the master document:
+ http://docutils.sourceforge.net/docs/ref/rst/restructuredtext.html.
+
+.. _reStructuredText Markup Specification:
+ ../../ref/rst/restructuredtext.html
+.. _Docutils-users: ../mailing-lists.html#docutils-users
+.. _Docutils project web site: http://docutils.sourceforge.net/
diff --git a/nikola/data/samplesite/stories/theming.meta b/nikola/data/samplesite/stories/theming.meta
new file mode 100644
index 0000000..db0832f
--- /dev/null
+++ b/nikola/data/samplesite/stories/theming.meta
@@ -0,0 +1,3 @@
+Theming Nikola
+theming
+2012/03/13 12:00
diff --git a/nikola/data/samplesite/stories/theming.txt b/nikola/data/samplesite/stories/theming.txt
new file mode 100644
index 0000000..339ecd4
--- /dev/null
+++ b/nikola/data/samplesite/stories/theming.txt
@@ -0,0 +1,236 @@
+Theming Nikola
+==============
+
+:Version: 2.1+svn
+:Author: Roberto Alsina <ralsina@netmanagers.com.ar>
+
+.. class:: alert alert-info pull-right
+
+.. contents::
+
+The Structure
+-------------
+
+Themes are located in the ``themes`` folder where Nikola is installed, one folder per theme.
+The folder name is the theme name.
+
+A Nikola theme consists of three folders:
+
+assets
+ This is where you would put your CSS, Javascript and image files. It will be copied
+ into ``output/assets`` when you build the site, and the templates will contain
+ references to them.
+
+ The included themes use `Bootstrap <http://twitter.github.com/bootstrap/>`_
+ and `Colorbox <http://www.jacklmoore.com/colorbox>`_ so they are in assets,
+ along with CSS files for syntax highligting and reStructuredText, and a
+ minified copy of jQuery.
+
+ If you want to base your theme on other frameworks (or on no framework at all)
+ just remember to put there everything you need for deployment.
+
+templates
+ This contains the templates used to generate the pages. While Nikola will use a
+ certain set of template names by default, you can add others for specific parts
+ of your site.
+
+messages
+ Nikola tries to be multilingual. This is where you put the strings for your theme
+ so that it can be translated into other languages.
+
+And these optional files:
+
+parent
+ A text file that, on its first line, contains the name of the **parent theme**.
+ Any resources missing on this theme, will be looked up in the parent theme
+ (and then in the grandparent, etc).
+
+ The ``parent`` is so you don't have to create a full theme each time: just create an
+ empty theme, set the parent, and add the bits you want modified.
+
+engine
+ A text file which, on the first line, contains the name of the template engine
+ this theme needs. Currently supported values are "mako" and "jinja".
+ If this file is not given, "mako" is assumed.
+
+bundles
+ A text file containing a list of files to be turned into bundles using WebAssets.
+ For example::
+
+ assets/css/all.css=bootstrap.css,bootstrap-responsive.css,rst.css,code.css,colorbox.css,custom.css
+
+ This creates a file called "assets/css/all.css" in your output that is the
+ combination of all the other file paths, relative to the output file.
+ This makes the page much more efficient because it avoids multiple connections to the server,
+ at the cost of some extra difficult debugging.
+
+ WebAssets supports bundling CSS and JS files.
+
+ Templates should use either the bundle or the individual files based on the ``use_bundles``
+ variable, which in turn is set by the ``USE_BUNDLES`` option.
+
+Creating a New Theme
+--------------------
+
+In your site's folder, create a ``themes`` folder. Choose a theme to start from, and
+create ``themes/yourthemename/parent`` as a file containing the parent theme's name.
+There, you just created a new theme. Of course it looks exactly like the other one,
+so let's customize it.
+
+Templates
+---------
+
+In templates there is a number of files whose name ends in ``.tmpl``. Those are the
+theme's page templates. They are done usig the `Mako <http://makotemplates.org>`_
+template language. If you want to do a theme, you should learn the Mako syntax first.
+
+Mako has a nifty concept of template inheritance. That means that, a
+template can inherit from another and only change small bits of the output. For example,
+``base.tmpl`` defines the whole layout for a page but has only a placeholder for content
+so ``post.tmpl`` only define the content, and the layout is inherited from ``base.tmpl``.
+
+These are the templates that come with the included themes:
+
+base.tmpl
+ This template defines the basic page layout for the site. It's mostly plain HTML
+ but defines a few blocks that can be re-defined by inheriting templates:
+
+ * ``extra_head`` is a block that is added before ``</head>``, (ex: for adding extra CSS)
+ * ``belowtitle`` is used by default to display a list of translations but you can put
+ anything there.
+ * ``content`` is where the inheriting templates will place the main content of the page.
+ * ``permalink`` is an absolute path to the page (ex: "/archive/index.html")
+
+ This template always receives the following variables you can use:
+
+ * ``lang`` is the laguage for this page.
+ * ``title`` is the page's title.
+ * ``description`` is the page's description.
+ * ``blog_title`` is the blog's title.
+ * ``blog_author`` is the blog's author.
+ * ``messages`` contains the theme's strings and translations.
+ * ``_link`` is an utility function to create links to other pages in the site.
+ It takes three arguments, kind, name, lang:
+
+ kind is one of:
+
+ * tag_index (name is ignored)
+ * tag (and name is the tag name)
+ * tag_rss (name is the tag name)
+ * archive (and name is the year, or None for the main archive index)
+ * index (name is the number in index-number)
+ * rss (name is ignored)
+ * gallery (name is the gallery name)
+
+ The returned value is always an absolute path, like "/archive/index.html".
+
+ * ``rel_link`` converts absolute paths to relative ones. You can use it with
+ ``_link`` and ``permalink`` to create relative links, which makes the site
+ able to work when moved inside the server. Example: ``rel_link(permalink, url)``
+
+ * Anything you put in your ``GLOBAL_CONTEXT`` option in ``dodo.py``. This
+ usually includes ``sidebar_links``, ``search_form``, and others.
+
+ The included themes use at least these:
+
+ * ``rss_link`` a link to custom RSS feed, although it may be empty)
+ * ``blog_url`` the URL for your site
+ * ``blog_title`` the name of your site
+ * ``content_footer`` things like copyright notices, disclaimers, etc.
+ * ``license`` a larger license badge
+ * ``analytics`` google scripts, or any JS you want to tack at the end of the body
+ of the page.
+ * ``disqus_forum``: a `Disqus <http://disqus.com>`_ ID you can use to enable comments.
+
+ It's probably a bad idea to do a theme that *requires* more than this (please put
+ a ``README`` in it saying what the user should add in its ``dodo.py``), but there is no
+ problem in requiring less.
+
+post.tmpl
+ Template used for blog posts. Can use everything ``base.tmpl`` uses, plus:
+
+ * ``post``: a Post object. This has a number of members:
+
+ * ``post.title(language)``: returns a localized title
+ * ``post.date``
+ * ``post.tags``: A list of tags
+ * ``post.text(language)``: the translated text of the post
+ * ``post.permalink(language, absolute)``: Link to the post in that language.
+ If ``absolute`` is ``True`` the link contains the full URL. This is useful
+ for things like Disqus comment forms.
+ * ``post.next_post`` is None or a Post object that is next newest in the timeline.
+ * ``post.prev_post`` is None or a Post object that is next oldest in the timeline.
+
+story.tmpl
+ Used for pages that are not part of a blog, usually a cleaner, less
+ intrusive layout than ``post.tmpl``, but same parameters.
+
+gallery.tmpl
+ Template used for image galleries. Can use everything ``base.tmpl`` uses, plus:
+
+ * ``text``: A descriptive text for the gallery.
+ * ``images``: A list of (thumbnail, image) paths.
+
+index.tmpl
+ Template used to render the multipost indexes. Can use everything ``base.tmpl`` uses, plus:
+
+ * ``posts``: a list of Post objects, as described above.
+ * ``prevlink``: a link to a previous page
+ * ``nextlink``: a link to the next page
+
+list.tmpl
+ Template used to display generic lists of links. Can use everything ``base.tmpl`` uses, plus:
+
+ * ``items``: a list of (text, link) elements.
+
+You can add other templates for specific pages, which the user can the use in his ``post_pages``
+option in ``dodo.py``. Also, keep in mind that your theme is yours, there is no reason why
+you would need to maintain the inheritance as it is, or not require whatever data you want.
+
+Messages and Translations
+-------------------------
+
+When you modify templates, you may want to add text in them (for example: "About Me").
+Instead of adding the text directly, which makes it impossible to translate to other
+languages, add it like this::
+
+ ${messages[lang]["About Me"]}
+
+Then, in ``messages/en.py`` add it along the other strings::
+
+ MESSAGES = [
+ u"Posts for year %s",
+ u"Archive",
+ u"Posts about %s:",
+ u"Tags",
+ u"Also available in: ",
+ u"More posts about",
+ u"Posted:",
+ u"Original site",
+ u"Read in english",
+ u"About Me",
+ ]
+
+Then, when I want to use your theme in spanish, all I have to do is add a line in ``messages/es.py``::
+
+ MESSAGES = {
+ u"LANGUAGE": u"Español",
+ u"Posts for year %s": u"Posts del año %s",
+ u"Archive": u"Archivo",
+ u"Posts about %s:": u"Posts sobre %s",
+ u"Tags": u"Tags",
+ u"Also available in: ": u"También disponible en: ",
+ u"More posts about": u"Más posts sobre",
+ u"Posted:": u"Publicado:",
+ u"Original site": u"Sitio original",
+ u"Read in english": u"Leer en español",
+ u"About Me": u"Acerca del autor",
+ }
+
+And voilá, your theme works in spanish. Don't remove strings from these files even if it seems
+your theme is not using them. Some are used internally in Nikola to generate titles and
+similar things.
+
+To create a new translation, just copy one of the existing ones, translate the right side of
+every string to your language, save it and send it to me, I will add it to Nikola!
+
diff --git a/nikola/data/themes/default/assets/css/bootstrap-responsive.css b/nikola/data/themes/default/assets/css/bootstrap-responsive.css
new file mode 100644
index 0000000..daafa91
--- /dev/null
+++ b/nikola/data/themes/default/assets/css/bootstrap-responsive.css
@@ -0,0 +1,1040 @@
+/*!
+ * Bootstrap Responsive v2.1.0
+ *
+ * Copyright 2012 Twitter, Inc
+ * Licensed under the Apache License v2.0
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Designed and built with all the love in the world @twitter by @mdo and @fat.
+ */
+
+.clearfix {
+ *zoom: 1;
+}
+
+.clearfix:before,
+.clearfix:after {
+ display: table;
+ line-height: 0;
+ content: "";
+}
+
+.clearfix:after {
+ clear: both;
+}
+
+.hide-text {
+ font: 0/0 a;
+ color: transparent;
+ text-shadow: none;
+ background-color: transparent;
+ border: 0;
+}
+
+.input-block-level {
+ display: block;
+ width: 100%;
+ min-height: 30px;
+ -webkit-box-sizing: border-box;
+ -moz-box-sizing: border-box;
+ box-sizing: border-box;
+}
+
+.hidden {
+ display: none;
+ visibility: hidden;
+}
+
+.visible-phone {
+ display: none !important;
+}
+
+.visible-tablet {
+ display: none !important;
+}
+
+.hidden-desktop {
+ display: none !important;
+}
+
+.visible-desktop {
+ display: inherit !important;
+}
+
+@media (min-width: 768px) and (max-width: 979px) {
+ .hidden-desktop {
+ display: inherit !important;
+ }
+ .visible-desktop {
+ display: none !important ;
+ }
+ .visible-tablet {
+ display: inherit !important;
+ }
+ .hidden-tablet {
+ display: none !important;
+ }
+}
+
+@media (max-width: 767px) {
+ .hidden-desktop {
+ display: inherit !important;
+ }
+ .visible-desktop {
+ display: none !important;
+ }
+ .visible-phone {
+ display: inherit !important;
+ }
+ .hidden-phone {
+ display: none !important;
+ }
+}
+
+@media (min-width: 1200px) {
+ .row {
+ margin-left: -30px;
+ *zoom: 1;
+ }
+ .row:before,
+ .row:after {
+ display: table;
+ line-height: 0;
+ content: "";
+ }
+ .row:after {
+ clear: both;
+ }
+ [class*="span"] {
+ float: left;
+ margin-left: 30px;
+ }
+ .container,
+ .navbar-static-top .container,
+ .navbar-fixed-top .container,
+ .navbar-fixed-bottom .container {
+ width: 1170px;
+ }
+ .span12 {
+ width: 1170px;
+ }
+ .span11 {
+ width: 1070px;
+ }
+ .span10 {
+ width: 970px;
+ }
+ .span9 {
+ width: 870px;
+ }
+ .span8 {
+ width: 770px;
+ }
+ .span7 {
+ width: 670px;
+ }
+ .span6 {
+ width: 570px;
+ }
+ .span5 {
+ width: 470px;
+ }
+ .span4 {
+ width: 370px;
+ }
+ .span3 {
+ width: 270px;
+ }
+ .span2 {
+ width: 170px;
+ }
+ .span1 {
+ width: 70px;
+ }
+ .offset12 {
+ margin-left: 1230px;
+ }
+ .offset11 {
+ margin-left: 1130px;
+ }
+ .offset10 {
+ margin-left: 1030px;
+ }
+ .offset9 {
+ margin-left: 930px;
+ }
+ .offset8 {
+ margin-left: 830px;
+ }
+ .offset7 {
+ margin-left: 730px;
+ }
+ .offset6 {
+ margin-left: 630px;
+ }
+ .offset5 {
+ margin-left: 530px;
+ }
+ .offset4 {
+ margin-left: 430px;
+ }
+ .offset3 {
+ margin-left: 330px;
+ }
+ .offset2 {
+ margin-left: 230px;
+ }
+ .offset1 {
+ margin-left: 130px;
+ }
+ .row-fluid {
+ width: 100%;
+ *zoom: 1;
+ }
+ .row-fluid:before,
+ .row-fluid:after {
+ display: table;
+ line-height: 0;
+ content: "";
+ }
+ .row-fluid:after {
+ clear: both;
+ }
+ .row-fluid [class*="span"] {
+ display: block;
+ float: left;
+ width: 100%;
+ min-height: 30px;
+ margin-left: 2.564102564102564%;
+ *margin-left: 2.5109110747408616%;
+ -webkit-box-sizing: border-box;
+ -moz-box-sizing: border-box;
+ box-sizing: border-box;
+ }
+ .row-fluid [class*="span"]:first-child {
+ margin-left: 0;
+ }
+ .row-fluid .span12 {
+ width: 100%;
+ *width: 99.94680851063829%;
+ }
+ .row-fluid .span11 {
+ width: 91.45299145299145%;
+ *width: 91.39979996362975%;
+ }
+ .row-fluid .span10 {
+ width: 82.90598290598291%;
+ *width: 82.8527914166212%;
+ }
+ .row-fluid .span9 {
+ width: 74.35897435897436%;
+ *width: 74.30578286961266%;
+ }
+ .row-fluid .span8 {
+ width: 65.81196581196582%;
+ *width: 65.75877432260411%;
+ }
+ .row-fluid .span7 {
+ width: 57.26495726495726%;
+ *width: 57.21176577559556%;
+ }
+ .row-fluid .span6 {
+ width: 48.717948717948715%;
+ *width: 48.664757228587014%;
+ }
+ .row-fluid .span5 {
+ width: 40.17094017094017%;
+ *width: 40.11774868157847%;
+ }
+ .row-fluid .span4 {
+ width: 31.623931623931625%;
+ *width: 31.570740134569924%;
+ }
+ .row-fluid .span3 {
+ width: 23.076923076923077%;
+ *width: 23.023731587561375%;
+ }
+ .row-fluid .span2 {
+ width: 14.52991452991453%;
+ *width: 14.476723040552828%;
+ }
+ .row-fluid .span1 {
+ width: 5.982905982905983%;
+ *width: 5.929714493544281%;
+ }
+ .row-fluid .offset12 {
+ margin-left: 105.12820512820512%;
+ *margin-left: 105.02182214948171%;
+ }
+ .row-fluid .offset12:first-child {
+ margin-left: 102.56410256410257%;
+ *margin-left: 102.45771958537915%;
+ }
+ .row-fluid .offset11 {
+ margin-left: 96.58119658119658%;
+ *margin-left: 96.47481360247316%;
+ }
+ .row-fluid .offset11:first-child {
+ margin-left: 94.01709401709402%;
+ *margin-left: 93.91071103837061%;
+ }
+ .row-fluid .offset10 {
+ margin-left: 88.03418803418803%;
+ *margin-left: 87.92780505546462%;
+ }
+ .row-fluid .offset10:first-child {
+ margin-left: 85.47008547008548%;
+ *margin-left: 85.36370249136206%;
+ }
+ .row-fluid .offset9 {
+ margin-left: 79.48717948717949%;
+ *margin-left: 79.38079650845607%;
+ }
+ .row-fluid .offset9:first-child {
+ margin-left: 76.92307692307693%;
+ *margin-left: 76.81669394435352%;
+ }
+ .row-fluid .offset8 {
+ margin-left: 70.94017094017094%;
+ *margin-left: 70.83378796144753%;
+ }
+ .row-fluid .offset8:first-child {
+ margin-left: 68.37606837606839%;
+ *margin-left: 68.26968539734497%;
+ }
+ .row-fluid .offset7 {
+ margin-left: 62.393162393162385%;
+ *margin-left: 62.28677941443899%;
+ }
+ .row-fluid .offset7:first-child {
+ margin-left: 59.82905982905982%;
+ *margin-left: 59.72267685033642%;
+ }
+ .row-fluid .offset6 {
+ margin-left: 53.84615384615384%;
+ *margin-left: 53.739770867430444%;
+ }
+ .row-fluid .offset6:first-child {
+ margin-left: 51.28205128205128%;
+ *margin-left: 51.175668303327875%;
+ }
+ .row-fluid .offset5 {
+ margin-left: 45.299145299145295%;
+ *margin-left: 45.1927623204219%;
+ }
+ .row-fluid .offset5:first-child {
+ margin-left: 42.73504273504273%;
+ *margin-left: 42.62865975631933%;
+ }
+ .row-fluid .offset4 {
+ margin-left: 36.75213675213675%;
+ *margin-left: 36.645753773413354%;
+ }
+ .row-fluid .offset4:first-child {
+ margin-left: 34.18803418803419%;
+ *margin-left: 34.081651209310785%;
+ }
+ .row-fluid .offset3 {
+ margin-left: 28.205128205128204%;
+ *margin-left: 28.0987452264048%;
+ }
+ .row-fluid .offset3:first-child {
+ margin-left: 25.641025641025642%;
+ *margin-left: 25.53464266230224%;
+ }
+ .row-fluid .offset2 {
+ margin-left: 19.65811965811966%;
+ *margin-left: 19.551736679396257%;
+ }
+ .row-fluid .offset2:first-child {
+ margin-left: 17.094017094017094%;
+ *margin-left: 16.98763411529369%;
+ }
+ .row-fluid .offset1 {
+ margin-left: 11.11111111111111%;
+ *margin-left: 11.004728132387708%;
+ }
+ .row-fluid .offset1:first-child {
+ margin-left: 8.547008547008547%;
+ *margin-left: 8.440625568285142%;
+ }
+ input,
+ textarea,
+ .uneditable-input {
+ margin-left: 0;
+ }
+ .controls-row [class*="span"] + [class*="span"] {
+ margin-left: 30px;
+ }
+ input.span12,
+ textarea.span12,
+ .uneditable-input.span12 {
+ width: 1156px;
+ }
+ input.span11,
+ textarea.span11,
+ .uneditable-input.span11 {
+ width: 1056px;
+ }
+ input.span10,
+ textarea.span10,
+ .uneditable-input.span10 {
+ width: 956px;
+ }
+ input.span9,
+ textarea.span9,
+ .uneditable-input.span9 {
+ width: 856px;
+ }
+ input.span8,
+ textarea.span8,
+ .uneditable-input.span8 {
+ width: 756px;
+ }
+ input.span7,
+ textarea.span7,
+ .uneditable-input.span7 {
+ width: 656px;
+ }
+ input.span6,
+ textarea.span6,
+ .uneditable-input.span6 {
+ width: 556px;
+ }
+ input.span5,
+ textarea.span5,
+ .uneditable-input.span5 {
+ width: 456px;
+ }
+ input.span4,
+ textarea.span4,
+ .uneditable-input.span4 {
+ width: 356px;
+ }
+ input.span3,
+ textarea.span3,
+ .uneditable-input.span3 {
+ width: 256px;
+ }
+ input.span2,
+ textarea.span2,
+ .uneditable-input.span2 {
+ width: 156px;
+ }
+ input.span1,
+ textarea.span1,
+ .uneditable-input.span1 {
+ width: 56px;
+ }
+ .thumbnails {
+ margin-left: -30px;
+ }
+ .thumbnails > li {
+ margin-left: 30px;
+ }
+ .row-fluid .thumbnails {
+ margin-left: 0;
+ }
+}
+
+@media (min-width: 768px) and (max-width: 979px) {
+ .row {
+ margin-left: -20px;
+ *zoom: 1;
+ }
+ .row:before,
+ .row:after {
+ display: table;
+ line-height: 0;
+ content: "";
+ }
+ .row:after {
+ clear: both;
+ }
+ [class*="span"] {
+ float: left;
+ margin-left: 20px;
+ }
+ .container,
+ .navbar-static-top .container,
+ .navbar-fixed-top .container,
+ .navbar-fixed-bottom .container {
+ width: 724px;
+ }
+ .span12 {
+ width: 724px;
+ }
+ .span11 {
+ width: 662px;
+ }
+ .span10 {
+ width: 600px;
+ }
+ .span9 {
+ width: 538px;
+ }
+ .span8 {
+ width: 476px;
+ }
+ .span7 {
+ width: 414px;
+ }
+ .span6 {
+ width: 352px;
+ }
+ .span5 {
+ width: 290px;
+ }
+ .span4 {
+ width: 228px;
+ }
+ .span3 {
+ width: 166px;
+ }
+ .span2 {
+ width: 104px;
+ }
+ .span1 {
+ width: 42px;
+ }
+ .offset12 {
+ margin-left: 764px;
+ }
+ .offset11 {
+ margin-left: 702px;
+ }
+ .offset10 {
+ margin-left: 640px;
+ }
+ .offset9 {
+ margin-left: 578px;
+ }
+ .offset8 {
+ margin-left: 516px;
+ }
+ .offset7 {
+ margin-left: 454px;
+ }
+ .offset6 {
+ margin-left: 392px;
+ }
+ .offset5 {
+ margin-left: 330px;
+ }
+ .offset4 {
+ margin-left: 268px;
+ }
+ .offset3 {
+ margin-left: 206px;
+ }
+ .offset2 {
+ margin-left: 144px;
+ }
+ .offset1 {
+ margin-left: 82px;
+ }
+ .row-fluid {
+ width: 100%;
+ *zoom: 1;
+ }
+ .row-fluid:before,
+ .row-fluid:after {
+ display: table;
+ line-height: 0;
+ content: "";
+ }
+ .row-fluid:after {
+ clear: both;
+ }
+ .row-fluid [class*="span"] {
+ display: block;
+ float: left;
+ width: 100%;
+ min-height: 30px;
+ margin-left: 2.7624309392265194%;
+ *margin-left: 2.709239449864817%;
+ -webkit-box-sizing: border-box;
+ -moz-box-sizing: border-box;
+ box-sizing: border-box;
+ }
+ .row-fluid [class*="span"]:first-child {
+ margin-left: 0;
+ }
+ .row-fluid .span12 {
+ width: 100%;
+ *width: 99.94680851063829%;
+ }
+ .row-fluid .span11 {
+ width: 91.43646408839778%;
+ *width: 91.38327259903608%;
+ }
+ .row-fluid .span10 {
+ width: 82.87292817679558%;
+ *width: 82.81973668743387%;
+ }
+ .row-fluid .span9 {
+ width: 74.30939226519337%;
+ *width: 74.25620077583166%;
+ }
+ .row-fluid .span8 {
+ width: 65.74585635359117%;
+ *width: 65.69266486422946%;
+ }
+ .row-fluid .span7 {
+ width: 57.18232044198895%;
+ *width: 57.12912895262725%;
+ }
+ .row-fluid .span6 {
+ width: 48.61878453038674%;
+ *width: 48.56559304102504%;
+ }
+ .row-fluid .span5 {
+ width: 40.05524861878453%;
+ *width: 40.00205712942283%;
+ }
+ .row-fluid .span4 {
+ width: 31.491712707182323%;
+ *width: 31.43852121782062%;
+ }
+ .row-fluid .span3 {
+ width: 22.92817679558011%;
+ *width: 22.87498530621841%;
+ }
+ .row-fluid .span2 {
+ width: 14.3646408839779%;
+ *width: 14.311449394616199%;
+ }
+ .row-fluid .span1 {
+ width: 5.801104972375691%;
+ *width: 5.747913483013988%;
+ }
+ .row-fluid .offset12 {
+ margin-left: 105.52486187845304%;
+ *margin-left: 105.41847889972962%;
+ }
+ .row-fluid .offset12:first-child {
+ margin-left: 102.76243093922652%;
+ *margin-left: 102.6560479605031%;
+ }
+ .row-fluid .offset11 {
+ margin-left: 96.96132596685082%;
+ *margin-left: 96.8549429881274%;
+ }
+ .row-fluid .offset11:first-child {
+ margin-left: 94.1988950276243%;
+ *margin-left: 94.09251204890089%;
+ }
+ .row-fluid .offset10 {
+ margin-left: 88.39779005524862%;
+ *margin-left: 88.2914070765252%;
+ }
+ .row-fluid .offset10:first-child {
+ margin-left: 85.6353591160221%;
+ *margin-left: 85.52897613729868%;
+ }
+ .row-fluid .offset9 {
+ margin-left: 79.8342541436464%;
+ *margin-left: 79.72787116492299%;
+ }
+ .row-fluid .offset9:first-child {
+ margin-left: 77.07182320441989%;
+ *margin-left: 76.96544022569647%;
+ }
+ .row-fluid .offset8 {
+ margin-left: 71.2707182320442%;
+ *margin-left: 71.16433525332079%;
+ }
+ .row-fluid .offset8:first-child {
+ margin-left: 68.50828729281768%;
+ *margin-left: 68.40190431409427%;
+ }
+ .row-fluid .offset7 {
+ margin-left: 62.70718232044199%;
+ *margin-left: 62.600799341718584%;
+ }
+ .row-fluid .offset7:first-child {
+ margin-left: 59.94475138121547%;
+ *margin-left: 59.838368402492065%;
+ }
+ .row-fluid .offset6 {
+ margin-left: 54.14364640883978%;
+ *margin-left: 54.037263430116376%;
+ }
+ .row-fluid .offset6:first-child {
+ margin-left: 51.38121546961326%;
+ *margin-left: 51.27483249088986%;
+ }
+ .row-fluid .offset5 {
+ margin-left: 45.58011049723757%;
+ *margin-left: 45.47372751851417%;
+ }
+ .row-fluid .offset5:first-child {
+ margin-left: 42.81767955801105%;
+ *margin-left: 42.71129657928765%;
+ }
+ .row-fluid .offset4 {
+ margin-left: 37.01657458563536%;
+ *margin-left: 36.91019160691196%;
+ }
+ .row-fluid .offset4:first-child {
+ margin-left: 34.25414364640884%;
+ *margin-left: 34.14776066768544%;
+ }
+ .row-fluid .offset3 {
+ margin-left: 28.45303867403315%;
+ *margin-left: 28.346655695309746%;
+ }
+ .row-fluid .offset3:first-child {
+ margin-left: 25.69060773480663%;
+ *margin-left: 25.584224756083227%;
+ }
+ .row-fluid .offset2 {
+ margin-left: 19.88950276243094%;
+ *margin-left: 19.783119783707537%;
+ }
+ .row-fluid .offset2:first-child {
+ margin-left: 17.12707182320442%;
+ *margin-left: 17.02068884448102%;
+ }
+ .row-fluid .offset1 {
+ margin-left: 11.32596685082873%;
+ *margin-left: 11.219583872105325%;
+ }
+ .row-fluid .offset1:first-child {
+ margin-left: 8.56353591160221%;
+ *margin-left: 8.457152932878806%;
+ }
+ input,
+ textarea,
+ .uneditable-input {
+ margin-left: 0;
+ }
+ .controls-row [class*="span"] + [class*="span"] {
+ margin-left: 20px;
+ }
+ input.span12,
+ textarea.span12,
+ .uneditable-input.span12 {
+ width: 710px;
+ }
+ input.span11,
+ textarea.span11,
+ .uneditable-input.span11 {
+ width: 648px;
+ }
+ input.span10,
+ textarea.span10,
+ .uneditable-input.span10 {
+ width: 586px;
+ }
+ input.span9,
+ textarea.span9,
+ .uneditable-input.span9 {
+ width: 524px;
+ }
+ input.span8,
+ textarea.span8,
+ .uneditable-input.span8 {
+ width: 462px;
+ }
+ input.span7,
+ textarea.span7,
+ .uneditable-input.span7 {
+ width: 400px;
+ }
+ input.span6,
+ textarea.span6,
+ .uneditable-input.span6 {
+ width: 338px;
+ }
+ input.span5,
+ textarea.span5,
+ .uneditable-input.span5 {
+ width: 276px;
+ }
+ input.span4,
+ textarea.span4,
+ .uneditable-input.span4 {
+ width: 214px;
+ }
+ input.span3,
+ textarea.span3,
+ .uneditable-input.span3 {
+ width: 152px;
+ }
+ input.span2,
+ textarea.span2,
+ .uneditable-input.span2 {
+ width: 90px;
+ }
+ input.span1,
+ textarea.span1,
+ .uneditable-input.span1 {
+ width: 28px;
+ }
+}
+
+@media (max-width: 767px) {
+ body {
+ padding-right: 20px;
+ padding-left: 20px;
+ }
+ .navbar-fixed-top,
+ .navbar-fixed-bottom {
+ margin-right: -20px;
+ margin-left: -20px;
+ }
+ .container-fluid {
+ padding: 0;
+ }
+ .dl-horizontal dt {
+ float: none;
+ width: auto;
+ clear: none;
+ text-align: left;
+ }
+ .dl-horizontal dd {
+ margin-left: 0;
+ }
+ .container {
+ width: auto;
+ }
+ .row-fluid {
+ width: 100%;
+ }
+ .row,
+ .thumbnails {
+ margin-left: 0;
+ }
+ .thumbnails > li {
+ float: none;
+ margin-left: 0;
+ }
+ [class*="span"],
+ .row-fluid [class*="span"] {
+ display: block;
+ float: none;
+ width: auto;
+ margin-left: 0;
+ }
+ .span12,
+ .row-fluid .span12 {
+ width: 100%;
+ -webkit-box-sizing: border-box;
+ -moz-box-sizing: border-box;
+ box-sizing: border-box;
+ }
+ .input-large,
+ .input-xlarge,
+ .input-xxlarge,
+ input[class*="span"],
+ select[class*="span"],
+ textarea[class*="span"],
+ .uneditable-input {
+ display: block;
+ width: 100%;
+ min-height: 30px;
+ -webkit-box-sizing: border-box;
+ -moz-box-sizing: border-box;
+ box-sizing: border-box;
+ }
+ .input-prepend input,
+ .input-append input,
+ .input-prepend input[class*="span"],
+ .input-append input[class*="span"] {
+ display: inline-block;
+ width: auto;
+ }
+ .modal {
+ position: fixed;
+ top: 20px;
+ right: 20px;
+ left: 20px;
+ width: auto;
+ margin: 0;
+ }
+ .modal.fade.in {
+ top: auto;
+ }
+}
+
+@media (max-width: 480px) {
+ .nav-collapse {
+ -webkit-transform: translate3d(0, 0, 0);
+ }
+ .page-header h1 small {
+ display: block;
+ line-height: 20px;
+ }
+ input[type="checkbox"],
+ input[type="radio"] {
+ border: 1px solid #ccc;
+ }
+ .form-horizontal .control-group > label {
+ float: none;
+ width: auto;
+ padding-top: 0;
+ text-align: left;
+ }
+ .form-horizontal .controls {
+ margin-left: 0;
+ }
+ .form-horizontal .control-list {
+ padding-top: 0;
+ }
+ .form-horizontal .form-actions {
+ padding-right: 10px;
+ padding-left: 10px;
+ }
+ .modal {
+ top: 10px;
+ right: 10px;
+ left: 10px;
+ }
+ .modal-header .close {
+ padding: 10px;
+ margin: -10px;
+ }
+ .carousel-caption {
+ position: static;
+ }
+}
+
+@media (max-width: 979px) {
+ body {
+ padding-top: 0;
+ }
+ .navbar-fixed-top,
+ .navbar-fixed-bottom {
+ position: static;
+ }
+ .navbar-fixed-top {
+ margin-bottom: 20px;
+ }
+ .navbar-fixed-bottom {
+ margin-top: 20px;
+ }
+ .navbar-fixed-top .navbar-inner,
+ .navbar-fixed-bottom .navbar-inner {
+ padding: 5px;
+ }
+ .navbar .container {
+ width: auto;
+ padding: 0;
+ }
+ .navbar .brand {
+ padding-right: 10px;
+ padding-left: 10px;
+ margin: 0 0 0 -5px;
+ }
+ .nav-collapse {
+ clear: both;
+ }
+ .nav-collapse .nav {
+ float: none;
+ margin: 0 0 10px;
+ }
+ .nav-collapse .nav > li {
+ float: none;
+ }
+ .nav-collapse .nav > li > a {
+ margin-bottom: 2px;
+ }
+ .nav-collapse .nav > .divider-vertical {
+ display: none;
+ }
+ .nav-collapse .nav .nav-header {
+ color: #555555;
+ text-shadow: none;
+ }
+ .nav-collapse .nav > li > a,
+ .nav-collapse .dropdown-menu a {
+ padding: 9px 15px;
+ font-weight: bold;
+ color: #555555;
+ -webkit-border-radius: 3px;
+ -moz-border-radius: 3px;
+ border-radius: 3px;
+ }
+ .nav-collapse .btn {
+ padding: 4px 10px 4px;
+ font-weight: normal;
+ -webkit-border-radius: 4px;
+ -moz-border-radius: 4px;
+ border-radius: 4px;
+ }
+ .nav-collapse .dropdown-menu li + li a {
+ margin-bottom: 2px;
+ }
+ .nav-collapse .nav > li > a:hover,
+ .nav-collapse .dropdown-menu a:hover {
+ background-color: #f2f2f2;
+ }
+ .navbar-inverse .nav-collapse .nav > li > a:hover,
+ .navbar-inverse .nav-collapse .dropdown-menu a:hover {
+ background-color: #111111;
+ }
+ .nav-collapse.in .btn-group {
+ padding: 0;
+ margin-top: 5px;
+ }
+ .nav-collapse .dropdown-menu {
+ position: static;
+ top: auto;
+ left: auto;
+ display: block;
+ float: none;
+ max-width: none;
+ padding: 0;
+ margin: 0 15px;
+ background-color: transparent;
+ border: none;
+ -webkit-border-radius: 0;
+ -moz-border-radius: 0;
+ border-radius: 0;
+ -webkit-box-shadow: none;
+ -moz-box-shadow: none;
+ box-shadow: none;
+ }
+ .nav-collapse .dropdown-menu:before,
+ .nav-collapse .dropdown-menu:after {
+ display: none;
+ }
+ .nav-collapse .dropdown-menu .divider {
+ display: none;
+ }
+ .nav-collapse .navbar-form,
+ .nav-collapse .navbar-search {
+ float: none;
+ padding: 10px 15px;
+ margin: 10px 0;
+ border-top: 1px solid #f2f2f2;
+ border-bottom: 1px solid #f2f2f2;
+ -webkit-box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.1), 0 1px 0 rgba(255, 255, 255, 0.1);
+ -moz-box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.1), 0 1px 0 rgba(255, 255, 255, 0.1);
+ box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.1), 0 1px 0 rgba(255, 255, 255, 0.1);
+ }
+ .navbar .nav-collapse .nav.pull-right {
+ float: none;
+ margin-left: 0;
+ }
+ .nav-collapse,
+ .nav-collapse.collapse {
+ height: 0;
+ overflow: hidden;
+ }
+ .navbar .btn-navbar {
+ display: block;
+ }
+ .navbar-static .navbar-inner {
+ padding-right: 10px;
+ padding-left: 10px;
+ }
+}
+
+@media (min-width: 980px) {
+ .nav-collapse.collapse {
+ height: auto !important;
+ overflow: visible !important;
+ }
+}
diff --git a/nikola/data/themes/default/assets/css/bootstrap.css b/nikola/data/themes/default/assets/css/bootstrap.css
new file mode 100644
index 0000000..0664207
--- /dev/null
+++ b/nikola/data/themes/default/assets/css/bootstrap.css
@@ -0,0 +1,5624 @@
+/*!
+ * Bootstrap v2.1.0
+ *
+ * Copyright 2012 Twitter, Inc
+ * Licensed under the Apache License v2.0
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Designed and built with all the love in the world @twitter by @mdo and @fat.
+ */
+
+article,
+aside,
+details,
+figcaption,
+figure,
+footer,
+header,
+hgroup,
+nav,
+section {
+ display: block;
+}
+
+audio,
+canvas,
+video {
+ display: inline-block;
+ *display: inline;
+ *zoom: 1;
+}
+
+audio:not([controls]) {
+ display: none;
+}
+
+html {
+ font-size: 100%;
+ -webkit-text-size-adjust: 100%;
+ -ms-text-size-adjust: 100%;
+}
+
+a:focus {
+ outline: thin dotted #333;
+ outline: 5px auto -webkit-focus-ring-color;
+ outline-offset: -2px;
+}
+
+a:hover,
+a:active {
+ outline: 0;
+}
+
+sub,
+sup {
+ position: relative;
+ font-size: 75%;
+ line-height: 0;
+ vertical-align: baseline;
+}
+
+sup {
+ top: -0.5em;
+}
+
+sub {
+ bottom: -0.25em;
+}
+
+img {
+ height: auto;
+ max-width: 100%;
+ vertical-align: middle;
+ border: 0;
+ -ms-interpolation-mode: bicubic;
+}
+
+#map_canvas img {
+ max-width: none;
+}
+
+button,
+input,
+select,
+textarea {
+ margin: 0;
+ font-size: 100%;
+ vertical-align: middle;
+}
+
+button,
+input {
+ *overflow: visible;
+ line-height: normal;
+}
+
+button::-moz-focus-inner,
+input::-moz-focus-inner {
+ padding: 0;
+ border: 0;
+}
+
+button,
+input[type="button"],
+input[type="reset"],
+input[type="submit"] {
+ cursor: pointer;
+ -webkit-appearance: button;
+}
+
+input[type="search"] {
+ -webkit-box-sizing: content-box;
+ -moz-box-sizing: content-box;
+ box-sizing: content-box;
+ -webkit-appearance: textfield;
+}
+
+input[type="search"]::-webkit-search-decoration,
+input[type="search"]::-webkit-search-cancel-button {
+ -webkit-appearance: none;
+}
+
+textarea {
+ overflow: auto;
+ vertical-align: top;
+}
+
+.clearfix {
+ *zoom: 1;
+}
+
+.clearfix:before,
+.clearfix:after {
+ display: table;
+ line-height: 0;
+ content: "";
+}
+
+.clearfix:after {
+ clear: both;
+}
+
+.hide-text {
+ font: 0/0 a;
+ color: transparent;
+ text-shadow: none;
+ background-color: transparent;
+ border: 0;
+}
+
+.input-block-level {
+ display: block;
+ width: 100%;
+ min-height: 30px;
+ -webkit-box-sizing: border-box;
+ -moz-box-sizing: border-box;
+ box-sizing: border-box;
+}
+
+body {
+ margin: 0;
+ font-family: "Helvetica Neue", Helvetica, Arial, sans-serif;
+ font-size: 14px;
+ line-height: 20px;
+ color: #333333;
+ background-color: #ffffff;
+}
+
+a {
+ color: #0088cc;
+ text-decoration: none;
+}
+
+a:hover {
+ color: #005580;
+ text-decoration: underline;
+}
+
+.img-rounded {
+ -webkit-border-radius: 6px;
+ -moz-border-radius: 6px;
+ border-radius: 6px;
+}
+
+.img-polaroid {
+ padding: 4px;
+ background-color: #fff;
+ border: 1px solid #ccc;
+ border: 1px solid rgba(0, 0, 0, 0.2);
+ -webkit-box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1);
+ -moz-box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1);
+ box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1);
+}
+
+.img-circle {
+ -webkit-border-radius: 500px;
+ -moz-border-radius: 500px;
+ border-radius: 500px;
+}
+
+.row {
+ margin-left: -20px;
+ *zoom: 1;
+}
+
+.row:before,
+.row:after {
+ display: table;
+ line-height: 0;
+ content: "";
+}
+
+.row:after {
+ clear: both;
+}
+
+[class*="span"] {
+ float: left;
+ margin-left: 20px;
+}
+
+.container,
+.navbar-static-top .container,
+.navbar-fixed-top .container,
+.navbar-fixed-bottom .container {
+ width: 940px;
+}
+
+.span12 {
+ width: 940px;
+}
+
+.span11 {
+ width: 860px;
+}
+
+.span10 {
+ width: 780px;
+}
+
+.span9 {
+ width: 700px;
+}
+
+.span8 {
+ width: 620px;
+}
+
+.span7 {
+ width: 540px;
+}
+
+.span6 {
+ width: 460px;
+}
+
+.span5 {
+ width: 380px;
+}
+
+.span4 {
+ width: 300px;
+}
+
+.span3 {
+ width: 220px;
+}
+
+.span2 {
+ width: 140px;
+}
+
+.span1 {
+ width: 60px;
+}
+
+.offset12 {
+ margin-left: 980px;
+}
+
+.offset11 {
+ margin-left: 900px;
+}
+
+.offset10 {
+ margin-left: 820px;
+}
+
+.offset9 {
+ margin-left: 740px;
+}
+
+.offset8 {
+ margin-left: 660px;
+}
+
+.offset7 {
+ margin-left: 580px;
+}
+
+.offset6 {
+ margin-left: 500px;
+}
+
+.offset5 {
+ margin-left: 420px;
+}
+
+.offset4 {
+ margin-left: 340px;
+}
+
+.offset3 {
+ margin-left: 260px;
+}
+
+.offset2 {
+ margin-left: 180px;
+}
+
+.offset1 {
+ margin-left: 100px;
+}
+
+.row-fluid {
+ width: 100%;
+ *zoom: 1;
+}
+
+.row-fluid:before,
+.row-fluid:after {
+ display: table;
+ line-height: 0;
+ content: "";
+}
+
+.row-fluid:after {
+ clear: both;
+}
+
+.row-fluid [class*="span"] {
+ display: block;
+ float: left;
+ width: 100%;
+ min-height: 30px;
+ margin-left: 2.127659574468085%;
+ *margin-left: 2.074468085106383%;
+ -webkit-box-sizing: border-box;
+ -moz-box-sizing: border-box;
+ box-sizing: border-box;
+}
+
+.row-fluid [class*="span"]:first-child {
+ margin-left: 0;
+}
+
+.row-fluid .span12 {
+ width: 100%;
+ *width: 99.94680851063829%;
+}
+
+.row-fluid .span11 {
+ width: 91.48936170212765%;
+ *width: 91.43617021276594%;
+}
+
+.row-fluid .span10 {
+ width: 82.97872340425532%;
+ *width: 82.92553191489361%;
+}
+
+.row-fluid .span9 {
+ width: 74.46808510638297%;
+ *width: 74.41489361702126%;
+}
+
+.row-fluid .span8 {
+ width: 65.95744680851064%;
+ *width: 65.90425531914893%;
+}
+
+.row-fluid .span7 {
+ width: 57.44680851063829%;
+ *width: 57.39361702127659%;
+}
+
+.row-fluid .span6 {
+ width: 48.93617021276595%;
+ *width: 48.88297872340425%;
+}
+
+.row-fluid .span5 {
+ width: 40.42553191489362%;
+ *width: 40.37234042553192%;
+}
+
+.row-fluid .span4 {
+ width: 31.914893617021278%;
+ *width: 31.861702127659576%;
+}
+
+.row-fluid .span3 {
+ width: 23.404255319148934%;
+ *width: 23.351063829787233%;
+}
+
+.row-fluid .span2 {
+ width: 14.893617021276595%;
+ *width: 14.840425531914894%;
+}
+
+.row-fluid .span1 {
+ width: 6.382978723404255%;
+ *width: 6.329787234042553%;
+}
+
+.row-fluid .offset12 {
+ margin-left: 104.25531914893617%;
+ *margin-left: 104.14893617021275%;
+}
+
+.row-fluid .offset12:first-child {
+ margin-left: 102.12765957446808%;
+ *margin-left: 102.02127659574467%;
+}
+
+.row-fluid .offset11 {
+ margin-left: 95.74468085106382%;
+ *margin-left: 95.6382978723404%;
+}
+
+.row-fluid .offset11:first-child {
+ margin-left: 93.61702127659574%;
+ *margin-left: 93.51063829787232%;
+}
+
+.row-fluid .offset10 {
+ margin-left: 87.23404255319149%;
+ *margin-left: 87.12765957446807%;
+}
+
+.row-fluid .offset10:first-child {
+ margin-left: 85.1063829787234%;
+ *margin-left: 84.99999999999999%;
+}
+
+.row-fluid .offset9 {
+ margin-left: 78.72340425531914%;
+ *margin-left: 78.61702127659572%;
+}
+
+.row-fluid .offset9:first-child {
+ margin-left: 76.59574468085106%;
+ *margin-left: 76.48936170212764%;
+}
+
+.row-fluid .offset8 {
+ margin-left: 70.2127659574468%;
+ *margin-left: 70.10638297872339%;
+}
+
+.row-fluid .offset8:first-child {
+ margin-left: 68.08510638297872%;
+ *margin-left: 67.9787234042553%;
+}
+
+.row-fluid .offset7 {
+ margin-left: 61.70212765957446%;
+ *margin-left: 61.59574468085106%;
+}
+
+.row-fluid .offset7:first-child {
+ margin-left: 59.574468085106375%;
+ *margin-left: 59.46808510638297%;
+}
+
+.row-fluid .offset6 {
+ margin-left: 53.191489361702125%;
+ *margin-left: 53.085106382978715%;
+}
+
+.row-fluid .offset6:first-child {
+ margin-left: 51.063829787234035%;
+ *margin-left: 50.95744680851063%;
+}
+
+.row-fluid .offset5 {
+ margin-left: 44.68085106382979%;
+ *margin-left: 44.57446808510638%;
+}
+
+.row-fluid .offset5:first-child {
+ margin-left: 42.5531914893617%;
+ *margin-left: 42.4468085106383%;
+}
+
+.row-fluid .offset4 {
+ margin-left: 36.170212765957444%;
+ *margin-left: 36.06382978723405%;
+}
+
+.row-fluid .offset4:first-child {
+ margin-left: 34.04255319148936%;
+ *margin-left: 33.93617021276596%;
+}
+
+.row-fluid .offset3 {
+ margin-left: 27.659574468085104%;
+ *margin-left: 27.5531914893617%;
+}
+
+.row-fluid .offset3:first-child {
+ margin-left: 25.53191489361702%;
+ *margin-left: 25.425531914893618%;
+}
+
+.row-fluid .offset2 {
+ margin-left: 19.148936170212764%;
+ *margin-left: 19.04255319148936%;
+}
+
+.row-fluid .offset2:first-child {
+ margin-left: 17.02127659574468%;
+ *margin-left: 16.914893617021278%;
+}
+
+.row-fluid .offset1 {
+ margin-left: 10.638297872340425%;
+ *margin-left: 10.53191489361702%;
+}
+
+.row-fluid .offset1:first-child {
+ margin-left: 8.51063829787234%;
+ *margin-left: 8.404255319148938%;
+}
+
+[class*="span"].hide,
+.row-fluid [class*="span"].hide {
+ display: none;
+}
+
+[class*="span"].pull-right,
+.row-fluid [class*="span"].pull-right {
+ float: right;
+}
+
+.container {
+ margin-right: auto;
+ margin-left: auto;
+ *zoom: 1;
+}
+
+.container:before,
+.container:after {
+ display: table;
+ line-height: 0;
+ content: "";
+}
+
+.container:after {
+ clear: both;
+}
+
+.container-fluid {
+ padding-right: 20px;
+ padding-left: 20px;
+ *zoom: 1;
+}
+
+.container-fluid:before,
+.container-fluid:after {
+ display: table;
+ line-height: 0;
+ content: "";
+}
+
+.container-fluid:after {
+ clear: both;
+}
+
+p {
+ margin: 0 0 10px;
+}
+
+.lead {
+ margin-bottom: 20px;
+ font-size: 20px;
+ font-weight: 200;
+ line-height: 30px;
+}
+
+small {
+ font-size: 85%;
+}
+
+strong {
+ font-weight: bold;
+}
+
+em {
+ font-style: italic;
+}
+
+cite {
+ font-style: normal;
+}
+
+.muted {
+ color: #999999;
+}
+
+h1,
+h2,
+h3,
+h4,
+h5,
+h6 {
+ margin: 10px 0;
+ font-family: inherit;
+ font-weight: bold;
+ line-height: 1;
+ color: inherit;
+ text-rendering: optimizelegibility;
+}
+
+h1 small,
+h2 small,
+h3 small,
+h4 small,
+h5 small,
+h6 small {
+ font-weight: normal;
+ line-height: 1;
+ color: #999999;
+}
+
+h1 {
+ font-size: 36px;
+ line-height: 40px;
+}
+
+h2 {
+ font-size: 30px;
+ line-height: 40px;
+}
+
+h3 {
+ font-size: 24px;
+ line-height: 40px;
+}
+
+h4 {
+ font-size: 18px;
+ line-height: 20px;
+}
+
+h5 {
+ font-size: 14px;
+ line-height: 20px;
+}
+
+h6 {
+ font-size: 12px;
+ line-height: 20px;
+}
+
+h1 small {
+ font-size: 24px;
+}
+
+h2 small {
+ font-size: 18px;
+}
+
+h3 small {
+ font-size: 14px;
+}
+
+h4 small {
+ font-size: 14px;
+}
+
+.page-header {
+ padding-bottom: 9px;
+ margin: 20px 0 30px;
+ border-bottom: 1px solid #eeeeee;
+}
+
+ul,
+ol {
+ padding: 0;
+ margin: 0 0 10px 25px;
+}
+
+ul ul,
+ul ol,
+ol ol,
+ol ul {
+ margin-bottom: 0;
+}
+
+li {
+ line-height: 20px;
+}
+
+ul.unstyled,
+ol.unstyled {
+ margin-left: 0;
+ list-style: none;
+}
+
+dl {
+ margin-bottom: 20px;
+}
+
+dt,
+dd {
+ line-height: 20px;
+}
+
+dt {
+ font-weight: bold;
+}
+
+dd {
+ margin-left: 10px;
+}
+
+.dl-horizontal dt {
+ float: left;
+ width: 120px;
+ overflow: hidden;
+ clear: left;
+ text-align: right;
+ text-overflow: ellipsis;
+ white-space: nowrap;
+}
+
+.dl-horizontal dd {
+ margin-left: 130px;
+}
+
+hr {
+ margin: 20px 0;
+ border: 0;
+ border-top: 1px solid #eeeeee;
+ border-bottom: 1px solid #ffffff;
+}
+
+abbr[title] {
+ cursor: help;
+ border-bottom: 1px dotted #999999;
+}
+
+abbr.initialism {
+ font-size: 90%;
+ text-transform: uppercase;
+}
+
+blockquote {
+ padding: 0 0 0 15px;
+ margin: 0 0 20px;
+ border-left: 5px solid #eeeeee;
+}
+
+blockquote p {
+ margin-bottom: 0;
+ font-size: 16px;
+ font-weight: 300;
+ line-height: 25px;
+}
+
+blockquote small {
+ display: block;
+ line-height: 20px;
+ color: #999999;
+}
+
+blockquote small:before {
+ content: '\2014 \00A0';
+}
+
+blockquote.pull-right {
+ float: right;
+ padding-right: 15px;
+ padding-left: 0;
+ border-right: 5px solid #eeeeee;
+ border-left: 0;
+}
+
+blockquote.pull-right p,
+blockquote.pull-right small {
+ text-align: right;
+}
+
+blockquote.pull-right small:before {
+ content: '';
+}
+
+blockquote.pull-right small:after {
+ content: '\00A0 \2014';
+}
+
+q:before,
+q:after,
+blockquote:before,
+blockquote:after {
+ content: "";
+}
+
+address {
+ display: block;
+ margin-bottom: 20px;
+ font-style: normal;
+ line-height: 20px;
+}
+
+code,
+pre {
+ padding: 0 3px 2px;
+ font-family: Monaco, Menlo, Consolas, "Courier New", monospace;
+ font-size: 12px;
+ color: #333333;
+ -webkit-border-radius: 3px;
+ -moz-border-radius: 3px;
+ border-radius: 3px;
+}
+
+code {
+ padding: 2px 4px;
+ color: #d14;
+ background-color: #f7f7f9;
+ border: 1px solid #e1e1e8;
+}
+
+pre {
+ display: block;
+ padding: 9.5px;
+ margin: 0 0 10px;
+ font-size: 13px;
+ line-height: 20px;
+ word-break: break-all;
+ word-wrap: break-word;
+ white-space: pre;
+ white-space: pre-wrap;
+ background-color: #f5f5f5;
+ border: 1px solid #ccc;
+ border: 1px solid rgba(0, 0, 0, 0.15);
+ -webkit-border-radius: 4px;
+ -moz-border-radius: 4px;
+ border-radius: 4px;
+}
+
+pre.prettyprint {
+ margin-bottom: 20px;
+}
+
+pre code {
+ padding: 0;
+ color: inherit;
+ background-color: transparent;
+ border: 0;
+}
+
+.pre-scrollable {
+ max-height: 340px;
+ overflow-y: scroll;
+}
+
+form {
+ margin: 0 0 20px;
+}
+
+fieldset {
+ padding: 0;
+ margin: 0;
+ border: 0;
+}
+
+legend {
+ display: block;
+ width: 100%;
+ padding: 0;
+ margin-bottom: 20px;
+ font-size: 21px;
+ line-height: 40px;
+ color: #333333;
+ border: 0;
+ border-bottom: 1px solid #e5e5e5;
+}
+
+legend small {
+ font-size: 15px;
+ color: #999999;
+}
+
+label,
+input,
+button,
+select,
+textarea {
+ font-size: 14px;
+ font-weight: normal;
+ line-height: 20px;
+}
+
+input,
+button,
+select,
+textarea {
+ font-family: "Helvetica Neue", Helvetica, Arial, sans-serif;
+}
+
+label {
+ display: block;
+ margin-bottom: 5px;
+}
+
+select,
+textarea,
+input[type="text"],
+input[type="password"],
+input[type="datetime"],
+input[type="datetime-local"],
+input[type="date"],
+input[type="month"],
+input[type="time"],
+input[type="week"],
+input[type="number"],
+input[type="email"],
+input[type="url"],
+input[type="search"],
+input[type="tel"],
+input[type="color"],
+.uneditable-input {
+ display: inline-block;
+ height: 20px;
+ padding: 4px 6px;
+ margin-bottom: 9px;
+ font-size: 14px;
+ line-height: 20px;
+ color: #555555;
+ -webkit-border-radius: 3px;
+ -moz-border-radius: 3px;
+ border-radius: 3px;
+}
+
+input,
+textarea {
+ width: 210px;
+}
+
+textarea {
+ height: auto;
+}
+
+textarea,
+input[type="text"],
+input[type="password"],
+input[type="datetime"],
+input[type="datetime-local"],
+input[type="date"],
+input[type="month"],
+input[type="time"],
+input[type="week"],
+input[type="number"],
+input[type="email"],
+input[type="url"],
+input[type="search"],
+input[type="tel"],
+input[type="color"],
+.uneditable-input {
+ background-color: #ffffff;
+ border: 1px solid #cccccc;
+ -webkit-box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075);
+ -moz-box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075);
+ box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075);
+ -webkit-transition: border linear 0.2s, box-shadow linear 0.2s;
+ -moz-transition: border linear 0.2s, box-shadow linear 0.2s;
+ -o-transition: border linear 0.2s, box-shadow linear 0.2s;
+ transition: border linear 0.2s, box-shadow linear 0.2s;
+}
+
+textarea:focus,
+input[type="text"]:focus,
+input[type="password"]:focus,
+input[type="datetime"]:focus,
+input[type="datetime-local"]:focus,
+input[type="date"]:focus,
+input[type="month"]:focus,
+input[type="time"]:focus,
+input[type="week"]:focus,
+input[type="number"]:focus,
+input[type="email"]:focus,
+input[type="url"]:focus,
+input[type="search"]:focus,
+input[type="tel"]:focus,
+input[type="color"]:focus,
+.uneditable-input:focus {
+ border-color: rgba(82, 168, 236, 0.8);
+ outline: 0;
+ outline: thin dotted \9;
+ /* IE6-9 */
+
+ -webkit-box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075), 0 0 8px rgba(82, 168, 236, 0.6);
+ -moz-box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075), 0 0 8px rgba(82, 168, 236, 0.6);
+ box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075), 0 0 8px rgba(82, 168, 236, 0.6);
+}
+
+input[type="radio"],
+input[type="checkbox"] {
+ margin: 4px 0 0;
+ margin-top: 1px \9;
+ *margin-top: 0;
+ line-height: normal;
+ cursor: pointer;
+}
+
+input[type="file"],
+input[type="image"],
+input[type="submit"],
+input[type="reset"],
+input[type="button"],
+input[type="radio"],
+input[type="checkbox"] {
+ width: auto;
+}
+
+select,
+input[type="file"] {
+ height: 30px;
+ /* In IE7, the height of the select element cannot be changed by height, only font-size */
+
+ *margin-top: 4px;
+ /* For IE7, add top margin to align select with labels */
+
+ line-height: 30px;
+}
+
+select {
+ width: 220px;
+ background-color: #ffffff;
+ border: 1px solid #bbb;
+}
+
+select[multiple],
+select[size] {
+ height: auto;
+}
+
+select:focus,
+input[type="file"]:focus,
+input[type="radio"]:focus,
+input[type="checkbox"]:focus {
+ outline: thin dotted #333;
+ outline: 5px auto -webkit-focus-ring-color;
+ outline-offset: -2px;
+}
+
+.uneditable-input,
+.uneditable-textarea {
+ color: #999999;
+ cursor: not-allowed;
+ background-color: #fcfcfc;
+ border-color: #cccccc;
+ -webkit-box-shadow: inset 0 1px 2px rgba(0, 0, 0, 0.025);
+ -moz-box-shadow: inset 0 1px 2px rgba(0, 0, 0, 0.025);
+ box-shadow: inset 0 1px 2px rgba(0, 0, 0, 0.025);
+}
+
+.uneditable-input {
+ overflow: hidden;
+ white-space: nowrap;
+}
+
+.uneditable-textarea {
+ width: auto;
+ height: auto;
+}
+
+input:-moz-placeholder,
+textarea:-moz-placeholder {
+ color: #999999;
+}
+
+input:-ms-input-placeholder,
+textarea:-ms-input-placeholder {
+ color: #999999;
+}
+
+input::-webkit-input-placeholder,
+textarea::-webkit-input-placeholder {
+ color: #999999;
+}
+
+.radio,
+.checkbox {
+ min-height: 18px;
+ padding-left: 18px;
+}
+
+.radio input[type="radio"],
+.checkbox input[type="checkbox"] {
+ float: left;
+ margin-left: -18px;
+}
+
+.controls > .radio:first-child,
+.controls > .checkbox:first-child {
+ padding-top: 5px;
+}
+
+.radio.inline,
+.checkbox.inline {
+ display: inline-block;
+ padding-top: 5px;
+ margin-bottom: 0;
+ vertical-align: middle;
+}
+
+.radio.inline + .radio.inline,
+.checkbox.inline + .checkbox.inline {
+ margin-left: 10px;
+}
+
+.input-mini {
+ width: 60px;
+}
+
+.input-small {
+ width: 90px;
+}
+
+.input-medium {
+ width: 150px;
+}
+
+.input-large {
+ width: 210px;
+}
+
+.input-xlarge {
+ width: 270px;
+}
+
+.input-xxlarge {
+ width: 530px;
+}
+
+input[class*="span"],
+select[class*="span"],
+textarea[class*="span"],
+.uneditable-input[class*="span"],
+.row-fluid input[class*="span"],
+.row-fluid select[class*="span"],
+.row-fluid textarea[class*="span"],
+.row-fluid .uneditable-input[class*="span"] {
+ float: none;
+ margin-left: 0;
+}
+
+.input-append input[class*="span"],
+.input-append .uneditable-input[class*="span"],
+.input-prepend input[class*="span"],
+.input-prepend .uneditable-input[class*="span"],
+.row-fluid input[class*="span"],
+.row-fluid select[class*="span"],
+.row-fluid textarea[class*="span"],
+.row-fluid .uneditable-input[class*="span"],
+.row-fluid .input-prepend [class*="span"],
+.row-fluid .input-append [class*="span"] {
+ display: inline-block;
+}
+
+input,
+textarea,
+.uneditable-input {
+ margin-left: 0;
+}
+
+.controls-row [class*="span"] + [class*="span"] {
+ margin-left: 20px;
+}
+
+input.span12,
+textarea.span12,
+.uneditable-input.span12 {
+ width: 926px;
+}
+
+input.span11,
+textarea.span11,
+.uneditable-input.span11 {
+ width: 846px;
+}
+
+input.span10,
+textarea.span10,
+.uneditable-input.span10 {
+ width: 766px;
+}
+
+input.span9,
+textarea.span9,
+.uneditable-input.span9 {
+ width: 686px;
+}
+
+input.span8,
+textarea.span8,
+.uneditable-input.span8 {
+ width: 606px;
+}
+
+input.span7,
+textarea.span7,
+.uneditable-input.span7 {
+ width: 526px;
+}
+
+input.span6,
+textarea.span6,
+.uneditable-input.span6 {
+ width: 446px;
+}
+
+input.span5,
+textarea.span5,
+.uneditable-input.span5 {
+ width: 366px;
+}
+
+input.span4,
+textarea.span4,
+.uneditable-input.span4 {
+ width: 286px;
+}
+
+input.span3,
+textarea.span3,
+.uneditable-input.span3 {
+ width: 206px;
+}
+
+input.span2,
+textarea.span2,
+.uneditable-input.span2 {
+ width: 126px;
+}
+
+input.span1,
+textarea.span1,
+.uneditable-input.span1 {
+ width: 46px;
+}
+
+.controls-row {
+ *zoom: 1;
+}
+
+.controls-row:before,
+.controls-row:after {
+ display: table;
+ line-height: 0;
+ content: "";
+}
+
+.controls-row:after {
+ clear: both;
+}
+
+.controls-row [class*="span"] {
+ float: left;
+}
+
+input[disabled],
+select[disabled],
+textarea[disabled],
+input[readonly],
+select[readonly],
+textarea[readonly] {
+ cursor: not-allowed;
+ background-color: #eeeeee;
+}
+
+input[type="radio"][disabled],
+input[type="checkbox"][disabled],
+input[type="radio"][readonly],
+input[type="checkbox"][readonly] {
+ background-color: transparent;
+}
+
+.control-group.warning > label,
+.control-group.warning .help-block,
+.control-group.warning .help-inline {
+ color: #c09853;
+}
+
+.control-group.warning .checkbox,
+.control-group.warning .radio,
+.control-group.warning input,
+.control-group.warning select,
+.control-group.warning textarea {
+ color: #c09853;
+ border-color: #c09853;
+ -webkit-box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075);
+ -moz-box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075);
+ box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075);
+}
+
+.control-group.warning .checkbox:focus,
+.control-group.warning .radio:focus,
+.control-group.warning input:focus,
+.control-group.warning select:focus,
+.control-group.warning textarea:focus {
+ border-color: #a47e3c;
+ -webkit-box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075), 0 0 6px #dbc59e;
+ -moz-box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075), 0 0 6px #dbc59e;
+ box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075), 0 0 6px #dbc59e;
+}
+
+.control-group.warning .input-prepend .add-on,
+.control-group.warning .input-append .add-on {
+ color: #c09853;
+ background-color: #fcf8e3;
+ border-color: #c09853;
+}
+
+.control-group.error > label,
+.control-group.error .help-block,
+.control-group.error .help-inline {
+ color: #b94a48;
+}
+
+.control-group.error .checkbox,
+.control-group.error .radio,
+.control-group.error input,
+.control-group.error select,
+.control-group.error textarea {
+ color: #b94a48;
+ border-color: #b94a48;
+ -webkit-box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075);
+ -moz-box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075);
+ box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075);
+}
+
+.control-group.error .checkbox:focus,
+.control-group.error .radio:focus,
+.control-group.error input:focus,
+.control-group.error select:focus,
+.control-group.error textarea:focus {
+ border-color: #953b39;
+ -webkit-box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075), 0 0 6px #d59392;
+ -moz-box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075), 0 0 6px #d59392;
+ box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075), 0 0 6px #d59392;
+}
+
+.control-group.error .input-prepend .add-on,
+.control-group.error .input-append .add-on {
+ color: #b94a48;
+ background-color: #f2dede;
+ border-color: #b94a48;
+}
+
+.control-group.success > label,
+.control-group.success .help-block,
+.control-group.success .help-inline {
+ color: #468847;
+}
+
+.control-group.success .checkbox,
+.control-group.success .radio,
+.control-group.success input,
+.control-group.success select,
+.control-group.success textarea {
+ color: #468847;
+ border-color: #468847;
+ -webkit-box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075);
+ -moz-box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075);
+ box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075);
+}
+
+.control-group.success .checkbox:focus,
+.control-group.success .radio:focus,
+.control-group.success input:focus,
+.control-group.success select:focus,
+.control-group.success textarea:focus {
+ border-color: #356635;
+ -webkit-box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075), 0 0 6px #7aba7b;
+ -moz-box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075), 0 0 6px #7aba7b;
+ box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075), 0 0 6px #7aba7b;
+}
+
+.control-group.success .input-prepend .add-on,
+.control-group.success .input-append .add-on {
+ color: #468847;
+ background-color: #dff0d8;
+ border-color: #468847;
+}
+
+input:focus:required:invalid,
+textarea:focus:required:invalid,
+select:focus:required:invalid {
+ color: #b94a48;
+ border-color: #ee5f5b;
+}
+
+input:focus:required:invalid:focus,
+textarea:focus:required:invalid:focus,
+select:focus:required:invalid:focus {
+ border-color: #e9322d;
+ -webkit-box-shadow: 0 0 6px #f8b9b7;
+ -moz-box-shadow: 0 0 6px #f8b9b7;
+ box-shadow: 0 0 6px #f8b9b7;
+}
+
+.form-actions {
+ padding: 19px 20px 20px;
+ margin-top: 20px;
+ margin-bottom: 20px;
+ background-color: #f5f5f5;
+ border-top: 1px solid #e5e5e5;
+ *zoom: 1;
+}
+
+.form-actions:before,
+.form-actions:after {
+ display: table;
+ line-height: 0;
+ content: "";
+}
+
+.form-actions:after {
+ clear: both;
+}
+
+.help-block,
+.help-inline {
+ color: #595959;
+}
+
+.help-block {
+ display: block;
+ margin-bottom: 10px;
+}
+
+.help-inline {
+ display: inline-block;
+ *display: inline;
+ padding-left: 5px;
+ vertical-align: middle;
+ *zoom: 1;
+}
+
+.input-append,
+.input-prepend {
+ margin-bottom: 5px;
+ font-size: 0;
+ white-space: nowrap;
+}
+
+.input-append input,
+.input-prepend input,
+.input-append select,
+.input-prepend select,
+.input-append .uneditable-input,
+.input-prepend .uneditable-input {
+ position: relative;
+ margin-bottom: 0;
+ *margin-left: 0;
+ font-size: 14px;
+ vertical-align: top;
+ -webkit-border-radius: 0 3px 3px 0;
+ -moz-border-radius: 0 3px 3px 0;
+ border-radius: 0 3px 3px 0;
+}
+
+.input-append input:focus,
+.input-prepend input:focus,
+.input-append select:focus,
+.input-prepend select:focus,
+.input-append .uneditable-input:focus,
+.input-prepend .uneditable-input:focus {
+ z-index: 2;
+}
+
+.input-append .add-on,
+.input-prepend .add-on {
+ display: inline-block;
+ width: auto;
+ height: 20px;
+ min-width: 16px;
+ padding: 4px 5px;
+ font-size: 14px;
+ font-weight: normal;
+ line-height: 20px;
+ text-align: center;
+ text-shadow: 0 1px 0 #ffffff;
+ background-color: #eeeeee;
+ border: 1px solid #ccc;
+}
+
+.input-append .add-on,
+.input-prepend .add-on,
+.input-append .btn,
+.input-prepend .btn {
+ margin-left: -1px;
+ vertical-align: top;
+ -webkit-border-radius: 0;
+ -moz-border-radius: 0;
+ border-radius: 0;
+}
+
+.input-append .active,
+.input-prepend .active {
+ background-color: #a9dba9;
+ border-color: #46a546;
+}
+
+.input-prepend .add-on,
+.input-prepend .btn {
+ margin-right: -1px;
+}
+
+.input-prepend .add-on:first-child,
+.input-prepend .btn:first-child {
+ -webkit-border-radius: 3px 0 0 3px;
+ -moz-border-radius: 3px 0 0 3px;
+ border-radius: 3px 0 0 3px;
+}
+
+.input-append input,
+.input-append select,
+.input-append .uneditable-input {
+ -webkit-border-radius: 3px 0 0 3px;
+ -moz-border-radius: 3px 0 0 3px;
+ border-radius: 3px 0 0 3px;
+}
+
+.input-append .add-on:last-child,
+.input-append .btn:last-child {
+ -webkit-border-radius: 0 3px 3px 0;
+ -moz-border-radius: 0 3px 3px 0;
+ border-radius: 0 3px 3px 0;
+}
+
+.input-prepend.input-append input,
+.input-prepend.input-append select,
+.input-prepend.input-append .uneditable-input {
+ -webkit-border-radius: 0;
+ -moz-border-radius: 0;
+ border-radius: 0;
+}
+
+.input-prepend.input-append .add-on:first-child,
+.input-prepend.input-append .btn:first-child {
+ margin-right: -1px;
+ -webkit-border-radius: 3px 0 0 3px;
+ -moz-border-radius: 3px 0 0 3px;
+ border-radius: 3px 0 0 3px;
+}
+
+.input-prepend.input-append .add-on:last-child,
+.input-prepend.input-append .btn:last-child {
+ margin-left: -1px;
+ -webkit-border-radius: 0 3px 3px 0;
+ -moz-border-radius: 0 3px 3px 0;
+ border-radius: 0 3px 3px 0;
+}
+
+input.search-query {
+ padding-right: 14px;
+ padding-right: 4px \9;
+ padding-left: 14px;
+ padding-left: 4px \9;
+ /* IE7-8 doesn't have border-radius, so don't indent the padding */
+
+ margin-bottom: 0;
+ -webkit-border-radius: 15px;
+ -moz-border-radius: 15px;
+ border-radius: 15px;
+}
+
+/* Allow for input prepend/append in search forms */
+
+.form-search .input-append .search-query,
+.form-search .input-prepend .search-query {
+ -webkit-border-radius: 0;
+ -moz-border-radius: 0;
+ border-radius: 0;
+}
+
+.form-search .input-append .search-query {
+ -webkit-border-radius: 14px 0 0 14px;
+ -moz-border-radius: 14px 0 0 14px;
+ border-radius: 14px 0 0 14px;
+}
+
+.form-search .input-append .btn {
+ -webkit-border-radius: 0 14px 14px 0;
+ -moz-border-radius: 0 14px 14px 0;
+ border-radius: 0 14px 14px 0;
+}
+
+.form-search .input-prepend .search-query {
+ -webkit-border-radius: 0 14px 14px 0;
+ -moz-border-radius: 0 14px 14px 0;
+ border-radius: 0 14px 14px 0;
+}
+
+.form-search .input-prepend .btn {
+ -webkit-border-radius: 14px 0 0 14px;
+ -moz-border-radius: 14px 0 0 14px;
+ border-radius: 14px 0 0 14px;
+}
+
+.form-search input,
+.form-inline input,
+.form-horizontal input,
+.form-search textarea,
+.form-inline textarea,
+.form-horizontal textarea,
+.form-search select,
+.form-inline select,
+.form-horizontal select,
+.form-search .help-inline,
+.form-inline .help-inline,
+.form-horizontal .help-inline,
+.form-search .uneditable-input,
+.form-inline .uneditable-input,
+.form-horizontal .uneditable-input,
+.form-search .input-prepend,
+.form-inline .input-prepend,
+.form-horizontal .input-prepend,
+.form-search .input-append,
+.form-inline .input-append,
+.form-horizontal .input-append {
+ display: inline-block;
+ *display: inline;
+ margin-bottom: 0;
+ vertical-align: middle;
+ *zoom: 1;
+}
+
+.form-search .hide,
+.form-inline .hide,
+.form-horizontal .hide {
+ display: none;
+}
+
+.form-search label,
+.form-inline label,
+.form-search .btn-group,
+.form-inline .btn-group {
+ display: inline-block;
+}
+
+.form-search .input-append,
+.form-inline .input-append,
+.form-search .input-prepend,
+.form-inline .input-prepend {
+ margin-bottom: 0;
+}
+
+.form-search .radio,
+.form-search .checkbox,
+.form-inline .radio,
+.form-inline .checkbox {
+ padding-left: 0;
+ margin-bottom: 0;
+ vertical-align: middle;
+}
+
+.form-search .radio input[type="radio"],
+.form-search .checkbox input[type="checkbox"],
+.form-inline .radio input[type="radio"],
+.form-inline .checkbox input[type="checkbox"] {
+ float: left;
+ margin-right: 3px;
+ margin-left: 0;
+}
+
+.control-group {
+ margin-bottom: 10px;
+}
+
+legend + .control-group {
+ margin-top: 20px;
+ -webkit-margin-top-collapse: separate;
+}
+
+.form-horizontal .control-group {
+ margin-bottom: 20px;
+ *zoom: 1;
+}
+
+.form-horizontal .control-group:before,
+.form-horizontal .control-group:after {
+ display: table;
+ line-height: 0;
+ content: "";
+}
+
+.form-horizontal .control-group:after {
+ clear: both;
+}
+
+.form-horizontal .control-label {
+ float: left;
+ width: 140px;
+ padding-top: 5px;
+ text-align: right;
+}
+
+.form-horizontal .controls {
+ *display: inline-block;
+ *padding-left: 20px;
+ margin-left: 160px;
+ *margin-left: 0;
+}
+
+.form-horizontal .controls:first-child {
+ *padding-left: 160px;
+}
+
+.form-horizontal .help-block {
+ margin-top: 10px;
+ margin-bottom: 0;
+}
+
+.form-horizontal .form-actions {
+ padding-left: 160px;
+}
+
+table {
+ max-width: 100%;
+ background-color: transparent;
+ border-collapse: collapse;
+ border-spacing: 0;
+}
+
+.table {
+ width: 100%;
+ margin-bottom: 20px;
+}
+
+.table th,
+.table td {
+ padding: 8px;
+ line-height: 20px;
+ text-align: left;
+ vertical-align: top;
+ border-top: 1px solid #dddddd;
+}
+
+.table th {
+ font-weight: bold;
+}
+
+.table thead th {
+ vertical-align: bottom;
+}
+
+.table caption + thead tr:first-child th,
+.table caption + thead tr:first-child td,
+.table colgroup + thead tr:first-child th,
+.table colgroup + thead tr:first-child td,
+.table thead:first-child tr:first-child th,
+.table thead:first-child tr:first-child td {
+ border-top: 0;
+}
+
+.table tbody + tbody {
+ border-top: 2px solid #dddddd;
+}
+
+.table-condensed th,
+.table-condensed td {
+ padding: 4px 5px;
+}
+
+.table-bordered {
+ border: 1px solid #dddddd;
+ border-collapse: separate;
+ *border-collapse: collapse;
+ border-left: 0;
+ -webkit-border-radius: 4px;
+ -moz-border-radius: 4px;
+ border-radius: 4px;
+}
+
+.table-bordered th,
+.table-bordered td {
+ border-left: 1px solid #dddddd;
+}
+
+.table-bordered caption + thead tr:first-child th,
+.table-bordered caption + tbody tr:first-child th,
+.table-bordered caption + tbody tr:first-child td,
+.table-bordered colgroup + thead tr:first-child th,
+.table-bordered colgroup + tbody tr:first-child th,
+.table-bordered colgroup + tbody tr:first-child td,
+.table-bordered thead:first-child tr:first-child th,
+.table-bordered tbody:first-child tr:first-child th,
+.table-bordered tbody:first-child tr:first-child td {
+ border-top: 0;
+}
+
+.table-bordered thead:first-child tr:first-child th:first-child,
+.table-bordered tbody:first-child tr:first-child td:first-child {
+ -webkit-border-top-left-radius: 4px;
+ border-top-left-radius: 4px;
+ -moz-border-radius-topleft: 4px;
+}
+
+.table-bordered thead:first-child tr:first-child th:last-child,
+.table-bordered tbody:first-child tr:first-child td:last-child {
+ -webkit-border-top-right-radius: 4px;
+ border-top-right-radius: 4px;
+ -moz-border-radius-topright: 4px;
+}
+
+.table-bordered thead:last-child tr:last-child th:first-child,
+.table-bordered tbody:last-child tr:last-child td:first-child,
+.table-bordered tfoot:last-child tr:last-child td:first-child {
+ -webkit-border-radius: 0 0 0 4px;
+ -moz-border-radius: 0 0 0 4px;
+ border-radius: 0 0 0 4px;
+ -webkit-border-bottom-left-radius: 4px;
+ border-bottom-left-radius: 4px;
+ -moz-border-radius-bottomleft: 4px;
+}
+
+.table-bordered thead:last-child tr:last-child th:last-child,
+.table-bordered tbody:last-child tr:last-child td:last-child,
+.table-bordered tfoot:last-child tr:last-child td:last-child {
+ -webkit-border-bottom-right-radius: 4px;
+ border-bottom-right-radius: 4px;
+ -moz-border-radius-bottomright: 4px;
+}
+
+.table-bordered caption + thead tr:first-child th:first-child,
+.table-bordered caption + tbody tr:first-child td:first-child,
+.table-bordered colgroup + thead tr:first-child th:first-child,
+.table-bordered colgroup + tbody tr:first-child td:first-child {
+ -webkit-border-top-left-radius: 4px;
+ border-top-left-radius: 4px;
+ -moz-border-radius-topleft: 4px;
+}
+
+.table-bordered caption + thead tr:first-child th:last-child,
+.table-bordered caption + tbody tr:first-child td:last-child,
+.table-bordered colgroup + thead tr:first-child th:last-child,
+.table-bordered colgroup + tbody tr:first-child td:last-child {
+ -webkit-border-top-right-radius: 4px;
+ border-top-right-radius: 4px;
+ -moz-border-right-topleft: 4px;
+}
+
+.table-striped tbody tr:nth-child(odd) td,
+.table-striped tbody tr:nth-child(odd) th {
+ background-color: #f9f9f9;
+}
+
+.table-hover tbody tr:hover td,
+.table-hover tbody tr:hover th {
+ background-color: #f5f5f5;
+}
+
+table [class*=span],
+.row-fluid table [class*=span] {
+ display: table-cell;
+ float: none;
+ margin-left: 0;
+}
+
+table .span1 {
+ float: none;
+ width: 44px;
+ margin-left: 0;
+}
+
+table .span2 {
+ float: none;
+ width: 124px;
+ margin-left: 0;
+}
+
+table .span3 {
+ float: none;
+ width: 204px;
+ margin-left: 0;
+}
+
+table .span4 {
+ float: none;
+ width: 284px;
+ margin-left: 0;
+}
+
+table .span5 {
+ float: none;
+ width: 364px;
+ margin-left: 0;
+}
+
+table .span6 {
+ float: none;
+ width: 444px;
+ margin-left: 0;
+}
+
+table .span7 {
+ float: none;
+ width: 524px;
+ margin-left: 0;
+}
+
+table .span8 {
+ float: none;
+ width: 604px;
+ margin-left: 0;
+}
+
+table .span9 {
+ float: none;
+ width: 684px;
+ margin-left: 0;
+}
+
+table .span10 {
+ float: none;
+ width: 764px;
+ margin-left: 0;
+}
+
+table .span11 {
+ float: none;
+ width: 844px;
+ margin-left: 0;
+}
+
+table .span12 {
+ float: none;
+ width: 924px;
+ margin-left: 0;
+}
+
+table .span13 {
+ float: none;
+ width: 1004px;
+ margin-left: 0;
+}
+
+table .span14 {
+ float: none;
+ width: 1084px;
+ margin-left: 0;
+}
+
+table .span15 {
+ float: none;
+ width: 1164px;
+ margin-left: 0;
+}
+
+table .span16 {
+ float: none;
+ width: 1244px;
+ margin-left: 0;
+}
+
+table .span17 {
+ float: none;
+ width: 1324px;
+ margin-left: 0;
+}
+
+table .span18 {
+ float: none;
+ width: 1404px;
+ margin-left: 0;
+}
+
+table .span19 {
+ float: none;
+ width: 1484px;
+ margin-left: 0;
+}
+
+table .span20 {
+ float: none;
+ width: 1564px;
+ margin-left: 0;
+}
+
+table .span21 {
+ float: none;
+ width: 1644px;
+ margin-left: 0;
+}
+
+table .span22 {
+ float: none;
+ width: 1724px;
+ margin-left: 0;
+}
+
+table .span23 {
+ float: none;
+ width: 1804px;
+ margin-left: 0;
+}
+
+table .span24 {
+ float: none;
+ width: 1884px;
+ margin-left: 0;
+}
+
+.table tbody tr.success td {
+ background-color: #dff0d8;
+}
+
+.table tbody tr.error td {
+ background-color: #f2dede;
+}
+
+.table tbody tr.info td {
+ background-color: #d9edf7;
+}
+
+[class^="icon-"],
+[class*=" icon-"] {
+ display: inline-block;
+ width: 14px;
+ height: 14px;
+ margin-top: 1px;
+ *margin-right: .3em;
+ line-height: 14px;
+ vertical-align: text-top;
+ background-image: url("../img/glyphicons-halflings.png");
+ background-position: 14px 14px;
+ background-repeat: no-repeat;
+}
+
+/* White icons with optional class, or on hover/active states of certain elements */
+
+.icon-white,
+.nav > .active > a > [class^="icon-"],
+.nav > .active > a > [class*=" icon-"],
+.dropdown-menu > li > a:hover > [class^="icon-"],
+.dropdown-menu > li > a:hover > [class*=" icon-"],
+.dropdown-menu > .active > a > [class^="icon-"],
+.dropdown-menu > .active > a > [class*=" icon-"] {
+ background-image: url("../img/glyphicons-halflings-white.png");
+}
+
+.icon-glass {
+ background-position: 0 0;
+}
+
+.icon-music {
+ background-position: -24px 0;
+}
+
+.icon-search {
+ background-position: -48px 0;
+}
+
+.icon-envelope {
+ background-position: -72px 0;
+}
+
+.icon-heart {
+ background-position: -96px 0;
+}
+
+.icon-star {
+ background-position: -120px 0;
+}
+
+.icon-star-empty {
+ background-position: -144px 0;
+}
+
+.icon-user {
+ background-position: -168px 0;
+}
+
+.icon-film {
+ background-position: -192px 0;
+}
+
+.icon-th-large {
+ background-position: -216px 0;
+}
+
+.icon-th {
+ background-position: -240px 0;
+}
+
+.icon-th-list {
+ background-position: -264px 0;
+}
+
+.icon-ok {
+ background-position: -288px 0;
+}
+
+.icon-remove {
+ background-position: -312px 0;
+}
+
+.icon-zoom-in {
+ background-position: -336px 0;
+}
+
+.icon-zoom-out {
+ background-position: -360px 0;
+}
+
+.icon-off {
+ background-position: -384px 0;
+}
+
+.icon-signal {
+ background-position: -408px 0;
+}
+
+.icon-cog {
+ background-position: -432px 0;
+}
+
+.icon-trash {
+ background-position: -456px 0;
+}
+
+.icon-home {
+ background-position: 0 -24px;
+}
+
+.icon-file {
+ background-position: -24px -24px;
+}
+
+.icon-time {
+ background-position: -48px -24px;
+}
+
+.icon-road {
+ background-position: -72px -24px;
+}
+
+.icon-download-alt {
+ background-position: -96px -24px;
+}
+
+.icon-download {
+ background-position: -120px -24px;
+}
+
+.icon-upload {
+ background-position: -144px -24px;
+}
+
+.icon-inbox {
+ background-position: -168px -24px;
+}
+
+.icon-play-circle {
+ background-position: -192px -24px;
+}
+
+.icon-repeat {
+ background-position: -216px -24px;
+}
+
+.icon-refresh {
+ background-position: -240px -24px;
+}
+
+.icon-list-alt {
+ background-position: -264px -24px;
+}
+
+.icon-lock {
+ background-position: -287px -24px;
+}
+
+.icon-flag {
+ background-position: -312px -24px;
+}
+
+.icon-headphones {
+ background-position: -336px -24px;
+}
+
+.icon-volume-off {
+ background-position: -360px -24px;
+}
+
+.icon-volume-down {
+ background-position: -384px -24px;
+}
+
+.icon-volume-up {
+ background-position: -408px -24px;
+}
+
+.icon-qrcode {
+ background-position: -432px -24px;
+}
+
+.icon-barcode {
+ background-position: -456px -24px;
+}
+
+.icon-tag {
+ background-position: 0 -48px;
+}
+
+.icon-tags {
+ background-position: -25px -48px;
+}
+
+.icon-book {
+ background-position: -48px -48px;
+}
+
+.icon-bookmark {
+ background-position: -72px -48px;
+}
+
+.icon-print {
+ background-position: -96px -48px;
+}
+
+.icon-camera {
+ background-position: -120px -48px;
+}
+
+.icon-font {
+ background-position: -144px -48px;
+}
+
+.icon-bold {
+ background-position: -167px -48px;
+}
+
+.icon-italic {
+ background-position: -192px -48px;
+}
+
+.icon-text-height {
+ background-position: -216px -48px;
+}
+
+.icon-text-width {
+ background-position: -240px -48px;
+}
+
+.icon-align-left {
+ background-position: -264px -48px;
+}
+
+.icon-align-center {
+ background-position: -288px -48px;
+}
+
+.icon-align-right {
+ background-position: -312px -48px;
+}
+
+.icon-align-justify {
+ background-position: -336px -48px;
+}
+
+.icon-list {
+ background-position: -360px -48px;
+}
+
+.icon-indent-left {
+ background-position: -384px -48px;
+}
+
+.icon-indent-right {
+ background-position: -408px -48px;
+}
+
+.icon-facetime-video {
+ background-position: -432px -48px;
+}
+
+.icon-picture {
+ background-position: -456px -48px;
+}
+
+.icon-pencil {
+ background-position: 0 -72px;
+}
+
+.icon-map-marker {
+ background-position: -24px -72px;
+}
+
+.icon-adjust {
+ background-position: -48px -72px;
+}
+
+.icon-tint {
+ background-position: -72px -72px;
+}
+
+.icon-edit {
+ background-position: -96px -72px;
+}
+
+.icon-share {
+ background-position: -120px -72px;
+}
+
+.icon-check {
+ background-position: -144px -72px;
+}
+
+.icon-move {
+ background-position: -168px -72px;
+}
+
+.icon-step-backward {
+ background-position: -192px -72px;
+}
+
+.icon-fast-backward {
+ background-position: -216px -72px;
+}
+
+.icon-backward {
+ background-position: -240px -72px;
+}
+
+.icon-play {
+ background-position: -264px -72px;
+}
+
+.icon-pause {
+ background-position: -288px -72px;
+}
+
+.icon-stop {
+ background-position: -312px -72px;
+}
+
+.icon-forward {
+ background-position: -336px -72px;
+}
+
+.icon-fast-forward {
+ background-position: -360px -72px;
+}
+
+.icon-step-forward {
+ background-position: -384px -72px;
+}
+
+.icon-eject {
+ background-position: -408px -72px;
+}
+
+.icon-chevron-left {
+ background-position: -432px -72px;
+}
+
+.icon-chevron-right {
+ background-position: -456px -72px;
+}
+
+.icon-plus-sign {
+ background-position: 0 -96px;
+}
+
+.icon-minus-sign {
+ background-position: -24px -96px;
+}
+
+.icon-remove-sign {
+ background-position: -48px -96px;
+}
+
+.icon-ok-sign {
+ background-position: -72px -96px;
+}
+
+.icon-question-sign {
+ background-position: -96px -96px;
+}
+
+.icon-info-sign {
+ background-position: -120px -96px;
+}
+
+.icon-screenshot {
+ background-position: -144px -96px;
+}
+
+.icon-remove-circle {
+ background-position: -168px -96px;
+}
+
+.icon-ok-circle {
+ background-position: -192px -96px;
+}
+
+.icon-ban-circle {
+ background-position: -216px -96px;
+}
+
+.icon-arrow-left {
+ background-position: -240px -96px;
+}
+
+.icon-arrow-right {
+ background-position: -264px -96px;
+}
+
+.icon-arrow-up {
+ background-position: -289px -96px;
+}
+
+.icon-arrow-down {
+ background-position: -312px -96px;
+}
+
+.icon-share-alt {
+ background-position: -336px -96px;
+}
+
+.icon-resize-full {
+ background-position: -360px -96px;
+}
+
+.icon-resize-small {
+ background-position: -384px -96px;
+}
+
+.icon-plus {
+ background-position: -408px -96px;
+}
+
+.icon-minus {
+ background-position: -433px -96px;
+}
+
+.icon-asterisk {
+ background-position: -456px -96px;
+}
+
+.icon-exclamation-sign {
+ background-position: 0 -120px;
+}
+
+.icon-gift {
+ background-position: -24px -120px;
+}
+
+.icon-leaf {
+ background-position: -48px -120px;
+}
+
+.icon-fire {
+ background-position: -72px -120px;
+}
+
+.icon-eye-open {
+ background-position: -96px -120px;
+}
+
+.icon-eye-close {
+ background-position: -120px -120px;
+}
+
+.icon-warning-sign {
+ background-position: -144px -120px;
+}
+
+.icon-plane {
+ background-position: -168px -120px;
+}
+
+.icon-calendar {
+ background-position: -192px -120px;
+}
+
+.icon-random {
+ width: 16px;
+ background-position: -216px -120px;
+}
+
+.icon-comment {
+ background-position: -240px -120px;
+}
+
+.icon-magnet {
+ background-position: -264px -120px;
+}
+
+.icon-chevron-up {
+ background-position: -288px -120px;
+}
+
+.icon-chevron-down {
+ background-position: -313px -119px;
+}
+
+.icon-retweet {
+ background-position: -336px -120px;
+}
+
+.icon-shopping-cart {
+ background-position: -360px -120px;
+}
+
+.icon-folder-close {
+ background-position: -384px -120px;
+}
+
+.icon-folder-open {
+ width: 16px;
+ background-position: -408px -120px;
+}
+
+.icon-resize-vertical {
+ background-position: -432px -119px;
+}
+
+.icon-resize-horizontal {
+ background-position: -456px -118px;
+}
+
+.icon-hdd {
+ background-position: 0 -144px;
+}
+
+.icon-bullhorn {
+ background-position: -24px -144px;
+}
+
+.icon-bell {
+ background-position: -48px -144px;
+}
+
+.icon-certificate {
+ background-position: -72px -144px;
+}
+
+.icon-thumbs-up {
+ background-position: -96px -144px;
+}
+
+.icon-thumbs-down {
+ background-position: -120px -144px;
+}
+
+.icon-hand-right {
+ background-position: -144px -144px;
+}
+
+.icon-hand-left {
+ background-position: -168px -144px;
+}
+
+.icon-hand-up {
+ background-position: -192px -144px;
+}
+
+.icon-hand-down {
+ background-position: -216px -144px;
+}
+
+.icon-circle-arrow-right {
+ background-position: -240px -144px;
+}
+
+.icon-circle-arrow-left {
+ background-position: -264px -144px;
+}
+
+.icon-circle-arrow-up {
+ background-position: -288px -144px;
+}
+
+.icon-circle-arrow-down {
+ background-position: -312px -144px;
+}
+
+.icon-globe {
+ background-position: -336px -144px;
+}
+
+.icon-wrench {
+ background-position: -360px -144px;
+}
+
+.icon-tasks {
+ background-position: -384px -144px;
+}
+
+.icon-filter {
+ background-position: -408px -144px;
+}
+
+.icon-briefcase {
+ background-position: -432px -144px;
+}
+
+.icon-fullscreen {
+ background-position: -456px -144px;
+}
+
+.dropup,
+.dropdown {
+ position: relative;
+}
+
+.dropdown-toggle {
+ *margin-bottom: -3px;
+}
+
+.dropdown-toggle:active,
+.open .dropdown-toggle {
+ outline: 0;
+}
+
+.caret {
+ display: inline-block;
+ width: 0;
+ height: 0;
+ vertical-align: top;
+ border-top: 4px solid #000000;
+ border-right: 4px solid transparent;
+ border-left: 4px solid transparent;
+ content: "";
+}
+
+.dropdown .caret {
+ margin-top: 8px;
+ margin-left: 2px;
+}
+
+.dropdown-menu {
+ position: absolute;
+ top: 100%;
+ left: 0;
+ z-index: 1000;
+ display: none;
+ float: left;
+ min-width: 160px;
+ padding: 5px 0;
+ margin: 2px 0 0;
+ list-style: none;
+ background-color: #ffffff;
+ border: 1px solid #ccc;
+ border: 1px solid rgba(0, 0, 0, 0.2);
+ *border-right-width: 2px;
+ *border-bottom-width: 2px;
+ -webkit-border-radius: 6px;
+ -moz-border-radius: 6px;
+ border-radius: 6px;
+ -webkit-box-shadow: 0 5px 10px rgba(0, 0, 0, 0.2);
+ -moz-box-shadow: 0 5px 10px rgba(0, 0, 0, 0.2);
+ box-shadow: 0 5px 10px rgba(0, 0, 0, 0.2);
+ -webkit-background-clip: padding-box;
+ -moz-background-clip: padding;
+ background-clip: padding-box;
+}
+
+.dropdown-menu.pull-right {
+ right: 0;
+ left: auto;
+}
+
+.dropdown-menu .divider {
+ *width: 100%;
+ height: 1px;
+ margin: 9px 1px;
+ *margin: -5px 0 5px;
+ overflow: hidden;
+ background-color: #e5e5e5;
+ border-bottom: 1px solid #ffffff;
+}
+
+.dropdown-menu a {
+ display: block;
+ padding: 3px 20px;
+ clear: both;
+ font-weight: normal;
+ line-height: 20px;
+ color: #333333;
+ white-space: nowrap;
+}
+
+.dropdown-menu li > a:hover,
+.dropdown-menu li > a:focus,
+.dropdown-submenu:hover > a {
+ color: #ffffff;
+ text-decoration: none;
+ background-color: #0088cc;
+ background-color: #0081c2;
+ background-image: -moz-linear-gradient(top, #0088cc, #0077b3);
+ background-image: -webkit-gradient(linear, 0 0, 0 100%, from(#0088cc), to(#0077b3));
+ background-image: -webkit-linear-gradient(top, #0088cc, #0077b3);
+ background-image: -o-linear-gradient(top, #0088cc, #0077b3);
+ background-image: linear-gradient(to bottom, #0088cc, #0077b3);
+ background-repeat: repeat-x;
+ filter: progid:dximagetransform.microsoft.gradient(startColorstr='#ff0088cc', endColorstr='#ff0077b3', GradientType=0);
+}
+
+.dropdown-menu .active > a,
+.dropdown-menu .active > a:hover {
+ color: #ffffff;
+ text-decoration: none;
+ background-color: #0088cc;
+ background-color: #0081c2;
+ background-image: linear-gradient(to bottom, #0088cc, #0077b3);
+ background-image: -moz-linear-gradient(top, #0088cc, #0077b3);
+ background-image: -webkit-gradient(linear, 0 0, 0 100%, from(#0088cc), to(#0077b3));
+ background-image: -webkit-linear-gradient(top, #0088cc, #0077b3);
+ background-image: -o-linear-gradient(top, #0088cc, #0077b3);
+ background-repeat: repeat-x;
+ outline: 0;
+ filter: progid:dximagetransform.microsoft.gradient(startColorstr='#ff0088cc', endColorstr='#ff0077b3', GradientType=0);
+}
+
+.dropdown-menu .disabled > a,
+.dropdown-menu .disabled > a:hover {
+ color: #999999;
+}
+
+.dropdown-menu .disabled > a:hover {
+ text-decoration: none;
+ cursor: default;
+ background-color: transparent;
+}
+
+.open {
+ *z-index: 1000;
+}
+
+.open > .dropdown-menu {
+ display: block;
+}
+
+.pull-right > .dropdown-menu {
+ right: 0;
+ left: auto;
+}
+
+.dropup .caret,
+.navbar-fixed-bottom .dropdown .caret {
+ border-top: 0;
+ border-bottom: 4px solid #000000;
+ content: "\2191";
+}
+
+.dropup .dropdown-menu,
+.navbar-fixed-bottom .dropdown .dropdown-menu {
+ top: auto;
+ bottom: 100%;
+ margin-bottom: 1px;
+}
+
+.dropdown-submenu {
+ position: relative;
+}
+
+.dropdown-submenu > .dropdown-menu {
+ top: 0;
+ left: 100%;
+ margin-top: -6px;
+ margin-left: -1px;
+ -webkit-border-radius: 0 6px 6px 6px;
+ -moz-border-radius: 0 6px 6px 6px;
+ border-radius: 0 6px 6px 6px;
+}
+
+.dropdown-submenu:hover .dropdown-menu {
+ display: block;
+}
+
+.dropdown-submenu > a:after {
+ display: block;
+ float: right;
+ width: 0;
+ height: 0;
+ margin-top: 5px;
+ margin-right: -10px;
+ border-color: transparent;
+ border-left-color: #cccccc;
+ border-style: solid;
+ border-width: 5px 0 5px 5px;
+ content: " ";
+}
+
+.dropdown-submenu:hover > a:after {
+ border-left-color: #ffffff;
+}
+
+.dropdown .dropdown-menu .nav-header {
+ padding-right: 20px;
+ padding-left: 20px;
+}
+
+.typeahead {
+ margin-top: 2px;
+ -webkit-border-radius: 4px;
+ -moz-border-radius: 4px;
+ border-radius: 4px;
+}
+
+.well {
+ min-height: 20px;
+ padding: 19px;
+ margin-bottom: 20px;
+ background-color: #f5f5f5;
+ border: 1px solid #e3e3e3;
+ -webkit-border-radius: 4px;
+ -moz-border-radius: 4px;
+ border-radius: 4px;
+ -webkit-box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.05);
+ -moz-box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.05);
+ box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.05);
+}
+
+.well blockquote {
+ border-color: #ddd;
+ border-color: rgba(0, 0, 0, 0.15);
+}
+
+.well-large {
+ padding: 24px;
+ -webkit-border-radius: 6px;
+ -moz-border-radius: 6px;
+ border-radius: 6px;
+}
+
+.well-small {
+ padding: 9px;
+ -webkit-border-radius: 3px;
+ -moz-border-radius: 3px;
+ border-radius: 3px;
+}
+
+.fade {
+ opacity: 0;
+ -webkit-transition: opacity 0.15s linear;
+ -moz-transition: opacity 0.15s linear;
+ -o-transition: opacity 0.15s linear;
+ transition: opacity 0.15s linear;
+}
+
+.fade.in {
+ opacity: 1;
+}
+
+.collapse {
+ position: relative;
+ height: 0;
+ overflow: hidden;
+ overflow: visible \9;
+ -webkit-transition: height 0.35s ease;
+ -moz-transition: height 0.35s ease;
+ -o-transition: height 0.35s ease;
+ transition: height 0.35s ease;
+}
+
+.collapse.in {
+ height: auto;
+}
+
+.close {
+ float: right;
+ font-size: 20px;
+ font-weight: bold;
+ line-height: 20px;
+ color: #000000;
+ text-shadow: 0 1px 0 #ffffff;
+ opacity: 0.2;
+ filter: alpha(opacity=20);
+}
+
+.close:hover {
+ color: #000000;
+ text-decoration: none;
+ cursor: pointer;
+ opacity: 0.4;
+ filter: alpha(opacity=40);
+}
+
+button.close {
+ padding: 0;
+ cursor: pointer;
+ background: transparent;
+ border: 0;
+ -webkit-appearance: none;
+}
+
+.btn {
+ display: inline-block;
+ *display: inline;
+ padding: 4px 14px;
+ margin-bottom: 0;
+ *margin-left: .3em;
+ font-size: 14px;
+ line-height: 20px;
+ *line-height: 20px;
+ color: #333333;
+ text-align: center;
+ text-shadow: 0 1px 1px rgba(255, 255, 255, 0.75);
+ vertical-align: middle;
+ cursor: pointer;
+ background-color: #f5f5f5;
+ *background-color: #e6e6e6;
+ background-image: -webkit-gradient(linear, 0 0, 0 100%, from(#ffffff), to(#e6e6e6));
+ background-image: -webkit-linear-gradient(top, #ffffff, #e6e6e6);
+ background-image: -o-linear-gradient(top, #ffffff, #e6e6e6);
+ background-image: linear-gradient(to bottom, #ffffff, #e6e6e6);
+ background-image: -moz-linear-gradient(top, #ffffff, #e6e6e6);
+ background-repeat: repeat-x;
+ border: 1px solid #bbbbbb;
+ *border: 0;
+ border-color: rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.25);
+ border-color: #e6e6e6 #e6e6e6 #bfbfbf;
+ border-bottom-color: #a2a2a2;
+ -webkit-border-radius: 4px;
+ -moz-border-radius: 4px;
+ border-radius: 4px;
+ filter: progid:dximagetransform.microsoft.gradient(startColorstr='#ffffffff', endColorstr='#ffe6e6e6', GradientType=0);
+ filter: progid:dximagetransform.microsoft.gradient(enabled=false);
+ *zoom: 1;
+ -webkit-box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.2), 0 1px 2px rgba(0, 0, 0, 0.05);
+ -moz-box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.2), 0 1px 2px rgba(0, 0, 0, 0.05);
+ box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.2), 0 1px 2px rgba(0, 0, 0, 0.05);
+}
+
+.btn:hover,
+.btn:active,
+.btn.active,
+.btn.disabled,
+.btn[disabled] {
+ color: #333333;
+ background-color: #e6e6e6;
+ *background-color: #d9d9d9;
+}
+
+.btn:active,
+.btn.active {
+ background-color: #cccccc \9;
+}
+
+.btn:first-child {
+ *margin-left: 0;
+}
+
+.btn:hover {
+ color: #333333;
+ text-decoration: none;
+ background-color: #e6e6e6;
+ *background-color: #d9d9d9;
+ /* Buttons in IE7 don't get borders, so darken on hover */
+
+ background-position: 0 -15px;
+ -webkit-transition: background-position 0.1s linear;
+ -moz-transition: background-position 0.1s linear;
+ -o-transition: background-position 0.1s linear;
+ transition: background-position 0.1s linear;
+}
+
+.btn:focus {
+ outline: thin dotted #333;
+ outline: 5px auto -webkit-focus-ring-color;
+ outline-offset: -2px;
+}
+
+.btn.active,
+.btn:active {
+ background-color: #e6e6e6;
+ background-color: #d9d9d9 \9;
+ background-image: none;
+ outline: 0;
+ -webkit-box-shadow: inset 0 2px 4px rgba(0, 0, 0, 0.15), 0 1px 2px rgba(0, 0, 0, 0.05);
+ -moz-box-shadow: inset 0 2px 4px rgba(0, 0, 0, 0.15), 0 1px 2px rgba(0, 0, 0, 0.05);
+ box-shadow: inset 0 2px 4px rgba(0, 0, 0, 0.15), 0 1px 2px rgba(0, 0, 0, 0.05);
+}
+
+.btn.disabled,
+.btn[disabled] {
+ cursor: default;
+ background-color: #e6e6e6;
+ background-image: none;
+ opacity: 0.65;
+ filter: alpha(opacity=65);
+ -webkit-box-shadow: none;
+ -moz-box-shadow: none;
+ box-shadow: none;
+}
+
+.btn-large {
+ padding: 9px 14px;
+ font-size: 16px;
+ line-height: normal;
+ -webkit-border-radius: 5px;
+ -moz-border-radius: 5px;
+ border-radius: 5px;
+}
+
+.btn-large [class^="icon-"] {
+ margin-top: 2px;
+}
+
+.btn-small {
+ padding: 3px 9px;
+ font-size: 12px;
+ line-height: 18px;
+}
+
+.btn-small [class^="icon-"] {
+ margin-top: 0;
+}
+
+.btn-mini {
+ padding: 2px 6px;
+ font-size: 11px;
+ line-height: 16px;
+}
+
+.btn-block {
+ display: block;
+ width: 100%;
+ padding-right: 0;
+ padding-left: 0;
+ -webkit-box-sizing: border-box;
+ -moz-box-sizing: border-box;
+ box-sizing: border-box;
+}
+
+.btn-block + .btn-block {
+ margin-top: 5px;
+}
+
+.btn-primary.active,
+.btn-warning.active,
+.btn-danger.active,
+.btn-success.active,
+.btn-info.active,
+.btn-inverse.active {
+ color: rgba(255, 255, 255, 0.75);
+}
+
+.btn {
+ border-color: #c5c5c5;
+ border-color: rgba(0, 0, 0, 0.15) rgba(0, 0, 0, 0.15) rgba(0, 0, 0, 0.25);
+}
+
+.btn-primary {
+ color: #ffffff;
+ text-shadow: 0 -1px 0 rgba(0, 0, 0, 0.25);
+ background-color: #006dcc;
+ *background-color: #0044cc;
+ background-image: -webkit-gradient(linear, 0 0, 0 100%, from(#0088cc), to(#0044cc));
+ background-image: -webkit-linear-gradient(top, #0088cc, #0044cc);
+ background-image: -o-linear-gradient(top, #0088cc, #0044cc);
+ background-image: linear-gradient(to bottom, #0088cc, #0044cc);
+ background-image: -moz-linear-gradient(top, #0088cc, #0044cc);
+ background-repeat: repeat-x;
+ border-color: #0044cc #0044cc #002a80;
+ border-color: rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.25);
+ filter: progid:dximagetransform.microsoft.gradient(startColorstr='#ff0088cc', endColorstr='#ff0044cc', GradientType=0);
+ filter: progid:dximagetransform.microsoft.gradient(enabled=false);
+}
+
+.btn-primary:hover,
+.btn-primary:active,
+.btn-primary.active,
+.btn-primary.disabled,
+.btn-primary[disabled] {
+ color: #ffffff;
+ background-color: #0044cc;
+ *background-color: #003bb3;
+}
+
+.btn-primary:active,
+.btn-primary.active {
+ background-color: #003399 \9;
+}
+
+.btn-warning {
+ color: #ffffff;
+ text-shadow: 0 -1px 0 rgba(0, 0, 0, 0.25);
+ background-color: #faa732;
+ *background-color: #f89406;
+ background-image: -webkit-gradient(linear, 0 0, 0 100%, from(#fbb450), to(#f89406));
+ background-image: -webkit-linear-gradient(top, #fbb450, #f89406);
+ background-image: -o-linear-gradient(top, #fbb450, #f89406);
+ background-image: linear-gradient(to bottom, #fbb450, #f89406);
+ background-image: -moz-linear-gradient(top, #fbb450, #f89406);
+ background-repeat: repeat-x;
+ border-color: #f89406 #f89406 #ad6704;
+ border-color: rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.25);
+ filter: progid:dximagetransform.microsoft.gradient(startColorstr='#fffbb450', endColorstr='#fff89406', GradientType=0);
+ filter: progid:dximagetransform.microsoft.gradient(enabled=false);
+}
+
+.btn-warning:hover,
+.btn-warning:active,
+.btn-warning.active,
+.btn-warning.disabled,
+.btn-warning[disabled] {
+ color: #ffffff;
+ background-color: #f89406;
+ *background-color: #df8505;
+}
+
+.btn-warning:active,
+.btn-warning.active {
+ background-color: #c67605 \9;
+}
+
+.btn-danger {
+ color: #ffffff;
+ text-shadow: 0 -1px 0 rgba(0, 0, 0, 0.25);
+ background-color: #da4f49;
+ *background-color: #bd362f;
+ background-image: -webkit-gradient(linear, 0 0, 0 100%, from(#ee5f5b), to(#bd362f));
+ background-image: -webkit-linear-gradient(top, #ee5f5b, #bd362f);
+ background-image: -o-linear-gradient(top, #ee5f5b, #bd362f);
+ background-image: linear-gradient(to bottom, #ee5f5b, #bd362f);
+ background-image: -moz-linear-gradient(top, #ee5f5b, #bd362f);
+ background-repeat: repeat-x;
+ border-color: #bd362f #bd362f #802420;
+ border-color: rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.25);
+ filter: progid:dximagetransform.microsoft.gradient(startColorstr='#ffee5f5b', endColorstr='#ffbd362f', GradientType=0);
+ filter: progid:dximagetransform.microsoft.gradient(enabled=false);
+}
+
+.btn-danger:hover,
+.btn-danger:active,
+.btn-danger.active,
+.btn-danger.disabled,
+.btn-danger[disabled] {
+ color: #ffffff;
+ background-color: #bd362f;
+ *background-color: #a9302a;
+}
+
+.btn-danger:active,
+.btn-danger.active {
+ background-color: #942a25 \9;
+}
+
+.btn-success {
+ color: #ffffff;
+ text-shadow: 0 -1px 0 rgba(0, 0, 0, 0.25);
+ background-color: #5bb75b;
+ *background-color: #51a351;
+ background-image: -webkit-gradient(linear, 0 0, 0 100%, from(#62c462), to(#51a351));
+ background-image: -webkit-linear-gradient(top, #62c462, #51a351);
+ background-image: -o-linear-gradient(top, #62c462, #51a351);
+ background-image: linear-gradient(to bottom, #62c462, #51a351);
+ background-image: -moz-linear-gradient(top, #62c462, #51a351);
+ background-repeat: repeat-x;
+ border-color: #51a351 #51a351 #387038;
+ border-color: rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.25);
+ filter: progid:dximagetransform.microsoft.gradient(startColorstr='#ff62c462', endColorstr='#ff51a351', GradientType=0);
+ filter: progid:dximagetransform.microsoft.gradient(enabled=false);
+}
+
+.btn-success:hover,
+.btn-success:active,
+.btn-success.active,
+.btn-success.disabled,
+.btn-success[disabled] {
+ color: #ffffff;
+ background-color: #51a351;
+ *background-color: #499249;
+}
+
+.btn-success:active,
+.btn-success.active {
+ background-color: #408140 \9;
+}
+
+.btn-info {
+ color: #ffffff;
+ text-shadow: 0 -1px 0 rgba(0, 0, 0, 0.25);
+ background-color: #49afcd;
+ *background-color: #2f96b4;
+ background-image: -webkit-gradient(linear, 0 0, 0 100%, from(#5bc0de), to(#2f96b4));
+ background-image: -webkit-linear-gradient(top, #5bc0de, #2f96b4);
+ background-image: -o-linear-gradient(top, #5bc0de, #2f96b4);
+ background-image: linear-gradient(to bottom, #5bc0de, #2f96b4);
+ background-image: -moz-linear-gradient(top, #5bc0de, #2f96b4);
+ background-repeat: repeat-x;
+ border-color: #2f96b4 #2f96b4 #1f6377;
+ border-color: rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.25);
+ filter: progid:dximagetransform.microsoft.gradient(startColorstr='#ff5bc0de', endColorstr='#ff2f96b4', GradientType=0);
+ filter: progid:dximagetransform.microsoft.gradient(enabled=false);
+}
+
+.btn-info:hover,
+.btn-info:active,
+.btn-info.active,
+.btn-info.disabled,
+.btn-info[disabled] {
+ color: #ffffff;
+ background-color: #2f96b4;
+ *background-color: #2a85a0;
+}
+
+.btn-info:active,
+.btn-info.active {
+ background-color: #24748c \9;
+}
+
+.btn-inverse {
+ color: #ffffff;
+ text-shadow: 0 -1px 0 rgba(0, 0, 0, 0.25);
+ background-color: #363636;
+ *background-color: #222222;
+ background-image: -webkit-gradient(linear, 0 0, 0 100%, from(#444444), to(#222222));
+ background-image: -webkit-linear-gradient(top, #444444, #222222);
+ background-image: -o-linear-gradient(top, #444444, #222222);
+ background-image: linear-gradient(to bottom, #444444, #222222);
+ background-image: -moz-linear-gradient(top, #444444, #222222);
+ background-repeat: repeat-x;
+ border-color: #222222 #222222 #000000;
+ border-color: rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.25);
+ filter: progid:dximagetransform.microsoft.gradient(startColorstr='#ff444444', endColorstr='#ff222222', GradientType=0);
+ filter: progid:dximagetransform.microsoft.gradient(enabled=false);
+}
+
+.btn-inverse:hover,
+.btn-inverse:active,
+.btn-inverse.active,
+.btn-inverse.disabled,
+.btn-inverse[disabled] {
+ color: #ffffff;
+ background-color: #222222;
+ *background-color: #151515;
+}
+
+.btn-inverse:active,
+.btn-inverse.active {
+ background-color: #080808 \9;
+}
+
+button.btn,
+input[type="submit"].btn {
+ *padding-top: 3px;
+ *padding-bottom: 3px;
+}
+
+button.btn::-moz-focus-inner,
+input[type="submit"].btn::-moz-focus-inner {
+ padding: 0;
+ border: 0;
+}
+
+button.btn.btn-large,
+input[type="submit"].btn.btn-large {
+ *padding-top: 7px;
+ *padding-bottom: 7px;
+}
+
+button.btn.btn-small,
+input[type="submit"].btn.btn-small {
+ *padding-top: 3px;
+ *padding-bottom: 3px;
+}
+
+button.btn.btn-mini,
+input[type="submit"].btn.btn-mini {
+ *padding-top: 1px;
+ *padding-bottom: 1px;
+}
+
+.btn-link,
+.btn-link:active {
+ background-color: transparent;
+ background-image: none;
+ -webkit-box-shadow: none;
+ -moz-box-shadow: none;
+ box-shadow: none;
+}
+
+.btn-link {
+ color: #0088cc;
+ cursor: pointer;
+ border-color: transparent;
+ -webkit-border-radius: 0;
+ -moz-border-radius: 0;
+ border-radius: 0;
+}
+
+.btn-link:hover {
+ color: #005580;
+ text-decoration: underline;
+ background-color: transparent;
+}
+
+.btn-group {
+ position: relative;
+ *margin-left: .3em;
+ font-size: 0;
+ white-space: nowrap;
+}
+
+.btn-group:first-child {
+ *margin-left: 0;
+}
+
+.btn-group + .btn-group {
+ margin-left: 5px;
+}
+
+.btn-toolbar {
+ margin-top: 10px;
+ margin-bottom: 10px;
+ font-size: 0;
+}
+
+.btn-toolbar .btn-group {
+ display: inline-block;
+ *display: inline;
+ /* IE7 inline-block hack */
+
+ *zoom: 1;
+}
+
+.btn-toolbar .btn + .btn,
+.btn-toolbar .btn-group + .btn,
+.btn-toolbar .btn + .btn-group {
+ margin-left: 5px;
+}
+
+.btn-group > .btn {
+ position: relative;
+ -webkit-border-radius: 0;
+ -moz-border-radius: 0;
+ border-radius: 0;
+}
+
+.btn-group > .btn + .btn {
+ margin-left: -1px;
+}
+
+.btn-group > .btn,
+.btn-group > .dropdown-menu {
+ font-size: 14px;
+}
+
+.btn-group > .btn-mini {
+ font-size: 11px;
+}
+
+.btn-group > .btn-small {
+ font-size: 12px;
+}
+
+.btn-group > .btn-large {
+ font-size: 16px;
+}
+
+.btn-group > .btn:first-child {
+ margin-left: 0;
+ -webkit-border-bottom-left-radius: 4px;
+ border-bottom-left-radius: 4px;
+ -webkit-border-top-left-radius: 4px;
+ border-top-left-radius: 4px;
+ -moz-border-radius-bottomleft: 4px;
+ -moz-border-radius-topleft: 4px;
+}
+
+.btn-group > .btn:last-child,
+.btn-group > .dropdown-toggle {
+ -webkit-border-top-right-radius: 4px;
+ border-top-right-radius: 4px;
+ -webkit-border-bottom-right-radius: 4px;
+ border-bottom-right-radius: 4px;
+ -moz-border-radius-topright: 4px;
+ -moz-border-radius-bottomright: 4px;
+}
+
+.btn-group > .btn.large:first-child {
+ margin-left: 0;
+ -webkit-border-bottom-left-radius: 6px;
+ border-bottom-left-radius: 6px;
+ -webkit-border-top-left-radius: 6px;
+ border-top-left-radius: 6px;
+ -moz-border-radius-bottomleft: 6px;
+ -moz-border-radius-topleft: 6px;
+}
+
+.btn-group > .btn.large:last-child,
+.btn-group > .large.dropdown-toggle {
+ -webkit-border-top-right-radius: 6px;
+ border-top-right-radius: 6px;
+ -webkit-border-bottom-right-radius: 6px;
+ border-bottom-right-radius: 6px;
+ -moz-border-radius-topright: 6px;
+ -moz-border-radius-bottomright: 6px;
+}
+
+.btn-group > .btn:hover,
+.btn-group > .btn:focus,
+.btn-group > .btn:active,
+.btn-group > .btn.active {
+ z-index: 2;
+}
+
+.btn-group .dropdown-toggle:active,
+.btn-group.open .dropdown-toggle {
+ outline: 0;
+}
+
+.btn-group > .btn + .dropdown-toggle {
+ *padding-top: 5px;
+ padding-right: 8px;
+ *padding-bottom: 5px;
+ padding-left: 8px;
+ -webkit-box-shadow: inset 1px 0 0 rgba(255, 255, 255, 0.125), inset 0 1px 0 rgba(255, 255, 255, 0.2), 0 1px 2px rgba(0, 0, 0, 0.05);
+ -moz-box-shadow: inset 1px 0 0 rgba(255, 255, 255, 0.125), inset 0 1px 0 rgba(255, 255, 255, 0.2), 0 1px 2px rgba(0, 0, 0, 0.05);
+ box-shadow: inset 1px 0 0 rgba(255, 255, 255, 0.125), inset 0 1px 0 rgba(255, 255, 255, 0.2), 0 1px 2px rgba(0, 0, 0, 0.05);
+}
+
+.btn-group > .btn-mini + .dropdown-toggle {
+ *padding-top: 2px;
+ padding-right: 5px;
+ *padding-bottom: 2px;
+ padding-left: 5px;
+}
+
+.btn-group > .btn-small + .dropdown-toggle {
+ *padding-top: 5px;
+ *padding-bottom: 4px;
+}
+
+.btn-group > .btn-large + .dropdown-toggle {
+ *padding-top: 7px;
+ padding-right: 12px;
+ *padding-bottom: 7px;
+ padding-left: 12px;
+}
+
+.btn-group.open .dropdown-toggle {
+ background-image: none;
+ -webkit-box-shadow: inset 0 2px 4px rgba(0, 0, 0, 0.15), 0 1px 2px rgba(0, 0, 0, 0.05);
+ -moz-box-shadow: inset 0 2px 4px rgba(0, 0, 0, 0.15), 0 1px 2px rgba(0, 0, 0, 0.05);
+ box-shadow: inset 0 2px 4px rgba(0, 0, 0, 0.15), 0 1px 2px rgba(0, 0, 0, 0.05);
+}
+
+.btn-group.open .btn.dropdown-toggle {
+ background-color: #e6e6e6;
+}
+
+.btn-group.open .btn-primary.dropdown-toggle {
+ background-color: #0044cc;
+}
+
+.btn-group.open .btn-warning.dropdown-toggle {
+ background-color: #f89406;
+}
+
+.btn-group.open .btn-danger.dropdown-toggle {
+ background-color: #bd362f;
+}
+
+.btn-group.open .btn-success.dropdown-toggle {
+ background-color: #51a351;
+}
+
+.btn-group.open .btn-info.dropdown-toggle {
+ background-color: #2f96b4;
+}
+
+.btn-group.open .btn-inverse.dropdown-toggle {
+ background-color: #222222;
+}
+
+.btn .caret {
+ margin-top: 8px;
+ margin-left: 0;
+}
+
+.btn-mini .caret,
+.btn-small .caret,
+.btn-large .caret {
+ margin-top: 6px;
+}
+
+.btn-large .caret {
+ border-top-width: 5px;
+ border-right-width: 5px;
+ border-left-width: 5px;
+}
+
+.dropup .btn-large .caret {
+ border-top: 0;
+ border-bottom: 5px solid #000000;
+}
+
+.btn-primary .caret,
+.btn-warning .caret,
+.btn-danger .caret,
+.btn-info .caret,
+.btn-success .caret,
+.btn-inverse .caret {
+ border-top-color: #ffffff;
+ border-bottom-color: #ffffff;
+}
+
+.btn-group-vertical {
+ display: inline-block;
+ *display: inline;
+ /* IE7 inline-block hack */
+
+ *zoom: 1;
+}
+
+.btn-group-vertical .btn {
+ display: block;
+ float: none;
+ width: 100%;
+ -webkit-border-radius: 0;
+ -moz-border-radius: 0;
+ border-radius: 0;
+}
+
+.btn-group-vertical .btn + .btn {
+ margin-top: -1px;
+ margin-left: 0;
+}
+
+.btn-group-vertical .btn:first-child {
+ -webkit-border-radius: 4px 4px 0 0;
+ -moz-border-radius: 4px 4px 0 0;
+ border-radius: 4px 4px 0 0;
+}
+
+.btn-group-vertical .btn:last-child {
+ -webkit-border-radius: 0 0 4px 4px;
+ -moz-border-radius: 0 0 4px 4px;
+ border-radius: 0 0 4px 4px;
+}
+
+.btn-group-vertical .btn-large:first-child {
+ -webkit-border-radius: 6px 6px 0 0;
+ -moz-border-radius: 6px 6px 0 0;
+ border-radius: 6px 6px 0 0;
+}
+
+.btn-group-vertical .btn-large:last-child {
+ -webkit-border-radius: 0 0 6px 6px;
+ -moz-border-radius: 0 0 6px 6px;
+ border-radius: 0 0 6px 6px;
+}
+
+.alert {
+ padding: 8px 35px 8px 14px;
+ margin-bottom: 20px;
+ color: #c09853;
+ text-shadow: 0 1px 0 rgba(255, 255, 255, 0.5);
+ background-color: #fcf8e3;
+ border: 1px solid #fbeed5;
+ -webkit-border-radius: 4px;
+ -moz-border-radius: 4px;
+ border-radius: 4px;
+}
+
+.alert h4 {
+ margin: 0;
+}
+
+.alert .close {
+ position: relative;
+ top: -2px;
+ right: -21px;
+ line-height: 20px;
+}
+
+.alert-success {
+ color: #468847;
+ background-color: #dff0d8;
+ border-color: #d6e9c6;
+}
+
+.alert-danger,
+.alert-error {
+ color: #b94a48;
+ background-color: #f2dede;
+ border-color: #eed3d7;
+}
+
+.alert-info {
+ color: #3a87ad;
+ background-color: #d9edf7;
+ border-color: #bce8f1;
+}
+
+.alert-block {
+ padding-top: 14px;
+ padding-bottom: 14px;
+}
+
+.alert-block > p,
+.alert-block > ul {
+ margin-bottom: 0;
+}
+
+.alert-block p + p {
+ margin-top: 5px;
+}
+
+.nav {
+ margin-bottom: 20px;
+ margin-left: 0;
+ list-style: none;
+}
+
+.nav > li > a {
+ display: block;
+}
+
+.nav > li > a:hover {
+ text-decoration: none;
+ background-color: #eeeeee;
+}
+
+.nav > .pull-right {
+ float: right;
+}
+
+.nav-header {
+ display: block;
+ padding: 3px 15px;
+ font-size: 11px;
+ font-weight: bold;
+ line-height: 20px;
+ color: #999999;
+ text-shadow: 0 1px 0 rgba(255, 255, 255, 0.5);
+ text-transform: uppercase;
+}
+
+.nav li + .nav-header {
+ margin-top: 9px;
+}
+
+.nav-list {
+ padding-right: 15px;
+ padding-left: 15px;
+ margin-bottom: 0;
+}
+
+.nav-list > li > a,
+.nav-list .nav-header {
+ margin-right: -15px;
+ margin-left: -15px;
+ text-shadow: 0 1px 0 rgba(255, 255, 255, 0.5);
+}
+
+.nav-list > li > a {
+ padding: 3px 15px;
+}
+
+.nav-list > .active > a,
+.nav-list > .active > a:hover {
+ color: #ffffff;
+ text-shadow: 0 -1px 0 rgba(0, 0, 0, 0.2);
+ background-color: #0088cc;
+}
+
+.nav-list [class^="icon-"] {
+ margin-right: 2px;
+}
+
+.nav-list .divider {
+ *width: 100%;
+ height: 1px;
+ margin: 9px 1px;
+ *margin: -5px 0 5px;
+ overflow: hidden;
+ background-color: #e5e5e5;
+ border-bottom: 1px solid #ffffff;
+}
+
+.nav-tabs,
+.nav-pills {
+ *zoom: 1;
+}
+
+.nav-tabs:before,
+.nav-pills:before,
+.nav-tabs:after,
+.nav-pills:after {
+ display: table;
+ line-height: 0;
+ content: "";
+}
+
+.nav-tabs:after,
+.nav-pills:after {
+ clear: both;
+}
+
+.nav-tabs > li,
+.nav-pills > li {
+ float: left;
+}
+
+.nav-tabs > li > a,
+.nav-pills > li > a {
+ padding-right: 12px;
+ padding-left: 12px;
+ margin-right: 2px;
+ line-height: 14px;
+}
+
+.nav-tabs {
+ border-bottom: 1px solid #ddd;
+}
+
+.nav-tabs > li {
+ margin-bottom: -1px;
+}
+
+.nav-tabs > li > a {
+ padding-top: 8px;
+ padding-bottom: 8px;
+ line-height: 20px;
+ border: 1px solid transparent;
+ -webkit-border-radius: 4px 4px 0 0;
+ -moz-border-radius: 4px 4px 0 0;
+ border-radius: 4px 4px 0 0;
+}
+
+.nav-tabs > li > a:hover {
+ border-color: #eeeeee #eeeeee #dddddd;
+}
+
+.nav-tabs > .active > a,
+.nav-tabs > .active > a:hover {
+ color: #555555;
+ cursor: default;
+ background-color: #ffffff;
+ border: 1px solid #ddd;
+ border-bottom-color: transparent;
+}
+
+.nav-pills > li > a {
+ padding-top: 8px;
+ padding-bottom: 8px;
+ margin-top: 2px;
+ margin-bottom: 2px;
+ -webkit-border-radius: 5px;
+ -moz-border-radius: 5px;
+ border-radius: 5px;
+}
+
+.nav-pills > .active > a,
+.nav-pills > .active > a:hover {
+ color: #ffffff;
+ background-color: #0088cc;
+}
+
+.nav-stacked > li {
+ float: none;
+}
+
+.nav-stacked > li > a {
+ margin-right: 0;
+}
+
+.nav-tabs.nav-stacked {
+ border-bottom: 0;
+}
+
+.nav-tabs.nav-stacked > li > a {
+ border: 1px solid #ddd;
+ -webkit-border-radius: 0;
+ -moz-border-radius: 0;
+ border-radius: 0;
+}
+
+.nav-tabs.nav-stacked > li:first-child > a {
+ -webkit-border-top-right-radius: 4px;
+ border-top-right-radius: 4px;
+ -webkit-border-top-left-radius: 4px;
+ border-top-left-radius: 4px;
+ -moz-border-radius-topright: 4px;
+ -moz-border-radius-topleft: 4px;
+}
+
+.nav-tabs.nav-stacked > li:last-child > a {
+ -webkit-border-bottom-right-radius: 4px;
+ border-bottom-right-radius: 4px;
+ -webkit-border-bottom-left-radius: 4px;
+ border-bottom-left-radius: 4px;
+ -moz-border-radius-bottomright: 4px;
+ -moz-border-radius-bottomleft: 4px;
+}
+
+.nav-tabs.nav-stacked > li > a:hover {
+ z-index: 2;
+ border-color: #ddd;
+}
+
+.nav-pills.nav-stacked > li > a {
+ margin-bottom: 3px;
+}
+
+.nav-pills.nav-stacked > li:last-child > a {
+ margin-bottom: 1px;
+}
+
+.nav-tabs .dropdown-menu {
+ -webkit-border-radius: 0 0 6px 6px;
+ -moz-border-radius: 0 0 6px 6px;
+ border-radius: 0 0 6px 6px;
+}
+
+.nav-pills .dropdown-menu {
+ -webkit-border-radius: 6px;
+ -moz-border-radius: 6px;
+ border-radius: 6px;
+}
+
+.nav .dropdown-toggle .caret {
+ margin-top: 6px;
+ border-top-color: #0088cc;
+ border-bottom-color: #0088cc;
+}
+
+.nav .dropdown-toggle:hover .caret {
+ border-top-color: #005580;
+ border-bottom-color: #005580;
+}
+
+/* move down carets for tabs */
+
+.nav-tabs .dropdown-toggle .caret {
+ margin-top: 8px;
+}
+
+.nav .active .dropdown-toggle .caret {
+ border-top-color: #fff;
+ border-bottom-color: #fff;
+}
+
+.nav-tabs .active .dropdown-toggle .caret {
+ border-top-color: #555555;
+ border-bottom-color: #555555;
+}
+
+.nav > .dropdown.active > a:hover {
+ cursor: pointer;
+}
+
+.nav-tabs .open .dropdown-toggle,
+.nav-pills .open .dropdown-toggle,
+.nav > li.dropdown.open.active > a:hover {
+ color: #ffffff;
+ background-color: #999999;
+ border-color: #999999;
+}
+
+.nav li.dropdown.open .caret,
+.nav li.dropdown.open.active .caret,
+.nav li.dropdown.open a:hover .caret {
+ border-top-color: #ffffff;
+ border-bottom-color: #ffffff;
+ opacity: 1;
+ filter: alpha(opacity=100);
+}
+
+.tabs-stacked .open > a:hover {
+ border-color: #999999;
+}
+
+.tabbable {
+ *zoom: 1;
+}
+
+.tabbable:before,
+.tabbable:after {
+ display: table;
+ line-height: 0;
+ content: "";
+}
+
+.tabbable:after {
+ clear: both;
+}
+
+.tab-content {
+ overflow: auto;
+}
+
+.tabs-below > .nav-tabs,
+.tabs-right > .nav-tabs,
+.tabs-left > .nav-tabs {
+ border-bottom: 0;
+}
+
+.tab-content > .tab-pane,
+.pill-content > .pill-pane {
+ display: none;
+}
+
+.tab-content > .active,
+.pill-content > .active {
+ display: block;
+}
+
+.tabs-below > .nav-tabs {
+ border-top: 1px solid #ddd;
+}
+
+.tabs-below > .nav-tabs > li {
+ margin-top: -1px;
+ margin-bottom: 0;
+}
+
+.tabs-below > .nav-tabs > li > a {
+ -webkit-border-radius: 0 0 4px 4px;
+ -moz-border-radius: 0 0 4px 4px;
+ border-radius: 0 0 4px 4px;
+}
+
+.tabs-below > .nav-tabs > li > a:hover {
+ border-top-color: #ddd;
+ border-bottom-color: transparent;
+}
+
+.tabs-below > .nav-tabs > .active > a,
+.tabs-below > .nav-tabs > .active > a:hover {
+ border-color: transparent #ddd #ddd #ddd;
+}
+
+.tabs-left > .nav-tabs > li,
+.tabs-right > .nav-tabs > li {
+ float: none;
+}
+
+.tabs-left > .nav-tabs > li > a,
+.tabs-right > .nav-tabs > li > a {
+ min-width: 74px;
+ margin-right: 0;
+ margin-bottom: 3px;
+}
+
+.tabs-left > .nav-tabs {
+ float: left;
+ margin-right: 19px;
+ border-right: 1px solid #ddd;
+}
+
+.tabs-left > .nav-tabs > li > a {
+ margin-right: -1px;
+ -webkit-border-radius: 4px 0 0 4px;
+ -moz-border-radius: 4px 0 0 4px;
+ border-radius: 4px 0 0 4px;
+}
+
+.tabs-left > .nav-tabs > li > a:hover {
+ border-color: #eeeeee #dddddd #eeeeee #eeeeee;
+}
+
+.tabs-left > .nav-tabs .active > a,
+.tabs-left > .nav-tabs .active > a:hover {
+ border-color: #ddd transparent #ddd #ddd;
+ *border-right-color: #ffffff;
+}
+
+.tabs-right > .nav-tabs {
+ float: right;
+ margin-left: 19px;
+ border-left: 1px solid #ddd;
+}
+
+.tabs-right > .nav-tabs > li > a {
+ margin-left: -1px;
+ -webkit-border-radius: 0 4px 4px 0;
+ -moz-border-radius: 0 4px 4px 0;
+ border-radius: 0 4px 4px 0;
+}
+
+.tabs-right > .nav-tabs > li > a:hover {
+ border-color: #eeeeee #eeeeee #eeeeee #dddddd;
+}
+
+.tabs-right > .nav-tabs .active > a,
+.tabs-right > .nav-tabs .active > a:hover {
+ border-color: #ddd #ddd #ddd transparent;
+ *border-left-color: #ffffff;
+}
+
+.nav > .disabled > a {
+ color: #999999;
+}
+
+.nav > .disabled > a:hover {
+ text-decoration: none;
+ cursor: default;
+ background-color: transparent;
+}
+
+.navbar {
+ *position: relative;
+ *z-index: 2;
+ margin-bottom: 20px;
+ overflow: visible;
+ color: #555555;
+}
+
+.navbar-inner {
+ min-height: 40px;
+ padding-right: 20px;
+ padding-left: 20px;
+ background-color: #fafafa;
+ background-image: -moz-linear-gradient(top, #ffffff, #f2f2f2);
+ background-image: -webkit-gradient(linear, 0 0, 0 100%, from(#ffffff), to(#f2f2f2));
+ background-image: -webkit-linear-gradient(top, #ffffff, #f2f2f2);
+ background-image: -o-linear-gradient(top, #ffffff, #f2f2f2);
+ background-image: linear-gradient(to bottom, #ffffff, #f2f2f2);
+ background-repeat: repeat-x;
+ border: 1px solid #d4d4d4;
+ -webkit-border-radius: 4px;
+ -moz-border-radius: 4px;
+ border-radius: 4px;
+ filter: progid:dximagetransform.microsoft.gradient(startColorstr='#ffffffff', endColorstr='#fff2f2f2', GradientType=0);
+ -webkit-box-shadow: 0 1px 4px rgba(0, 0, 0, 0.065);
+ -moz-box-shadow: 0 1px 4px rgba(0, 0, 0, 0.065);
+ box-shadow: 0 1px 4px rgba(0, 0, 0, 0.065);
+}
+
+.navbar .container {
+ width: auto;
+}
+
+.nav-collapse.collapse {
+ height: auto;
+}
+
+.navbar .brand {
+ display: block;
+ float: left;
+ padding: 10px 20px 10px;
+ margin-left: -20px;
+ font-size: 20px;
+ font-weight: 200;
+ color: #555555;
+ text-shadow: 0 1px 0 #ffffff;
+}
+
+.navbar .brand:hover {
+ text-decoration: none;
+}
+
+.navbar-text {
+ margin-bottom: 0;
+ line-height: 40px;
+}
+
+.navbar-link {
+ color: #555555;
+}
+
+.navbar-link:hover {
+ color: #333333;
+}
+
+.navbar .divider-vertical {
+ height: 40px;
+ margin: 0 9px;
+ border-right: 1px solid #ffffff;
+ border-left: 1px solid #f2f2f2;
+}
+
+.navbar .btn,
+.navbar .btn-group {
+ margin-top: 6px;
+}
+
+.navbar .btn-group .btn {
+ margin: 0;
+}
+
+.navbar-form {
+ margin-bottom: 0;
+ *zoom: 1;
+}
+
+.navbar-form:before,
+.navbar-form:after {
+ display: table;
+ line-height: 0;
+ content: "";
+}
+
+.navbar-form:after {
+ clear: both;
+}
+
+.navbar-form input,
+.navbar-form select,
+.navbar-form .radio,
+.navbar-form .checkbox {
+ margin-top: 5px;
+}
+
+.navbar-form input,
+.navbar-form select,
+.navbar-form .btn {
+ display: inline-block;
+ margin-bottom: 0;
+}
+
+.navbar-form input[type="image"],
+.navbar-form input[type="checkbox"],
+.navbar-form input[type="radio"] {
+ margin-top: 3px;
+}
+
+.navbar-form .input-append,
+.navbar-form .input-prepend {
+ margin-top: 6px;
+ white-space: nowrap;
+}
+
+.navbar-form .input-append input,
+.navbar-form .input-prepend input {
+ margin-top: 0;
+}
+
+.navbar-search {
+ position: relative;
+ float: left;
+ margin-top: 5px;
+ margin-bottom: 0;
+}
+
+.navbar-search .search-query {
+ padding: 4px 14px;
+ margin-bottom: 0;
+ font-family: "Helvetica Neue", Helvetica, Arial, sans-serif;
+ font-size: 13px;
+ font-weight: normal;
+ line-height: 1;
+ -webkit-border-radius: 15px;
+ -moz-border-radius: 15px;
+ border-radius: 15px;
+}
+
+.navbar-static-top {
+ position: static;
+ width: 100%;
+ margin-bottom: 0;
+}
+
+.navbar-static-top .navbar-inner {
+ -webkit-border-radius: 0;
+ -moz-border-radius: 0;
+ border-radius: 0;
+}
+
+.navbar-fixed-top,
+.navbar-fixed-bottom {
+ position: fixed;
+ right: 0;
+ left: 0;
+ z-index: 1030;
+ margin-bottom: 0;
+}
+
+.navbar-fixed-top .navbar-inner,
+.navbar-fixed-bottom .navbar-inner,
+.navbar-static-top .navbar-inner {
+ border: 0;
+}
+
+.navbar-fixed-top .navbar-inner,
+.navbar-fixed-bottom .navbar-inner {
+ padding-right: 0;
+ padding-left: 0;
+ -webkit-border-radius: 0;
+ -moz-border-radius: 0;
+ border-radius: 0;
+}
+
+.navbar-static-top .container,
+.navbar-fixed-top .container,
+.navbar-fixed-bottom .container {
+ width: 940px;
+}
+
+.navbar-fixed-top {
+ top: 0;
+}
+
+.navbar-fixed-top .navbar-inner,
+.navbar-static-top .navbar-inner {
+ -webkit-box-shadow: inset 0 -1px 0 rgba(0, 0, 0, 0.1), 0 1px 10px rgba(0, 0, 0, 0.1);
+ -moz-box-shadow: inset 0 -1px 0 rgba(0, 0, 0, 0.1), 0 1px 10px rgba(0, 0, 0, 0.1);
+ box-shadow: inset 0 -1px 0 rgba(0, 0, 0, 0.1), 0 1px 10px rgba(0, 0, 0, 0.1);
+}
+
+.navbar-fixed-bottom {
+ bottom: 0;
+}
+
+.navbar-fixed-bottom .navbar-inner {
+ -webkit-box-shadow: inset 0 1px 0 rgba(0, 0, 0, 0.1), 0 -1px 10px rgba(0, 0, 0, 0.1);
+ -moz-box-shadow: inset 0 1px 0 rgba(0, 0, 0, 0.1), 0 -1px 10px rgba(0, 0, 0, 0.1);
+ box-shadow: inset 0 1px 0 rgba(0, 0, 0, 0.1), 0 -1px 10px rgba(0, 0, 0, 0.1);
+}
+
+.navbar .nav {
+ position: relative;
+ left: 0;
+ display: block;
+ float: left;
+ margin: 0 10px 0 0;
+}
+
+.navbar .nav.pull-right {
+ float: right;
+}
+
+.navbar .nav > li {
+ float: left;
+}
+
+.navbar .nav > li > a {
+ float: none;
+ padding: 10px 15px 10px;
+ color: #555555;
+ text-decoration: none;
+ text-shadow: 0 1px 0 #ffffff;
+}
+
+.navbar .nav .dropdown-toggle .caret {
+ margin-top: 8px;
+}
+
+.navbar .nav > li > a:focus,
+.navbar .nav > li > a:hover {
+ color: #333333;
+ text-decoration: none;
+ background-color: transparent;
+}
+
+.navbar .nav > .active > a,
+.navbar .nav > .active > a:hover,
+.navbar .nav > .active > a:focus {
+ color: #555555;
+ text-decoration: none;
+ background-color: #e5e5e5;
+ -webkit-box-shadow: inset 0 3px 8px rgba(0, 0, 0, 0.125);
+ -moz-box-shadow: inset 0 3px 8px rgba(0, 0, 0, 0.125);
+ box-shadow: inset 0 3px 8px rgba(0, 0, 0, 0.125);
+}
+
+.navbar .btn-navbar {
+ display: none;
+ float: right;
+ padding: 7px 10px;
+ margin-right: 5px;
+ margin-left: 5px;
+ color: #ffffff;
+ text-shadow: 0 -1px 0 rgba(0, 0, 0, 0.25);
+ background-color: #ededed;
+ *background-color: #e5e5e5;
+ background-image: -webkit-gradient(linear, 0 0, 0 100%, from(#f2f2f2), to(#e5e5e5));
+ background-image: -webkit-linear-gradient(top, #f2f2f2, #e5e5e5);
+ background-image: -o-linear-gradient(top, #f2f2f2, #e5e5e5);
+ background-image: linear-gradient(to bottom, #f2f2f2, #e5e5e5);
+ background-image: -moz-linear-gradient(top, #f2f2f2, #e5e5e5);
+ background-repeat: repeat-x;
+ border-color: #e5e5e5 #e5e5e5 #bfbfbf;
+ border-color: rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.25);
+ filter: progid:dximagetransform.microsoft.gradient(startColorstr='#fff2f2f2', endColorstr='#ffe5e5e5', GradientType=0);
+ filter: progid:dximagetransform.microsoft.gradient(enabled=false);
+ -webkit-box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.1), 0 1px 0 rgba(255, 255, 255, 0.075);
+ -moz-box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.1), 0 1px 0 rgba(255, 255, 255, 0.075);
+ box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.1), 0 1px 0 rgba(255, 255, 255, 0.075);
+}
+
+.navbar .btn-navbar:hover,
+.navbar .btn-navbar:active,
+.navbar .btn-navbar.active,
+.navbar .btn-navbar.disabled,
+.navbar .btn-navbar[disabled] {
+ color: #ffffff;
+ background-color: #e5e5e5;
+ *background-color: #d9d9d9;
+}
+
+.navbar .btn-navbar:active,
+.navbar .btn-navbar.active {
+ background-color: #cccccc \9;
+}
+
+.navbar .btn-navbar .icon-bar {
+ display: block;
+ width: 18px;
+ height: 2px;
+ background-color: #f5f5f5;
+ -webkit-border-radius: 1px;
+ -moz-border-radius: 1px;
+ border-radius: 1px;
+ -webkit-box-shadow: 0 1px 0 rgba(0, 0, 0, 0.25);
+ -moz-box-shadow: 0 1px 0 rgba(0, 0, 0, 0.25);
+ box-shadow: 0 1px 0 rgba(0, 0, 0, 0.25);
+}
+
+.btn-navbar .icon-bar + .icon-bar {
+ margin-top: 3px;
+}
+
+.navbar .nav > li > .dropdown-menu:before {
+ position: absolute;
+ top: -7px;
+ left: 9px;
+ display: inline-block;
+ border-right: 7px solid transparent;
+ border-bottom: 7px solid #ccc;
+ border-left: 7px solid transparent;
+ border-bottom-color: rgba(0, 0, 0, 0.2);
+ content: '';
+}
+
+.navbar .nav > li > .dropdown-menu:after {
+ position: absolute;
+ top: -6px;
+ left: 10px;
+ display: inline-block;
+ border-right: 6px solid transparent;
+ border-bottom: 6px solid #ffffff;
+ border-left: 6px solid transparent;
+ content: '';
+}
+
+.navbar-fixed-bottom .nav > li > .dropdown-menu:before {
+ top: auto;
+ bottom: -7px;
+ border-top: 7px solid #ccc;
+ border-bottom: 0;
+ border-top-color: rgba(0, 0, 0, 0.2);
+}
+
+.navbar-fixed-bottom .nav > li > .dropdown-menu:after {
+ top: auto;
+ bottom: -6px;
+ border-top: 6px solid #ffffff;
+ border-bottom: 0;
+}
+
+.navbar .nav li.dropdown.open > .dropdown-toggle,
+.navbar .nav li.dropdown.active > .dropdown-toggle,
+.navbar .nav li.dropdown.open.active > .dropdown-toggle {
+ color: #555555;
+ background-color: #e5e5e5;
+}
+
+.navbar .nav li.dropdown > .dropdown-toggle .caret {
+ border-top-color: #555555;
+ border-bottom-color: #555555;
+}
+
+.navbar .nav li.dropdown.open > .dropdown-toggle .caret,
+.navbar .nav li.dropdown.active > .dropdown-toggle .caret,
+.navbar .nav li.dropdown.open.active > .dropdown-toggle .caret {
+ border-top-color: #555555;
+ border-bottom-color: #555555;
+}
+
+.navbar .pull-right > li > .dropdown-menu,
+.navbar .nav > li > .dropdown-menu.pull-right {
+ right: 0;
+ left: auto;
+}
+
+.navbar .pull-right > li > .dropdown-menu:before,
+.navbar .nav > li > .dropdown-menu.pull-right:before {
+ right: 12px;
+ left: auto;
+}
+
+.navbar .pull-right > li > .dropdown-menu:after,
+.navbar .nav > li > .dropdown-menu.pull-right:after {
+ right: 13px;
+ left: auto;
+}
+
+.navbar .pull-right > li > .dropdown-menu .dropdown-menu,
+.navbar .nav > li > .dropdown-menu.pull-right .dropdown-menu {
+ right: 100%;
+ left: auto;
+ margin-right: -1px;
+ margin-left: 0;
+ -webkit-border-radius: 6px 0 6px 6px;
+ -moz-border-radius: 6px 0 6px 6px;
+ border-radius: 6px 0 6px 6px;
+}
+
+.navbar-inverse {
+ color: #999999;
+}
+
+.navbar-inverse .navbar-inner {
+ background-color: #1b1b1b;
+ background-image: -moz-linear-gradient(top, #222222, #111111);
+ background-image: -webkit-gradient(linear, 0 0, 0 100%, from(#222222), to(#111111));
+ background-image: -webkit-linear-gradient(top, #222222, #111111);
+ background-image: -o-linear-gradient(top, #222222, #111111);
+ background-image: linear-gradient(to bottom, #222222, #111111);
+ background-repeat: repeat-x;
+ border-color: #252525;
+ filter: progid:dximagetransform.microsoft.gradient(startColorstr='#ff222222', endColorstr='#ff111111', GradientType=0);
+}
+
+.navbar-inverse .brand,
+.navbar-inverse .nav > li > a {
+ color: #999999;
+ text-shadow: 0 -1px 0 rgba(0, 0, 0, 0.25);
+}
+
+.navbar-inverse .brand:hover,
+.navbar-inverse .nav > li > a:hover {
+ color: #ffffff;
+}
+
+.navbar-inverse .nav > li > a:focus,
+.navbar-inverse .nav > li > a:hover {
+ color: #ffffff;
+ background-color: transparent;
+}
+
+.navbar-inverse .nav .active > a,
+.navbar-inverse .nav .active > a:hover,
+.navbar-inverse .nav .active > a:focus {
+ color: #ffffff;
+ background-color: #111111;
+}
+
+.navbar-inverse .navbar-link {
+ color: #999999;
+}
+
+.navbar-inverse .navbar-link:hover {
+ color: #ffffff;
+}
+
+.navbar-inverse .divider-vertical {
+ border-right-color: #222222;
+ border-left-color: #111111;
+}
+
+.navbar-inverse .nav li.dropdown.open > .dropdown-toggle,
+.navbar-inverse .nav li.dropdown.active > .dropdown-toggle,
+.navbar-inverse .nav li.dropdown.open.active > .dropdown-toggle {
+ color: #ffffff;
+ background-color: #111111;
+}
+
+.navbar-inverse .nav li.dropdown > .dropdown-toggle .caret {
+ border-top-color: #999999;
+ border-bottom-color: #999999;
+}
+
+.navbar-inverse .nav li.dropdown.open > .dropdown-toggle .caret,
+.navbar-inverse .nav li.dropdown.active > .dropdown-toggle .caret,
+.navbar-inverse .nav li.dropdown.open.active > .dropdown-toggle .caret {
+ border-top-color: #ffffff;
+ border-bottom-color: #ffffff;
+}
+
+.navbar-inverse .navbar-search .search-query {
+ color: #ffffff;
+ background-color: #515151;
+ border-color: #111111;
+ -webkit-box-shadow: inset 0 1px 2px rgba(0, 0, 0, 0.1), 0 1px 0 rgba(255, 255, 255, 0.15);
+ -moz-box-shadow: inset 0 1px 2px rgba(0, 0, 0, 0.1), 0 1px 0 rgba(255, 255, 255, 0.15);
+ box-shadow: inset 0 1px 2px rgba(0, 0, 0, 0.1), 0 1px 0 rgba(255, 255, 255, 0.15);
+ -webkit-transition: none;
+ -moz-transition: none;
+ -o-transition: none;
+ transition: none;
+}
+
+.navbar-inverse .navbar-search .search-query:-moz-placeholder {
+ color: #cccccc;
+}
+
+.navbar-inverse .navbar-search .search-query:-ms-input-placeholder {
+ color: #cccccc;
+}
+
+.navbar-inverse .navbar-search .search-query::-webkit-input-placeholder {
+ color: #cccccc;
+}
+
+.navbar-inverse .navbar-search .search-query:focus,
+.navbar-inverse .navbar-search .search-query.focused {
+ padding: 5px 15px;
+ color: #333333;
+ text-shadow: 0 1px 0 #ffffff;
+ background-color: #ffffff;
+ border: 0;
+ outline: 0;
+ -webkit-box-shadow: 0 0 3px rgba(0, 0, 0, 0.15);
+ -moz-box-shadow: 0 0 3px rgba(0, 0, 0, 0.15);
+ box-shadow: 0 0 3px rgba(0, 0, 0, 0.15);
+}
+
+.navbar-inverse .btn-navbar {
+ color: #ffffff;
+ text-shadow: 0 -1px 0 rgba(0, 0, 0, 0.25);
+ background-color: #0e0e0e;
+ *background-color: #040404;
+ background-image: -webkit-gradient(linear, 0 0, 0 100%, from(#151515), to(#040404));
+ background-image: -webkit-linear-gradient(top, #151515, #040404);
+ background-image: -o-linear-gradient(top, #151515, #040404);
+ background-image: linear-gradient(to bottom, #151515, #040404);
+ background-image: -moz-linear-gradient(top, #151515, #040404);
+ background-repeat: repeat-x;
+ border-color: #040404 #040404 #000000;
+ border-color: rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.25);
+ filter: progid:dximagetransform.microsoft.gradient(startColorstr='#ff151515', endColorstr='#ff040404', GradientType=0);
+ filter: progid:dximagetransform.microsoft.gradient(enabled=false);
+}
+
+.navbar-inverse .btn-navbar:hover,
+.navbar-inverse .btn-navbar:active,
+.navbar-inverse .btn-navbar.active,
+.navbar-inverse .btn-navbar.disabled,
+.navbar-inverse .btn-navbar[disabled] {
+ color: #ffffff;
+ background-color: #040404;
+ *background-color: #000000;
+}
+
+.navbar-inverse .btn-navbar:active,
+.navbar-inverse .btn-navbar.active {
+ background-color: #000000 \9;
+}
+
+.breadcrumb {
+ padding: 8px 15px;
+ margin: 0 0 20px;
+ list-style: none;
+ background-color: #f5f5f5;
+ -webkit-border-radius: 4px;
+ -moz-border-radius: 4px;
+ border-radius: 4px;
+}
+
+.breadcrumb li {
+ display: inline-block;
+ *display: inline;
+ text-shadow: 0 1px 0 #ffffff;
+ *zoom: 1;
+}
+
+.breadcrumb .divider {
+ padding: 0 5px;
+ color: #ccc;
+}
+
+.breadcrumb .active {
+ color: #999999;
+}
+
+.pagination {
+ height: 40px;
+ margin: 20px 0;
+}
+
+.pagination ul {
+ display: inline-block;
+ *display: inline;
+ margin-bottom: 0;
+ margin-left: 0;
+ -webkit-border-radius: 3px;
+ -moz-border-radius: 3px;
+ border-radius: 3px;
+ *zoom: 1;
+ -webkit-box-shadow: 0 1px 2px rgba(0, 0, 0, 0.05);
+ -moz-box-shadow: 0 1px 2px rgba(0, 0, 0, 0.05);
+ box-shadow: 0 1px 2px rgba(0, 0, 0, 0.05);
+}
+
+.pagination li {
+ display: inline;
+}
+
+.pagination a,
+.pagination span {
+ float: left;
+ padding: 0 14px;
+ line-height: 38px;
+ text-decoration: none;
+ background-color: #ffffff;
+ border: 1px solid #dddddd;
+ border-left-width: 0;
+}
+
+.pagination a:hover,
+.pagination .active a,
+.pagination .active span {
+ background-color: #f5f5f5;
+}
+
+.pagination .active a,
+.pagination .active span {
+ color: #999999;
+ cursor: default;
+}
+
+.pagination .disabled span,
+.pagination .disabled a,
+.pagination .disabled a:hover {
+ color: #999999;
+ cursor: default;
+ background-color: transparent;
+}
+
+.pagination li:first-child a,
+.pagination li:first-child span {
+ border-left-width: 1px;
+ -webkit-border-radius: 3px 0 0 3px;
+ -moz-border-radius: 3px 0 0 3px;
+ border-radius: 3px 0 0 3px;
+}
+
+.pagination li:last-child a,
+.pagination li:last-child span {
+ -webkit-border-radius: 0 3px 3px 0;
+ -moz-border-radius: 0 3px 3px 0;
+ border-radius: 0 3px 3px 0;
+}
+
+.pagination-centered {
+ text-align: center;
+}
+
+.pagination-right {
+ text-align: right;
+}
+
+.pager {
+ margin: 20px 0;
+ text-align: center;
+ list-style: none;
+ *zoom: 1;
+}
+
+.pager:before,
+.pager:after {
+ display: table;
+ line-height: 0;
+ content: "";
+}
+
+.pager:after {
+ clear: both;
+}
+
+.pager li {
+ display: inline;
+}
+
+.pager a {
+ display: inline-block;
+ padding: 5px 14px;
+ background-color: #fff;
+ border: 1px solid #ddd;
+ -webkit-border-radius: 15px;
+ -moz-border-radius: 15px;
+ border-radius: 15px;
+}
+
+.pager a:hover {
+ text-decoration: none;
+ background-color: #f5f5f5;
+}
+
+.pager .next a {
+ float: right;
+}
+
+.pager .previous a {
+ float: left;
+}
+
+.pager .disabled a,
+.pager .disabled a:hover {
+ color: #999999;
+ cursor: default;
+ background-color: #fff;
+}
+
+.modal-open .dropdown-menu {
+ z-index: 2050;
+}
+
+.modal-open .dropdown.open {
+ *z-index: 2050;
+}
+
+.modal-open .popover {
+ z-index: 2060;
+}
+
+.modal-open .tooltip {
+ z-index: 2080;
+}
+
+.modal-backdrop {
+ position: fixed;
+ top: 0;
+ right: 0;
+ bottom: 0;
+ left: 0;
+ z-index: 1040;
+ background-color: #000000;
+}
+
+.modal-backdrop.fade {
+ opacity: 0;
+}
+
+.modal-backdrop,
+.modal-backdrop.fade.in {
+ opacity: 0.8;
+ filter: alpha(opacity=80);
+}
+
+.modal {
+ position: fixed;
+ top: 50%;
+ left: 50%;
+ z-index: 1050;
+ width: 560px;
+ margin: -250px 0 0 -280px;
+ overflow: auto;
+ background-color: #ffffff;
+ border: 1px solid #999;
+ border: 1px solid rgba(0, 0, 0, 0.3);
+ *border: 1px solid #999;
+ -webkit-border-radius: 6px;
+ -moz-border-radius: 6px;
+ border-radius: 6px;
+ -webkit-box-shadow: 0 3px 7px rgba(0, 0, 0, 0.3);
+ -moz-box-shadow: 0 3px 7px rgba(0, 0, 0, 0.3);
+ box-shadow: 0 3px 7px rgba(0, 0, 0, 0.3);
+ -webkit-background-clip: padding-box;
+ -moz-background-clip: padding-box;
+ background-clip: padding-box;
+}
+
+.modal.fade {
+ top: -25%;
+ -webkit-transition: opacity 0.3s linear, top 0.3s ease-out;
+ -moz-transition: opacity 0.3s linear, top 0.3s ease-out;
+ -o-transition: opacity 0.3s linear, top 0.3s ease-out;
+ transition: opacity 0.3s linear, top 0.3s ease-out;
+}
+
+.modal.fade.in {
+ top: 50%;
+}
+
+.modal-header {
+ padding: 9px 15px;
+ border-bottom: 1px solid #eee;
+}
+
+.modal-header .close {
+ margin-top: 2px;
+}
+
+.modal-header h3 {
+ margin: 0;
+ line-height: 30px;
+}
+
+.modal-body {
+ max-height: 400px;
+ padding: 15px;
+ overflow-y: auto;
+}
+
+.modal-form {
+ margin-bottom: 0;
+}
+
+.modal-footer {
+ padding: 14px 15px 15px;
+ margin-bottom: 0;
+ text-align: right;
+ background-color: #f5f5f5;
+ border-top: 1px solid #ddd;
+ -webkit-border-radius: 0 0 6px 6px;
+ -moz-border-radius: 0 0 6px 6px;
+ border-radius: 0 0 6px 6px;
+ *zoom: 1;
+ -webkit-box-shadow: inset 0 1px 0 #ffffff;
+ -moz-box-shadow: inset 0 1px 0 #ffffff;
+ box-shadow: inset 0 1px 0 #ffffff;
+}
+
+.modal-footer:before,
+.modal-footer:after {
+ display: table;
+ line-height: 0;
+ content: "";
+}
+
+.modal-footer:after {
+ clear: both;
+}
+
+.modal-footer .btn + .btn {
+ margin-bottom: 0;
+ margin-left: 5px;
+}
+
+.modal-footer .btn-group .btn + .btn {
+ margin-left: -1px;
+}
+
+.tooltip {
+ position: absolute;
+ z-index: 1030;
+ display: block;
+ padding: 5px;
+ font-size: 11px;
+ opacity: 0;
+ filter: alpha(opacity=0);
+ visibility: visible;
+}
+
+.tooltip.in {
+ opacity: 0.8;
+ filter: alpha(opacity=80);
+}
+
+.tooltip.top {
+ margin-top: -3px;
+}
+
+.tooltip.right {
+ margin-left: 3px;
+}
+
+.tooltip.bottom {
+ margin-top: 3px;
+}
+
+.tooltip.left {
+ margin-left: -3px;
+}
+
+.tooltip-inner {
+ max-width: 200px;
+ padding: 3px 8px;
+ color: #ffffff;
+ text-align: center;
+ text-decoration: none;
+ background-color: #000000;
+ -webkit-border-radius: 4px;
+ -moz-border-radius: 4px;
+ border-radius: 4px;
+}
+
+.tooltip-arrow {
+ position: absolute;
+ width: 0;
+ height: 0;
+ border-color: transparent;
+ border-style: solid;
+}
+
+.tooltip.top .tooltip-arrow {
+ bottom: 0;
+ left: 50%;
+ margin-left: -5px;
+ border-top-color: #000000;
+ border-width: 5px 5px 0;
+}
+
+.tooltip.right .tooltip-arrow {
+ top: 50%;
+ left: 0;
+ margin-top: -5px;
+ border-right-color: #000000;
+ border-width: 5px 5px 5px 0;
+}
+
+.tooltip.left .tooltip-arrow {
+ top: 50%;
+ right: 0;
+ margin-top: -5px;
+ border-left-color: #000000;
+ border-width: 5px 0 5px 5px;
+}
+
+.tooltip.bottom .tooltip-arrow {
+ top: 0;
+ left: 50%;
+ margin-left: -5px;
+ border-bottom-color: #000000;
+ border-width: 0 5px 5px;
+}
+
+.popover {
+ position: absolute;
+ top: 0;
+ left: 0;
+ z-index: 1010;
+ display: none;
+ width: 236px;
+ padding: 1px;
+ background-color: #ffffff;
+ border: 1px solid #ccc;
+ border: 1px solid rgba(0, 0, 0, 0.2);
+ -webkit-border-radius: 6px;
+ -moz-border-radius: 6px;
+ border-radius: 6px;
+ -webkit-box-shadow: 0 5px 10px rgba(0, 0, 0, 0.2);
+ -moz-box-shadow: 0 5px 10px rgba(0, 0, 0, 0.2);
+ box-shadow: 0 5px 10px rgba(0, 0, 0, 0.2);
+ -webkit-background-clip: padding-box;
+ -moz-background-clip: padding;
+ background-clip: padding-box;
+}
+
+.popover.top {
+ margin-bottom: 10px;
+}
+
+.popover.right {
+ margin-left: 10px;
+}
+
+.popover.bottom {
+ margin-top: 10px;
+}
+
+.popover.left {
+ margin-right: 10px;
+}
+
+.popover-title {
+ padding: 8px 14px;
+ margin: 0;
+ font-size: 14px;
+ font-weight: normal;
+ line-height: 18px;
+ background-color: #f7f7f7;
+ border-bottom: 1px solid #ebebeb;
+ -webkit-border-radius: 5px 5px 0 0;
+ -moz-border-radius: 5px 5px 0 0;
+ border-radius: 5px 5px 0 0;
+}
+
+.popover-content {
+ padding: 9px 14px;
+}
+
+.popover-content p,
+.popover-content ul,
+.popover-content ol {
+ margin-bottom: 0;
+}
+
+.popover .arrow,
+.popover .arrow:after {
+ position: absolute;
+ display: inline-block;
+ width: 0;
+ height: 0;
+ border-color: transparent;
+ border-style: solid;
+}
+
+.popover .arrow:after {
+ z-index: -1;
+ content: "";
+}
+
+.popover.top .arrow {
+ bottom: -10px;
+ left: 50%;
+ margin-left: -10px;
+ border-top-color: #ffffff;
+ border-width: 10px 10px 0;
+}
+
+.popover.top .arrow:after {
+ bottom: -1px;
+ left: -11px;
+ border-top-color: rgba(0, 0, 0, 0.25);
+ border-width: 11px 11px 0;
+}
+
+.popover.right .arrow {
+ top: 50%;
+ left: -10px;
+ margin-top: -10px;
+ border-right-color: #ffffff;
+ border-width: 10px 10px 10px 0;
+}
+
+.popover.right .arrow:after {
+ bottom: -11px;
+ left: -1px;
+ border-right-color: rgba(0, 0, 0, 0.25);
+ border-width: 11px 11px 11px 0;
+}
+
+.popover.bottom .arrow {
+ top: -10px;
+ left: 50%;
+ margin-left: -10px;
+ border-bottom-color: #ffffff;
+ border-width: 0 10px 10px;
+}
+
+.popover.bottom .arrow:after {
+ top: -1px;
+ left: -11px;
+ border-bottom-color: rgba(0, 0, 0, 0.25);
+ border-width: 0 11px 11px;
+}
+
+.popover.left .arrow {
+ top: 50%;
+ right: -10px;
+ margin-top: -10px;
+ border-left-color: #ffffff;
+ border-width: 10px 0 10px 10px;
+}
+
+.popover.left .arrow:after {
+ right: -1px;
+ bottom: -11px;
+ border-left-color: rgba(0, 0, 0, 0.25);
+ border-width: 11px 0 11px 11px;
+}
+
+.thumbnails {
+ margin-left: -20px;
+ list-style: none;
+ *zoom: 1;
+}
+
+.thumbnails:before,
+.thumbnails:after {
+ display: table;
+ line-height: 0;
+ content: "";
+}
+
+.thumbnails:after {
+ clear: both;
+}
+
+.row-fluid .thumbnails {
+ margin-left: 0;
+}
+
+.thumbnails > li {
+ float: left;
+ margin-bottom: 20px;
+ margin-left: 20px;
+}
+
+.thumbnail {
+ display: block;
+ padding: 4px;
+ line-height: 20px;
+ border: 1px solid #ddd;
+ -webkit-border-radius: 4px;
+ -moz-border-radius: 4px;
+ border-radius: 4px;
+ -webkit-box-shadow: 0 1px 3px rgba(0, 0, 0, 0.055);
+ -moz-box-shadow: 0 1px 3px rgba(0, 0, 0, 0.055);
+ box-shadow: 0 1px 3px rgba(0, 0, 0, 0.055);
+ -webkit-transition: all 0.2s ease-in-out;
+ -moz-transition: all 0.2s ease-in-out;
+ -o-transition: all 0.2s ease-in-out;
+ transition: all 0.2s ease-in-out;
+}
+
+a.thumbnail:hover {
+ border-color: #0088cc;
+ -webkit-box-shadow: 0 1px 4px rgba(0, 105, 214, 0.25);
+ -moz-box-shadow: 0 1px 4px rgba(0, 105, 214, 0.25);
+ box-shadow: 0 1px 4px rgba(0, 105, 214, 0.25);
+}
+
+.thumbnail > img {
+ display: block;
+ max-width: 100%;
+ margin-right: auto;
+ margin-left: auto;
+}
+
+.thumbnail .caption {
+ padding: 9px;
+ color: #555555;
+}
+
+.label,
+.badge {
+ font-size: 11.844px;
+ font-weight: bold;
+ line-height: 14px;
+ color: #ffffff;
+ text-shadow: 0 -1px 0 rgba(0, 0, 0, 0.25);
+ white-space: nowrap;
+ vertical-align: baseline;
+ background-color: #999999;
+}
+
+.label {
+ padding: 1px 4px 2px;
+ -webkit-border-radius: 3px;
+ -moz-border-radius: 3px;
+ border-radius: 3px;
+}
+
+.badge {
+ padding: 1px 9px 2px;
+ -webkit-border-radius: 9px;
+ -moz-border-radius: 9px;
+ border-radius: 9px;
+}
+
+a.label:hover,
+a.badge:hover {
+ color: #ffffff;
+ text-decoration: none;
+ cursor: pointer;
+}
+
+.label-important,
+.badge-important {
+ background-color: #b94a48;
+}
+
+.label-important[href],
+.badge-important[href] {
+ background-color: #953b39;
+}
+
+.label-warning,
+.badge-warning {
+ background-color: #f89406;
+}
+
+.label-warning[href],
+.badge-warning[href] {
+ background-color: #c67605;
+}
+
+.label-success,
+.badge-success {
+ background-color: #468847;
+}
+
+.label-success[href],
+.badge-success[href] {
+ background-color: #356635;
+}
+
+.label-info,
+.badge-info {
+ background-color: #3a87ad;
+}
+
+.label-info[href],
+.badge-info[href] {
+ background-color: #2d6987;
+}
+
+.label-inverse,
+.badge-inverse {
+ background-color: #333333;
+}
+
+.label-inverse[href],
+.badge-inverse[href] {
+ background-color: #1a1a1a;
+}
+
+.btn .label,
+.btn .badge {
+ position: relative;
+ top: -1px;
+}
+
+.btn-mini .label,
+.btn-mini .badge {
+ top: 0;
+}
+
+@-webkit-keyframes progress-bar-stripes {
+ from {
+ background-position: 40px 0;
+ }
+ to {
+ background-position: 0 0;
+ }
+}
+
+@-moz-keyframes progress-bar-stripes {
+ from {
+ background-position: 40px 0;
+ }
+ to {
+ background-position: 0 0;
+ }
+}
+
+@-ms-keyframes progress-bar-stripes {
+ from {
+ background-position: 40px 0;
+ }
+ to {
+ background-position: 0 0;
+ }
+}
+
+@-o-keyframes progress-bar-stripes {
+ from {
+ background-position: 0 0;
+ }
+ to {
+ background-position: 40px 0;
+ }
+}
+
+@keyframes progress-bar-stripes {
+ from {
+ background-position: 40px 0;
+ }
+ to {
+ background-position: 0 0;
+ }
+}
+
+.progress {
+ height: 20px;
+ margin-bottom: 20px;
+ overflow: hidden;
+ background-color: #f7f7f7;
+ background-image: -moz-linear-gradient(top, #f5f5f5, #f9f9f9);
+ background-image: -webkit-gradient(linear, 0 0, 0 100%, from(#f5f5f5), to(#f9f9f9));
+ background-image: -webkit-linear-gradient(top, #f5f5f5, #f9f9f9);
+ background-image: -o-linear-gradient(top, #f5f5f5, #f9f9f9);
+ background-image: linear-gradient(to bottom, #f5f5f5, #f9f9f9);
+ background-repeat: repeat-x;
+ -webkit-border-radius: 4px;
+ -moz-border-radius: 4px;
+ border-radius: 4px;
+ filter: progid:dximagetransform.microsoft.gradient(startColorstr='#fff5f5f5', endColorstr='#fff9f9f9', GradientType=0);
+ -webkit-box-shadow: inset 0 1px 2px rgba(0, 0, 0, 0.1);
+ -moz-box-shadow: inset 0 1px 2px rgba(0, 0, 0, 0.1);
+ box-shadow: inset 0 1px 2px rgba(0, 0, 0, 0.1);
+}
+
+.progress .bar {
+ float: left;
+ width: 0;
+ height: 100%;
+ font-size: 12px;
+ color: #ffffff;
+ text-align: center;
+ text-shadow: 0 -1px 0 rgba(0, 0, 0, 0.25);
+ background-color: #0e90d2;
+ background-image: -moz-linear-gradient(top, #149bdf, #0480be);
+ background-image: -webkit-gradient(linear, 0 0, 0 100%, from(#149bdf), to(#0480be));
+ background-image: -webkit-linear-gradient(top, #149bdf, #0480be);
+ background-image: -o-linear-gradient(top, #149bdf, #0480be);
+ background-image: linear-gradient(to bottom, #149bdf, #0480be);
+ background-repeat: repeat-x;
+ filter: progid:dximagetransform.microsoft.gradient(startColorstr='#ff149bdf', endColorstr='#ff0480be', GradientType=0);
+ -webkit-box-shadow: inset 0 -1px 0 rgba(0, 0, 0, 0.15);
+ -moz-box-shadow: inset 0 -1px 0 rgba(0, 0, 0, 0.15);
+ box-shadow: inset 0 -1px 0 rgba(0, 0, 0, 0.15);
+ -webkit-box-sizing: border-box;
+ -moz-box-sizing: border-box;
+ box-sizing: border-box;
+ -webkit-transition: width 0.6s ease;
+ -moz-transition: width 0.6s ease;
+ -o-transition: width 0.6s ease;
+ transition: width 0.6s ease;
+}
+
+.progress .bar + .bar {
+ -webkit-box-shadow: inset 1px 0 0 rgba(0, 0, 0, 0.15), inset 0 -1px 0 rgba(0, 0, 0, 0.15);
+ -moz-box-shadow: inset 1px 0 0 rgba(0, 0, 0, 0.15), inset 0 -1px 0 rgba(0, 0, 0, 0.15);
+ box-shadow: inset 1px 0 0 rgba(0, 0, 0, 0.15), inset 0 -1px 0 rgba(0, 0, 0, 0.15);
+}
+
+.progress-striped .bar {
+ background-color: #149bdf;
+ background-image: -webkit-gradient(linear, 0 100%, 100% 0, color-stop(0.25, rgba(255, 255, 255, 0.15)), color-stop(0.25, transparent), color-stop(0.5, transparent), color-stop(0.5, rgba(255, 255, 255, 0.15)), color-stop(0.75, rgba(255, 255, 255, 0.15)), color-stop(0.75, transparent), to(transparent));
+ background-image: -webkit-linear-gradient(45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent);
+ background-image: -moz-linear-gradient(45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent);
+ background-image: -o-linear-gradient(45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent);
+ background-image: linear-gradient(45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent);
+ -webkit-background-size: 40px 40px;
+ -moz-background-size: 40px 40px;
+ -o-background-size: 40px 40px;
+ background-size: 40px 40px;
+}
+
+.progress.active .bar {
+ -webkit-animation: progress-bar-stripes 2s linear infinite;
+ -moz-animation: progress-bar-stripes 2s linear infinite;
+ -ms-animation: progress-bar-stripes 2s linear infinite;
+ -o-animation: progress-bar-stripes 2s linear infinite;
+ animation: progress-bar-stripes 2s linear infinite;
+}
+
+.progress-danger .bar,
+.progress .bar-danger {
+ background-color: #dd514c;
+ background-image: -moz-linear-gradient(top, #ee5f5b, #c43c35);
+ background-image: -webkit-gradient(linear, 0 0, 0 100%, from(#ee5f5b), to(#c43c35));
+ background-image: -webkit-linear-gradient(top, #ee5f5b, #c43c35);
+ background-image: -o-linear-gradient(top, #ee5f5b, #c43c35);
+ background-image: linear-gradient(to bottom, #ee5f5b, #c43c35);
+ background-repeat: repeat-x;
+ filter: progid:dximagetransform.microsoft.gradient(startColorstr='#ffee5f5b', endColorstr='#ffc43c35', GradientType=0);
+}
+
+.progress-danger.progress-striped .bar,
+.progress-striped .bar-danger {
+ background-color: #ee5f5b;
+ background-image: -webkit-gradient(linear, 0 100%, 100% 0, color-stop(0.25, rgba(255, 255, 255, 0.15)), color-stop(0.25, transparent), color-stop(0.5, transparent), color-stop(0.5, rgba(255, 255, 255, 0.15)), color-stop(0.75, rgba(255, 255, 255, 0.15)), color-stop(0.75, transparent), to(transparent));
+ background-image: -webkit-linear-gradient(45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent);
+ background-image: -moz-linear-gradient(45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent);
+ background-image: -o-linear-gradient(45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent);
+ background-image: linear-gradient(45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent);
+}
+
+.progress-success .bar,
+.progress .bar-success {
+ background-color: #5eb95e;
+ background-image: -moz-linear-gradient(top, #62c462, #57a957);
+ background-image: -webkit-gradient(linear, 0 0, 0 100%, from(#62c462), to(#57a957));
+ background-image: -webkit-linear-gradient(top, #62c462, #57a957);
+ background-image: -o-linear-gradient(top, #62c462, #57a957);
+ background-image: linear-gradient(to bottom, #62c462, #57a957);
+ background-repeat: repeat-x;
+ filter: progid:dximagetransform.microsoft.gradient(startColorstr='#ff62c462', endColorstr='#ff57a957', GradientType=0);
+}
+
+.progress-success.progress-striped .bar,
+.progress-striped .bar-success {
+ background-color: #62c462;
+ background-image: -webkit-gradient(linear, 0 100%, 100% 0, color-stop(0.25, rgba(255, 255, 255, 0.15)), color-stop(0.25, transparent), color-stop(0.5, transparent), color-stop(0.5, rgba(255, 255, 255, 0.15)), color-stop(0.75, rgba(255, 255, 255, 0.15)), color-stop(0.75, transparent), to(transparent));
+ background-image: -webkit-linear-gradient(45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent);
+ background-image: -moz-linear-gradient(45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent);
+ background-image: -o-linear-gradient(45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent);
+ background-image: linear-gradient(45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent);
+}
+
+.progress-info .bar,
+.progress .bar-info {
+ background-color: #4bb1cf;
+ background-image: -moz-linear-gradient(top, #5bc0de, #339bb9);
+ background-image: -webkit-gradient(linear, 0 0, 0 100%, from(#5bc0de), to(#339bb9));
+ background-image: -webkit-linear-gradient(top, #5bc0de, #339bb9);
+ background-image: -o-linear-gradient(top, #5bc0de, #339bb9);
+ background-image: linear-gradient(to bottom, #5bc0de, #339bb9);
+ background-repeat: repeat-x;
+ filter: progid:dximagetransform.microsoft.gradient(startColorstr='#ff5bc0de', endColorstr='#ff339bb9', GradientType=0);
+}
+
+.progress-info.progress-striped .bar,
+.progress-striped .bar-info {
+ background-color: #5bc0de;
+ background-image: -webkit-gradient(linear, 0 100%, 100% 0, color-stop(0.25, rgba(255, 255, 255, 0.15)), color-stop(0.25, transparent), color-stop(0.5, transparent), color-stop(0.5, rgba(255, 255, 255, 0.15)), color-stop(0.75, rgba(255, 255, 255, 0.15)), color-stop(0.75, transparent), to(transparent));
+ background-image: -webkit-linear-gradient(45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent);
+ background-image: -moz-linear-gradient(45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent);
+ background-image: -o-linear-gradient(45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent);
+ background-image: linear-gradient(45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent);
+}
+
+.progress-warning .bar,
+.progress .bar-warning {
+ background-color: #faa732;
+ background-image: -moz-linear-gradient(top, #fbb450, #f89406);
+ background-image: -webkit-gradient(linear, 0 0, 0 100%, from(#fbb450), to(#f89406));
+ background-image: -webkit-linear-gradient(top, #fbb450, #f89406);
+ background-image: -o-linear-gradient(top, #fbb450, #f89406);
+ background-image: linear-gradient(to bottom, #fbb450, #f89406);
+ background-repeat: repeat-x;
+ filter: progid:dximagetransform.microsoft.gradient(startColorstr='#fffbb450', endColorstr='#fff89406', GradientType=0);
+}
+
+.progress-warning.progress-striped .bar,
+.progress-striped .bar-warning {
+ background-color: #fbb450;
+ background-image: -webkit-gradient(linear, 0 100%, 100% 0, color-stop(0.25, rgba(255, 255, 255, 0.15)), color-stop(0.25, transparent), color-stop(0.5, transparent), color-stop(0.5, rgba(255, 255, 255, 0.15)), color-stop(0.75, rgba(255, 255, 255, 0.15)), color-stop(0.75, transparent), to(transparent));
+ background-image: -webkit-linear-gradient(45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent);
+ background-image: -moz-linear-gradient(45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent);
+ background-image: -o-linear-gradient(45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent);
+ background-image: linear-gradient(45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent);
+}
+
+.accordion {
+ margin-bottom: 20px;
+}
+
+.accordion-group {
+ margin-bottom: 2px;
+ border: 1px solid #e5e5e5;
+ -webkit-border-radius: 4px;
+ -moz-border-radius: 4px;
+ border-radius: 4px;
+}
+
+.accordion-heading {
+ border-bottom: 0;
+}
+
+.accordion-heading .accordion-toggle {
+ display: block;
+ padding: 8px 15px;
+}
+
+.accordion-toggle {
+ cursor: pointer;
+}
+
+.accordion-inner {
+ padding: 9px 15px;
+ border-top: 1px solid #e5e5e5;
+}
+
+.carousel {
+ position: relative;
+ margin-bottom: 20px;
+ line-height: 1;
+}
+
+.carousel-inner {
+ position: relative;
+ width: 100%;
+ overflow: hidden;
+}
+
+.carousel .item {
+ position: relative;
+ display: none;
+ -webkit-transition: 0.6s ease-in-out left;
+ -moz-transition: 0.6s ease-in-out left;
+ -o-transition: 0.6s ease-in-out left;
+ transition: 0.6s ease-in-out left;
+}
+
+.carousel .item > img {
+ display: block;
+ line-height: 1;
+}
+
+.carousel .active,
+.carousel .next,
+.carousel .prev {
+ display: block;
+}
+
+.carousel .active {
+ left: 0;
+}
+
+.carousel .next,
+.carousel .prev {
+ position: absolute;
+ top: 0;
+ width: 100%;
+}
+
+.carousel .next {
+ left: 100%;
+}
+
+.carousel .prev {
+ left: -100%;
+}
+
+.carousel .next.left,
+.carousel .prev.right {
+ left: 0;
+}
+
+.carousel .active.left {
+ left: -100%;
+}
+
+.carousel .active.right {
+ left: 100%;
+}
+
+.carousel-control {
+ position: absolute;
+ top: 40%;
+ left: 15px;
+ width: 40px;
+ height: 40px;
+ margin-top: -20px;
+ font-size: 60px;
+ font-weight: 100;
+ line-height: 30px;
+ color: #ffffff;
+ text-align: center;
+ background: #222222;
+ border: 3px solid #ffffff;
+ -webkit-border-radius: 23px;
+ -moz-border-radius: 23px;
+ border-radius: 23px;
+ opacity: 0.5;
+ filter: alpha(opacity=50);
+}
+
+.carousel-control.right {
+ right: 15px;
+ left: auto;
+}
+
+.carousel-control:hover {
+ color: #ffffff;
+ text-decoration: none;
+ opacity: 0.9;
+ filter: alpha(opacity=90);
+}
+
+.carousel-caption {
+ position: absolute;
+ right: 0;
+ bottom: 0;
+ left: 0;
+ padding: 15px;
+ background: #333333;
+ background: rgba(0, 0, 0, 0.75);
+}
+
+.carousel-caption h4,
+.carousel-caption p {
+ line-height: 20px;
+ color: #ffffff;
+}
+
+.carousel-caption h4 {
+ margin: 0 0 5px;
+}
+
+.carousel-caption p {
+ margin-bottom: 0;
+}
+
+.hero-unit {
+ padding: 60px;
+ margin-bottom: 30px;
+ background-color: #eeeeee;
+ -webkit-border-radius: 6px;
+ -moz-border-radius: 6px;
+ border-radius: 6px;
+}
+
+.hero-unit h1 {
+ margin-bottom: 0;
+ font-size: 60px;
+ line-height: 1;
+ letter-spacing: -1px;
+ color: inherit;
+}
+
+.hero-unit p {
+ font-size: 18px;
+ font-weight: 200;
+ line-height: 30px;
+ color: inherit;
+}
+
+.pull-right {
+ float: right;
+}
+
+.pull-left {
+ float: left;
+}
+
+.hide {
+ display: none;
+}
+
+.show {
+ display: block;
+}
+
+.invisible {
+ visibility: hidden;
+}
+
+.affix {
+ position: fixed;
+}
diff --git a/nikola/data/themes/default/assets/css/code.css b/nikola/data/themes/default/assets/css/code.css
new file mode 100644
index 0000000..b1d7ace
--- /dev/null
+++ b/nikola/data/themes/default/assets/css/code.css
@@ -0,0 +1,62 @@
+pre { word-break: pre; white-space: pre; word-wrap: pre; overflow: auto; max-width: 100%;}
+td.linenos { vertical-align: top; width: 4em;}
+div.code > pre, .code
+{ background: #f8f8f8; white-space: pre;}
+.code .c { color: #008800; font-style: italic } /* Comment */
+.code .err { border: 1px solid #FF0000 } /* Error */
+.code .k { color: #AA22FF; font-weight: bold } /* Keyword */
+.code .o { color: #666666 } /* Operator */
+.code .cm { color: #008800; font-style: italic } /* Comment.Multiline */
+.code .cp { color: #008800 } /* Comment.Preproc */
+.code .c1 { color: #008800; font-style: italic } /* Comment.Single */
+.code .cs { color: #008800; font-weight: bold } /* Comment.Special */
+.code .gd { color: #A00000 } /* Generic.Deleted */
+.code .ge { font-style: italic } /* Generic.Emph */
+.code .gr { color: #FF0000 } /* Generic.Error */
+.code .gh { color: #000080; font-weight: bold } /* Generic.Heading */
+.code .gi { color: #00A000 } /* Generic.Inserted */
+.code .go { color: #808080 } /* Generic.Output */
+.code .gp { color: #000080; font-weight: bold } /* Generic.Prompt */
+.code .gs { font-weight: bold } /* Generic.Strong */
+.code .gu { color: #800080; font-weight: bold } /* Generic.Subheading */
+.code .gt { color: #0040D0 } /* Generic.Traceback */
+.code .kc { color: #AA22FF; font-weight: bold } /* Keyword.Constant */
+.code .kd { color: #AA22FF; font-weight: bold } /* Keyword.Declaration */
+.code .kp { color: #AA22FF } /* Keyword.Pseudo */
+.code .kr { color: #AA22FF; font-weight: bold } /* Keyword.Reserved */
+.code .kt { color: #AA22FF; font-weight: bold } /* Keyword.Type */
+.code .m { color: #666666 } /* Literal.Number */
+.code .s { color: #BB4444 } /* Literal.String */
+.code .na { color: #BB4444 } /* Name.Attribute */
+.code .nb { color: #AA22FF } /* Name.Builtin */
+.code .nc { color: #0000FF } /* Name.Class */
+.code .no { color: #880000 } /* Name.Constant */
+.code .nd { color: #AA22FF } /* Name.Decorator */
+.code .ni { color: #999999; font-weight: bold } /* Name.Entity */
+.code .ne { color: #D2413A; font-weight: bold } /* Name.Exception */
+.code .nf { color: #00A000 } /* Name.Function */
+.code .nl { color: #A0A000 } /* Name.Label */
+.code .nn { color: #0000FF; font-weight: bold } /* Name.Namespace */
+.code .nt { color: #008000; font-weight: bold } /* Name.Tag */
+.code .nv { color: #B8860B } /* Name.Variable */
+.code .ow { color: #AA22FF; font-weight: bold } /* Operator.Word */
+.code .mf { color: #666666 } /* Literal.Number.Float */
+.code .mh { color: #666666 } /* Literal.Number.Hex */
+.code .mi { color: #666666 } /* Literal.Number.Integer */
+.code .mo { color: #666666 } /* Literal.Number.Oct */
+.code .sb { color: #BB4444 } /* Literal.String.Backtick */
+.code .sc { color: #BB4444 } /* Literal.String.Char */
+.code .sd { color: #BB4444; font-style: italic } /* Literal.String.Doc */
+.code .s2 { color: #BB4444 } /* Literal.String.Double */
+.code .se { color: #BB6622; font-weight: bold } /* Literal.String.Escape */
+.code .sh { color: #BB4444 } /* Literal.String.Heredoc */
+.code .si { color: #BB6688; font-weight: bold } /* Literal.String.Interpol */
+.code .sx { color: #008000 } /* Literal.String.Other */
+.code .sr { color: #BB6688 } /* Literal.String.Regex */
+.code .s1 { color: #BB4444 } /* Literal.String.Single */
+.code .ss { color: #B8860B } /* Literal.String.Symbol */
+.code .bp { color: #AA22FF } /* Name.Builtin.Pseudo */
+.code .vc { color: #B8860B } /* Name.Variable.Class */
+.code .vg { color: #B8860B } /* Name.Variable.Global */
+.code .vi { color: #B8860B } /* Name.Variable.Instance */
+.code .il { color: #666666 } /* Literal.Number.Integer.Long */
diff --git a/nikola/data/themes/default/assets/css/colorbox.css b/nikola/data/themes/default/assets/css/colorbox.css
new file mode 100644
index 0000000..f67c346
--- /dev/null
+++ b/nikola/data/themes/default/assets/css/colorbox.css
@@ -0,0 +1,85 @@
+/*
+ ColorBox Core Style:
+ The following CSS is consistent between example themes and should not be altered.
+*/
+#colorbox, #cboxOverlay, #cboxWrapper{position:absolute; top:0; left:0; z-index:9999; overflow:hidden;}
+#cboxOverlay{position:fixed; width:100%; height:100%;}
+#cboxMiddleLeft, #cboxBottomLeft{clear:left;}
+#cboxContent{position:relative;}
+#cboxLoadedContent{overflow:auto;}
+#cboxTitle{margin:0;}
+#cboxLoadingOverlay, #cboxLoadingGraphic{position:absolute; top:0; left:0; width:100%; height:100%;}
+#cboxPrevious, #cboxNext, #cboxClose, #cboxSlideshow{cursor:pointer;}
+.cboxPhoto{float:left; margin:auto; border:0; display:block;}
+.cboxIframe{width:100%; height:100%; display:block; border:0;}
+
+/*
+ User Style:
+ Change the following styles to modify the appearance of ColorBox. They are
+ ordered & tabbed in a way that represents the nesting of the generated HTML.
+*/
+#cboxOverlay{background:url(images/overlay.png) repeat 0 0;}
+#colorbox{}
+ #cboxTopLeft{width:21px; height:21px; background:url(images/controls.png) no-repeat -101px 0;}
+ #cboxTopRight{width:21px; height:21px; background:url(images/controls.png) no-repeat -130px 0;}
+ #cboxBottomLeft{width:21px; height:21px; background:url(images/controls.png) no-repeat -101px -29px;}
+ #cboxBottomRight{width:21px; height:21px; background:url(images/controls.png) no-repeat -130px -29px;}
+ #cboxMiddleLeft{width:21px; background:url(images/controls.png) left top repeat-y;}
+ #cboxMiddleRight{width:21px; background:url(images/controls.png) right top repeat-y;}
+ #cboxTopCenter{height:21px; background:url(images/border.png) 0 0 repeat-x;}
+ #cboxBottomCenter{height:21px; background:url(images/border.png) 0 -29px repeat-x;}
+ #cboxContent{background:#fff; overflow:hidden;}
+ .cboxIframe{background:#fff;}
+ #cboxError{padding:50px; border:1px solid #ccc;}
+ #cboxLoadedContent{margin-bottom:28px;}
+ #cboxTitle{position:absolute; bottom:4px; left:0; text-align:center; width:100%; color:#949494;}
+ #cboxCurrent{position:absolute; bottom:4px; left:58px; color:#949494;}
+ #cboxSlideshow{position:absolute; bottom:4px; right:30px; color:#0092ef;}
+ #cboxPrevious{position:absolute; bottom:0; left:0; background:url(images/controls.png) no-repeat -75px 0; width:25px; height:25px; text-indent:-9999px;}
+ #cboxPrevious:hover{background-position:-75px -25px;}
+ #cboxNext{position:absolute; bottom:0; left:27px; background:url(images/controls.png) no-repeat -50px 0; width:25px; height:25px; text-indent:-9999px;}
+ #cboxNext:hover{background-position:-50px -25px;}
+ #cboxLoadingOverlay{background:url(images/loading_background.png) no-repeat center center;}
+ #cboxLoadingGraphic{background:url(images/loading.gif) no-repeat center center;}
+ #cboxClose{position:absolute; bottom:0; right:0; background:url(images/controls.png) no-repeat -25px 0; width:25px; height:25px; text-indent:-9999px;}
+ #cboxClose:hover{background-position:-25px -25px;}
+
+/*
+ The following fixes a problem where IE7 and IE8 replace a PNG's alpha transparency with a black fill
+ when an alpha filter (opacity change) is set on the element or ancestor element. This style is not applied to or needed in IE9.
+ See: http://jacklmoore.com/notes/ie-transparency-problems/
+*/
+.cboxIE #cboxTopLeft,
+.cboxIE #cboxTopCenter,
+.cboxIE #cboxTopRight,
+.cboxIE #cboxBottomLeft,
+.cboxIE #cboxBottomCenter,
+.cboxIE #cboxBottomRight,
+.cboxIE #cboxMiddleLeft,
+.cboxIE #cboxMiddleRight {
+ filter: progid:DXImageTransform.Microsoft.gradient(startColorstr=#00FFFFFF,endColorstr=#00FFFFFF);
+}
+
+/*
+ The following provides PNG transparency support for IE6
+ Feel free to remove this and the /ie6/ directory if you have dropped IE6 support.
+*/
+.cboxIE6 #cboxTopLeft{background:url(images/ie6/borderTopLeft.png);}
+.cboxIE6 #cboxTopCenter{background:url(images/ie6/borderTopCenter.png);}
+.cboxIE6 #cboxTopRight{background:url(images/ie6/borderTopRight.png);}
+.cboxIE6 #cboxBottomLeft{background:url(images/ie6/borderBottomLeft.png);}
+.cboxIE6 #cboxBottomCenter{background:url(images/ie6/borderBottomCenter.png);}
+.cboxIE6 #cboxBottomRight{background:url(images/ie6/borderBottomRight.png);}
+.cboxIE6 #cboxMiddleLeft{background:url(images/ie6/borderMiddleLeft.png);}
+.cboxIE6 #cboxMiddleRight{background:url(images/ie6/borderMiddleRight.png);}
+
+.cboxIE6 #cboxTopLeft,
+.cboxIE6 #cboxTopCenter,
+.cboxIE6 #cboxTopRight,
+.cboxIE6 #cboxBottomLeft,
+.cboxIE6 #cboxBottomCenter,
+.cboxIE6 #cboxBottomRight,
+.cboxIE6 #cboxMiddleLeft,
+.cboxIE6 #cboxMiddleRight {
+ _behavior: expression(this.src = this.src ? this.src : this.currentStyle.backgroundImage.split('"')[1], this.style.background = "none", this.style.filter = "progid:DXImageTransform.Microsoft.AlphaImageLoader(src=" + this.src + ", sizingMethod='scale')");
+}
diff --git a/nikola/data/themes/default/assets/css/images/border.png b/nikola/data/themes/default/assets/css/images/border.png
new file mode 100644
index 0000000..f463a10
--- /dev/null
+++ b/nikola/data/themes/default/assets/css/images/border.png
Binary files differ
diff --git a/nikola/data/themes/default/assets/css/images/controls.png b/nikola/data/themes/default/assets/css/images/controls.png
new file mode 100644
index 0000000..dcfd6fb
--- /dev/null
+++ b/nikola/data/themes/default/assets/css/images/controls.png
Binary files differ
diff --git a/nikola/data/themes/default/assets/css/images/ie6/borderBottomCenter.png b/nikola/data/themes/default/assets/css/images/ie6/borderBottomCenter.png
new file mode 100644
index 0000000..0d4475e
--- /dev/null
+++ b/nikola/data/themes/default/assets/css/images/ie6/borderBottomCenter.png
Binary files differ
diff --git a/nikola/data/themes/default/assets/css/images/ie6/borderBottomLeft.png b/nikola/data/themes/default/assets/css/images/ie6/borderBottomLeft.png
new file mode 100644
index 0000000..2775eba
--- /dev/null
+++ b/nikola/data/themes/default/assets/css/images/ie6/borderBottomLeft.png
Binary files differ
diff --git a/nikola/data/themes/default/assets/css/images/ie6/borderBottomRight.png b/nikola/data/themes/default/assets/css/images/ie6/borderBottomRight.png
new file mode 100644
index 0000000..f7f5137
--- /dev/null
+++ b/nikola/data/themes/default/assets/css/images/ie6/borderBottomRight.png
Binary files differ
diff --git a/nikola/data/themes/default/assets/css/images/ie6/borderMiddleLeft.png b/nikola/data/themes/default/assets/css/images/ie6/borderMiddleLeft.png
new file mode 100644
index 0000000..a2d63d1
--- /dev/null
+++ b/nikola/data/themes/default/assets/css/images/ie6/borderMiddleLeft.png
Binary files differ
diff --git a/nikola/data/themes/default/assets/css/images/ie6/borderMiddleRight.png b/nikola/data/themes/default/assets/css/images/ie6/borderMiddleRight.png
new file mode 100644
index 0000000..fd7c3e8
--- /dev/null
+++ b/nikola/data/themes/default/assets/css/images/ie6/borderMiddleRight.png
Binary files differ
diff --git a/nikola/data/themes/default/assets/css/images/ie6/borderTopCenter.png b/nikola/data/themes/default/assets/css/images/ie6/borderTopCenter.png
new file mode 100644
index 0000000..2937a9c
--- /dev/null
+++ b/nikola/data/themes/default/assets/css/images/ie6/borderTopCenter.png
Binary files differ
diff --git a/nikola/data/themes/default/assets/css/images/ie6/borderTopLeft.png b/nikola/data/themes/default/assets/css/images/ie6/borderTopLeft.png
new file mode 100644
index 0000000..f9d458b
--- /dev/null
+++ b/nikola/data/themes/default/assets/css/images/ie6/borderTopLeft.png
Binary files differ
diff --git a/nikola/data/themes/default/assets/css/images/ie6/borderTopRight.png b/nikola/data/themes/default/assets/css/images/ie6/borderTopRight.png
new file mode 100644
index 0000000..74b8583
--- /dev/null
+++ b/nikola/data/themes/default/assets/css/images/ie6/borderTopRight.png
Binary files differ
diff --git a/nikola/data/themes/default/assets/css/images/loading.gif b/nikola/data/themes/default/assets/css/images/loading.gif
new file mode 100644
index 0000000..b4695d8
--- /dev/null
+++ b/nikola/data/themes/default/assets/css/images/loading.gif
Binary files differ
diff --git a/nikola/data/themes/default/assets/css/images/loading_background.png b/nikola/data/themes/default/assets/css/images/loading_background.png
new file mode 100644
index 0000000..6ae83e6
--- /dev/null
+++ b/nikola/data/themes/default/assets/css/images/loading_background.png
Binary files differ
diff --git a/nikola/data/themes/default/assets/css/images/overlay.png b/nikola/data/themes/default/assets/css/images/overlay.png
new file mode 100644
index 0000000..53ea98f
--- /dev/null
+++ b/nikola/data/themes/default/assets/css/images/overlay.png
Binary files differ
diff --git a/nikola/data/themes/default/assets/css/rst.css b/nikola/data/themes/default/assets/css/rst.css
new file mode 100644
index 0000000..1f0edcb
--- /dev/null
+++ b/nikola/data/themes/default/assets/css/rst.css
@@ -0,0 +1,315 @@
+/*
+:Author: David Goodger
+:Contact: goodger@users.sourceforge.net
+:Date: $Date: 2005-12-18 01:56:14 +0100 (Sun, 18 Dec 2005) $
+:Revision: $Revision: 4224 $
+:Copyright: This stylesheet has been placed in the public domain.
+
+Default cascading style sheet for the HTML output of Docutils.
+
+See http://docutils.sf.net/docs/howto/html-stylesheets.html for how to
+customize this style sheet.
+*/
+
+/* used to remove borders from tables and images */
+.borderless, table.borderless td, table.borderless th {
+ border: 0 }
+
+table.borderless td, table.borderless th {
+ /* Override padding for "table.docutils td" with "! important".
+ The right padding separates the table cells. */
+ padding: 0 0.5em 0 0 ! important }
+
+.first {
+ /* Override more specific margin styles with "! important". */
+ margin-top: 0 ! important }
+
+.last, .with-subtitle {
+ margin-bottom: 0 ! important }
+
+.hidden {
+ display: none }
+
+a.toc-backref {
+ text-decoration: none ;
+ color: black }
+
+blockquote.epigraph {
+ margin: 2em 1em ; }
+
+dl.docutils dd {
+ margin-bottom: 0.5em }
+
+/* Uncomment (and remove this text!) to get bold-faced definition list terms
+dl.docutils dt {
+ font-weight: bold }
+*/
+
+div.abstract {
+ margin: 2em 5em }
+
+div.abstract p.topic-title {
+ font-weight: bold ;
+ text-align: center }
+
+div.admonition, div.attention, div.caution, div.danger, div.error,
+div.hint, div.important, div.note, div.tip, div.warning {
+ padding: 8px 35px 8px 14px;
+ margin-bottom: 18px;
+ text-shadow: 0 1px 0 rgba(255, 255, 255, 0.5);
+ background-color: #d9edf7;
+ color: #3a87ad;
+ border: 1px solid #bce8f1;
+ -webkit-border-radius: 4px;
+ -moz-border-radius: 4px;
+ border-radius: 4px;
+}
+
+div.admonition p.admonition-title, div.hint p.admonition-title,
+div.important p.admonition-title, div.note p.admonition-title,
+div.tip p.admonition-title {
+ font-weight: bold ;
+ font-family: sans-serif }
+
+div.attention p.admonition-title, div.caution p.admonition-title,
+div.danger p.admonition-title, div.error p.admonition-title,
+div.warning p.admonition-title {
+ color: red ;
+ font-weight: bold ;
+ font-family: sans-serif }
+
+/* Uncomment (and remove this text!) to get reduced vertical space in
+ compound paragraphs.
+div.compound .compound-first, div.compound .compound-middle {
+ margin-bottom: 0.5em }
+
+div.compound .compound-last, div.compound .compound-middle {
+ margin-top: 0.5em }
+*/
+
+div.dedication {
+ margin: 2em 5em ;
+ text-align: center ;
+ font-style: italic }
+
+div.dedication p.topic-title {
+ font-weight: bold ;
+ font-style: normal }
+
+div.figure {
+ text-align: center;
+ margin-left: 2em ;
+ margin-right: 2em }
+
+div.footer, div.header {
+ clear: both;
+ font-size: smaller }
+
+div.line-block {
+ display: block ;
+ margin-top: 1em ;
+ margin-bottom: 1em }
+
+div.line-block div.line-block {
+ margin-top: 0 ;
+ margin-bottom: 0 ;
+ margin-left: 1.5em }
+
+div.sidebar {
+ margin-left: 1em ;
+ border: medium outset ;
+ padding: 1em ;
+ background-color: #ffffee ;
+ width: 40% ;
+ float: right ;
+ clear: right }
+
+div.sidebar p.rubric {
+ font-family: sans-serif ;
+ font-size: medium }
+
+div.system-messages {
+ margin: 5em }
+
+div.system-messages h1 {
+ color: red }
+
+div.system-message {
+ padding: 8px 35px 8px 14px;
+ margin-bottom: 18px;
+ text-shadow: 0 1px 0 rgba(255, 255, 255, 0.5);
+ border: 1px solid #eed3d7;
+ -webkit-border-radius: 4px;
+ -moz-border-radius: 4px;
+ border-radius: 4px;
+ padding: 1em;
+ background-color: #f2dede;
+ color: #b94a48;
+
+}
+
+div.system-message p.system-message-title {
+ color: inherit ;
+ font-weight: bold }
+
+div.topic {
+ margin: 2em }
+
+h1.section-subtitle, h2.section-subtitle, h3.section-subtitle,
+h4.section-subtitle, h5.section-subtitle, h6.section-subtitle {
+ margin-top: 0.4em }
+
+h1.title {
+ text-align: center }
+
+h2.subtitle {
+ text-align: center }
+
+hr.docutils {
+ width: 75% }
+
+img.align-left {
+ clear: left }
+
+img.align-right {
+ clear: right }
+
+ol.simple, ul.simple {
+ margin-bottom: 1em }
+
+ol.arabic {
+ list-style: decimal }
+
+ol.loweralpha {
+ list-style: lower-alpha }
+
+ol.upperalpha {
+ list-style: upper-alpha }
+
+ol.lowerroman {
+ list-style: lower-roman }
+
+ol.upperroman {
+ list-style: upper-roman }
+
+p.attribution {
+ text-align: right ;
+ margin-left: 50% }
+
+p.caption {
+ font-style: italic }
+
+p.credits {
+ font-style: italic ;
+ font-size: smaller }
+
+p.label {
+ white-space: nowrap }
+
+p.rubric {
+ font-weight: bold ;
+ font-size: larger ;
+ color: maroon ;
+ text-align: center }
+
+p.sidebar-title {
+ font-family: sans-serif ;
+ font-weight: bold ;
+ font-size: larger }
+
+p.sidebar-subtitle {
+ font-family: sans-serif ;
+ font-weight: bold }
+
+p.topic-title {
+ font-weight: bold }
+
+pre.address {
+ margin-bottom: 0 ;
+ margin-top: 0 ;
+ font-family: serif ;
+ font-size: 100% }
+
+pre.literal-block, pre.doctest-block {
+ margin: 0 0 0 0 ;
+ background-color: #eeeeee;
+ padding: 1em;
+ overflow: auto;
+/* font-family: "Courier New", Courier, monospace;*/
+}
+
+span.classifier {
+ font-family: sans-serif ;
+ font-style: oblique }
+
+span.classifier-delimiter {
+ font-family: sans-serif ;
+ font-weight: bold }
+
+span.interpreted {
+ font-family: sans-serif }
+
+span.option {
+ white-space: nowrap }
+
+span.pre {
+ white-space: pre }
+
+span.problematic {
+ color: red }
+
+span.section-subtitle {
+ /* font-size relative to parent (h1..h6 element) */
+ font-size: 80% }
+
+table.citation {
+ border-left: solid 1px gray;
+ margin-left: 1px }
+
+table.docinfo {
+ margin: 2em 4em }
+
+table.docutils {
+ margin-top: 0.5em ;
+ margin-bottom: 0.5em }
+
+table.footnote {
+ border-left: solid 1px black;
+ margin-left: 1px }
+
+table.docutils td, table.docutils th,
+table.docinfo td, table.docinfo th {
+ padding-left: 0.5em ;
+ padding-right: 0.5em ;
+ vertical-align: top }
+
+table.docutils th.field-name, table.docinfo th.docinfo-name {
+ font-weight: bold ;
+ text-align: left ;
+ white-space: nowrap ;
+ padding-left: 0 }
+
+h1 tt.docutils, h2 tt.docutils, h3 tt.docutils,
+h4 tt.docutils, h5 tt.docutils, h6 tt.docutils {
+ font-size: 100% }
+
+tt.docutils {
+ background-color: #eeeeee }
+
+ul.auto-toc {
+ list-style-type: none }
+
+#blog-title {
+ font-size: 34pt;
+ /*margin:0 0.3em -14px;*/
+ background-color: #FFF;
+ font-family: "courier";
+ text-align: right;
+ margin-top: 20px;
+ margin-bottom: 10px;
+}
+
+img {
+ margin-top: 12px;
+ margin-bottom: 12px;
+}
diff --git a/nikola/data/themes/default/assets/css/theme.css b/nikola/data/themes/default/assets/css/theme.css
new file mode 100644
index 0000000..7fe11ac
--- /dev/null
+++ b/nikola/data/themes/default/assets/css/theme.css
@@ -0,0 +1,26 @@
+#container {
+ width: 960px;
+ margin: 0 auto;
+}
+
+#contentcolumn {
+ max-width: 760px;
+}
+#q {
+ width: 150px;
+}
+
+img {
+ max-width: 90%;
+}
+
+.postbox {
+ border-bottom: 2px solid darkgrey;
+ margin-bottom: 12px;
+}
+
+.titlebox {
+ text-align: right;
+}
+
+#addthisbox {margin-bottom: 12px;}
diff --git a/nikola/data/themes/default/assets/img/glyphicons-halflings-white.png b/nikola/data/themes/default/assets/img/glyphicons-halflings-white.png
new file mode 100644
index 0000000..3bf6484
--- /dev/null
+++ b/nikola/data/themes/default/assets/img/glyphicons-halflings-white.png
Binary files differ
diff --git a/nikola/data/themes/default/assets/img/glyphicons-halflings.png b/nikola/data/themes/default/assets/img/glyphicons-halflings.png
new file mode 100644
index 0000000..a996999
--- /dev/null
+++ b/nikola/data/themes/default/assets/img/glyphicons-halflings.png
Binary files differ
diff --git a/nikola/data/themes/default/assets/js/bootstrap.js b/nikola/data/themes/default/assets/js/bootstrap.js
new file mode 100644
index 0000000..7f303eb
--- /dev/null
+++ b/nikola/data/themes/default/assets/js/bootstrap.js
@@ -0,0 +1,2027 @@
+/* ===================================================
+ * bootstrap-transition.js v2.1.0
+ * http://twitter.github.com/bootstrap/javascript.html#transitions
+ * ===================================================
+ * Copyright 2012 Twitter, Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * ========================================================== */
+
+
+!function ($) {
+
+ $(function () {
+
+ "use strict"; // jshint ;_;
+
+
+ /* CSS TRANSITION SUPPORT (http://www.modernizr.com/)
+ * ======================================================= */
+
+ $.support.transition = (function () {
+
+ var transitionEnd = (function () {
+
+ var el = document.createElement('bootstrap')
+ , transEndEventNames = {
+ 'WebkitTransition' : 'webkitTransitionEnd'
+ , 'MozTransition' : 'transitionend'
+ , 'OTransition' : 'oTransitionEnd otransitionend'
+ , 'transition' : 'transitionend'
+ }
+ , name
+
+ for (name in transEndEventNames){
+ if (el.style[name] !== undefined) {
+ return transEndEventNames[name]
+ }
+ }
+
+ }())
+
+ return transitionEnd && {
+ end: transitionEnd
+ }
+
+ })()
+
+ })
+
+}(window.jQuery);/* ==========================================================
+ * bootstrap-alert.js v2.1.0
+ * http://twitter.github.com/bootstrap/javascript.html#alerts
+ * ==========================================================
+ * Copyright 2012 Twitter, Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * ========================================================== */
+
+
+!function ($) {
+
+ "use strict"; // jshint ;_;
+
+
+ /* ALERT CLASS DEFINITION
+ * ====================== */
+
+ var dismiss = '[data-dismiss="alert"]'
+ , Alert = function (el) {
+ $(el).on('click', dismiss, this.close)
+ }
+
+ Alert.prototype.close = function (e) {
+ var $this = $(this)
+ , selector = $this.attr('data-target')
+ , $parent
+
+ if (!selector) {
+ selector = $this.attr('href')
+ selector = selector && selector.replace(/.*(?=#[^\s]*$)/, '') //strip for ie7
+ }
+
+ $parent = $(selector)
+
+ e && e.preventDefault()
+
+ $parent.length || ($parent = $this.hasClass('alert') ? $this : $this.parent())
+
+ $parent.trigger(e = $.Event('close'))
+
+ if (e.isDefaultPrevented()) return
+
+ $parent.removeClass('in')
+
+ function removeElement() {
+ $parent
+ .trigger('closed')
+ .remove()
+ }
+
+ $.support.transition && $parent.hasClass('fade') ?
+ $parent.on($.support.transition.end, removeElement) :
+ removeElement()
+ }
+
+
+ /* ALERT PLUGIN DEFINITION
+ * ======================= */
+
+ $.fn.alert = function (option) {
+ return this.each(function () {
+ var $this = $(this)
+ , data = $this.data('alert')
+ if (!data) $this.data('alert', (data = new Alert(this)))
+ if (typeof option == 'string') data[option].call($this)
+ })
+ }
+
+ $.fn.alert.Constructor = Alert
+
+
+ /* ALERT DATA-API
+ * ============== */
+
+ $(function () {
+ $('body').on('click.alert.data-api', dismiss, Alert.prototype.close)
+ })
+
+}(window.jQuery);/* ============================================================
+ * bootstrap-button.js v2.1.0
+ * http://twitter.github.com/bootstrap/javascript.html#buttons
+ * ============================================================
+ * Copyright 2012 Twitter, Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * ============================================================ */
+
+
+!function ($) {
+
+ "use strict"; // jshint ;_;
+
+
+ /* BUTTON PUBLIC CLASS DEFINITION
+ * ============================== */
+
+ var Button = function (element, options) {
+ this.$element = $(element)
+ this.options = $.extend({}, $.fn.button.defaults, options)
+ }
+
+ Button.prototype.setState = function (state) {
+ var d = 'disabled'
+ , $el = this.$element
+ , data = $el.data()
+ , val = $el.is('input') ? 'val' : 'html'
+
+ state = state + 'Text'
+ data.resetText || $el.data('resetText', $el[val]())
+
+ $el[val](data[state] || this.options[state])
+
+ // push to event loop to allow forms to submit
+ setTimeout(function () {
+ state == 'loadingText' ?
+ $el.addClass(d).attr(d, d) :
+ $el.removeClass(d).removeAttr(d)
+ }, 0)
+ }
+
+ Button.prototype.toggle = function () {
+ var $parent = this.$element.parent('[data-toggle="buttons-radio"]')
+
+ $parent && $parent
+ .find('.active')
+ .removeClass('active')
+
+ this.$element.toggleClass('active')
+ }
+
+
+ /* BUTTON PLUGIN DEFINITION
+ * ======================== */
+
+ $.fn.button = function (option) {
+ return this.each(function () {
+ var $this = $(this)
+ , data = $this.data('button')
+ , options = typeof option == 'object' && option
+ if (!data) $this.data('button', (data = new Button(this, options)))
+ if (option == 'toggle') data.toggle()
+ else if (option) data.setState(option)
+ })
+ }
+
+ $.fn.button.defaults = {
+ loadingText: 'loading...'
+ }
+
+ $.fn.button.Constructor = Button
+
+
+ /* BUTTON DATA-API
+ * =============== */
+
+ $(function () {
+ $('body').on('click.button.data-api', '[data-toggle^=button]', function ( e ) {
+ var $btn = $(e.target)
+ if (!$btn.hasClass('btn')) $btn = $btn.closest('.btn')
+ $btn.button('toggle')
+ })
+ })
+
+}(window.jQuery);/* ==========================================================
+ * bootstrap-carousel.js v2.1.0
+ * http://twitter.github.com/bootstrap/javascript.html#carousel
+ * ==========================================================
+ * Copyright 2012 Twitter, Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * ========================================================== */
+
+
+!function ($) {
+
+ "use strict"; // jshint ;_;
+
+
+ /* CAROUSEL CLASS DEFINITION
+ * ========================= */
+
+ var Carousel = function (element, options) {
+ this.$element = $(element)
+ this.options = options
+ this.options.slide && this.slide(this.options.slide)
+ this.options.pause == 'hover' && this.$element
+ .on('mouseenter', $.proxy(this.pause, this))
+ .on('mouseleave', $.proxy(this.cycle, this))
+ }
+
+ Carousel.prototype = {
+
+ cycle: function (e) {
+ if (!e) this.paused = false
+ this.options.interval
+ && !this.paused
+ && (this.interval = setInterval($.proxy(this.next, this), this.options.interval))
+ return this
+ }
+
+ , to: function (pos) {
+ var $active = this.$element.find('.item.active')
+ , children = $active.parent().children()
+ , activePos = children.index($active)
+ , that = this
+
+ if (pos > (children.length - 1) || pos < 0) return
+
+ if (this.sliding) {
+ return this.$element.one('slid', function () {
+ that.to(pos)
+ })
+ }
+
+ if (activePos == pos) {
+ return this.pause().cycle()
+ }
+
+ return this.slide(pos > activePos ? 'next' : 'prev', $(children[pos]))
+ }
+
+ , pause: function (e) {
+ if (!e) this.paused = true
+ if (this.$element.find('.next, .prev').length && $.support.transition.end) {
+ this.$element.trigger($.support.transition.end)
+ this.cycle()
+ }
+ clearInterval(this.interval)
+ this.interval = null
+ return this
+ }
+
+ , next: function () {
+ if (this.sliding) return
+ return this.slide('next')
+ }
+
+ , prev: function () {
+ if (this.sliding) return
+ return this.slide('prev')
+ }
+
+ , slide: function (type, next) {
+ var $active = this.$element.find('.item.active')
+ , $next = next || $active[type]()
+ , isCycling = this.interval
+ , direction = type == 'next' ? 'left' : 'right'
+ , fallback = type == 'next' ? 'first' : 'last'
+ , that = this
+ , e = $.Event('slide', {
+ relatedTarget: $next[0]
+ })
+
+ this.sliding = true
+
+ isCycling && this.pause()
+
+ $next = $next.length ? $next : this.$element.find('.item')[fallback]()
+
+ if ($next.hasClass('active')) return
+
+ if ($.support.transition && this.$element.hasClass('slide')) {
+ this.$element.trigger(e)
+ if (e.isDefaultPrevented()) return
+ $next.addClass(type)
+ $next[0].offsetWidth // force reflow
+ $active.addClass(direction)
+ $next.addClass(direction)
+ this.$element.one($.support.transition.end, function () {
+ $next.removeClass([type, direction].join(' ')).addClass('active')
+ $active.removeClass(['active', direction].join(' '))
+ that.sliding = false
+ setTimeout(function () { that.$element.trigger('slid') }, 0)
+ })
+ } else {
+ this.$element.trigger(e)
+ if (e.isDefaultPrevented()) return
+ $active.removeClass('active')
+ $next.addClass('active')
+ this.sliding = false
+ this.$element.trigger('slid')
+ }
+
+ isCycling && this.cycle()
+
+ return this
+ }
+
+ }
+
+
+ /* CAROUSEL PLUGIN DEFINITION
+ * ========================== */
+
+ $.fn.carousel = function (option) {
+ return this.each(function () {
+ var $this = $(this)
+ , data = $this.data('carousel')
+ , options = $.extend({}, $.fn.carousel.defaults, typeof option == 'object' && option)
+ , action = typeof option == 'string' ? option : options.slide
+ if (!data) $this.data('carousel', (data = new Carousel(this, options)))
+ if (typeof option == 'number') data.to(option)
+ else if (action) data[action]()
+ else if (options.interval) data.cycle()
+ })
+ }
+
+ $.fn.carousel.defaults = {
+ interval: 5000
+ , pause: 'hover'
+ }
+
+ $.fn.carousel.Constructor = Carousel
+
+
+ /* CAROUSEL DATA-API
+ * ================= */
+
+ $(function () {
+ $('body').on('click.carousel.data-api', '[data-slide]', function ( e ) {
+ var $this = $(this), href
+ , $target = $($this.attr('data-target') || (href = $this.attr('href')) && href.replace(/.*(?=#[^\s]+$)/, '')) //strip for ie7
+ , options = !$target.data('modal') && $.extend({}, $target.data(), $this.data())
+ $target.carousel(options)
+ e.preventDefault()
+ })
+ })
+
+}(window.jQuery);/* =============================================================
+ * bootstrap-collapse.js v2.1.0
+ * http://twitter.github.com/bootstrap/javascript.html#collapse
+ * =============================================================
+ * Copyright 2012 Twitter, Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * ============================================================ */
+
+
+!function ($) {
+
+ "use strict"; // jshint ;_;
+
+
+ /* COLLAPSE PUBLIC CLASS DEFINITION
+ * ================================ */
+
+ var Collapse = function (element, options) {
+ this.$element = $(element)
+ this.options = $.extend({}, $.fn.collapse.defaults, options)
+
+ if (this.options.parent) {
+ this.$parent = $(this.options.parent)
+ }
+
+ this.options.toggle && this.toggle()
+ }
+
+ Collapse.prototype = {
+
+ constructor: Collapse
+
+ , dimension: function () {
+ var hasWidth = this.$element.hasClass('width')
+ return hasWidth ? 'width' : 'height'
+ }
+
+ , show: function () {
+ var dimension
+ , scroll
+ , actives
+ , hasData
+
+ if (this.transitioning) return
+
+ dimension = this.dimension()
+ scroll = $.camelCase(['scroll', dimension].join('-'))
+ actives = this.$parent && this.$parent.find('> .accordion-group > .in')
+
+ if (actives && actives.length) {
+ hasData = actives.data('collapse')
+ if (hasData && hasData.transitioning) return
+ actives.collapse('hide')
+ hasData || actives.data('collapse', null)
+ }
+
+ this.$element[dimension](0)
+ this.transition('addClass', $.Event('show'), 'shown')
+ $.support.transition && this.$element[dimension](this.$element[0][scroll])
+ }
+
+ , hide: function () {
+ var dimension
+ if (this.transitioning) return
+ dimension = this.dimension()
+ this.reset(this.$element[dimension]())
+ this.transition('removeClass', $.Event('hide'), 'hidden')
+ this.$element[dimension](0)
+ }
+
+ , reset: function (size) {
+ var dimension = this.dimension()
+
+ this.$element
+ .removeClass('collapse')
+ [dimension](size || 'auto')
+ [0].offsetWidth
+
+ this.$element[size !== null ? 'addClass' : 'removeClass']('collapse')
+
+ return this
+ }
+
+ , transition: function (method, startEvent, completeEvent) {
+ var that = this
+ , complete = function () {
+ if (startEvent.type == 'show') that.reset()
+ that.transitioning = 0
+ that.$element.trigger(completeEvent)
+ }
+
+ this.$element.trigger(startEvent)
+
+ if (startEvent.isDefaultPrevented()) return
+
+ this.transitioning = 1
+
+ this.$element[method]('in')
+
+ $.support.transition && this.$element.hasClass('collapse') ?
+ this.$element.one($.support.transition.end, complete) :
+ complete()
+ }
+
+ , toggle: function () {
+ this[this.$element.hasClass('in') ? 'hide' : 'show']()
+ }
+
+ }
+
+
+ /* COLLAPSIBLE PLUGIN DEFINITION
+ * ============================== */
+
+ $.fn.collapse = function (option) {
+ return this.each(function () {
+ var $this = $(this)
+ , data = $this.data('collapse')
+ , options = typeof option == 'object' && option
+ if (!data) $this.data('collapse', (data = new Collapse(this, options)))
+ if (typeof option == 'string') data[option]()
+ })
+ }
+
+ $.fn.collapse.defaults = {
+ toggle: true
+ }
+
+ $.fn.collapse.Constructor = Collapse
+
+
+ /* COLLAPSIBLE DATA-API
+ * ==================== */
+
+ $(function () {
+ $('body').on('click.collapse.data-api', '[data-toggle=collapse]', function (e) {
+ var $this = $(this), href
+ , target = $this.attr('data-target')
+ || e.preventDefault()
+ || (href = $this.attr('href')) && href.replace(/.*(?=#[^\s]+$)/, '') //strip for ie7
+ , option = $(target).data('collapse') ? 'toggle' : $this.data()
+ $this[$(target).hasClass('in') ? 'addClass' : 'removeClass']('collapsed')
+ $(target).collapse(option)
+ })
+ })
+
+}(window.jQuery);/* ============================================================
+ * bootstrap-dropdown.js v2.1.0
+ * http://twitter.github.com/bootstrap/javascript.html#dropdowns
+ * ============================================================
+ * Copyright 2012 Twitter, Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * ============================================================ */
+
+
+!function ($) {
+
+ "use strict"; // jshint ;_;
+
+
+ /* DROPDOWN CLASS DEFINITION
+ * ========================= */
+
+ var toggle = '[data-toggle=dropdown]'
+ , Dropdown = function (element) {
+ var $el = $(element).on('click.dropdown.data-api', this.toggle)
+ $('html').on('click.dropdown.data-api', function () {
+ $el.parent().removeClass('open')
+ })
+ }
+
+ Dropdown.prototype = {
+
+ constructor: Dropdown
+
+ , toggle: function (e) {
+ var $this = $(this)
+ , $parent
+ , isActive
+
+ if ($this.is('.disabled, :disabled')) return
+
+ $parent = getParent($this)
+
+ isActive = $parent.hasClass('open')
+
+ clearMenus()
+
+ if (!isActive) {
+ $parent.toggleClass('open')
+ $this.focus()
+ }
+
+ return false
+ }
+
+ , keydown: function (e) {
+ var $this
+ , $items
+ , $active
+ , $parent
+ , isActive
+ , index
+
+ if (!/(38|40|27)/.test(e.keyCode)) return
+
+ $this = $(this)
+
+ e.preventDefault()
+ e.stopPropagation()
+
+ if ($this.is('.disabled, :disabled')) return
+
+ $parent = getParent($this)
+
+ isActive = $parent.hasClass('open')
+
+ if (!isActive || (isActive && e.keyCode == 27)) return $this.click()
+
+ $items = $('[role=menu] li:not(.divider) a', $parent)
+
+ if (!$items.length) return
+
+ index = $items.index($items.filter(':focus'))
+
+ if (e.keyCode == 38 && index > 0) index-- // up
+ if (e.keyCode == 40 && index < $items.length - 1) index++ // down
+ if (!~index) index = 0
+
+ $items
+ .eq(index)
+ .focus()
+ }
+
+ }
+
+ function clearMenus() {
+ getParent($(toggle))
+ .removeClass('open')
+ }
+
+ function getParent($this) {
+ var selector = $this.attr('data-target')
+ , $parent
+
+ if (!selector) {
+ selector = $this.attr('href')
+ selector = selector && selector.replace(/.*(?=#[^\s]*$)/, '') //strip for ie7
+ }
+
+ $parent = $(selector)
+ $parent.length || ($parent = $this.parent())
+
+ return $parent
+ }
+
+
+ /* DROPDOWN PLUGIN DEFINITION
+ * ========================== */
+
+ $.fn.dropdown = function (option) {
+ return this.each(function () {
+ var $this = $(this)
+ , data = $this.data('dropdown')
+ if (!data) $this.data('dropdown', (data = new Dropdown(this)))
+ if (typeof option == 'string') data[option].call($this)
+ })
+ }
+
+ $.fn.dropdown.Constructor = Dropdown
+
+
+ /* APPLY TO STANDARD DROPDOWN ELEMENTS
+ * =================================== */
+
+ $(function () {
+ $('html')
+ .on('click.dropdown.data-api touchstart.dropdown.data-api', clearMenus)
+ $('body')
+ .on('click.dropdown touchstart.dropdown.data-api', '.dropdown', function (e) { e.stopPropagation() })
+ .on('click.dropdown.data-api touchstart.dropdown.data-api' , toggle, Dropdown.prototype.toggle)
+ .on('keydown.dropdown.data-api touchstart.dropdown.data-api', toggle + ', [role=menu]' , Dropdown.prototype.keydown)
+ })
+
+}(window.jQuery);/* =========================================================
+ * bootstrap-modal.js v2.1.0
+ * http://twitter.github.com/bootstrap/javascript.html#modals
+ * =========================================================
+ * Copyright 2012 Twitter, Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * ========================================================= */
+
+
+!function ($) {
+
+ "use strict"; // jshint ;_;
+
+
+ /* MODAL CLASS DEFINITION
+ * ====================== */
+
+ var Modal = function (element, options) {
+ this.options = options
+ this.$element = $(element)
+ .delegate('[data-dismiss="modal"]', 'click.dismiss.modal', $.proxy(this.hide, this))
+ this.options.remote && this.$element.find('.modal-body').load(this.options.remote)
+ }
+
+ Modal.prototype = {
+
+ constructor: Modal
+
+ , toggle: function () {
+ return this[!this.isShown ? 'show' : 'hide']()
+ }
+
+ , show: function () {
+ var that = this
+ , e = $.Event('show')
+
+ this.$element.trigger(e)
+
+ if (this.isShown || e.isDefaultPrevented()) return
+
+ $('body').addClass('modal-open')
+
+ this.isShown = true
+
+ this.escape()
+
+ this.backdrop(function () {
+ var transition = $.support.transition && that.$element.hasClass('fade')
+
+ if (!that.$element.parent().length) {
+ that.$element.appendTo(document.body) //don't move modals dom position
+ }
+
+ that.$element
+ .show()
+
+ if (transition) {
+ that.$element[0].offsetWidth // force reflow
+ }
+
+ that.$element
+ .addClass('in')
+ .attr('aria-hidden', false)
+ .focus()
+
+ that.enforceFocus()
+
+ transition ?
+ that.$element.one($.support.transition.end, function () { that.$element.trigger('shown') }) :
+ that.$element.trigger('shown')
+
+ })
+ }
+
+ , hide: function (e) {
+ e && e.preventDefault()
+
+ var that = this
+
+ e = $.Event('hide')
+
+ this.$element.trigger(e)
+
+ if (!this.isShown || e.isDefaultPrevented()) return
+
+ this.isShown = false
+
+ $('body').removeClass('modal-open')
+
+ this.escape()
+
+ $(document).off('focusin.modal')
+
+ this.$element
+ .removeClass('in')
+ .attr('aria-hidden', true)
+
+ $.support.transition && this.$element.hasClass('fade') ?
+ this.hideWithTransition() :
+ this.hideModal()
+ }
+
+ , enforceFocus: function () {
+ var that = this
+ $(document).on('focusin.modal', function (e) {
+ if (that.$element[0] !== e.target && !that.$element.has(e.target).length) {
+ that.$element.focus()
+ }
+ })
+ }
+
+ , escape: function () {
+ var that = this
+ if (this.isShown && this.options.keyboard) {
+ this.$element.on('keyup.dismiss.modal', function ( e ) {
+ e.which == 27 && that.hide()
+ })
+ } else if (!this.isShown) {
+ this.$element.off('keyup.dismiss.modal')
+ }
+ }
+
+ , hideWithTransition: function () {
+ var that = this
+ , timeout = setTimeout(function () {
+ that.$element.off($.support.transition.end)
+ that.hideModal()
+ }, 500)
+
+ this.$element.one($.support.transition.end, function () {
+ clearTimeout(timeout)
+ that.hideModal()
+ })
+ }
+
+ , hideModal: function (that) {
+ this.$element
+ .hide()
+ .trigger('hidden')
+
+ this.backdrop()
+ }
+
+ , removeBackdrop: function () {
+ this.$backdrop.remove()
+ this.$backdrop = null
+ }
+
+ , backdrop: function (callback) {
+ var that = this
+ , animate = this.$element.hasClass('fade') ? 'fade' : ''
+
+ if (this.isShown && this.options.backdrop) {
+ var doAnimate = $.support.transition && animate
+
+ this.$backdrop = $('<div class="modal-backdrop ' + animate + '" />')
+ .appendTo(document.body)
+
+ if (this.options.backdrop != 'static') {
+ this.$backdrop.click($.proxy(this.hide, this))
+ }
+
+ if (doAnimate) this.$backdrop[0].offsetWidth // force reflow
+
+ this.$backdrop.addClass('in')
+
+ doAnimate ?
+ this.$backdrop.one($.support.transition.end, callback) :
+ callback()
+
+ } else if (!this.isShown && this.$backdrop) {
+ this.$backdrop.removeClass('in')
+
+ $.support.transition && this.$element.hasClass('fade')?
+ this.$backdrop.one($.support.transition.end, $.proxy(this.removeBackdrop, this)) :
+ this.removeBackdrop()
+
+ } else if (callback) {
+ callback()
+ }
+ }
+ }
+
+
+ /* MODAL PLUGIN DEFINITION
+ * ======================= */
+
+ $.fn.modal = function (option) {
+ return this.each(function () {
+ var $this = $(this)
+ , data = $this.data('modal')
+ , options = $.extend({}, $.fn.modal.defaults, $this.data(), typeof option == 'object' && option)
+ if (!data) $this.data('modal', (data = new Modal(this, options)))
+ if (typeof option == 'string') data[option]()
+ else if (options.show) data.show()
+ })
+ }
+
+ $.fn.modal.defaults = {
+ backdrop: true
+ , keyboard: true
+ , show: true
+ }
+
+ $.fn.modal.Constructor = Modal
+
+
+ /* MODAL DATA-API
+ * ============== */
+
+ $(function () {
+ $('body').on('click.modal.data-api', '[data-toggle="modal"]', function ( e ) {
+ var $this = $(this)
+ , href = $this.attr('href')
+ , $target = $($this.attr('data-target') || (href && href.replace(/.*(?=#[^\s]+$)/, ''))) //strip for ie7
+ , option = $target.data('modal') ? 'toggle' : $.extend({ remote: !/#/.test(href) && href }, $target.data(), $this.data())
+
+ e.preventDefault()
+
+ $target
+ .modal(option)
+ .one('hide', function () {
+ $this.focus()
+ })
+ })
+ })
+
+}(window.jQuery);/* ===========================================================
+ * bootstrap-tooltip.js v2.1.0
+ * http://twitter.github.com/bootstrap/javascript.html#tooltips
+ * Inspired by the original jQuery.tipsy by Jason Frame
+ * ===========================================================
+ * Copyright 2012 Twitter, Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * ========================================================== */
+
+
+!function ($) {
+
+ "use strict"; // jshint ;_;
+
+
+ /* TOOLTIP PUBLIC CLASS DEFINITION
+ * =============================== */
+
+ var Tooltip = function (element, options) {
+ this.init('tooltip', element, options)
+ }
+
+ Tooltip.prototype = {
+
+ constructor: Tooltip
+
+ , init: function (type, element, options) {
+ var eventIn
+ , eventOut
+
+ this.type = type
+ this.$element = $(element)
+ this.options = this.getOptions(options)
+ this.enabled = true
+
+ if (this.options.trigger == 'click') {
+ this.$element.on('click.' + this.type, this.options.selector, $.proxy(this.toggle, this))
+ } else if (this.options.trigger != 'manual') {
+ eventIn = this.options.trigger == 'hover' ? 'mouseenter' : 'focus'
+ eventOut = this.options.trigger == 'hover' ? 'mouseleave' : 'blur'
+ this.$element.on(eventIn + '.' + this.type, this.options.selector, $.proxy(this.enter, this))
+ this.$element.on(eventOut + '.' + this.type, this.options.selector, $.proxy(this.leave, this))
+ }
+
+ this.options.selector ?
+ (this._options = $.extend({}, this.options, { trigger: 'manual', selector: '' })) :
+ this.fixTitle()
+ }
+
+ , getOptions: function (options) {
+ options = $.extend({}, $.fn[this.type].defaults, options, this.$element.data())
+
+ if (options.delay && typeof options.delay == 'number') {
+ options.delay = {
+ show: options.delay
+ , hide: options.delay
+ }
+ }
+
+ return options
+ }
+
+ , enter: function (e) {
+ var self = $(e.currentTarget)[this.type](this._options).data(this.type)
+
+ if (!self.options.delay || !self.options.delay.show) return self.show()
+
+ clearTimeout(this.timeout)
+ self.hoverState = 'in'
+ this.timeout = setTimeout(function() {
+ if (self.hoverState == 'in') self.show()
+ }, self.options.delay.show)
+ }
+
+ , leave: function (e) {
+ var self = $(e.currentTarget)[this.type](this._options).data(this.type)
+
+ if (this.timeout) clearTimeout(this.timeout)
+ if (!self.options.delay || !self.options.delay.hide) return self.hide()
+
+ self.hoverState = 'out'
+ this.timeout = setTimeout(function() {
+ if (self.hoverState == 'out') self.hide()
+ }, self.options.delay.hide)
+ }
+
+ , show: function () {
+ var $tip
+ , inside
+ , pos
+ , actualWidth
+ , actualHeight
+ , placement
+ , tp
+
+ if (this.hasContent() && this.enabled) {
+ $tip = this.tip()
+ this.setContent()
+
+ if (this.options.animation) {
+ $tip.addClass('fade')
+ }
+
+ placement = typeof this.options.placement == 'function' ?
+ this.options.placement.call(this, $tip[0], this.$element[0]) :
+ this.options.placement
+
+ inside = /in/.test(placement)
+
+ $tip
+ .remove()
+ .css({ top: 0, left: 0, display: 'block' })
+ .appendTo(inside ? this.$element : document.body)
+
+ pos = this.getPosition(inside)
+
+ actualWidth = $tip[0].offsetWidth
+ actualHeight = $tip[0].offsetHeight
+
+ switch (inside ? placement.split(' ')[1] : placement) {
+ case 'bottom':
+ tp = {top: pos.top + pos.height, left: pos.left + pos.width / 2 - actualWidth / 2}
+ break
+ case 'top':
+ tp = {top: pos.top - actualHeight, left: pos.left + pos.width / 2 - actualWidth / 2}
+ break
+ case 'left':
+ tp = {top: pos.top + pos.height / 2 - actualHeight / 2, left: pos.left - actualWidth}
+ break
+ case 'right':
+ tp = {top: pos.top + pos.height / 2 - actualHeight / 2, left: pos.left + pos.width}
+ break
+ }
+
+ $tip
+ .css(tp)
+ .addClass(placement)
+ .addClass('in')
+ }
+ }
+
+ , setContent: function () {
+ var $tip = this.tip()
+ , title = this.getTitle()
+
+ $tip.find('.tooltip-inner')[this.options.html ? 'html' : 'text'](title)
+ $tip.removeClass('fade in top bottom left right')
+ }
+
+ , hide: function () {
+ var that = this
+ , $tip = this.tip()
+
+ $tip.removeClass('in')
+
+ function removeWithAnimation() {
+ var timeout = setTimeout(function () {
+ $tip.off($.support.transition.end).remove()
+ }, 500)
+
+ $tip.one($.support.transition.end, function () {
+ clearTimeout(timeout)
+ $tip.remove()
+ })
+ }
+
+ $.support.transition && this.$tip.hasClass('fade') ?
+ removeWithAnimation() :
+ $tip.remove()
+
+ return this
+ }
+
+ , fixTitle: function () {
+ var $e = this.$element
+ if ($e.attr('title') || typeof($e.attr('data-original-title')) != 'string') {
+ $e.attr('data-original-title', $e.attr('title') || '').removeAttr('title')
+ }
+ }
+
+ , hasContent: function () {
+ return this.getTitle()
+ }
+
+ , getPosition: function (inside) {
+ return $.extend({}, (inside ? {top: 0, left: 0} : this.$element.offset()), {
+ width: this.$element[0].offsetWidth
+ , height: this.$element[0].offsetHeight
+ })
+ }
+
+ , getTitle: function () {
+ var title
+ , $e = this.$element
+ , o = this.options
+
+ title = $e.attr('data-original-title')
+ || (typeof o.title == 'function' ? o.title.call($e[0]) : o.title)
+
+ return title
+ }
+
+ , tip: function () {
+ return this.$tip = this.$tip || $(this.options.template)
+ }
+
+ , validate: function () {
+ if (!this.$element[0].parentNode) {
+ this.hide()
+ this.$element = null
+ this.options = null
+ }
+ }
+
+ , enable: function () {
+ this.enabled = true
+ }
+
+ , disable: function () {
+ this.enabled = false
+ }
+
+ , toggleEnabled: function () {
+ this.enabled = !this.enabled
+ }
+
+ , toggle: function () {
+ this[this.tip().hasClass('in') ? 'hide' : 'show']()
+ }
+
+ , destroy: function () {
+ this.hide().$element.off('.' + this.type).removeData(this.type)
+ }
+
+ }
+
+
+ /* TOOLTIP PLUGIN DEFINITION
+ * ========================= */
+
+ $.fn.tooltip = function ( option ) {
+ return this.each(function () {
+ var $this = $(this)
+ , data = $this.data('tooltip')
+ , options = typeof option == 'object' && option
+ if (!data) $this.data('tooltip', (data = new Tooltip(this, options)))
+ if (typeof option == 'string') data[option]()
+ })
+ }
+
+ $.fn.tooltip.Constructor = Tooltip
+
+ $.fn.tooltip.defaults = {
+ animation: true
+ , placement: 'top'
+ , selector: false
+ , template: '<div class="tooltip"><div class="tooltip-arrow"></div><div class="tooltip-inner"></div></div>'
+ , trigger: 'hover'
+ , title: ''
+ , delay: 0
+ , html: true
+ }
+
+}(window.jQuery);
+/* ===========================================================
+ * bootstrap-popover.js v2.1.0
+ * http://twitter.github.com/bootstrap/javascript.html#popovers
+ * ===========================================================
+ * Copyright 2012 Twitter, Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * =========================================================== */
+
+
+!function ($) {
+
+ "use strict"; // jshint ;_;
+
+
+ /* POPOVER PUBLIC CLASS DEFINITION
+ * =============================== */
+
+ var Popover = function (element, options) {
+ this.init('popover', element, options)
+ }
+
+
+ /* NOTE: POPOVER EXTENDS BOOTSTRAP-TOOLTIP.js
+ ========================================== */
+
+ Popover.prototype = $.extend({}, $.fn.tooltip.Constructor.prototype, {
+
+ constructor: Popover
+
+ , setContent: function () {
+ var $tip = this.tip()
+ , title = this.getTitle()
+ , content = this.getContent()
+
+ $tip.find('.popover-title')[this.options.html ? 'html' : 'text'](title)
+ $tip.find('.popover-content > *')[this.options.html ? 'html' : 'text'](content)
+
+ $tip.removeClass('fade top bottom left right in')
+ }
+
+ , hasContent: function () {
+ return this.getTitle() || this.getContent()
+ }
+
+ , getContent: function () {
+ var content
+ , $e = this.$element
+ , o = this.options
+
+ content = $e.attr('data-content')
+ || (typeof o.content == 'function' ? o.content.call($e[0]) : o.content)
+
+ return content
+ }
+
+ , tip: function () {
+ if (!this.$tip) {
+ this.$tip = $(this.options.template)
+ }
+ return this.$tip
+ }
+
+ , destroy: function () {
+ this.hide().$element.off('.' + this.type).removeData(this.type)
+ }
+
+ })
+
+
+ /* POPOVER PLUGIN DEFINITION
+ * ======================= */
+
+ $.fn.popover = function (option) {
+ return this.each(function () {
+ var $this = $(this)
+ , data = $this.data('popover')
+ , options = typeof option == 'object' && option
+ if (!data) $this.data('popover', (data = new Popover(this, options)))
+ if (typeof option == 'string') data[option]()
+ })
+ }
+
+ $.fn.popover.Constructor = Popover
+
+ $.fn.popover.defaults = $.extend({} , $.fn.tooltip.defaults, {
+ placement: 'right'
+ , trigger: 'click'
+ , content: ''
+ , template: '<div class="popover"><div class="arrow"></div><div class="popover-inner"><h3 class="popover-title"></h3><div class="popover-content"><p></p></div></div></div>'
+ })
+
+}(window.jQuery);/* =============================================================
+ * bootstrap-scrollspy.js v2.1.0
+ * http://twitter.github.com/bootstrap/javascript.html#scrollspy
+ * =============================================================
+ * Copyright 2012 Twitter, Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * ============================================================== */
+
+
+!function ($) {
+
+ "use strict"; // jshint ;_;
+
+
+ /* SCROLLSPY CLASS DEFINITION
+ * ========================== */
+
+ function ScrollSpy(element, options) {
+ var process = $.proxy(this.process, this)
+ , $element = $(element).is('body') ? $(window) : $(element)
+ , href
+ this.options = $.extend({}, $.fn.scrollspy.defaults, options)
+ this.$scrollElement = $element.on('scroll.scroll-spy.data-api', process)
+ this.selector = (this.options.target
+ || ((href = $(element).attr('href')) && href.replace(/.*(?=#[^\s]+$)/, '')) //strip for ie7
+ || '') + ' .nav li > a'
+ this.$body = $('body')
+ this.refresh()
+ this.process()
+ }
+
+ ScrollSpy.prototype = {
+
+ constructor: ScrollSpy
+
+ , refresh: function () {
+ var self = this
+ , $targets
+
+ this.offsets = $([])
+ this.targets = $([])
+
+ $targets = this.$body
+ .find(this.selector)
+ .map(function () {
+ var $el = $(this)
+ , href = $el.data('target') || $el.attr('href')
+ , $href = /^#\w/.test(href) && $(href)
+ return ( $href
+ && $href.length
+ && [[ $href.position().top, href ]] ) || null
+ })
+ .sort(function (a, b) { return a[0] - b[0] })
+ .each(function () {
+ self.offsets.push(this[0])
+ self.targets.push(this[1])
+ })
+ }
+
+ , process: function () {
+ var scrollTop = this.$scrollElement.scrollTop() + this.options.offset
+ , scrollHeight = this.$scrollElement[0].scrollHeight || this.$body[0].scrollHeight
+ , maxScroll = scrollHeight - this.$scrollElement.height()
+ , offsets = this.offsets
+ , targets = this.targets
+ , activeTarget = this.activeTarget
+ , i
+
+ if (scrollTop >= maxScroll) {
+ return activeTarget != (i = targets.last()[0])
+ && this.activate ( i )
+ }
+
+ for (i = offsets.length; i--;) {
+ activeTarget != targets[i]
+ && scrollTop >= offsets[i]
+ && (!offsets[i + 1] || scrollTop <= offsets[i + 1])
+ && this.activate( targets[i] )
+ }
+ }
+
+ , activate: function (target) {
+ var active
+ , selector
+
+ this.activeTarget = target
+
+ $(this.selector)
+ .parent('.active')
+ .removeClass('active')
+
+ selector = this.selector
+ + '[data-target="' + target + '"],'
+ + this.selector + '[href="' + target + '"]'
+
+ active = $(selector)
+ .parent('li')
+ .addClass('active')
+
+ if (active.parent('.dropdown-menu').length) {
+ active = active.closest('li.dropdown').addClass('active')
+ }
+
+ active.trigger('activate')
+ }
+
+ }
+
+
+ /* SCROLLSPY PLUGIN DEFINITION
+ * =========================== */
+
+ $.fn.scrollspy = function (option) {
+ return this.each(function () {
+ var $this = $(this)
+ , data = $this.data('scrollspy')
+ , options = typeof option == 'object' && option
+ if (!data) $this.data('scrollspy', (data = new ScrollSpy(this, options)))
+ if (typeof option == 'string') data[option]()
+ })
+ }
+
+ $.fn.scrollspy.Constructor = ScrollSpy
+
+ $.fn.scrollspy.defaults = {
+ offset: 10
+ }
+
+
+ /* SCROLLSPY DATA-API
+ * ================== */
+
+ $(window).on('load', function () {
+ $('[data-spy="scroll"]').each(function () {
+ var $spy = $(this)
+ $spy.scrollspy($spy.data())
+ })
+ })
+
+}(window.jQuery);/* ========================================================
+ * bootstrap-tab.js v2.1.0
+ * http://twitter.github.com/bootstrap/javascript.html#tabs
+ * ========================================================
+ * Copyright 2012 Twitter, Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * ======================================================== */
+
+
+!function ($) {
+
+ "use strict"; // jshint ;_;
+
+
+ /* TAB CLASS DEFINITION
+ * ==================== */
+
+ var Tab = function (element) {
+ this.element = $(element)
+ }
+
+ Tab.prototype = {
+
+ constructor: Tab
+
+ , show: function () {
+ var $this = this.element
+ , $ul = $this.closest('ul:not(.dropdown-menu)')
+ , selector = $this.attr('data-target')
+ , previous
+ , $target
+ , e
+
+ if (!selector) {
+ selector = $this.attr('href')
+ selector = selector && selector.replace(/.*(?=#[^\s]*$)/, '') //strip for ie7
+ }
+
+ if ( $this.parent('li').hasClass('active') ) return
+
+ previous = $ul.find('.active a').last()[0]
+
+ e = $.Event('show', {
+ relatedTarget: previous
+ })
+
+ $this.trigger(e)
+
+ if (e.isDefaultPrevented()) return
+
+ $target = $(selector)
+
+ this.activate($this.parent('li'), $ul)
+ this.activate($target, $target.parent(), function () {
+ $this.trigger({
+ type: 'shown'
+ , relatedTarget: previous
+ })
+ })
+ }
+
+ , activate: function ( element, container, callback) {
+ var $active = container.find('> .active')
+ , transition = callback
+ && $.support.transition
+ && $active.hasClass('fade')
+
+ function next() {
+ $active
+ .removeClass('active')
+ .find('> .dropdown-menu > .active')
+ .removeClass('active')
+
+ element.addClass('active')
+
+ if (transition) {
+ element[0].offsetWidth // reflow for transition
+ element.addClass('in')
+ } else {
+ element.removeClass('fade')
+ }
+
+ if ( element.parent('.dropdown-menu') ) {
+ element.closest('li.dropdown').addClass('active')
+ }
+
+ callback && callback()
+ }
+
+ transition ?
+ $active.one($.support.transition.end, next) :
+ next()
+
+ $active.removeClass('in')
+ }
+ }
+
+
+ /* TAB PLUGIN DEFINITION
+ * ===================== */
+
+ $.fn.tab = function ( option ) {
+ return this.each(function () {
+ var $this = $(this)
+ , data = $this.data('tab')
+ if (!data) $this.data('tab', (data = new Tab(this)))
+ if (typeof option == 'string') data[option]()
+ })
+ }
+
+ $.fn.tab.Constructor = Tab
+
+
+ /* TAB DATA-API
+ * ============ */
+
+ $(function () {
+ $('body').on('click.tab.data-api', '[data-toggle="tab"], [data-toggle="pill"]', function (e) {
+ e.preventDefault()
+ $(this).tab('show')
+ })
+ })
+
+}(window.jQuery);/* =============================================================
+ * bootstrap-typeahead.js v2.1.0
+ * http://twitter.github.com/bootstrap/javascript.html#typeahead
+ * =============================================================
+ * Copyright 2012 Twitter, Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * ============================================================ */
+
+
+!function($){
+
+ "use strict"; // jshint ;_;
+
+
+ /* TYPEAHEAD PUBLIC CLASS DEFINITION
+ * ================================= */
+
+ var Typeahead = function (element, options) {
+ this.$element = $(element)
+ this.options = $.extend({}, $.fn.typeahead.defaults, options)
+ this.matcher = this.options.matcher || this.matcher
+ this.sorter = this.options.sorter || this.sorter
+ this.highlighter = this.options.highlighter || this.highlighter
+ this.updater = this.options.updater || this.updater
+ this.$menu = $(this.options.menu).appendTo('body')
+ this.source = this.options.source
+ this.shown = false
+ this.listen()
+ }
+
+ Typeahead.prototype = {
+
+ constructor: Typeahead
+
+ , select: function () {
+ var val = this.$menu.find('.active').attr('data-value')
+ this.$element
+ .val(this.updater(val))
+ .change()
+ return this.hide()
+ }
+
+ , updater: function (item) {
+ return item
+ }
+
+ , show: function () {
+ var pos = $.extend({}, this.$element.offset(), {
+ height: this.$element[0].offsetHeight
+ })
+
+ this.$menu.css({
+ top: pos.top + pos.height
+ , left: pos.left
+ })
+
+ this.$menu.show()
+ this.shown = true
+ return this
+ }
+
+ , hide: function () {
+ this.$menu.hide()
+ this.shown = false
+ return this
+ }
+
+ , lookup: function (event) {
+ var items
+
+ this.query = this.$element.val()
+
+ if (!this.query || this.query.length < this.options.minLength) {
+ return this.shown ? this.hide() : this
+ }
+
+ items = $.isFunction(this.source) ? this.source(this.query, $.proxy(this.process, this)) : this.source
+
+ return items ? this.process(items) : this
+ }
+
+ , process: function (items) {
+ var that = this
+
+ items = $.grep(items, function (item) {
+ return that.matcher(item)
+ })
+
+ items = this.sorter(items)
+
+ if (!items.length) {
+ return this.shown ? this.hide() : this
+ }
+
+ return this.render(items.slice(0, this.options.items)).show()
+ }
+
+ , matcher: function (item) {
+ return ~item.toLowerCase().indexOf(this.query.toLowerCase())
+ }
+
+ , sorter: function (items) {
+ var beginswith = []
+ , caseSensitive = []
+ , caseInsensitive = []
+ , item
+
+ while (item = items.shift()) {
+ if (!item.toLowerCase().indexOf(this.query.toLowerCase())) beginswith.push(item)
+ else if (~item.indexOf(this.query)) caseSensitive.push(item)
+ else caseInsensitive.push(item)
+ }
+
+ return beginswith.concat(caseSensitive, caseInsensitive)
+ }
+
+ , highlighter: function (item) {
+ var query = this.query.replace(/[\-\[\]{}()*+?.,\\\^$|#\s]/g, '\\$&')
+ return item.replace(new RegExp('(' + query + ')', 'ig'), function ($1, match) {
+ return '<strong>' + match + '</strong>'
+ })
+ }
+
+ , render: function (items) {
+ var that = this
+
+ items = $(items).map(function (i, item) {
+ i = $(that.options.item).attr('data-value', item)
+ i.find('a').html(that.highlighter(item))
+ return i[0]
+ })
+
+ items.first().addClass('active')
+ this.$menu.html(items)
+ return this
+ }
+
+ , next: function (event) {
+ var active = this.$menu.find('.active').removeClass('active')
+ , next = active.next()
+
+ if (!next.length) {
+ next = $(this.$menu.find('li')[0])
+ }
+
+ next.addClass('active')
+ }
+
+ , prev: function (event) {
+ var active = this.$menu.find('.active').removeClass('active')
+ , prev = active.prev()
+
+ if (!prev.length) {
+ prev = this.$menu.find('li').last()
+ }
+
+ prev.addClass('active')
+ }
+
+ , listen: function () {
+ this.$element
+ .on('blur', $.proxy(this.blur, this))
+ .on('keypress', $.proxy(this.keypress, this))
+ .on('keyup', $.proxy(this.keyup, this))
+
+ if ($.browser.webkit || $.browser.msie) {
+ this.$element.on('keydown', $.proxy(this.keydown, this))
+ }
+
+ this.$menu
+ .on('click', $.proxy(this.click, this))
+ .on('mouseenter', 'li', $.proxy(this.mouseenter, this))
+ }
+
+ , move: function (e) {
+ if (!this.shown) return
+
+ switch(e.keyCode) {
+ case 9: // tab
+ case 13: // enter
+ case 27: // escape
+ e.preventDefault()
+ break
+
+ case 38: // up arrow
+ e.preventDefault()
+ this.prev()
+ break
+
+ case 40: // down arrow
+ e.preventDefault()
+ this.next()
+ break
+ }
+
+ e.stopPropagation()
+ }
+
+ , keydown: function (e) {
+ this.suppressKeyPressRepeat = !~$.inArray(e.keyCode, [40,38,9,13,27])
+ this.move(e)
+ }
+
+ , keypress: function (e) {
+ if (this.suppressKeyPressRepeat) return
+ this.move(e)
+ }
+
+ , keyup: function (e) {
+ switch(e.keyCode) {
+ case 40: // down arrow
+ case 38: // up arrow
+ break
+
+ case 9: // tab
+ case 13: // enter
+ if (!this.shown) return
+ this.select()
+ break
+
+ case 27: // escape
+ if (!this.shown) return
+ this.hide()
+ break
+
+ default:
+ this.lookup()
+ }
+
+ e.stopPropagation()
+ e.preventDefault()
+ }
+
+ , blur: function (e) {
+ var that = this
+ setTimeout(function () { that.hide() }, 150)
+ }
+
+ , click: function (e) {
+ e.stopPropagation()
+ e.preventDefault()
+ this.select()
+ }
+
+ , mouseenter: function (e) {
+ this.$menu.find('.active').removeClass('active')
+ $(e.currentTarget).addClass('active')
+ }
+
+ }
+
+
+ /* TYPEAHEAD PLUGIN DEFINITION
+ * =========================== */
+
+ $.fn.typeahead = function (option) {
+ return this.each(function () {
+ var $this = $(this)
+ , data = $this.data('typeahead')
+ , options = typeof option == 'object' && option
+ if (!data) $this.data('typeahead', (data = new Typeahead(this, options)))
+ if (typeof option == 'string') data[option]()
+ })
+ }
+
+ $.fn.typeahead.defaults = {
+ source: []
+ , items: 8
+ , menu: '<ul class="typeahead dropdown-menu"></ul>'
+ , item: '<li><a href="#"></a></li>'
+ , minLength: 1
+ }
+
+ $.fn.typeahead.Constructor = Typeahead
+
+
+ /* TYPEAHEAD DATA-API
+ * ================== */
+
+ $(function () {
+ $('body').on('focus.typeahead.data-api', '[data-provide="typeahead"]', function (e) {
+ var $this = $(this)
+ if ($this.data('typeahead')) return
+ e.preventDefault()
+ $this.typeahead($this.data())
+ })
+ })
+
+}(window.jQuery);
+/* ==========================================================
+ * bootstrap-affix.js v2.1.0
+ * http://twitter.github.com/bootstrap/javascript.html#affix
+ * ==========================================================
+ * Copyright 2012 Twitter, Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * ========================================================== */
+
+
+!function ($) {
+
+ "use strict"; // jshint ;_;
+
+
+ /* AFFIX CLASS DEFINITION
+ * ====================== */
+
+ var Affix = function (element, options) {
+ this.options = $.extend({}, $.fn.affix.defaults, options)
+ this.$window = $(window).on('scroll.affix.data-api', $.proxy(this.checkPosition, this))
+ this.$element = $(element)
+ this.checkPosition()
+ }
+
+ Affix.prototype.checkPosition = function () {
+ if (!this.$element.is(':visible')) return
+
+ var scrollHeight = $(document).height()
+ , scrollTop = this.$window.scrollTop()
+ , position = this.$element.offset()
+ , offset = this.options.offset
+ , offsetBottom = offset.bottom
+ , offsetTop = offset.top
+ , reset = 'affix affix-top affix-bottom'
+ , affix
+
+ if (typeof offset != 'object') offsetBottom = offsetTop = offset
+ if (typeof offsetTop == 'function') offsetTop = offset.top()
+ if (typeof offsetBottom == 'function') offsetBottom = offset.bottom()
+
+ affix = this.unpin != null && (scrollTop + this.unpin <= position.top) ?
+ false : offsetBottom != null && (position.top + this.$element.height() >= scrollHeight - offsetBottom) ?
+ 'bottom' : offsetTop != null && scrollTop <= offsetTop ?
+ 'top' : false
+
+ if (this.affixed === affix) return
+
+ this.affixed = affix
+ this.unpin = affix == 'bottom' ? position.top - scrollTop : null
+
+ this.$element.removeClass(reset).addClass('affix' + (affix ? '-' + affix : ''))
+ }
+
+
+ /* AFFIX PLUGIN DEFINITION
+ * ======================= */
+
+ $.fn.affix = function (option) {
+ return this.each(function () {
+ var $this = $(this)
+ , data = $this.data('affix')
+ , options = typeof option == 'object' && option
+ if (!data) $this.data('affix', (data = new Affix(this, options)))
+ if (typeof option == 'string') data[option]()
+ })
+ }
+
+ $.fn.affix.Constructor = Affix
+
+ $.fn.affix.defaults = {
+ offset: 0
+ }
+
+
+ /* AFFIX DATA-API
+ * ============== */
+
+ $(window).on('load', function () {
+ $('[data-spy="affix"]').each(function () {
+ var $spy = $(this)
+ , data = $spy.data()
+
+ data.offset = data.offset || {}
+
+ data.offsetBottom && (data.offset.bottom = data.offsetBottom)
+ data.offsetTop && (data.offset.top = data.offsetTop)
+
+ $spy.affix(data)
+ })
+ })
+
+
+}(window.jQuery); \ No newline at end of file
diff --git a/nikola/data/themes/default/bundles b/nikola/data/themes/default/bundles
new file mode 100644
index 0000000..9d77983
--- /dev/null
+++ b/nikola/data/themes/default/bundles
@@ -0,0 +1,2 @@
+assets/css/all.css=bootstrap.css,bootstrap-responsive.css,rst.css,code.css,colorbox.css,theme.css,custom.css
+assets/js/all.js=jquery-1.7.2.min.js,jquery.colorbox-min.js
diff --git a/nikola/data/themes/default/engine b/nikola/data/themes/default/engine
new file mode 100644
index 0000000..2951cdd
--- /dev/null
+++ b/nikola/data/themes/default/engine
@@ -0,0 +1 @@
+mako
diff --git a/nikola/data/themes/default/messages/de.py b/nikola/data/themes/default/messages/de.py
new file mode 100644
index 0000000..f58b0a1
--- /dev/null
+++ b/nikola/data/themes/default/messages/de.py
@@ -0,0 +1,19 @@
+# -*- encoding:utf-8 -*-
+
+MESSAGES = {
+ u"LANGUAGE": u"Deutsch",
+ u"Posts for year %s": u"Eintr&auml;ge aus dem Jahr %s",
+ u"Archive": u"Archiv",
+ u"Posts about %s:": u"Eintr&auml;ge &uuml;ber %s",
+ u"Tags": u"Tags",
+ u"Also available in: ": u"Auch verf&uuml;gbar in: ",
+ u"More posts about": u"Weitere Eintr&auml;ge &uuml;ber",
+ u"Posted:": u"Ver&ouml;ffentlicht:",
+ u"Original site": u"Original-Seite",
+ u"Read in English": u"Auf Deutsch lesen",
+ u"Older posts &rarr;": u"&Auml;ltere Eintr&auml;ge &rarr;",
+ u"&larr; Newer posts": u"&larr; Neuere Eintr&auml;ge",
+ u"&larr; Previous post": u"&larr; Vorheriger Eintrag",
+ u"Next post &rarr;": u"N&auml;chster Eintrag &rarr;",
+ u"Source": u"Source",
+}
diff --git a/nikola/data/themes/default/messages/en.py b/nikola/data/themes/default/messages/en.py
new file mode 100644
index 0000000..5a4a9bd
--- /dev/null
+++ b/nikola/data/themes/default/messages/en.py
@@ -0,0 +1,25 @@
+MESSAGES = [
+ u"Posts for year %s",
+ u"Archive",
+ u"Posts about %s:",
+ u"Tags",
+ u"Also available in: ",
+ u"More posts about",
+ u"Posted:",
+ u"Original site",
+ u"Read in English",
+ u"&larr; Newer posts",
+ u"Older posts &rarr;",
+ u"&larr; Previous post",
+ u"Next post &rarr;",
+ u"old posts page %d",
+ u"Read more",
+ u"Source",
+]
+
+# In english things are not translated
+msg_dict = {}
+for msg in MESSAGES:
+ msg_dict[msg] = msg
+MESSAGES = msg_dict
+MESSAGES[u"LANGUAGE"] = "English"
diff --git a/nikola/data/themes/default/messages/es.py b/nikola/data/themes/default/messages/es.py
new file mode 100644
index 0000000..82d2300
--- /dev/null
+++ b/nikola/data/themes/default/messages/es.py
@@ -0,0 +1,21 @@
+# -*- encoding:utf-8 -*-
+
+MESSAGES = {
+ u"LANGUAGE": u"Español",
+ u"Posts for year %s": u"Posts del año %s",
+ u"Archive": u"Archivo",
+ u"Posts about %s:": u"Posts sobre %s",
+ u"Tags": u"Tags",
+ u"Also available in: ": u"También disponible en: ",
+ u"More posts about": u"Más posts sobre",
+ u"Posted:": u"Publicado:",
+ u"Original site": u"Sitio original",
+ u"Read in English": u"Leer en español",
+ u"Older posts &rarr;": u"Posts anteriores &rarr;",
+ u"&larr; Newer posts": u"&larr; Posts posteriores",
+ u"&larr; Previous post": u"&larr; Post anterior",
+ u"Next post &rarr;": u"Siguiente post &rarr;",
+ u"old posts page %d": u"posts antiguos página %d",
+ u"Read more": u"Leer mas",
+ u"Source": u"Código",
+}
diff --git a/nikola/data/themes/default/messages/fr.py b/nikola/data/themes/default/messages/fr.py
new file mode 100644
index 0000000..d4bf0a6
--- /dev/null
+++ b/nikola/data/themes/default/messages/fr.py
@@ -0,0 +1,17 @@
+# -*- encoding:utf-8 -*-
+
+MESSAGES = {
+ u"LANGUAGE": u"Français",
+ u"Posts for year %s": u"Billets de l'année %s",
+ u"Archive": u"Archives",
+ u"Posts about %s:": u"Billets sur %s",
+ u"Tags": u"Étiquettes",
+ u"Also available in: ": u"Disponible aussi en : ",
+ u"More posts about": u"Plus de billets sur",
+ u"Posted:": u"Publié :",
+ u"Original site": u"Site d'origine",
+ u"Read in English": u"Lire en français",
+ u"&larr; Newer posts": u"&larr; Billets récents",
+ u"Older posts &rarr;": u"Anciens billets &rarr;",
+ u"Source": u"Source",
+}
diff --git a/nikola/data/themes/default/messages/gr.py b/nikola/data/themes/default/messages/gr.py
new file mode 100644
index 0000000..62139c9
--- /dev/null
+++ b/nikola/data/themes/default/messages/gr.py
@@ -0,0 +1,20 @@
+# -*- encoding:utf-8 -*-
+
+MESSAGES = {
+ u"LANGUAGE": u"Ελληνικά",
+ u"Posts for year %s": u"Αναρτήσεις για τη χρονιά %s",
+ u"Archive": u"Αρχείο",
+ u"Posts about %s:": u"Αναρτήσεις για %s",
+ u"Tags": u"Ετικέτες",
+ u"Also available in: ": u"Διαθέσιμο και στο: ",
+ u"More posts about": u"Περισσότερες αναρτήσεις για",
+ u"Posted:": u"Αναρτήθηκε :",
+ u"Original site": u"Ιστοσελίδα αρχικής ανάρτησης",
+ u"Read in English": u"Διαβάστε στα Ελληνικά",
+ u"&larr; Newer posts": u"&larr; Νεότερες αναρτήσεις",
+ u"Older posts &rarr;": u"Παλαιότερες αναρτήσεις &rarr;",
+ u"&larr; Previous post": u"&larr; Προηγούμενη ανάρτηση",
+ u"Next post &rarr;": u"Επόμενη ανάρτηση &rarr;",
+ u"old posts page %d": u"σελίδα παλαιότερων αναρτήσεων %d",
+ u"Source": u"Source",
+}
diff --git a/nikola/data/themes/default/messages/it.py b/nikola/data/themes/default/messages/it.py
new file mode 100644
index 0000000..a4f37f0
--- /dev/null
+++ b/nikola/data/themes/default/messages/it.py
@@ -0,0 +1,20 @@
+MESSAGES = {
+ u"LANGUAGE": u"Italiano",
+ u"Posts for year %s": u"Articoli per l'anno %s",
+ u"Archive": u"Archivio",
+ u"Posts about %s:": u"Articoli su %s",
+ u"Tags": u"Tags",
+ u"Also available in: ": u"Anche disponibile in: ",
+ u"More posts about": u"Altri articoli su",
+ u"Posted:": u"Pubblicato:",
+ u"Original site": u"Sito originale",
+ u"Read in English": u"Leggi in italiano",
+ u"&larr; Newer posts": u"&larr; Articoli recenti",
+ u"Older posts &rarr;": u"Articoli più vecchi",
+ u"Older posts &rarr;": u"Articoli vecchi",
+ u"&larr; Previous post": u"&larr; Articolo precedente",
+ u"Next post &rarr;": u"&larr; Articolo successivo",
+ u"old posts page %d": u"pagina dei vecchi articoli %d",
+ u"Read more": u"Espandi",
+ u"Source": u"Source",
+}
diff --git a/nikola/data/themes/default/messages/ru.py b/nikola/data/themes/default/messages/ru.py
new file mode 100644
index 0000000..2bd652b
--- /dev/null
+++ b/nikola/data/themes/default/messages/ru.py
@@ -0,0 +1,17 @@
+# -*- encoding:utf-8 -*-
+
+MESSAGES = {
+ u"LANGUAGE": u"Русский",
+ u"Posts for year %s": u"Записи за %s год",
+ u"Archive": u"Архив",
+ u"Posts about %s:": u"Записи с тэгом %s:",
+ u"Tags": u"Тэги",
+ u"Also available in: ": u"Также доступно в: ",
+ u"More posts about": u"Больше записей о",
+ u"Posted:": u"Опубликовано",
+ u"Original site": u"Оригинальный сайт",
+ u"Read in English": u"Прочесть по-русски",
+ u"Older posts &rarr;": u"Старые записи &rarr;",
+ u"&larr; Newer posts": u"&larr; Новые записи",
+ u"Source": u"Source",
+}
diff --git a/nikola/data/themes/default/templates/base.tmpl b/nikola/data/themes/default/templates/base.tmpl
new file mode 100644
index 0000000..b031423
--- /dev/null
+++ b/nikola/data/themes/default/templates/base.tmpl
@@ -0,0 +1,103 @@
+## -*- coding: utf-8 -*-
+<!DOCTYPE html>
+<html lang="${lang}">
+<head>
+ <meta charset="utf-8">
+ <meta name="title" content="${title} | ${blog_title}" >
+ <meta name="description" content="${description}" >
+ <meta name="author" content="${blog_author}">
+ <title>${title} | ${blog_title}</title>
+ <!-- Le styles -->
+ %if use_bundles:
+ <link href="/assets/css/all.css" rel="stylesheet" type="text/css">
+ <script src="/assets/js/all.js" type="text/javascript"></script>
+ %else:
+ <link href="/assets/css/bootstrap.css" rel="stylesheet" type="text/css">
+ <link href="/assets/css/bootstrap-responsive.css" rel="stylesheet" type="text/css">
+ <link href="/assets/css/rst.css" rel="stylesheet" type="text/css">
+ <link href="/assets/css/code.css" rel="stylesheet" type="text/css">
+ <link href="/assets/css/colorbox.css" rel="stylesheet" type="text/css"/>
+ <link href="/assets/css/theme.css" rel="stylesheet" type="text/css"/>
+ %if exists("files/assets/css/custom.css", not_empty=True):
+ <link href="/assets/css/custom.css" rel="stylesheet" type="text/css">
+ %endif
+ <script src="/assets/js/jquery-1.7.2.min.js" type="text/javascript"></script>
+ <script src="/assets/js/jquery.colorbox-min.js" type="text/javascript"></script>
+ %endif
+ <!-- Le HTML5 shim, for IE6-8 support of HTML5 elements -->
+ <!--[if lt IE 9]>
+ <script src="http://html5shim.googlecode.com/svn/trunk/html5.js" type="text/javascript"></script>
+ <![endif]-->
+ %if rss_link:
+ ${rss_link}
+ %else:
+ %for language in translations:
+ <link rel="alternate" type="application/rss+xml" type="application/rss+xml" title="RSS (${language})" href="${_link("rss", None, lang)}">
+ %endfor
+ %endif
+ <%block name="extra_head">
+ </%block>
+</head>
+<body>
+ %if add_this_buttons:
+ <script type="text/javascript">var addthis_config={"ui_language":"${lang}"};</script>
+ % endif
+<div class="container-fluid" id="container">
+ <div class="row-fluid" id="titlerow">
+ <div class="span12" id="titlecolumn">
+ <!-- Banner-like substance !-->
+ <div class="titlebox">
+ <h1 id="blog-title">
+ <a href="${abs_link('/')}" title="${blog_title}">${blog_title}</a>
+ </h1>
+ <%block name="belowtitle">
+ %if len(translations) > 1:
+ <small>
+ ${(messages[lang][u"Also available in: "])}
+ %for langname in translations.keys():
+ %if langname != lang:
+ <a href="${_link("index", None, langname)}">${messages[langname]["LANGUAGE"]}</a>
+ %endif
+ %endfor
+ </small>
+ %endif
+ </%block>
+ <hr>
+ </div>
+ <!-- End of banner-like substance !-->
+ <div class="row" id="contentrow">
+ <div class="span10" id="contentcolumn">
+ <!--Body content-->
+ <%block name="content"></%block>
+ <!--End of body content-->
+ <hr>
+ <small>${content_footer}</small>
+ </div>
+ <div class="span2" id="sidebar">
+ <!--Sidebar content-->
+ <ul class="unstyled">
+ <li>${license}
+ <!-- social buttons -->
+ %if add_this_buttons:
+ <li>
+ <div id="addthisbox" class="addthis_toolbox addthis_default_style">
+ <a class="addthis_button_preferred_1"></a>
+ <a class="addthis_button_preferred_2"></a>
+ <a class="addthis_button_preferred_3"></a>
+ <a class="addthis_button_preferred_4"></a>
+ <a class="addthis_button_compact"></a>
+ <a class="addthis_counter addthis_bubble_style"></a>
+ </div>
+ <script type="text/javascript" src="http://s7.addthis.com/js/250/addthis_widget.js#pubid=ra-4f7088a56bb93798"></script>
+ <!-- End of social buttons -->
+ % endif
+ %for url, text in sidebar_links[lang]:
+ <li><a href="${url}">${text}</a>
+ %endfor
+ <li>${search_form}
+ </ul>
+ <!--End of sidebar content-->
+ </div>
+ ${analytics}
+ <script type="text/javascript">jQuery("a.image-reference").colorbox({rel:"gal",maxWidth:"80%",maxHeight:"80%",scalePhotos:true});</script>
+</body>
diff --git a/nikola/data/themes/default/templates/gallery.tmpl b/nikola/data/themes/default/templates/gallery.tmpl
new file mode 100644
index 0000000..3c48413
--- /dev/null
+++ b/nikola/data/themes/default/templates/gallery.tmpl
@@ -0,0 +1,17 @@
+## -*- coding: utf-8 -*-
+<%inherit file="base.tmpl"/>
+<%block name="sourcelink"></%block>
+
+<%block name="content">
+ %if text:
+ <p>
+ ${text}
+ </p>
+ %endif
+ <ul class="thumbnails">
+ %for image in images:
+ <li><a href="${image[0]}" class="thumbnail image-reference" ${image[2]}>
+ <img src="${image[1]}" /></a></li>
+ %endfor
+ </ul>
+</%block>
diff --git a/nikola/data/themes/default/templates/index.tmpl b/nikola/data/themes/default/templates/index.tmpl
new file mode 100644
index 0000000..45e2172
--- /dev/null
+++ b/nikola/data/themes/default/templates/index.tmpl
@@ -0,0 +1,36 @@
+## -*- coding: utf-8 -*-
+<%inherit file="base.tmpl"/>
+<%block name="content">
+ % for post in posts:
+ <div class="postbox">
+ <h1><a href="${post.permalink(lang)}">${post.title(lang)}</a>
+ <small>&nbsp;&nbsp;
+ ${messages[lang]["Posted:"]} ${post.date}
+ </small></h1>
+ <hr>
+ ${post.text(lang, index_teasers)}
+ <p>
+ %if disqus_forum:
+ <a href="${post.permalink()}#disqus_thread">Comments</a>
+ %endif
+ </div>
+ % endfor
+ <div>
+<ul class="pager">
+ %if prevlink:
+ <li class="previous">
+ <a href="${prevlink}">${messages[lang]["&larr; Newer posts"]}</a>
+ </li>
+ %endif
+ %if nextlink:
+ <li class="next">
+ <a href="${nextlink}">${messages[lang]["Older posts &rarr;"]}</a>
+ </li>
+ %endif
+</ul>
+
+ </div>
+ %if disqus_forum:
+ <script type="text/javascript">var disqus_shortname="${disqus_forum}";(function(){var a=document.createElement("script");a.async=true;a.type="text/javascript";a.src="http://"+disqus_shortname+".disqus.com/count.js";(document.getElementsByTagName("HEAD")[0]||document.getElementsByTagName("BODY")[0]).appendChild(a)}());</script>
+ %endif
+</%block>
diff --git a/nikola/data/themes/default/templates/list.tmpl b/nikola/data/themes/default/templates/list.tmpl
new file mode 100644
index 0000000..a60b508
--- /dev/null
+++ b/nikola/data/themes/default/templates/list.tmpl
@@ -0,0 +1,14 @@
+## -*- coding: utf-8 -*-
+<%inherit file="base.tmpl"/>
+<%block name="content">
+ <!--Body content-->
+ <div class="postbox">
+ <h1>${title}</h1>
+ <ul class="unstyled">
+ % for text, link in items:
+ <li><a href="${link}">${text}</a>
+ % endfor
+ </ul>
+ </div>
+ <!--End of body content-->
+</%block>
diff --git a/nikola/data/themes/default/templates/listing.tmpl b/nikola/data/themes/default/templates/listing.tmpl
new file mode 100644
index 0000000..596a704
--- /dev/null
+++ b/nikola/data/themes/default/templates/listing.tmpl
@@ -0,0 +1,10 @@
+## -*- coding: utf-8 -*-
+<%inherit file="base.tmpl"/>
+<%block name="content">
+<ul class="breadcrumb">
+ % for link, crumb in crumbs:
+ <li><a href="${link}">/ ${crumb}</a></li>
+ % endfor
+</ul>
+${code}
+</%block>
diff --git a/nikola/data/themes/default/templates/post.tmpl b/nikola/data/themes/default/templates/post.tmpl
new file mode 100644
index 0000000..b40ff89
--- /dev/null
+++ b/nikola/data/themes/default/templates/post.tmpl
@@ -0,0 +1,51 @@
+## -*- coding: utf-8 -*-
+<%inherit file="base.tmpl"/>
+<%block name="content">
+ <div class="postbox">
+ <h1><a href='${permalink}'>${title}</a></h1>
+ % if link:
+ <p><a href='${link}'>${messages[lang]["Original site"]}</a></p>
+ % endif
+ <hr>
+ <small>
+ ${messages[lang]["Posted:"]} ${post.date}&nbsp;&nbsp;|&nbsp;&nbsp;
+
+ %if len(translations) > 1:
+ %for langname in translations.keys():
+ %if langname != lang:
+ <a href="${post.permalink(langname)}">${messages[langname][u"Read in English"]}</a>
+ &nbsp;&nbsp;|&nbsp;&nbsp;
+ %endif
+ %endfor
+ %endif
+
+ <a href="${post.pagenames[lang]+".txt"}">${messages[lang]["Source"]}</a>
+ %if post.tags:
+ &nbsp;&nbsp;|&nbsp;&nbsp;${messages[lang]["More posts about"]}
+ %for tag in post.tags:
+ <a href="${_link("tag", tag, lang)}"><span class="badge badge-info">${tag}</span></a>
+ %endfor
+ %endif
+ </small>
+ <hr>
+ ${post.text(lang)}
+ <ul class="pager">
+ %if post.prev_post:
+ <li class="previous">
+ <a href="${post.prev_post.permalink(lang)}">${messages[lang]["&larr; Previous post"]}</a>
+ </li>
+ %endif
+ %if post.next_post:
+ <li class="next">
+ <a href="${post.next_post.permalink(lang)}">${messages[lang]["Next post &rarr;"]}</a>
+ </li>
+ %endif
+ </ul>
+ %if disqus_forum:
+ <div id="disqus_thread"></div>
+ <script type="text/javascript">var disqus_shortname="${disqus_forum}";var disqus_url="${post.permalink(absolute=True)}";(function(){var a=document.createElement("script");a.type="text/javascript";a.async=true;a.src="http://"+disqus_shortname+".disqus.com/embed.js";(document.getElementsByTagName("head")[0]||document.getElementsByTagName("body")[0]).appendChild(a)})(); </script>
+ <noscript>Please enable JavaScript to view the <a href="http://disqus.com/?ref_noscript">comments powered by Disqus.</a></noscript>
+ <a href="http://disqus.com" class="dsq-brlink">comments powered by <span class="logo-disqus">Disqus</span></a>
+ %endif
+ </div>
+</%block>
diff --git a/nikola/data/themes/default/templates/story.tmpl b/nikola/data/themes/default/templates/story.tmpl
new file mode 100644
index 0000000..deb0a46
--- /dev/null
+++ b/nikola/data/themes/default/templates/story.tmpl
@@ -0,0 +1,8 @@
+## -*- coding: utf-8 -*-
+<%inherit file="post.tmpl"/>
+<%block name="content">
+%if title:
+ <h1>${title}</h1>
+%endif
+ ${post.text(lang)}
+</%block>
diff --git a/nikola/data/themes/default/templates/tag.tmpl b/nikola/data/themes/default/templates/tag.tmpl
new file mode 100644
index 0000000..ac97829
--- /dev/null
+++ b/nikola/data/themes/default/templates/tag.tmpl
@@ -0,0 +1,7 @@
+## -*- coding: utf-8 -*-
+<%inherit file="list.tmpl"/>
+<%block name="extra_head">
+ %for language in translations:
+ <link rel="alternate" type="application/rss+xml" type="application/rss+xml" title="RSS for tag ${tag} (${language})" href="${_link("tag_rss", tag, lang)}">
+ %endfor
+</%block>
diff --git a/nikola/data/themes/default/templates/tags.tmpl b/nikola/data/themes/default/templates/tags.tmpl
new file mode 100644
index 0000000..10040ca
--- /dev/null
+++ b/nikola/data/themes/default/templates/tags.tmpl
@@ -0,0 +1,14 @@
+## -*- coding: utf-8 -*-
+<%inherit file="base.tmpl"/>
+<%block name="content">
+ <div class="postbox">
+ <!--Body content-->
+ <h1>${title}</h1>
+ <ul class="unstyled">
+ % for text, link in items:
+ <li><a href="${link}"><span class="badge badge-info">${text}</span></a>
+ % endfor
+ </ul>
+ <!--End of body content-->
+ </div>
+</%block>
diff --git a/nikola/data/themes/jinja-default/README b/nikola/data/themes/jinja-default/README
new file mode 100644
index 0000000..3fb7d3f
--- /dev/null
+++ b/nikola/data/themes/jinja-default/README
@@ -0,0 +1,3 @@
+This theme is exactly the same as "default" but using Jinja2 templates.
+
+To try it, set TEMPLATE_ENGINE="jinja" and THEME="jinja-default" in your 'conf.py'
diff --git a/nikola/data/themes/jinja-default/engine b/nikola/data/themes/jinja-default/engine
new file mode 100644
index 0000000..6f04b30
--- /dev/null
+++ b/nikola/data/themes/jinja-default/engine
@@ -0,0 +1 @@
+jinja
diff --git a/nikola/data/themes/jinja-default/parent b/nikola/data/themes/jinja-default/parent
new file mode 100644
index 0000000..4ad96d5
--- /dev/null
+++ b/nikola/data/themes/jinja-default/parent
@@ -0,0 +1 @@
+default
diff --git a/nikola/data/themes/jinja-default/templates/base.tmpl b/nikola/data/themes/jinja-default/templates/base.tmpl
new file mode 100644
index 0000000..cdd911c
--- /dev/null
+++ b/nikola/data/themes/jinja-default/templates/base.tmpl
@@ -0,0 +1,102 @@
+<!DOCTYPE html>
+<html lang="{{lang}}">
+<head>
+ <meta charset="utf-8">
+ <meta name="title" content="{{title}} | {{blog_title}}" >
+ <meta name="description" content="{{description}}" >
+ <meta name="author" content="{{blog_author}}">
+ <title>{{title}} | {{blog_title}}</title>
+ <!-- Le styles -->
+ {% if use_bundles %}
+ <link href="/assets/css/all.css" rel="stylesheet" type="text/css">
+ <script src="/assets/js/all.js" type="text/javascript"></script>
+ {% else %}
+ <link href="/assets/css/bootstrap.css" rel="stylesheet" type="text/css">
+ <link href="/assets/css/bootstrap-responsive.css" rel="stylesheet" type="text/css">
+ <link href="/assets/css/rst.css" rel="stylesheet" type="text/css">
+ <link href="/assets/css/code.css" rel="stylesheet" type="text/css">
+ <link href="/assets/css/colorbox.css" rel="stylesheet" type="text/css"/>
+ <link href="/assets/css/theme.css" rel="stylesheet" type="text/css"/>
+ {% if exists("files/assets/css/custom.css", not_empty=True) %}
+ <link href="/assets/css/custom.css" rel="stylesheet">
+ {% endif %}
+ <script src="/assets/js/jquery-1.7.2.min.js" type="text/javascript"></script>
+ <script src="/assets/js/jquery.colorbox-min.js" type="text/javascript"></script>
+ {% endif %}
+ <!-- Le HTML5 shim, for IE6-8 support of HTML5 elements -->
+ <!--[if lt IE 9]>
+ <script src="http://html5shim.googlecode.com/svn/trunk/html5.js" type="text/javascript"></script>
+ <![endif]-->
+ {% if rss_link %}
+ {{rss_link}}
+ {% else %}
+ {% for language in translations %}
+ <link rel="alternate" type="application/rss+xml" title="RSS ({{language}})" href="{{_link("rss", None, lang)}}">
+ {% endfor %}
+ {% endif %}
+ {% block extra_head %}
+ {% endblock %}
+</head>
+<body>
+ {% if add_this_buttons %}
+ <script type="text/javascript">var addthis_config={"ui_language":"{{lang}}"};</script>
+ {% endif %}
+<div class="container-fluid" id="container">
+ <div class="row-fluid" id="titlerow">
+ <div class="span12" id="titlecolumn">
+ <!-- Banner-like substance !-->
+ <div class="titlebox">
+ <h1 id="blog-title">
+ <a href="{{abs_link('/')}}" title="{{blog_title}}">{{blog_title}}</a>
+ </h1>
+ {% block belowtitle%}
+ {% if translations|length > 1 %}
+ <small>
+ {{ messages[lang]["Also available in: "] }}
+ {% for langname in translations.keys() %}
+ {% if langname != lang %}
+ <a href="{{_link("index", None, langname)}}">{{messages[langname]["LANGUAGE"]}}</a>
+ {% endif %}
+ {% endfor %}
+ </small>
+ {% endif %}
+ {% endblock %}
+ <hr>
+ </div>
+ <!-- End of banner-like substance !-->
+ <div class="row" id="contentrow">
+ <div class="span10" id="contentcolumn">
+ <!--Body content-->
+ {% block content %}{% endblock %}
+ <!--End of body content-->
+ <hr>
+ <small>{{content_footer}}</small>
+ </div>
+ <div class="span2" id="sidebar">
+ <!--Sidebar content-->
+ <ul class="unstyled">
+ <li>{{license}}
+ <!-- social buttons -->
+` {% if add_this_buttons %}
+ <li>
+ <div id="addthisbox" class="addthis_toolbox addthis_default_style">
+ <a class="addthis_button_preferred_1"></a>
+ <a class="addthis_button_preferred_2"></a>
+ <a class="addthis_button_preferred_3"></a>
+ <a class="addthis_button_preferred_4"></a>
+ <a class="addthis_button_compact"></a>
+ <a class="addthis_counter addthis_bubble_style"></a>
+ </div>
+ <script type="text/javascript" src="http://s7.addthis.com/js/250/addthis_widget.js#pubid=ra-4f7088a56bb93798"></script>
+ <!-- End of social buttons -->
+ {% endif %}
+ {% for url, text in sidebar_links[lang] %}
+ <li><a href="{{url}}">{{text}}</a>
+ {% endfor %}
+ <li>{{search_form}}
+ </ul>
+ <!--End of sidebar content-->
+ </div>
+ {{analytics}}
+ <script type="text/javascript">jQuery("a.image-reference").colorbox({rel:"gal",maxWidth:"80%",maxHeight:"80%",scalePhotos:true});</script>
+</body>
diff --git a/nikola/data/themes/jinja-default/templates/gallery.tmpl b/nikola/data/themes/jinja-default/templates/gallery.tmpl
new file mode 100644
index 0000000..a08b148
--- /dev/null
+++ b/nikola/data/themes/jinja-default/templates/gallery.tmpl
@@ -0,0 +1,17 @@
+{% extends "base.tmpl" %}
+{% block sourcelink %}{% endblock %}
+
+{% block content %}
+ {% if text %}
+ <p>
+ {{ text }}
+ </p>
+ {% endif %}
+ <ul class="thumbnails">
+ {% for image in images %}
+ <li><a href="{{image[0]}}" class="thumbnail image-reference"><img src="{{image[2]}}" /></a></li>
+ <img src="${image[1]}" /></a></li>
+ {% endfor %}
+ </ul>
+{% endblock %}
+
diff --git a/nikola/data/themes/jinja-default/templates/index.tmpl b/nikola/data/themes/jinja-default/templates/index.tmpl
new file mode 100644
index 0000000..c1fbb94
--- /dev/null
+++ b/nikola/data/themes/jinja-default/templates/index.tmpl
@@ -0,0 +1,36 @@
+{% extends "base.tmpl" %}
+{% block content %}
+ {% for post in posts %}
+ <div class="postbox">
+ <h1><a href="{{post.permalink(lang)}}">{{post.title(lang)}}</a>
+ <small>&nbsp;&nbsp;
+ {{messages[lang]["Posted:"]}} {{post.date}}
+ </small></h1>
+ <hr>
+ {{post.text(lang, index_teasers)}}
+ <p>
+ {% if disqus_forum %}
+ <a href="{{post.permalink()}}#disqus_thread">Comments</a>
+ {% endif %}
+ </div>
+ {% endfor %}
+ <div>
+<ul class="pager">
+ {%if prevlink %}
+ <li class="previous">
+ <a href="{{prevlink}}">${messages[lang]["&larr; Newer posts"]}</a>
+ </li>
+ {% endif %}
+ {% if nextlink %}
+ <li class="next">
+ <a href="{{nextlink}}">${messages[lang]["Older posts &rarr;"]}</a>
+ </li>
+ {% endif %}
+</ul>
+
+ </div>
+ <hr>
+ {% if disqus_forum %}
+ <script type="text/javascript"> var disqus_shortname = '{{disqus_forum}}'; (function () { var s = document.createElement('script'); s.async = true; s.type = 'text/javascript'; s.src = 'http://' + disqus_shortname + '.disqus.com/count.js'; (document.getElementsByTagName('HEAD')[0] || document.getElementsByTagName('BODY')[0]).appendChild(s); }()); </script>
+ {% endif %}
+{% endblock %}
diff --git a/nikola/data/themes/jinja-default/templates/list.tmpl b/nikola/data/themes/jinja-default/templates/list.tmpl
new file mode 100644
index 0000000..eb11fd0
--- /dev/null
+++ b/nikola/data/themes/jinja-default/templates/list.tmpl
@@ -0,0 +1,13 @@
+{% extends "base.tmpl" %}
+{% block content %}
+ <!--Body content-->
+ <div class="postbox">
+ <h1>{{title}}</h1>
+ <ul class="unstyled">
+ {% for text, link in items %}
+ <li><a href="{{link}}">{{text}}</a>
+ {% endfor %}
+ </ul>
+ </div>
+ <!--End of body content-->
+{% endblock %}
diff --git a/nikola/data/themes/jinja-default/templates/listing.tmpl b/nikola/data/themes/jinja-default/templates/listing.tmpl
new file mode 100644
index 0000000..8310635
--- /dev/null
+++ b/nikola/data/themes/jinja-default/templates/listing.tmpl
@@ -0,0 +1,9 @@
+{% extends "base.tmpl" %}
+{% block content %}
+<ul class="breadcrumb">
+ {% for link, crumb in crumbs %}
+ <li><a href="{{link}}">/ {{crumb}}</a></li>
+ {% endfor %}
+</ul>
+{{code}}
+{% endblock %}
diff --git a/nikola/data/themes/jinja-default/templates/post.tmpl b/nikola/data/themes/jinja-default/templates/post.tmpl
new file mode 100644
index 0000000..876c1a7
--- /dev/null
+++ b/nikola/data/themes/jinja-default/templates/post.tmpl
@@ -0,0 +1,50 @@
+{% extends "base.tmpl" %}
+{% block content %}
+ <div class="postbox">
+ <h1><a href='{{permalink}}'>{{title}}</a></h1>
+ {% if link %}
+ <p><a href='{{link}}'>{{messages[lang]["Original site"]}}</a></p>
+ {% endif %}
+ <hr>
+ <small>
+ {{messages[lang]["Posted:"]}} {{post.date}}&nbsp;&nbsp;|&nbsp;&nbsp;
+
+ {% if translations|length > 1 %}
+ {% for langname in translations.keys() %}
+ {% if langname != lang %}
+ <a href="{{post.permalink(langname)}}">{{messages[langname]["Read in English"]}}</a>
+ &nbsp;&nbsp;|&nbsp;&nbsp;
+ {% endif %}
+ {% endfor %}
+ {% endif %}
+
+ <a href="{{post.pagenames[lang]+".txt"}}">{{messages[lang]["Source"]}}</a>
+ {% if post.tags %}
+ &nbsp;&nbsp;|&nbsp;&nbsp;{{messages[lang]["More posts about"]}}
+ {% for tag in post.tags %}
+ <a href="{{_link("tag", tag, lang)}}"><span class="badge badge-info">{{tag}}</span></a>
+ {% endfor %}
+ {% endif %}
+ </small>
+ <hr>
+ {{post.text(lang)}}
+ <ul class="pager">
+ {%if post.prev_post %}
+ <li class="previous">
+ <a href="{{rel_link(permalink, post.prev_post.permalink(lang))}}">{{messages[lang]["&larr; Previous post"]}}</a>
+ </li>
+ {% endif %}
+ {%if post.next_post %}
+ <li class="next">
+ <a href="{{rel_link(permalink, post.next_post.permalink(lang))}}">{{messages[lang]["Next post &rarr;"]}}</a>
+ </li>
+ {% endif %}
+ </ul>
+ {% if disqus_forum %}
+ <div id="disqus_thread"></div>
+ <script type="text/javascript"> var disqus_shortname = '{{disqus_forum}}'; var disqus_url = '{{post.permalink(absolute=True)}}'; (function() { var dsq = document.createElement('script'); dsq.type = 'text/javascript'; dsq.async = true; dsq.src = 'http://' + disqus_shortname + '.disqus.com/embed.js'; (document.getElementsByTagName('head')[0] || document.getElementsByTagName('body')[0]).appendChild(dsq); })(); </script>
+ <noscript>Please enable JavaScript to view the <a href="http://disqus.com/?ref_noscript">comments powered by Disqus.</a></noscript>
+ <a href="http://disqus.com" class="dsq-brlink">comments powered by <span class="logo-disqus">Disqus</span></a>
+ {% endif %}
+ </div>
+{% endblock %}
diff --git a/nikola/data/themes/jinja-default/templates/story.tmpl b/nikola/data/themes/jinja-default/templates/story.tmpl
new file mode 100644
index 0000000..411c269
--- /dev/null
+++ b/nikola/data/themes/jinja-default/templates/story.tmpl
@@ -0,0 +1,7 @@
+{% extends "post.tmpl" %}
+{% block content %}
+{% if title %}
+ <h1>{{title}}</h1>
+{% endif %}
+ {{post.text(lang)}}
+{% endblock %}
diff --git a/nikola/data/themes/jinja-default/templates/tag.tmpl b/nikola/data/themes/jinja-default/templates/tag.tmpl
new file mode 100644
index 0000000..59facb4
--- /dev/null
+++ b/nikola/data/themes/jinja-default/templates/tag.tmpl
@@ -0,0 +1,6 @@
+{% extends "list.tmpl"%}
+{%block extra_head %}
+ {% for language in translations %}
+ <link rel="alternate" type="application/rss+xml" type="application/rss+xml" title="RSS for tag {{tag}} ({{language}})" href="{{_link("tag_rss", tag, lang)}}">
+ {% endfor %}
+{% endblock %}
diff --git a/nikola/data/themes/jinja-default/templates/tags.tmpl b/nikola/data/themes/jinja-default/templates/tags.tmpl
new file mode 100644
index 0000000..3eae88d
--- /dev/null
+++ b/nikola/data/themes/jinja-default/templates/tags.tmpl
@@ -0,0 +1,13 @@
+{% extends "base.tmpl" %}
+{% block content %}
+ <div class="postbox">
+ <!--Body content-->
+ <h1>{{title}}</h1>
+ <ul class="unstyled">
+ {% for text, link in items %}
+ <li><a href="{{link}}"><span class="badge badge-info">{{text}}</span></a>
+ {% endfor %}
+ </ul>
+ <!--End of body content-->
+ </div>
+{% endblock %}
diff --git a/nikola/data/themes/site/README b/nikola/data/themes/site/README
new file mode 100644
index 0000000..6cbc7dd
--- /dev/null
+++ b/nikola/data/themes/site/README
@@ -0,0 +1,32 @@
+A more "website-done-with-bootstrap" theme, so to speak. This theme should
+be completely compatible with the default theme and need no configuration
+changes, except for the search form, as explained below.
+
+
+Has a fixed navigation bar at top that displays:
+
+* Site name
+* navigation links (taken from sidebar_links in dodo.py)
+* search form (taken from SEARCH_FORM in dodo.py)
+* Link to page's source code (if available)
+* Floating social bar on the left margin
+* The below-title block is displayed flush right
+
+This theme is used in Nikola's website: http://nikola.ralsina.com.ar
+
+Important: To fit in the bootstrap navigation bar, the search form needs the
+navbar-form and pull-left CSS classes applied. Here is an example with Nikola's
+default duckduckgo search form:
+
+ SEARCH_FORM = """
+ <!-- Custom search -->
+ <form method="get" id="search" action="http://duckduckgo.com/" class="navbar-form pull-left">
+ <input type="hidden" name="sites" value="%s"/>
+ <input type="hidden" name="k8" value="#444444"/>
+ <input type="hidden" name="k9" value="#D51920"/>
+ <input type="hidden" name="kt" value="h"/>
+ <input type="text" name="q" maxlength="255" placeholder="Search&hellip;" class="span2" style="margin-top: 4px;"/>
+ <input type="submit" value="DuckDuckGo Search" style="visibility: hidden;" />
+ </form>
+ <!-- End of custom search -->
+ """ % BLOG_URL
diff --git a/nikola/data/themes/site/assets/css/theme.css b/nikola/data/themes/site/assets/css/theme.css
new file mode 100644
index 0000000..6b21e2e
--- /dev/null
+++ b/nikola/data/themes/site/assets/css/theme.css
@@ -0,0 +1,27 @@
+body { margin-top: 50px;}
+
+#container {
+ width: 960px;
+ margin: 50 auto;
+}
+
+#contentcolumn {
+ max-width: 760px;
+}
+#q {
+ width: 150px;
+}
+
+img {
+ max-width: 90%;
+}
+
+.postbox {
+ border-bottom: 2px solid darkgrey;
+ margin-bottom: 12px;
+}
+
+.footerbox {padding: 15px; text-align: center; margin-bottom: 15px;}
+
+#addthisbox {top: 180px; left:10px;}
+
diff --git a/nikola/data/themes/site/engine b/nikola/data/themes/site/engine
new file mode 100644
index 0000000..2951cdd
--- /dev/null
+++ b/nikola/data/themes/site/engine
@@ -0,0 +1 @@
+mako
diff --git a/nikola/data/themes/site/parent b/nikola/data/themes/site/parent
new file mode 100644
index 0000000..4ad96d5
--- /dev/null
+++ b/nikola/data/themes/site/parent
@@ -0,0 +1 @@
+default
diff --git a/nikola/data/themes/site/templates/base.tmpl b/nikola/data/themes/site/templates/base.tmpl
new file mode 100644
index 0000000..46e9b39
--- /dev/null
+++ b/nikola/data/themes/site/templates/base.tmpl
@@ -0,0 +1,104 @@
+## -*- coding: utf-8 -*-
+<!DOCTYPE html>
+<html lang="${lang}">
+<head>
+ <meta charset="utf-8">
+ <meta name="title" content="${title} | ${blog_title}" >
+ <meta name="description" content="${description}" >
+ <meta name="author" content="${blog_author}">
+ <title>${title} | ${blog_title}</title>
+ <!-- Le styles -->
+ %if use_bundles:
+ <link href="/assets/css/all.css" rel="stylesheet" type="text/css">
+ <script src="/assets/js/all.js" type="text/javascript"></script>
+ %else:
+ <link href="/assets/css/bootstrap.css" rel="stylesheet" type="text/css">
+ <link href="/assets/css/bootstrap-responsive.css" rel="stylesheet" type="text/css">
+ <link href="/assets/css/rst.css" rel="stylesheet" type="text/css">
+ <link href="/assets/css/code.css" rel="stylesheet" type="text/css">
+ <link href="/assets/css/colorbox.css" rel="stylesheet" type="text/css"/>
+ <link href="/assets/css/theme.css" rel="stylesheet" type="text/css"/>
+ %if exists("files/assets/css/custom.css", not_empty=True):
+ <link href="/assets/css/custom.css" rel="stylesheet" type="text/css">
+ %endif
+ <script src="/assets/js/jquery-1.7.2.min.js" type="text/javascript"></script>
+ <script src="/assets/js/jquery.colorbox-min.js" type="text/javascript"></script>
+ %endif
+ <!-- Le HTML5 shim, for IE6-8 support of HTML5 elements -->
+ <!--[if lt IE 9]>
+ <script src="http://html5shim.googlecode.com/svn/trunk/html5.js"></script>
+ <![endif]-->
+ %if rss_link:
+ ${rss_link}
+ %else:
+ %for language in translations:
+ <link rel="alternate" type="application/rss+xml" title="RSS (${language})" href="${_link("rss", None, lang)}">
+ %endfor
+ %endif
+ <%block name="extra_head">
+ </%block>
+</head>
+<body>
+<!-- Menubar -->
+<div class="navbar navbar-fixed-top">
+ <div class="navbar-inner">
+ <div class="container">
+ <a class="brand" href="${abs_link('/')}">
+ ${blog_title}
+ </a>
+ <ul class="nav">
+ %for url, text in sidebar_links[lang]:
+ % if rel_link(permalink, url) == "#":
+ <li class="active"><a href="${url}">${text}</a>
+ %else:
+ <li><a href="${url}">${text}</a>
+ %endif
+ %endfor
+ </ul>
+ %if search_form:
+ ${search_form}
+ %endif
+ <ul class="nav pull-right">
+ <%block name="belowtitle">
+ %if len(translations) > 1:
+ <li>
+ %for langname in translations.keys():
+ %if langname != lang:
+ <a href="${_link("index", None, langname)}">${messages[langname]["LANGUAGE"]}</a>
+ %endif
+ %endfor
+ </li>
+ %endif
+ </%block>
+ <%block name="sourcelink"> </%block>
+ </ul>
+ </div>
+ </div>
+</div>
+<!-- End of Menubar -->
+
+<div class="container" id="container">
+ <!--Body content-->
+ <%block name="content"></%block>
+ <!--End of body content-->
+ <div class="footerbox">
+ ${content_footer}
+ </div>
+</div>
+ %if add_this_buttons:
+ <!-- addthis -->
+ <div id="addthisbox" class="addthis_bar addthis_bar_vertical addthis_bar_small">
+ <div class="addthis_toolbox addthis_default_style">
+ <span><a class="addthis_button_preferred_1"></a></span>
+ <span><a class="addthis_button_preferred_2"></a></span>
+ <span><a class="addthis_button_preferred_3"></a></span>
+ <span><a class="addthis_button_preferred_4"></a></span>
+ <span><a class="addthis_button_compact"></a></span>
+ </div>
+ </div>
+ <script type="text/javascript" src="http://s7.addthis.com/js/300/addthis_widget.js#pubid=ra-4f7088a56bb93798"></script>
+ <!-- End of addthis -->
+ %endif
+${analytics}
+ <script type="text/javascript">jQuery("a.image-reference").colorbox({rel:"gal",maxWidth:"80%",maxHeight:"80%",scalePhotos:true});</script>
+</body>
diff --git a/nikola/data/themes/site/templates/post.tmpl b/nikola/data/themes/site/templates/post.tmpl
new file mode 100644
index 0000000..99c0f1f
--- /dev/null
+++ b/nikola/data/themes/site/templates/post.tmpl
@@ -0,0 +1,55 @@
+## -*- coding: utf-8 -*-
+<%inherit file="base.tmpl"/>
+<%block name="content">
+ <div class="postbox">
+ <h1>${title}</h1>
+ % if link:
+ <p><a href='${link}'>${messages[lang]["Original site"]}</a></p>
+ % endif
+ <hr>
+ <small>
+ ${messages[lang]["Posted:"]} ${post.date}
+
+ %if len(translations) > 1:
+ %for langname in translations.keys():
+ %if langname != lang:
+ &nbsp;&nbsp;|&nbsp;&nbsp;
+ <a href="${post.permalink(langname)}">${messages[langname][u"Read in English"]}</a>
+ %endif
+ %endfor
+ %endif
+ %if post.tags:
+ &nbsp;&nbsp;|&nbsp;&nbsp;${messages[lang]["More posts about"]}
+ %for tag in post.tags:
+ <a href="${_link("tag", tag, lang)}"><span class="badge badge-info">${tag}</span></a>
+ %endfor
+ %endif
+ </small>
+ <hr>
+ ${post.text(lang)}
+ <ul class="pager">
+ %if post.prev_post:
+ <li class="previous">
+ <a href="${post.prev_post.permalink(lang)}">${messages[lang]["&larr; Previous post"]}</a>
+ </li>
+ %endif
+ %if post.next_post:
+ <li class="next">
+ <a href="${post.next_post.permalink(lang)}">${messages[lang]["Next post &rarr;"]}</a>
+ </li>
+ %endif
+ </ul>
+ %if disqus_forum:
+ <div id="disqus_thread"></div>
+ <script type="text/javascript">var disqus_shortname="${disqus_forum}";var disqus_url="${post.permalink(absolute=True)}";(function(){var a=document.createElement("script");a.type="text/javascript";a.async=true;a.src="http://"+disqus_shortname+".disqus.com/embed.js";(document.getElementsByTagName("head")[0]||document.getElementsByTagName("body")[0]).appendChild(a)})();</script>
+ <noscript>Please enable JavaScript to view the <a href="http://disqus.com/?ref_noscript">comments powered by Disqus.</a></noscript>
+ <a href="http://disqus.com" class="dsq-brlink">comments powered by <span class="logo-disqus">Disqus</span></a>
+ %endif
+</%block>
+
+<%block name="sourcelink">
+ <li>
+ <a href="${post.pagenames[lang]+post.source_ext()}">${messages[lang]["Source"]}</a>
+ </li>
+ </div>
+</%block>
diff --git a/nikola/filters.py b/nikola/filters.py
new file mode 100644
index 0000000..caea95e
--- /dev/null
+++ b/nikola/filters.py
@@ -0,0 +1,29 @@
+"""Utility functions to help you run filters on files."""
+
+import os
+import subprocess
+import tempfile
+
+def runinplace(command, infile):
+ """Runs a command in-place on a file.
+
+ command is a string of the form: "commandname %1 %2" and
+ it will be execed with infile as %1 and a temporary file
+ as %2. Then, that temporary file will be moved over %1.
+
+ Example usage:
+
+ runinplace("yui-compressor %1 -o %2", "myfile.css")
+
+ That will replace myfile.css with a minified version.
+
+ """
+
+ tmpdir = tempfile.mkdtemp()
+ tmpfname = os.path.join(tmpdir, os.path.basename(infile))
+ command = command.replace('%1', infile)
+ command = command.replace('%2', infile)
+ subprocess.check_call(command, shell=True)
+
+def yui_compressor(infile):
+ return runinplace('yui-compressor %1 -o %2', infile) \ No newline at end of file
diff --git a/nikola/jinja_templates.py b/nikola/jinja_templates.py
new file mode 100644
index 0000000..f55465f
--- /dev/null
+++ b/nikola/jinja_templates.py
@@ -0,0 +1,37 @@
+########################################
+# Jinja template handlers
+########################################
+
+import os
+
+import jinja2
+
+lookup = None
+cache = {}
+
+
+def get_template_lookup(directories):
+ return jinja2.Environment(loader=jinja2.FileSystemLoader(
+ directories,
+ encoding='utf-8',
+ ))
+
+
+def render_template(template_name, output_name, context, global_context):
+ template = lookup.get_template(template_name)
+ local_context = {}
+ local_context.update(global_context)
+ local_context.update(context)
+ output = template.render(**local_context)
+ if output_name is not None:
+ try:
+ os.makedirs(os.path.dirname(output_name))
+ except:
+ pass
+ with open(output_name, 'w+') as output:
+ output.write(output.encode('utf8'))
+ return output
+
+
+def template_deps(template_name):
+ return []
diff --git a/nikola/mako_templates.py b/nikola/mako_templates.py
new file mode 100644
index 0000000..e4a79d9
--- /dev/null
+++ b/nikola/mako_templates.py
@@ -0,0 +1,65 @@
+########################################
+# Mako template handlers
+########################################
+
+import os
+import shutil
+
+from mako import util, lexer
+from mako.lookup import TemplateLookup
+
+lookup = None
+cache = {}
+
+
+def get_deps(filename):
+ text = util.read_file(filename)
+ lex = lexer.Lexer(text=text, filename=filename)
+ lex.parse()
+
+ deps = []
+ for n in lex.template.nodes:
+ if getattr(n, 'keyword', None) == "inherit":
+ deps.append(n.attributes['file'])
+ # TODO: include tags are not handled
+ return deps
+
+
+def get_template_lookup(directories):
+ cache_dir = os.path.join('cache', '.mako.tmp')
+ if os.path.exists(cache_dir):
+ shutil.rmtree(cache_dir)
+ return TemplateLookup(
+ directories=directories,
+ module_directory=cache_dir,
+ output_encoding='utf-8',
+ )
+
+
+def render_template(template_name, output_name, context, global_context):
+ template = lookup.get_template(template_name)
+ local_context = {}
+ local_context.update(global_context)
+ local_context.update(context)
+ data = template.render_unicode(**local_context)
+ if output_name is not None:
+ try:
+ os.makedirs(os.path.dirname(output_name))
+ except:
+ pass
+ with open(output_name, 'w+') as output:
+ output.write(data)
+ return data
+
+
+def template_deps(template_name):
+ # We can cache here because depedencies should
+ # not change between runs
+ if cache.get(template_name, None) is None:
+ template = lookup.get_template(template_name)
+ dep_filenames = get_deps(template.filename)
+ deps = [template.filename]
+ for fname in dep_filenames:
+ deps += template_deps(fname)
+ cache[template_name] = tuple(deps)
+ return list(cache[template_name])
diff --git a/nikola/md.py b/nikola/md.py
new file mode 100644
index 0000000..16bcec8
--- /dev/null
+++ b/nikola/md.py
@@ -0,0 +1,29 @@
+"""Implementation of compile_html based on markdown."""
+
+__all__ = ['compile_html']
+
+import codecs
+import os
+import re
+
+from markdown import markdown
+
+
+def compile_html(source, dest):
+ try:
+ os.makedirs(os.path.dirname(dest))
+ except:
+ pass
+ with codecs.open(dest, "w+", "utf8") as out_file:
+ with codecs.open(source, "r", "utf8") as in_file:
+ data = in_file.read()
+
+ output = markdown(data, ['fenced_code', 'codehilite'])
+ # remove the H1 because there is "title" h1.
+ output = re.sub(r'<h1>.*</h1>', '', output)
+ # python-markdown's highlighter uses the class 'codehilite' to wrap
+ # code, # instead of the standard 'code'. None of the standard pygments
+ # stylesheets use this class, so swap it to be 'code'
+ output = re.sub(r'(<div[^>]+class="[^"]*)codehilite([^>]+)',
+ r'\1code\2', output)
+ out_file.write(output)
diff --git a/nikola/nikola.py b/nikola/nikola.py
new file mode 100644
index 0000000..aa43398
--- /dev/null
+++ b/nikola/nikola.py
@@ -0,0 +1,1711 @@
+# -*- 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 post import Post
+import utils
+
+config_changed = utils.config_changed
+
+__all__ = ['Nikola', 'nikola_main']
+
+
+class Nikola(object):
+
+ """Class that handles site generation.
+
+ Takes a site config as argument on creation.
+ """
+
+ def __init__(self, **config):
+ """Setup proper environment for running tasks."""
+
+ self.global_data = {}
+ self.posts_per_year = defaultdict(list)
+ self.posts_per_tag = defaultdict(list)
+ self.timeline = []
+ self.pages = []
+ self._scanned = False
+
+ # This is the default config
+ # TODO: fill it
+ self.config = {
+ 'ARCHIVE_PATH': "",
+ 'ARCHIVE_FILENAME': "archive.html",
+ 'DEFAULT_LANG': "en",
+ 'OUTPUT_FOLDER': 'output',
+ 'FILES_FOLDERS': {'files': ''},
+ 'LISTINGS_FOLDER': 'listings',
+ 'ADD_THIS_BUTTONS': True,
+ 'INDEX_DISPLAY_POST_COUNT': 10,
+ 'INDEX_TEASERS': False,
+ 'MAX_IMAGE_SIZE': 1280,
+ 'USE_FILENAME_AS_TITLE': True,
+ 'SLUG_TAG_PATH': False,
+ 'INDEXES_TITLE': "",
+ 'INDEXES_PAGES': "",
+ 'FILTERS': {},
+ 'USE_BUNDLES': True,
+ 'TAG_PAGES_ARE_INDEXES': False,
+ 'post_compilers': {
+ "rest": ['.txt', '.rst'],
+ "markdown": ['.md', '.mdown', '.markdown'],
+ "html": ['.html', '.htm'],
+ },
+ }
+ 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.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.GLOBAL_CONTEXT['_link'] = self.link
+ self.GLOBAL_CONTEXT['rel_link'] = self.rel_link
+ self.GLOBAL_CONTEXT['abs_link'] = self.abs_link
+ self.GLOBAL_CONTEXT['exists'] = self.file_exists
+ self.GLOBAL_CONTEXT['add_this_buttons'] = self.config[
+ 'ADD_THIS_BUTTONS']
+ self.GLOBAL_CONTEXT['index_display_post_count'] = self.config[
+ '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
+
+ def render_template(self, template_name, output_name, context):
+ data = self.templates_module.render_template(
+ template_name, None, context, self.GLOBAL_CONTEXT)
+
+ assert output_name.startswith(self.config["OUTPUT_FOLDER"])
+ url_part = output_name[len(self.config["OUTPUT_FOLDER"]) + 1:]
+
+ #this to support windows paths
+ url_part = "/".join(url_part.split(os.sep))
+
+ src = urlparse.urljoin(self.config["BLOG_URL"], url_part)
+
+ parsed_src = urlparse.urlsplit(src)
+ src_elems = parsed_src.path.split('/')[1:]
+
+ def replacer(dst):
+ # Refuse to replace links that are full URLs.
+ dst_url = urlparse.urlparse(dst)
+ if dst_url.netloc:
+ if dst_url.scheme == 'link': # Magic link
+ dst = self.link(dst_url.netloc, dst_url.path.lstrip('/'),
+ context['lang'])
+ else:
+ return dst
+
+ # Normalize
+ dst = urlparse.urljoin(src, dst)
+ # Avoid empty links.
+ if src == dst:
+ return "#"
+ # Check that link can be made relative, otherwise return dest
+ parsed_dst = urlparse.urlsplit(dst)
+ if parsed_src[:2] != parsed_dst[:2]:
+ return dst
+
+ # Now both paths are on the same site and absolute
+ dst_elems = parsed_dst.path.split('/')[1:]
+
+ i = 0
+ for (i, s), d in zip(enumerate(src_elems), dst_elems):
+ if s != d:
+ break
+ # Now i is the longest common prefix
+ result = '/'.join(['..'] * (len(src_elems) - i - 1) +
+ dst_elems[i:])
+
+ if not result:
+ result = "."
+
+ # Don't forget the fragment (anchor) part of the link
+ if parsed_dst.fragment:
+ result += "#" + parsed_dst.fragment
+
+ assert result, (src, dst, i, src_elems, dst_elems)
+
+ return result
+
+ try:
+ os.makedirs(os.path.dirname(output_name))
+ except:
+ pass
+ doc = lxml.html.document_fromstring(data)
+ doc.rewrite_links(replacer)
+ data = '<!DOCTYPE html>' + lxml.html.tostring(doc, encoding='utf8')
+ with open(output_name, "w+") as post_file:
+ post_file.write(data)
+
+ def path(self, kind, name, lang, is_link=False):
+ """Build the path to a certain kind of page.
+
+ kind is one of:
+
+ * tag_index (name is ignored)
+ * tag (and name is the tag name)
+ * tag_rss (name is the tag name)
+ * archive (and name is the year, or None for the main archive index)
+ * index (name is the number in index-number)
+ * rss (name is ignored)
+ * gallery (name is the gallery name)
+ * listing (name is the source code file name)
+
+ The returned value is always a path relative to output, like
+ "categories/whatever.html"
+
+ If is_link is True, the path is absolute and uses "/" as separator
+ (ex: "/archive/index.html").
+ If is_link is False, the path is relative to output and uses the
+ platform's separator.
+ (ex: "archive\\index.html")
+ """
+
+ path = []
+
+ if kind == "tag_index":
+ path = filter(None, [self.config['TRANSLATIONS'][lang],
+ self.config['TAG_PATH'], 'index.html'])
+ elif kind == "tag":
+ if self.config['SLUG_TAG_PATH']:
+ name = utils.slugify(name)
+ path = filter(None, [self.config['TRANSLATIONS'][lang],
+ self.config['TAG_PATH'], name + ".html"])
+ elif kind == "tag_rss":
+ if self.config['SLUG_TAG_PATH']:
+ name = utils.slugify(name)
+ path = filter(None, [self.config['TRANSLATIONS'][lang],
+ self.config['TAG_PATH'], name + ".xml"])
+ elif kind == "index":
+ if name > 0:
+ path = filter(None, [self.config['TRANSLATIONS'][lang],
+ self.config['INDEX_PATH'], 'index-%s.html' % name])
+ else:
+ path = filter(None, [self.config['TRANSLATIONS'][lang],
+ self.config['INDEX_PATH'], 'index.html'])
+ elif kind == "rss":
+ path = filter(None, [self.config['TRANSLATIONS'][lang],
+ self.config['RSS_PATH'], 'rss.xml'])
+ elif kind == "archive":
+ if name:
+ path = filter(None, [self.config['TRANSLATIONS'][lang],
+ self.config['ARCHIVE_PATH'], name, 'index.html'])
+ else:
+ path = filter(None, [self.config['TRANSLATIONS'][lang],
+ self.config['ARCHIVE_PATH'], self.config['ARCHIVE_FILENAME']])
+ elif kind == "gallery":
+ path = filter(None,
+ [self.config['GALLERY_PATH'], name, 'index.html'])
+ elif kind == "listing":
+ path = filter(None,
+ [self.config['LISTINGS_FOLDER'], name + '.html'])
+ if is_link:
+ return '/' + ('/'.join(path))
+ else:
+ return os.path.join(*path)
+
+ def link(self, *args):
+ return self.path(*args, is_link=True)
+
+ def abs_link(self, dst):
+ # Normalize
+ dst = urlparse.urljoin(self.config['BLOG_URL'], dst)
+
+ return urlparse.urlparse(dst).path
+
+ def rel_link(self, src, dst):
+ # Normalize
+ src = urlparse.urljoin(self.config['BLOG_URL'], src)
+ dst = urlparse.urljoin(src, dst)
+ # Avoid empty links.
+ if src == dst:
+ return "#"
+ # Check that link can be made relative, otherwise return dest
+ parsed_src = urlparse.urlsplit(src)
+ parsed_dst = urlparse.urlsplit(dst)
+ if parsed_src[:2] != parsed_dst[:2]:
+ return dst
+ # Now both paths are on the same site and absolute
+ src_elems = parsed_src.path.split('/')[1:]
+ dst_elems = parsed_dst.path.split('/')[1:]
+ i = 0
+ for (i, s), d in zip(enumerate(src_elems), dst_elems):
+ if s != d:
+ break
+ else:
+ i += 1
+ # Now i is the longest common prefix
+ return '/'.join(['..'] * (len(src_elems) - i - 1) + dst_elems[i:])
+
+ def file_exists(self, path, not_empty=False):
+ """Returns True if the file exists. If not_empty is True,
+ it also has to be not empty."""
+ exists = os.path.exists(path)
+ if exists and not_empty:
+ exists = os.stat(path).st_size > 0
+ return exists
+
+ def gen_tasks(self):
+
+ 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' )
+
+ 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']:
+ 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])
+ if dest in targets:
+ raise Exception(
+ 'Duplicated output path %r in post %r' %
+ (post.pagenames[lang], base_path))
+ targets.add(dest)
+ self.global_data[post.post_name] = post
+ if post.use_in_feeds:
+ self.posts_per_year[
+ str(post.date.year)].append(post.post_name)
+ for tag in post.tags:
+ self.posts_per_tag[tag].append(post.post_name)
+ else:
+ self.pages.append(post)
+ for name, post in self.global_data.items():
+ self.timeline.append(post)
+ self.timeline.sort(cmp=lambda a, b: cmp(a.date, b.date))
+ self.timeline.reverse()
+ post_timeline = [p for p in self.timeline if p.use_in_feeds]
+ for i, p in enumerate(post_timeline[1:]):
+ p.next_post = post_timeline[i]
+ for i, p in enumerate(post_timeline[:-1]):
+ p.prev_post = post_timeline[i + 1]
+ self._scanned = True
+ print "done!"
+
+ def generic_page_renderer(self, lang, wildcard,
+ template_name, destination, filters):
+ """Render post fragments to final HTML pages."""
+ for post in glob.glob(wildcard):
+ post_name = os.path.splitext(post)[0]
+ context = {}
+ post = self.global_data[post_name]
+ deps = post.deps(lang) + self.template_deps(template_name)
+ context['post'] = post
+ context['lang'] = lang
+ context['title'] = post.title(lang)
+ context['description'] = post.description(lang)
+ context['permalink'] = post.permalink(lang)
+ context['page_list'] = self.pages
+ output_name = os.path.join(
+ self.config['OUTPUT_FOLDER'],
+ self.config['TRANSLATIONS'][lang],
+ destination,
+ post.pagenames[lang] + ".html")
+ deps_dict = copy(context)
+ deps_dict.pop('post')
+ if post.prev_post:
+ deps_dict['PREV_LINK'] = [post.prev_post.permalink(lang)]
+ if post.next_post:
+ deps_dict['NEXT_LINK'] = [post.next_post.permalink(lang)]
+ deps_dict['OUTPUT_FOLDER'] = self.config['OUTPUT_FOLDER']
+ deps_dict['TRANSLATIONS'] = self.config['TRANSLATIONS']
+
+ task = {
+ 'name': output_name.encode('utf-8'),
+ 'file_dep': deps,
+ 'targets': [output_name],
+ 'actions': [(self.render_template,
+ [template_name, output_name, context])],
+ 'clean': True,
+ 'uptodate': [config_changed(deps_dict)],
+ }
+
+ 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)
+ for post in posts:
+ deps += post.deps(lang)
+ context = {}
+ context["posts"] = posts
+ context["title"] = self.config['BLOG_TITLE']
+ context["description"] = self.config['BLOG_DESCRIPTION']
+ context["lang"] = lang
+ context["prevlink"] = None
+ context["nextlink"] = None
+ context.update(extra_context)
+ deps_context = copy(context)
+ deps_context["posts"] = [(p.titles[lang], p.permalink(lang))
+ for p in posts]
+ task = {
+ 'name': output_name.encode('utf8'),
+ 'targets': [output_name],
+ 'file_dep': deps,
+ 'actions': [(self.render_template,
+ [template_name, output_name, context])],
+ 'clean': True,
+ 'uptodate': [config_changed(deps_context)]
+ }
+
+ 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__)
diff --git a/nikola/post.py b/nikola/post.py
new file mode 100644
index 0000000..9b2d73f
--- /dev/null
+++ b/nikola/post.py
@@ -0,0 +1,180 @@
+# -*- coding: utf-8 -*-
+
+import codecs
+import os
+
+import lxml.html
+
+import utils
+
+__all__ = ['Post']
+
+
+class Post(object):
+
+ """Represents a blog post or web page."""
+
+ def __init__(self, source_path, destination, use_in_feeds,
+ translations, default_lang, blog_url, compile_html, messages):
+ """Initialize post.
+
+ The base path is the .txt post file. From it we calculate
+ the meta file, as well as any translations available, and
+ the .html fragment file path.
+
+ `compile_html` is a function that knows how to compile this Post to
+ html.
+ """
+ self.prev_post = None
+ self.next_post = None
+ self.blog_url = blog_url
+ self.is_draft = False
+ self.source_path = source_path # posts/blah.txt
+ self.post_name = os.path.splitext(source_path)[0] # posts/blah
+ # cache/posts/blah.html
+ self.base_path = os.path.join('cache', self.post_name + ".html")
+ self.metadata_path = self.post_name + ".meta" # posts/blah.meta
+ self.folder = destination
+ self.translations = translations
+ self.default_lang = default_lang
+ self.messages = messages
+ if os.path.isfile(self.metadata_path):
+ with codecs.open(self.metadata_path, "r", "utf8") as meta_file:
+ meta_data = meta_file.readlines()
+ while len(meta_data) < 6:
+ meta_data.append("")
+ (default_title, default_pagename, self.date, self.tags,
+ self.link, default_description) = \
+ [x.strip() for x in meta_data][:6]
+ else:
+ (default_title, default_pagename, self.date, self.tags,
+ self.link, default_description) = \
+ utils.get_meta(self.source_path)
+
+ if not default_title or not default_pagename or not self.date:
+ raise OSError("You must set a title and slug and date!")
+
+ self.date = utils.to_datetime(self.date)
+ self.tags = [x.strip() for x in self.tags.split(',')]
+ self.tags = filter(None, self.tags)
+
+ # While draft comes from the tags, it's not really a tag
+ self.use_in_feeds = use_in_feeds and "draft" not in self.tags
+ self.is_draft = 'draft' in self.tags
+ self.tags = [t for t in self.tags if t != 'draft']
+
+ self.compile_html = compile_html
+
+ self.pagenames = {}
+ self.titles = {}
+ self.descriptions = {}
+ # Load internationalized titles
+ # TODO: this has gotten much too complicated. Rethink.
+ for lang in translations:
+ if lang == default_lang:
+ self.titles[lang] = default_title
+ self.pagenames[lang] = default_pagename
+ self.descriptions[lang] = default_description
+ else:
+ metadata_path = self.metadata_path + "." + lang
+ source_path = self.source_path + "." + lang
+ try:
+ if os.path.isfile(metadata_path):
+ with codecs.open(
+ metadata_path, "r", "utf8") as meta_file:
+ meta_data = [x.strip() for x in
+ meta_file.readlines()]
+ while len(meta_data) < 6:
+ meta_data.append("")
+ self.titles[lang] = meta_data[0] or default_title
+ self.pagenames[lang] = meta_data[1] or\
+ default_pagename
+ self.descriptions[lang] = meta_data[5] or\
+ default_description
+ else:
+ ttitle, ppagename, tmp1, tmp2, tmp3, ddescription = \
+ utils.get_meta(source_path)
+ self.titles[lang] = ttitle or default_title
+ self.pagenames[lang] = ppagename or default_pagename
+ self.descriptions[lang] = ddescription or\
+ default_description
+ except:
+ self.titles[lang] = default_title
+ self.pagenames[lang] = default_pagename
+ self.descriptions[lang] = default_description
+
+ def title(self, lang):
+ """Return localized title."""
+ return self.titles[lang]
+
+ def description(self, lang):
+ """Return localized description."""
+ return self.descriptions[lang]
+
+ def deps(self, lang):
+ """Return a list of dependencies to build this post's page."""
+ deps = [self.base_path]
+ if lang != self.default_lang:
+ deps += [self.base_path + "." + lang]
+ deps += self.fragment_deps(lang)
+ return deps
+
+ def fragment_deps(self, lang):
+ """Return a list of dependencies to build this post's fragment."""
+ deps = [self.source_path]
+ if os.path.isfile(self.metadata_path):
+ deps.append(self.metadata_path)
+ if lang != self.default_lang:
+ lang_deps = filter(os.path.exists, [x + "." + lang for x in deps])
+ deps += lang_deps
+ return deps
+
+ def text(self, lang, teaser_only=False):
+ """Read the post file for that language and return its contents"""
+ file_name = self.base_path
+ if lang != self.default_lang:
+ file_name_lang = file_name + ".%s" % lang
+ if os.path.exists(file_name_lang):
+ file_name = file_name_lang
+ with codecs.open(file_name, "r", "utf8") as post_file:
+ data = post_file.read()
+
+ if data:
+ data = lxml.html.make_links_absolute(data, self.permalink())
+ if data and teaser_only:
+ e = lxml.html.fromstring(data)
+ teaser = []
+ flag = False
+ for elem in e:
+ elem_string = lxml.html.tostring(elem)
+ if '<!-- TEASER_END -->' in elem_string.upper():
+ flag = True
+ break
+ teaser.append(elem_string)
+ if flag:
+ teaser.append('<p><a href="%s">%s...</a></p>' %
+ (self.permalink(lang), self.messages[lang]["Read more"]))
+ data = ''.join(teaser)
+ return data
+
+ def destination_path(self, lang, extension='.html'):
+ path = os.path.join(self.translations[lang],
+ self.folder, self.pagenames[lang] + extension)
+ return path
+
+ def permalink(self, lang=None, absolute=False, extension='.html'):
+ if lang is None:
+ lang = self.default_lang
+ pieces = list(os.path.split(self.translations[lang]))
+ pieces += list(os.path.split(self.folder))
+ pieces += [self.pagenames[lang] + extension]
+ pieces = filter(None, pieces)
+ if absolute:
+ pieces = [self.blog_url] + pieces
+ else:
+ pieces = [""] + pieces
+ link = "/".join(pieces)
+ return link
+
+ def source_ext(self):
+ return os.path.splitext(self.source_path)[1]
diff --git a/nikola/pygments_code_block_directive.py b/nikola/pygments_code_block_directive.py
new file mode 100644
index 0000000..ac91f3c
--- /dev/null
+++ b/nikola/pygments_code_block_directive.py
@@ -0,0 +1,401 @@
+# -*- coding: utf-8 -*-
+#$Date: 2012-02-28 21:07:21 -0300 (Tue, 28 Feb 2012) $
+#$Revision: 2443 $
+
+# :Author: a Pygments author|contributor; Felix Wiemann; Guenter Milde
+# :Date: $Date: 2012-02-28 21:07:21 -0300 (Tue, 28 Feb 2012) $
+# :Copyright: This module has been placed in the public domain.
+#
+# This is a merge of `Using Pygments in ReST documents`_ from the pygments_
+# documentation, and a `proof of concept`_ by Felix Wiemann.
+#
+# ========== ===========================================================
+# 2007-06-01 Removed redundancy from class values.
+# 2007-06-04 Merge of successive tokens of same type
+# (code taken from pygments.formatters.others).
+# 2007-06-05 Separate docutils formatter script
+# Use pygments' CSS class names (like the html formatter)
+# allowing the use of pygments-produced style sheets.
+# 2007-06-07 Merge in the formatting of the parsed tokens
+# (misnamed as docutils_formatter) as class DocutilsInterface
+# 2007-06-08 Failsave implementation (fallback to a standard literal block
+# if pygments not found)
+# ========== ===========================================================
+#
+# ::
+
+"""Define and register a code-block directive using pygments"""
+
+
+# Requirements
+# ------------
+# ::
+
+import codecs
+from copy import copy
+import os
+import urlparse
+
+from docutils import nodes, core
+from docutils.parsers.rst import directives
+
+pygments = None
+try:
+ import pygments
+ from pygments.lexers import get_lexer_by_name
+ from pygments.formatters.html import _get_ttype_class
+except ImportError:
+ pass
+
+
+# Customisation
+# -------------
+#
+# Do not insert inline nodes for the following tokens.
+# (You could add e.g. Token.Punctuation like ``['', 'p']``.) ::
+
+unstyled_tokens = ['']
+
+
+# DocutilsInterface
+# -----------------
+#
+# This interface class combines code from
+# pygments.formatters.html and pygments.formatters.others.
+#
+# It does not require anything of docutils and could also become a part of
+# pygments::
+
+class DocutilsInterface(object):
+ """Parse `code` string and yield "classified" tokens.
+
+ Arguments
+
+ code -- string of source code to parse
+ language -- formal language the code is written in.
+
+ Merge subsequent tokens of the same token-type.
+
+ Yields the tokens as ``(ttype_class, value)`` tuples,
+ where ttype_class is taken from pygments.token.STANDARD_TYPES and
+ corresponds to the class argument used in pygments html output.
+
+ """
+
+ def __init__(self, code, language, custom_args={}):
+ self.code = code
+ self.language = language
+ self.custom_args = custom_args
+
+ def lex(self):
+ """Get lexer for language (use text as fallback)"""
+ try:
+ if self.language and unicode(self.language).lower() != 'none':
+ lexer = get_lexer_by_name(self.language.lower(),
+ **self.custom_args
+ )
+ else:
+ lexer = get_lexer_by_name('text', **self.custom_args)
+ except ValueError:
+ # what happens if pygment isn't present ?
+ lexer = get_lexer_by_name('text')
+ return pygments.lex(self.code, lexer)
+
+ def join(self, tokens):
+ """join subsequent tokens of same token-type
+ """
+ tokens = iter(tokens)
+ (lasttype, lastval) = tokens.next()
+ for ttype, value in tokens:
+ if ttype is lasttype:
+ lastval += value
+ else:
+ yield(lasttype, lastval)
+ (lasttype, lastval) = (ttype, value)
+ yield(lasttype, lastval)
+
+ def __iter__(self):
+ """parse code string and yield "clasified" tokens
+ """
+ try:
+ tokens = self.lex()
+ except IOError:
+ yield ('', self.code)
+ return
+
+ for ttype, value in self.join(tokens):
+ yield (_get_ttype_class(ttype), value)
+
+
+# code_block_directive
+# --------------------
+# ::
+
+def code_block_directive(name, arguments, options, content, lineno,
+ content_offset, block_text, state, state_machine):
+ """Parse and classify content of a code_block."""
+ if 'include' in options:
+ try:
+ if 'encoding' in options:
+ encoding = options['encoding']
+ else:
+ encoding = 'utf-8'
+ content = codecs.open(
+ options['include'], 'r', encoding).read().rstrip()
+ except (IOError, UnicodeError): # no file or problem reading it
+ content = u''
+ line_offset = 0
+ if content:
+ # here we define the start-at and end-at options
+ # so that limit is included in extraction
+ # this is different than the start-after directive of docutils
+ # (docutils/parsers/rst/directives/misc.py L73+)
+ # which excludes the beginning
+ # the reason is we want to be able to define a start-at like
+ # def mymethod(self)
+ # and have such a definition included
+
+ after_text = options.get('start-at', None)
+ if after_text:
+ # skip content in include_text before
+ # *and NOT incl.* a matching text
+ after_index = content.find(after_text)
+ if after_index < 0:
+ raise state_machine.reporter.severe(
+ 'Problem with "start-at" option of "%s" '
+ 'code-block directive:\nText not found.' %
+ options['start-at'])
+ content = content[after_index:]
+ line_offset = len(content[:after_index].splitlines())
+
+ after_text = options.get('start-after', None)
+ if after_text:
+ # skip content in include_text before
+ # *and incl.* a matching text
+ after_index = content.find(after_text)
+ if after_index < 0:
+ raise state_machine.reporter.severe(
+ 'Problem with "start-after" option of "%s" '
+ 'code-block directive:\nText not found.' %
+ options['start-after'])
+ line_offset = len(content[:after_index +
+ len(after_text)].splitlines())
+ content = content[after_index + len(after_text):]
+
+ # same changes here for the same reason
+ before_text = options.get('end-at', None)
+ if before_text:
+ # skip content in include_text after
+ # *and incl.* a matching text
+ before_index = content.find(before_text)
+ if before_index < 0:
+ raise state_machine.reporter.severe(
+ 'Problem with "end-at" option of "%s" '
+ 'code-block directive:\nText not found.' %
+ options['end-at'])
+ content = content[:before_index + len(before_text)]
+
+ before_text = options.get('end-before', None)
+ if before_text:
+ # skip content in include_text after
+ # *and NOT incl.* a matching text
+ before_index = content.find(before_text)
+ if before_index < 0:
+ raise state_machine.reporter.severe(
+ 'Problem with "end-before" option of "%s" '
+ 'code-block directive:\nText not found.' %
+ options['end-before'])
+ content = content[:before_index]
+
+ else:
+ content = u'\n'.join(content)
+
+ if 'tabsize' in options:
+ tabw = options['tabsize']
+ else:
+ tabw = int(options.get('tab-width', 8))
+
+ content = content.replace('\t', ' ' * tabw)
+
+ withln = "linenos" in options
+ if not "linenos_offset" in options:
+ line_offset = 0
+
+ language = arguments[0]
+ # create a literal block element and set class argument
+ code_block = nodes.literal_block(classes=["code", language])
+
+ if withln:
+ lineno = 1 + line_offset
+ total_lines = content.count('\n') + 1 + line_offset
+ lnwidth = len(str(total_lines))
+ fstr = "\n%%%dd " % lnwidth
+ code_block += nodes.inline(fstr[1:] % lineno, fstr[1:] % lineno,
+ classes=['linenumber'])
+
+ # parse content with pygments and add to code_block element
+ content = content.rstrip()
+ if pygments is None:
+ code_block += nodes.Text(content, content)
+ else:
+ # The [:-1] is because pygments adds a trailing \n which looks bad
+ l = list(DocutilsInterface(content, language, options))
+ if l[-1] == ('', u'\n'):
+ l = l[:-1]
+ for cls, value in l:
+ if withln and "\n" in value:
+ # Split on the "\n"s
+ values = value.split("\n")
+ # The first piece, pass as-is
+ code_block += nodes.Text(values[0], values[0])
+ # On the second and later pieces, insert \n and linenos
+ linenos = range(lineno, lineno + len(values))
+ for chunk, ln in zip(values, linenos)[1:]:
+ if ln <= total_lines:
+ code_block += nodes.inline(fstr % ln, fstr % ln,
+ classes=['linenumber'])
+ code_block += nodes.Text(chunk, chunk)
+ lineno += len(values) - 1
+
+ elif cls in unstyled_tokens:
+ # insert as Text to decrease the verbosity of the output.
+ code_block += nodes.Text(value, value)
+ else:
+ code_block += nodes.inline(value, value, classes=[cls])
+
+ return [code_block]
+
+# Custom argument validators
+# --------------------------
+# ::
+#
+# Move to separated module??
+
+
+def string_list(argument):
+ """
+ Converts a space- or comma-separated list of values into a python list
+ of strings.
+ (Directive option conversion function)
+ Based in positive_int_list of docutils.parsers.rst.directives
+ """
+ if ',' in argument:
+ entries = argument.split(',')
+ else:
+ entries = argument.split()
+ return entries
+
+
+def string_bool(argument):
+ """
+ Converts True, true, False, False in python boolean values
+ """
+ if argument is None:
+ msg = 'argument required but none supplied; choose "True" or "False"'
+ raise ValueError(msg)
+
+ elif argument.lower() == 'true':
+ return True
+ elif argument.lower() == 'false':
+ return False
+ else:
+ raise ValueError('"%s" unknown; choose from "True" or "False"'
+ % argument)
+
+
+def csharp_unicodelevel(argument):
+ return directives.choice(argument, ('none', 'basic', 'full'))
+
+
+def lhs_litstyle(argument):
+ return directives.choice(argument, ('bird', 'latex'))
+
+
+def raw_compress(argument):
+ return directives.choice(argument, ('gz', 'bz2'))
+
+
+def listings_directive(name, arguments, options, content, lineno,
+ content_offset, block_text, state, state_machine):
+ fname = arguments[0]
+ options['include'] = os.path.join('listings', fname)
+ target = urlparse.urlunsplit(("link", 'listing', fname, '', ''))
+ generated_nodes = [core.publish_doctree('`%s <%s>`_' % (fname, target))[0]]
+ generated_nodes += code_block_directive(name, [arguments[1]],
+ options, content, lineno, content_offset, block_text,
+ state, state_machine)
+ return generated_nodes
+
+code_block_directive.arguments = (1, 0, 1)
+listings_directive.arguments = (2, 0, 1)
+code_block_directive.content = 1
+listings_directive.content = 1
+code_block_directive.options = {'include': directives.unchanged_required,
+ 'start-at': directives.unchanged_required,
+ 'end-at': directives.unchanged_required,
+ 'start-after': directives.unchanged_required,
+ 'end-before': directives.unchanged_required,
+ 'linenos': directives.unchanged,
+ 'linenos_offset': directives.unchanged,
+ 'tab-width': directives.unchanged,
+ # generic
+ 'stripnl': string_bool,
+ 'stripall': string_bool,
+ 'ensurenl': string_bool,
+ 'tabsize': directives.positive_int,
+ 'encoding': directives.encoding,
+ # Lua
+ 'func_name_hightlighting': string_bool,
+ 'disabled_modules': string_list,
+ # Python Console
+ 'python3': string_bool,
+ # Delphi
+ 'turbopascal': string_bool,
+ 'delphi': string_bool,
+ 'freepascal': string_bool,
+ 'units': string_list,
+ # Modula2
+ 'pim': string_bool,
+ 'iso': string_bool,
+ 'objm2': string_bool,
+ 'gm2ext': string_bool,
+ # CSharp
+ 'unicodelevel': csharp_unicodelevel,
+ # Literate haskell
+ 'litstyle': lhs_litstyle,
+ # Raw
+ 'compress': raw_compress,
+ # Rst
+ 'handlecodeblocks': string_bool,
+ # Php
+ 'startinline': string_bool,
+ 'funcnamehighlighting': string_bool,
+ 'disabledmodules': string_list,
+ }
+
+listings_directive.options = copy(code_block_directive.options)
+listings_directive.options.pop('include')
+
+# .. _doctutils: http://docutils.sf.net/
+# .. _pygments: http://pygments.org/
+# .. _Using Pygments in ReST documents: http://pygments.org/docs/rstdirective/
+# .. _proof of concept:
+# http://article.gmane.org/gmane.text.docutils.user/3689
+#
+# Test output
+# -----------
+#
+# If called from the command line, call the docutils publisher to render the
+# input::
+
+if __name__ == '__main__':
+ from docutils.core import publish_cmdline, default_description
+ from docutils.parsers.rst import directives
+ directives.register_directive('code-block', code_block_directive)
+ description = "code-block directive test output" + default_description
+ try:
+ import locale
+ locale.setlocale(locale.LC_ALL, '')
+ except Exception:
+ pass
+ publish_cmdline(writer_name='html', description=description)
diff --git a/nikola/rest.py b/nikola/rest.py
new file mode 100644
index 0000000..071c6c8
--- /dev/null
+++ b/nikola/rest.py
@@ -0,0 +1,78 @@
+"""Implementation of compile_html based on reStructuredText and docutils."""
+
+__all__ = ['compile_html']
+
+import codecs
+import os
+
+########################################
+# custom rst directives and renderer
+########################################
+import docutils.core
+import docutils.io
+from docutils.parsers.rst import directives
+
+from pygments_code_block_directive import (
+ code_block_directive,
+ listings_directive)
+directives.register_directive('code-block', code_block_directive)
+directives.register_directive('listing', listings_directive)
+
+import pygments_code_block_directive
+# Below is to make pyflakes happy (sigh)
+pygments_code_block_directive
+from youtube import youtube
+directives.register_directive('youtube', youtube)
+
+
+def compile_html(source, dest):
+ try:
+ os.makedirs(os.path.dirname(dest))
+ except:
+ pass
+ error_level = 100
+ with codecs.open(dest, "w+", "utf8") as out_file:
+ with codecs.open(source, "r", "utf8") as in_file:
+ data = in_file.read()
+ output, error_level = rst2html(data,
+ settings_overrides={'initial_header_level': 2})
+ out_file.write(output)
+ if error_level < 3:
+ return True
+ else:
+ return False
+
+
+def rst2html(source, source_path=None, source_class=docutils.io.StringInput,
+ destination_path=None,
+ reader=None, reader_name='standalone',
+ parser=None, parser_name='restructuredtext',
+ writer=None, writer_name='html',
+ settings=None, settings_spec=None,
+ settings_overrides=None, config_section=None,
+ enable_exit_status=None):
+ """
+ Set up & run a `Publisher`, and return a dictionary of document parts.
+ Dictionary keys are the names of parts, and values are Unicode strings;
+ encoding is up to the client. For programmatic use with string I/O.
+
+ For encoded string input, be sure to set the 'input_encoding' setting to
+ the desired encoding. Set it to 'unicode' for unencoded Unicode string
+ input. Here's how::
+
+ publish_parts(..., settings_overrides={'input_encoding': 'unicode'})
+
+ Parameters: see `publish_programmatically`.
+ """
+ output, pub = docutils.core.publish_programmatically(
+ source=source, source_path=source_path, source_class=source_class,
+ destination_class=docutils.io.StringOutput,
+ destination=None, destination_path=destination_path,
+ reader=reader, reader_name=reader_name,
+ parser=parser, parser_name=parser_name,
+ writer=writer, writer_name=writer_name,
+ settings=settings, settings_spec=settings_spec,
+ settings_overrides=settings_overrides,
+ config_section=config_section,
+ enable_exit_status=enable_exit_status)
+ return pub.writer.parts['fragment'], pub.document.reporter.max_level
diff --git a/nikola/sitemap_gen.py b/nikola/sitemap_gen.py
new file mode 100755
index 0000000..e5d28b4
--- /dev/null
+++ b/nikola/sitemap_gen.py
@@ -0,0 +1,2240 @@
+#!/usr/bin/env python
+#
+# Copyright (c) 2004, 2005 Google Inc.
+# All rights reserved.
+#
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions
+# are met:
+#
+# * Redistributions of source code must retain the above copyright
+# notice, this list of conditions and the following disclaimer.
+#
+# * Redistributions in binary form must reproduce the above copyright
+# notice, this list of conditions and the following disclaimer in
+# the documentation and/or other materials provided with the
+# distribution.
+#
+# * Neither the name of Google nor the names of its contributors may
+# be used to endorse or promote products derived from this software
+# without specific prior written permission.
+#
+# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
+# FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
+# COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
+# INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
+# BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+# LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+# CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
+# ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+# POSSIBILITY OF SUCH DAMAGE.
+#
+#
+# The sitemap_gen.py script is written in Python 2.2 and released to
+# the open source community for continuous improvements under the BSD
+# 2.0 new license, which can be found at:
+#
+# http://www.opensource.org/licenses/bsd-license.php
+#
+
+__usage__ = \
+"""A simple script to automatically produce sitemaps for a webserver,
+in the Google Sitemap Protocol (GSP).
+
+Usage: python sitemap_gen.py --config=config.xml [--help] [--testing]
+ --config=config.xml, specifies config file location
+ --help, displays usage message
+ --testing, specified when user is experimenting
+"""
+
+# Please be careful that all syntax used in this file can be parsed on
+# Python 1.5 -- this version check is not evaluated until after the
+# entire file has been parsed.
+import sys
+if sys.hexversion < 0x02020000:
+ print 'This script requires Python 2.2 or later.'
+ print 'Currently run with version: %s' % sys.version
+ sys.exit(1)
+
+import fnmatch
+import glob
+import gzip
+import hashlib
+import os
+import re
+import stat
+import time
+import types
+import urllib
+import urlparse
+import xml.sax
+
+# True and False were introduced in Python2.2.2
+try:
+ testTrue=True
+ del testTrue
+except NameError:
+ True=1
+ False=0
+
+# Text encodings
+ENC_ASCII = 'ASCII'
+ENC_UTF8 = 'UTF-8'
+ENC_IDNA = 'IDNA'
+ENC_ASCII_LIST = ['ASCII', 'US-ASCII', 'US', 'IBM367', 'CP367', 'ISO646-US'
+ 'ISO_646.IRV:1991', 'ISO-IR-6', 'ANSI_X3.4-1968',
+ 'ANSI_X3.4-1986', 'CPASCII' ]
+ENC_DEFAULT_LIST = ['ISO-8859-1', 'ISO-8859-2', 'ISO-8859-5']
+
+# Maximum number of urls in each sitemap, before next Sitemap is created
+MAXURLS_PER_SITEMAP = 50000
+
+# Suffix on a Sitemap index file
+SITEINDEX_SUFFIX = '_index.xml'
+
+# Regular expressions tried for extracting URLs from access logs.
+ACCESSLOG_CLF_PATTERN = re.compile(
+ r'.+\s+"([^\s]+)\s+([^\s]+)\s+HTTP/\d+\.\d+"\s+200\s+.*'
+ )
+
+# Match patterns for lastmod attributes
+LASTMOD_PATTERNS = map(re.compile, [
+ r'^\d\d\d\d$',
+ r'^\d\d\d\d-\d\d$',
+ r'^\d\d\d\d-\d\d-\d\d$',
+ r'^\d\d\d\d-\d\d-\d\dT\d\d:\d\dZ$',
+ r'^\d\d\d\d-\d\d-\d\dT\d\d:\d\d[+-]\d\d:\d\d$',
+ r'^\d\d\d\d-\d\d-\d\dT\d\d:\d\d:\d\d(\.\d+)?Z$',
+ r'^\d\d\d\d-\d\d-\d\dT\d\d:\d\d:\d\d(\.\d+)?[+-]\d\d:\d\d$',
+ ])
+
+# Match patterns for changefreq attributes
+CHANGEFREQ_PATTERNS = [
+ 'always', 'hourly', 'daily', 'weekly', 'monthly', 'yearly', 'never'
+ ]
+
+# XML formats
+SITEINDEX_HEADER = \
+ '<?xml version="1.0" encoding="UTF-8"?>\n' \
+ '<?xml-stylesheet type="text/xsl" href="gss.xsl"?>\n' \
+ '<sitemapindex\n' \
+ ' xmlns="http://www.google.com/schemas/sitemap/0.84"\n' \
+ ' xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"\n' \
+ ' xsi:schemaLocation="http://www.google.com/schemas/sitemap/0.84\n' \
+ ' http://www.google.com/schemas/sitemap/0.84/' \
+ 'siteindex.xsd">\n'
+SITEINDEX_FOOTER = '</sitemapindex>\n'
+SITEINDEX_ENTRY = \
+ ' <sitemap>\n' \
+ ' <loc>%(loc)s</loc>\n' \
+ ' <lastmod>%(lastmod)s</lastmod>\n' \
+ ' </sitemap>\n'
+SITEMAP_HEADER = \
+ '<?xml version="1.0" encoding="UTF-8"?>\n' \
+ '<urlset\n' \
+ ' xmlns="http://www.google.com/schemas/sitemap/0.84"\n' \
+ ' xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"\n' \
+ ' xsi:schemaLocation="http://www.google.com/schemas/sitemap/0.84\n' \
+ ' http://www.google.com/schemas/sitemap/0.84/' \
+ 'sitemap.xsd">\n'
+SITEMAP_FOOTER = '</urlset>\n'
+SITEURL_XML_PREFIX = ' <url>\n'
+SITEURL_XML_SUFFIX = ' </url>\n'
+
+# Search engines to notify with the updated sitemaps
+#
+# This list is very non-obvious in what's going on. Here's the gist:
+# Each item in the list is a 6-tuple of items. The first 5 are "almost"
+# the same as the input arguments to urlparse.urlunsplit():
+# 0 - schema
+# 1 - netloc
+# 2 - path
+# 3 - query <-- EXCEPTION: specify a query map rather than a string
+# 4 - fragment
+# Additionally, add item 5:
+# 5 - query attribute that should be set to the new Sitemap URL
+# Clear as mud, I know.
+NOTIFICATION_SITES = [
+ ('http', 'www.google.com', 'webmasters/sitemaps/ping', {}, '', 'sitemap')
+ ]
+
+
+class Error(Exception):
+ """
+ Base exception class. In this module we tend not to use our own exception
+ types for very much, but they come in very handy on XML parsing with SAX.
+ """
+ pass
+#end class Error
+
+
+class SchemaError(Error):
+ """Failure to process an XML file according to the schema we know."""
+ pass
+#end class SchemeError
+
+
+class Encoder:
+ """
+ Manages wide-character/narrow-character conversions for just about all
+ text that flows into or out of the script.
+
+ You should always use this class for string coercion, as opposed to
+ letting Python handle coercions automatically. Reason: Python
+ usually assumes ASCII (7-bit) as a default narrow character encoding,
+ which is not the kind of data we generally deal with.
+
+ General high-level methodologies used in sitemap_gen:
+
+ [PATHS]
+ File system paths may be wide or narrow, depending on platform.
+ This works fine, just be aware of it and be very careful to not
+ mix them. That is, if you have to pass several file path arguments
+ into a library call, make sure they are all narrow or all wide.
+ This class has MaybeNarrowPath() which should be called on every
+ file system path you deal with.
+
+ [URLS]
+ URL locations are stored in Narrow form, already escaped. This has the
+ benefit of keeping escaping and encoding as close as possible to the format
+ we read them in. The downside is we may end up with URLs that have
+ intermingled encodings -- the root path may be encoded in one way
+ while the filename is encoded in another. This is obviously wrong, but
+ it should hopefully be an issue hit by very few users. The workaround
+ from the user level (assuming they notice) is to specify a default_encoding
+ parameter in their config file.
+
+ [OTHER]
+ Other text, such as attributes of the URL class, configuration options,
+ etc, are generally stored in Unicode for simplicity.
+ """
+
+ def __init__(self):
+ self._user = None # User-specified default encoding
+ self._learned = [] # Learned default encodings
+ self._widefiles = False # File system can be wide
+
+ # Can the file system be Unicode?
+ try:
+ self._widefiles = os.path.supports_unicode_filenames
+ except AttributeError:
+ try:
+ self._widefiles = sys.getwindowsversion() == os.VER_PLATFORM_WIN32_NT
+ except AttributeError:
+ pass
+
+ # Try to guess a working default
+ try:
+ encoding = sys.getfilesystemencoding()
+ if encoding and not (encoding.upper() in ENC_ASCII_LIST):
+ self._learned = [ encoding ]
+ except AttributeError:
+ pass
+
+ if not self._learned:
+ encoding = sys.getdefaultencoding()
+ if encoding and not (encoding.upper() in ENC_ASCII_LIST):
+ self._learned = [ encoding ]
+
+ # If we had no guesses, start with some European defaults
+ if not self._learned:
+ self._learned = ENC_DEFAULT_LIST
+ #end def __init__
+
+ def SetUserEncoding(self, encoding):
+ self._user = encoding
+ #end def SetUserEncoding
+
+ def NarrowText(self, text, encoding):
+ """ Narrow a piece of arbitrary text """
+ if type(text) != types.UnicodeType:
+ return text
+
+ # Try the passed in preference
+ if encoding:
+ try:
+ result = text.encode(encoding)
+ if not encoding in self._learned:
+ self._learned.append(encoding)
+ return result
+ except UnicodeError:
+ pass
+ except LookupError:
+ output.Warn('Unknown encoding: %s' % encoding)
+
+ # Try the user preference
+ if self._user:
+ try:
+ return text.encode(self._user)
+ except UnicodeError:
+ pass
+ except LookupError:
+ temp = self._user
+ self._user = None
+ output.Warn('Unknown default_encoding: %s' % temp)
+
+ # Look through learned defaults, knock any failing ones out of the list
+ while self._learned:
+ try:
+ return text.encode(self._learned[0])
+ except:
+ del self._learned[0]
+
+ # When all other defaults are exhausted, use UTF-8
+ try:
+ return text.encode(ENC_UTF8)
+ except UnicodeError:
+ pass
+
+ # Something is seriously wrong if we get to here
+ return text.encode(ENC_ASCII, 'ignore')
+ #end def NarrowText
+
+ def MaybeNarrowPath(self, text):
+ """ Paths may be allowed to stay wide """
+ if self._widefiles:
+ return text
+ return self.NarrowText(text, None)
+ #end def MaybeNarrowPath
+
+ def WidenText(self, text, encoding):
+ """ Widen a piece of arbitrary text """
+ if type(text) != types.StringType:
+ return text
+
+ # Try the passed in preference
+ if encoding:
+ try:
+ result = unicode(text, encoding)
+ if not encoding in self._learned:
+ self._learned.append(encoding)
+ return result
+ except UnicodeError:
+ pass
+ except LookupError:
+ output.Warn('Unknown encoding: %s' % encoding)
+
+ # Try the user preference
+ if self._user:
+ try:
+ return unicode(text, self._user)
+ except UnicodeError:
+ pass
+ except LookupError:
+ temp = self._user
+ self._user = None
+ output.Warn('Unknown default_encoding: %s' % temp)
+
+ # Look through learned defaults, knock any failing ones out of the list
+ while self._learned:
+ try:
+ return unicode(text, self._learned[0])
+ except:
+ del self._learned[0]
+
+ # When all other defaults are exhausted, use UTF-8
+ try:
+ return unicode(text, ENC_UTF8)
+ except UnicodeError:
+ pass
+
+ # Getting here means it wasn't UTF-8 and we had no working default.
+ # We really don't have anything "right" we can do anymore.
+ output.Warn('Unrecognized encoding in text: %s' % text)
+ if not self._user:
+ output.Warn('You may need to set a default_encoding in your '
+ 'configuration file.')
+ return text.decode(ENC_ASCII, 'ignore')
+ #end def WidenText
+#end class Encoder
+encoder = Encoder()
+
+
+class Output:
+ """
+ Exposes logging functionality, and tracks how many errors
+ we have thus output.
+
+ Logging levels should be used as thus:
+ Fatal -- extremely sparingly
+ Error -- config errors, entire blocks of user 'intention' lost
+ Warn -- individual URLs lost
+ Log(,0) -- Un-suppressable text that's not an error
+ Log(,1) -- touched files, major actions
+ Log(,2) -- parsing notes, filtered or duplicated URLs
+ Log(,3) -- each accepted URL
+ """
+
+ def __init__(self):
+ self.num_errors = 0 # Count of errors
+ self.num_warns = 0 # Count of warnings
+
+ self._errors_shown = {} # Shown errors
+ self._warns_shown = {} # Shown warnings
+ self._verbose = 0 # Level of verbosity
+ #end def __init__
+
+ def Log(self, text, level):
+ """ Output a blurb of diagnostic text, if the verbose level allows it """
+ if text:
+ text = encoder.NarrowText(text, None)
+ if self._verbose >= level:
+ print text
+ #end def Log
+
+ def Warn(self, text):
+ """ Output and count a warning. Suppress duplicate warnings. """
+ if text:
+ text = encoder.NarrowText(text, None)
+ hash = hashlib.md5(text).digest()
+ if not self._warns_shown.has_key(hash):
+ self._warns_shown[hash] = 1
+ print '[WARNING] ' + text
+ else:
+ self.Log('(suppressed) [WARNING] ' + text, 3)
+ self.num_warns = self.num_warns + 1
+ #end def Warn
+
+ def Error(self, text):
+ """ Output and count an error. Suppress duplicate errors. """
+ if text:
+ text = encoder.NarrowText(text, None)
+ hash = hashlib.md5(text).digest()
+ if not self._errors_shown.has_key(hash):
+ self._errors_shown[hash] = 1
+ print '[ERROR] ' + text
+ else:
+ self.Log('(suppressed) [ERROR] ' + text, 3)
+ self.num_errors = self.num_errors + 1
+ #end def Error
+
+ def Fatal(self, text):
+ """ Output an error and terminate the program. """
+ if text:
+ text = encoder.NarrowText(text, None)
+ print '[FATAL] ' + text
+ else:
+ print 'Fatal error.'
+ sys.exit(1)
+ #end def Fatal
+
+ def SetVerbose(self, level):
+ """ Sets the verbose level. """
+ try:
+ if type(level) != types.IntType:
+ level = int(level)
+ if (level >= 0) and (level <= 3):
+ self._verbose = level
+ return
+ except ValueError:
+ pass
+ self.Error('Verbose level (%s) must be between 0 and 3 inclusive.' % level)
+ #end def SetVerbose
+#end class Output
+output = Output()
+
+
+class URL(object):
+ """ URL is a smart structure grouping together the properties we
+ care about for a single web reference. """
+ __slots__ = 'loc', 'lastmod', 'changefreq', 'priority'
+
+ def __init__(self):
+ self.loc = None # URL -- in Narrow characters
+ self.lastmod = None # ISO8601 timestamp of last modify
+ self.changefreq = None # Text term for update frequency
+ self.priority = None # Float between 0 and 1 (inc)
+ #end def __init__
+
+ def __cmp__(self, other):
+ if self.loc < other.loc:
+ return -1
+ if self.loc > other.loc:
+ return 1
+ return 0
+ #end def __cmp__
+
+ def TrySetAttribute(self, attribute, value):
+ """ Attempt to set the attribute to the value, with a pretty try
+ block around it. """
+ if attribute == 'loc':
+ self.loc = self.Canonicalize(value)
+ else:
+ try:
+ setattr(self, attribute, value)
+ except AttributeError:
+ output.Warn('Unknown URL attribute: %s' % attribute)
+ #end def TrySetAttribute
+
+ def IsAbsolute(loc):
+ """ Decide if the URL is absolute or not """
+ if not loc:
+ return False
+ narrow = encoder.NarrowText(loc, None)
+ (scheme, netloc, path, query, frag) = urlparse.urlsplit(narrow)
+ if (not scheme) or (not netloc):
+ return False
+ return True
+ #end def IsAbsolute
+ IsAbsolute = staticmethod(IsAbsolute)
+
+ def Canonicalize(loc):
+ """ Do encoding and canonicalization on a URL string """
+ if not loc:
+ return loc
+
+ # Let the encoder try to narrow it
+ narrow = encoder.NarrowText(loc, None)
+
+ # Escape components individually
+ (scheme, netloc, path, query, frag) = urlparse.urlsplit(narrow)
+ unr = '-._~'
+ sub = '!$&\'()*+,;='
+ netloc = urllib.quote(netloc, unr + sub + '%:@/[]')
+ path = urllib.quote(path, unr + sub + '%:@/')
+ query = urllib.quote(query, unr + sub + '%:@/?')
+ frag = urllib.quote(frag, unr + sub + '%:@/?')
+
+ # Try built-in IDNA encoding on the netloc
+ try:
+ (ignore, widenetloc, ignore, ignore, ignore) = urlparse.urlsplit(loc)
+ for c in widenetloc:
+ if c >= unichr(128):
+ netloc = widenetloc.encode(ENC_IDNA)
+ netloc = urllib.quote(netloc, unr + sub + '%:@/[]')
+ break
+ except UnicodeError:
+ # urlsplit must have failed, based on implementation differences in the
+ # library. There is not much we can do here, except ignore it.
+ pass
+ except LookupError:
+ output.Warn('An International Domain Name (IDN) is being used, but this '
+ 'version of Python does not have support for IDNA encoding. '
+ ' (IDNA support was introduced in Python 2.3) The encoding '
+ 'we have used instead is wrong and will probably not yield '
+ 'valid URLs.')
+ bad_netloc = False
+ if '%' in netloc:
+ bad_netloc = True
+
+ # Put it all back together
+ narrow = urlparse.urlunsplit((scheme, netloc, path, query, frag))
+
+ # I let '%' through. Fix any that aren't pre-existing escapes.
+ HEXDIG = '0123456789abcdefABCDEF'
+ list = narrow.split('%')
+ narrow = list[0]
+ del list[0]
+ for item in list:
+ if (len(item) >= 2) and (item[0] in HEXDIG) and (item[1] in HEXDIG):
+ narrow = narrow + '%' + item
+ else:
+ narrow = narrow + '%25' + item
+
+ # Issue a warning if this is a bad URL
+ if bad_netloc:
+ output.Warn('Invalid characters in the host or domain portion of a URL: '
+ + narrow)
+
+ return narrow
+ #end def Canonicalize
+ Canonicalize = staticmethod(Canonicalize)
+
+ def Validate(self, base_url, allow_fragment):
+ """ Verify the data in this URL is well-formed, and override if not. """
+ assert type(base_url) == types.StringType
+
+ # Test (and normalize) the ref
+ if not self.loc:
+ output.Warn('Empty URL')
+ return False
+ if allow_fragment:
+ self.loc = urlparse.urljoin(base_url, self.loc)
+ if not self.loc.startswith(base_url):
+ output.Warn('Discarded URL for not starting with the base_url: %s' %
+ self.loc)
+ self.loc = None
+ return False
+
+ # Test the lastmod
+ if self.lastmod:
+ match = False
+ self.lastmod = self.lastmod.upper()
+ for pattern in LASTMOD_PATTERNS:
+ match = pattern.match(self.lastmod)
+ if match:
+ break
+ if not match:
+ output.Warn('Lastmod "%s" does not appear to be in ISO8601 format on '
+ 'URL: %s' % (self.lastmod, self.loc))
+ self.lastmod = None
+
+ # Test the changefreq
+ if self.changefreq:
+ match = False
+ self.changefreq = self.changefreq.lower()
+ for pattern in CHANGEFREQ_PATTERNS:
+ if self.changefreq == pattern:
+ match = True
+ break
+ if not match:
+ output.Warn('Changefreq "%s" is not a valid change frequency on URL '
+ ': %s' % (self.changefreq, self.loc))
+ self.changefreq = None
+
+ # Test the priority
+ if self.priority:
+ priority = -1.0
+ try:
+ priority = float(self.priority)
+ except ValueError:
+ pass
+ if (priority < 0.0) or (priority > 1.0):
+ output.Warn('Priority "%s" is not a number between 0 and 1 inclusive '
+ 'on URL: %s' % (self.priority, self.loc))
+ self.priority = None
+
+ return True
+ #end def Validate
+
+ def MakeHash(self):
+ """ Provides a uniform way of hashing URLs """
+ if not self.loc:
+ return None
+ if self.loc.endswith('/'):
+ return hashlib.md5(self.loc[:-1]).digest()
+ return hashlib.md5(self.loc).digest()
+ #end def MakeHash
+
+ def Log(self, prefix='URL', level=3):
+ """ Dump the contents, empty or not, to the log. """
+ out = prefix + ':'
+
+ for attribute in self.__slots__:
+ value = getattr(self, attribute)
+ if not value:
+ value = ''
+ out = out + (' %s=[%s]' % (attribute, value))
+
+ output.Log('%s' % encoder.NarrowText(out, None), level)
+ #end def Log
+
+ def WriteXML(self, file):
+ """ Dump non-empty contents to the output file, in XML format. """
+ if not self.loc:
+ return
+ out = SITEURL_XML_PREFIX
+
+ for attribute in self.__slots__:
+ value = getattr(self, attribute)
+ if value:
+ if type(value) == types.UnicodeType:
+ value = encoder.NarrowText(value, None)
+ elif type(value) != types.StringType:
+ value = str(value)
+ value = xml.sax.saxutils.escape(value)
+ out = out + (' <%s>%s</%s>\n' % (attribute, value, attribute))
+
+ out = out + SITEURL_XML_SUFFIX
+ file.write(out)
+ #end def WriteXML
+#end class URL
+
+
+class Filter:
+ """
+ A filter on the stream of URLs we find. A filter is, in essence,
+ a wildcard applied to the stream. You can think of this as an
+ operator that returns a tri-state when given a URL:
+
+ True -- this URL is to be included in the sitemap
+ None -- this URL is undecided
+ False -- this URL is to be dropped from the sitemap
+ """
+
+ def __init__(self, attributes):
+ self._wildcard = None # Pattern for wildcard match
+ self._regexp = None # Pattern for regexp match
+ self._pass = False # "Drop" filter vs. "Pass" filter
+
+ if not ValidateAttributes('FILTER', attributes,
+ ('pattern', 'type', 'action')):
+ return
+
+ # Check error count on the way in
+ num_errors = output.num_errors
+
+ # Fetch the attributes
+ pattern = attributes.get('pattern')
+ type = attributes.get('type', 'wildcard')
+ action = attributes.get('action', 'drop')
+ if type:
+ type = type.lower()
+ if action:
+ action = action.lower()
+
+ # Verify the attributes
+ if not pattern:
+ output.Error('On a filter you must specify a "pattern" to match')
+ elif (not type) or ((type != 'wildcard') and (type != 'regexp')):
+ output.Error('On a filter you must specify either \'type="wildcard"\' '
+ 'or \'type="regexp"\'')
+ elif (action != 'pass') and (action != 'drop'):
+ output.Error('If you specify a filter action, it must be either '
+ '\'action="pass"\' or \'action="drop"\'')
+
+ # Set the rule
+ if action == 'drop':
+ self._pass = False
+ elif action == 'pass':
+ self._pass = True
+
+ if type == 'wildcard':
+ self._wildcard = pattern
+ elif type == 'regexp':
+ try:
+ self._regexp = re.compile(pattern)
+ except re.error:
+ output.Error('Bad regular expression: %s' % pattern)
+
+ # Log the final results iff we didn't add any errors
+ if num_errors == output.num_errors:
+ output.Log('Filter: %s any URL that matches %s "%s"' %
+ (action, type, pattern), 2)
+ #end def __init__
+
+ def Apply(self, url):
+ """ Process the URL, as above. """
+ if (not url) or (not url.loc):
+ return None
+
+ if self._wildcard:
+ if fnmatch.fnmatchcase(url.loc, self._wildcard):
+ return self._pass
+ return None
+
+ if self._regexp:
+ if self._regexp.search(url.loc):
+ return self._pass
+ return None
+
+ assert False # unreachable
+ #end def Apply
+#end class Filter
+
+
+class InputURL:
+ """
+ Each Input class knows how to yield a set of URLs from a data source.
+
+ This one handles a single URL, manually specified in the config file.
+ """
+
+ def __init__(self, attributes):
+ self._url = None # The lonely URL
+
+ if not ValidateAttributes('URL', attributes,
+ ('href', 'lastmod', 'changefreq', 'priority')):
+ return
+
+ url = URL()
+ for attr in attributes.keys():
+ if attr == 'href':
+ url.TrySetAttribute('loc', attributes[attr])
+ else:
+ url.TrySetAttribute(attr, attributes[attr])
+
+ if not url.loc:
+ output.Error('Url entries must have an href attribute.')
+ return
+
+ self._url = url
+ output.Log('Input: From URL "%s"' % self._url.loc, 2)
+ #end def __init__
+
+ def ProduceURLs(self, consumer):
+ """ Produces URLs from our data source, hands them in to the consumer. """
+ if self._url:
+ consumer(self._url, True)
+ #end def ProduceURLs
+#end class InputURL
+
+
+class InputURLList:
+ """
+ Each Input class knows how to yield a set of URLs from a data source.
+
+ This one handles a text file with a list of URLs
+ """
+
+ def __init__(self, attributes):
+ self._path = None # The file path
+ self._encoding = None # Encoding of that file
+
+ if not ValidateAttributes('URLLIST', attributes, ('path', 'encoding')):
+ return
+
+ self._path = attributes.get('path')
+ self._encoding = attributes.get('encoding', ENC_UTF8)
+ if self._path:
+ self._path = encoder.MaybeNarrowPath(self._path)
+ if os.path.isfile(self._path):
+ output.Log('Input: From URLLIST "%s"' % self._path, 2)
+ else:
+ output.Error('Can not locate file: %s' % self._path)
+ self._path = None
+ else:
+ output.Error('Urllist entries must have a "path" attribute.')
+ #end def __init__
+
+ def ProduceURLs(self, consumer):
+ """ Produces URLs from our data source, hands them in to the consumer. """
+
+ # Open the file
+ (frame, file) = OpenFileForRead(self._path, 'URLLIST')
+ if not file:
+ return
+
+ # Iterate lines
+ linenum = 0
+ for line in file.readlines():
+ linenum = linenum + 1
+
+ # Strip comments and empty lines
+ if self._encoding:
+ line = encoder.WidenText(line, self._encoding)
+ line = line.strip()
+ if (not line) or line[0] == '#':
+ continue
+
+ # Split the line on space
+ url = URL()
+ cols = line.split(' ')
+ for i in range(0,len(cols)):
+ cols[i] = cols[i].strip()
+ url.TrySetAttribute('loc', cols[0])
+
+ # Extract attributes from the other columns
+ for i in range(1,len(cols)):
+ if cols[i]:
+ try:
+ (attr_name, attr_val) = cols[i].split('=', 1)
+ url.TrySetAttribute(attr_name, attr_val)
+ except ValueError:
+ output.Warn('Line %d: Unable to parse attribute: %s' %
+ (linenum, cols[i]))
+
+ # Pass it on
+ consumer(url, False)
+
+ file.close()
+ if frame:
+ frame.close()
+ #end def ProduceURLs
+#end class InputURLList
+
+
+class InputDirectory:
+ """
+ Each Input class knows how to yield a set of URLs from a data source.
+
+ This one handles a directory that acts as base for walking the filesystem.
+ """
+
+ def __init__(self, attributes, base_url):
+ self._path = None # The directory
+ self._url = None # The URL equivelant
+ self._default_file = None
+
+ if not ValidateAttributes('DIRECTORY', attributes, ('path', 'url',
+ 'default_file')):
+ return
+
+ # Prep the path -- it MUST end in a sep
+ path = attributes.get('path')
+ if not path:
+ output.Error('Directory entries must have both "path" and "url" '
+ 'attributes')
+ return
+ path = encoder.MaybeNarrowPath(path)
+ if not path.endswith(os.sep):
+ path = path + os.sep
+ if not os.path.isdir(path):
+ output.Error('Can not locate directory: %s' % path)
+ return
+
+ # Prep the URL -- it MUST end in a sep
+ url = attributes.get('url')
+ if not url:
+ output.Error('Directory entries must have both "path" and "url" '
+ 'attributes')
+ return
+ url = URL.Canonicalize(url)
+ if not url.endswith('/'):
+ url = url + '/'
+ if not url.startswith(base_url):
+ url = urlparse.urljoin(base_url, url)
+ if not url.startswith(base_url):
+ output.Error('The directory URL "%s" is not relative to the '
+ 'base_url: %s' % (url, base_url))
+ return
+
+ # Prep the default file -- it MUST be just a filename
+ file = attributes.get('default_file')
+ if file:
+ file = encoder.MaybeNarrowPath(file)
+ if os.sep in file:
+ output.Error('The default_file "%s" can not include path information.'
+ % file)
+ file = None
+
+ self._path = path
+ self._url = url
+ self._default_file = file
+ if file:
+ output.Log('Input: From DIRECTORY "%s" (%s) with default file "%s"'
+ % (path, url, file), 2)
+ else:
+ output.Log('Input: From DIRECTORY "%s" (%s) with no default file'
+ % (path, url), 2)
+ #end def __init__
+
+ def ProduceURLs(self, consumer):
+ """ Produces URLs from our data source, hands them in to the consumer. """
+ if not self._path:
+ return
+
+ root_path = self._path
+ root_URL = self._url
+ root_file = "index.html"
+
+ def DecideFilename(name):
+ assert "/" not in name
+
+ if name in ( "robots.txt, " ):
+ return False
+
+ if ".thumbnail." in name:
+ return False
+
+ if re.match( r"google[a-f0-9]+.html", name ):
+ return False
+
+ return not re.match( r"^index(\-\d+)?.html$", name )
+
+ def DecideDirectory(dirpath):
+ subpath = dirpath[len(root_path):]
+
+ assert not subpath.startswith( "/" ), subpath
+
+ for remove in ( "assets", ):
+ if subpath == remove or subpath.startswith( remove + os.path.sep ):
+ return False
+ else:
+ return True
+
+ def PerFile(dirpath, name):
+ """
+ Called once per file.
+ Note that 'name' will occasionally be None -- for a directory itself
+ """
+ if not DecideDirectory(dirpath):
+ return
+
+ if name is not None and not DecideFilename(name):
+ return
+
+ # Pull a timestamp
+ url = URL()
+ isdir = False
+ try:
+ if name:
+ path = os.path.join(dirpath, name)
+ else:
+ path = dirpath
+ isdir = os.path.isdir(path)
+ time = None
+ if isdir and root_file:
+ file = os.path.join(path, root_file)
+ try:
+ time = os.stat(file)[stat.ST_MTIME];
+ except OSError:
+ pass
+ if not time:
+ time = os.stat(path)[stat.ST_MTIME];
+ url.lastmod = TimestampISO8601(time)
+ except OSError:
+ pass
+ except ValueError:
+ pass
+
+ # Build a URL
+ middle = dirpath[len(root_path):]
+ if os.sep != '/':
+ middle = middle.replace(os.sep, '/')
+ if middle:
+ middle = middle + '/'
+ if name:
+ middle = middle + name
+ if isdir:
+ middle = middle + '/'
+ url.TrySetAttribute('loc', root_URL + encoder.WidenText(middle, None))
+
+ # Suppress default files. (All the way down here so we can log it.)
+ if name and (root_file == name):
+ url.Log(prefix='IGNORED (default file)', level=2)
+ return
+
+ consumer(url, False)
+ #end def PerFile
+
+ def PerDirectory(ignore, dirpath, namelist):
+ """
+ Called once per directory with a list of all the contained files/dirs.
+ """
+ ignore = ignore # Avoid warnings of an unused parameter
+
+ if not dirpath.startswith(root_path):
+ output.Warn('Unable to decide what the root path is for directory: '
+ '%s' % dirpath)
+ return
+
+ if not DecideDirectory(dirpath):
+ return
+
+ for name in namelist:
+ PerFile(dirpath, name)
+ #end def PerDirectory
+
+ output.Log('Walking DIRECTORY "%s"' % self._path, 1)
+ PerFile(self._path, None)
+ os.path.walk(self._path, PerDirectory, None)
+ #end def ProduceURLs
+#end class InputDirectory
+
+
+class InputAccessLog:
+ """
+ Each Input class knows how to yield a set of URLs from a data source.
+
+ This one handles access logs. It's non-trivial in that we want to
+ auto-detect log files in the Common Logfile Format (as used by Apache,
+ for instance) and the Extended Log File Format (as used by IIS, for
+ instance).
+ """
+
+ def __init__(self, attributes):
+ self._path = None # The file path
+ self._encoding = None # Encoding of that file
+ self._is_elf = False # Extended Log File Format?
+ self._is_clf = False # Common Logfile Format?
+ self._elf_status = -1 # ELF field: '200'
+ self._elf_method = -1 # ELF field: 'HEAD'
+ self._elf_uri = -1 # ELF field: '/foo?bar=1'
+ self._elf_urifrag1 = -1 # ELF field: '/foo'
+ self._elf_urifrag2 = -1 # ELF field: 'bar=1'
+
+ if not ValidateAttributes('ACCESSLOG', attributes, ('path', 'encoding')):
+ return
+
+ self._path = attributes.get('path')
+ self._encoding = attributes.get('encoding', ENC_UTF8)
+ if self._path:
+ self._path = encoder.MaybeNarrowPath(self._path)
+ if os.path.isfile(self._path):
+ output.Log('Input: From ACCESSLOG "%s"' % self._path, 2)
+ else:
+ output.Error('Can not locate file: %s' % self._path)
+ self._path = None
+ else:
+ output.Error('Accesslog entries must have a "path" attribute.')
+ #end def __init__
+
+ def RecognizeELFLine(self, line):
+ """ Recognize the Fields directive that heads an ELF file """
+ if not line.startswith('#Fields:'):
+ return False
+ fields = line.split(' ')
+ del fields[0]
+ for i in range(0, len(fields)):
+ field = fields[i].strip()
+ if field == 'sc-status':
+ self._elf_status = i
+ elif field == 'cs-method':
+ self._elf_method = i
+ elif field == 'cs-uri':
+ self._elf_uri = i
+ elif field == 'cs-uri-stem':
+ self._elf_urifrag1 = i
+ elif field == 'cs-uri-query':
+ self._elf_urifrag2 = i
+ output.Log('Recognized an Extended Log File Format file.', 2)
+ return True
+ #end def RecognizeELFLine
+
+ def GetELFLine(self, line):
+ """ Fetch the requested URL from an ELF line """
+ fields = line.split(' ')
+ count = len(fields)
+
+ # Verify status was Ok
+ if self._elf_status >= 0:
+ if self._elf_status >= count:
+ return None
+ if not fields[self._elf_status].strip() == '200':
+ return None
+
+ # Verify method was HEAD or GET
+ if self._elf_method >= 0:
+ if self._elf_method >= count:
+ return None
+ if not fields[self._elf_method].strip() in ('HEAD', 'GET'):
+ return None
+
+ # Pull the full URL if we can
+ if self._elf_uri >= 0:
+ if self._elf_uri >= count:
+ return None
+ url = fields[self._elf_uri].strip()
+ if url != '-':
+ return url
+
+ # Put together a fragmentary URL
+ if self._elf_urifrag1 >= 0:
+ if self._elf_urifrag1 >= count or self._elf_urifrag2 >= count:
+ return None
+ urlfrag1 = fields[self._elf_urifrag1].strip()
+ urlfrag2 = None
+ if self._elf_urifrag2 >= 0:
+ urlfrag2 = fields[self._elf_urifrag2]
+ if urlfrag1 and (urlfrag1 != '-'):
+ if urlfrag2 and (urlfrag2 != '-'):
+ urlfrag1 = urlfrag1 + '?' + urlfrag2
+ return urlfrag1
+
+ return None
+ #end def GetELFLine
+
+ def RecognizeCLFLine(self, line):
+ """ Try to tokenize a logfile line according to CLF pattern and see if
+ it works. """
+ match = ACCESSLOG_CLF_PATTERN.match(line)
+ recognize = match and (match.group(1) in ('HEAD', 'GET'))
+ if recognize:
+ output.Log('Recognized a Common Logfile Format file.', 2)
+ return recognize
+ #end def RecognizeCLFLine
+
+ def GetCLFLine(self, line):
+ """ Fetch the requested URL from a CLF line """
+ match = ACCESSLOG_CLF_PATTERN.match(line)
+ if match:
+ request = match.group(1)
+ if request in ('HEAD', 'GET'):
+ return match.group(2)
+ return None
+ #end def GetCLFLine
+
+ def ProduceURLs(self, consumer):
+ """ Produces URLs from our data source, hands them in to the consumer. """
+
+ # Open the file
+ (frame, file) = OpenFileForRead(self._path, 'ACCESSLOG')
+ if not file:
+ return
+
+ # Iterate lines
+ for line in file.readlines():
+ if self._encoding:
+ line = encoder.WidenText(line, self._encoding)
+ line = line.strip()
+
+ # If we don't know the format yet, try them both
+ if (not self._is_clf) and (not self._is_elf):
+ self._is_elf = self.RecognizeELFLine(line)
+ self._is_clf = self.RecognizeCLFLine(line)
+
+ # Digest the line
+ match = None
+ if self._is_elf:
+ match = self.GetELFLine(line)
+ elif self._is_clf:
+ match = self.GetCLFLine(line)
+ if not match:
+ continue
+
+ # Pass it on
+ url = URL()
+ url.TrySetAttribute('loc', match)
+ consumer(url, True)
+
+ file.close()
+ if frame:
+ frame.close()
+ #end def ProduceURLs
+#end class InputAccessLog
+
+
+class InputSitemap(xml.sax.handler.ContentHandler):
+
+ """
+ Each Input class knows how to yield a set of URLs from a data source.
+
+ This one handles Sitemap files and Sitemap index files. For the sake
+ of simplicity in design (and simplicity in interfacing with the SAX
+ package), we do not handle these at the same time, recursively. Instead
+ we read an index file completely and make a list of Sitemap files, then
+ go back and process each Sitemap.
+ """
+
+ class _ContextBase(object):
+
+ """Base class for context handlers in our SAX processing. A context
+ handler is a class that is responsible for understanding one level of
+ depth in the XML schema. The class knows what sub-tags are allowed,
+ and doing any processing specific for the tag we're in.
+
+ This base class is the API filled in by specific context handlers,
+ all defined below.
+ """
+
+ def __init__(self, subtags):
+ """Initialize with a sequence of the sub-tags that would be valid in
+ this context."""
+ self._allowed_tags = subtags # Sequence of sub-tags we can have
+ self._last_tag = None # Most recent seen sub-tag
+ #end def __init__
+
+ def AcceptTag(self, tag):
+ """Returns True iff opening a sub-tag is valid in this context."""
+ valid = tag in self._allowed_tags
+ if valid:
+ self._last_tag = tag
+ else:
+ self._last_tag = None
+ return valid
+ #end def AcceptTag
+
+ def AcceptText(self, text):
+ """Returns True iff a blurb of text is valid in this context."""
+ return False
+ #end def AcceptText
+
+ def Open(self):
+ """The context is opening. Do initialization."""
+ pass
+ #end def Open
+
+ def Close(self):
+ """The context is closing. Return our result, if any."""
+ pass
+ #end def Close
+
+ def Return(self, result):
+ """We're returning to this context after handling a sub-tag. This
+ method is called with the result data from the sub-tag that just
+ closed. Here in _ContextBase, if we ever see a result it means
+ the derived child class forgot to override this method."""
+ if result:
+ raise NotImplementedError
+ #end def Return
+ #end class _ContextBase
+
+ class _ContextUrlSet(_ContextBase):
+
+ """Context handler for the document node in a Sitemap."""
+
+ def __init__(self):
+ InputSitemap._ContextBase.__init__(self, ('url',))
+ #end def __init__
+ #end class _ContextUrlSet
+
+ class _ContextUrl(_ContextBase):
+
+ """Context handler for a URL node in a Sitemap."""
+
+ def __init__(self, consumer):
+ """Initialize this context handler with the callable consumer that
+ wants our URLs."""
+ InputSitemap._ContextBase.__init__(self, URL.__slots__)
+ self._url = None # The URL object we're building
+ self._consumer = consumer # Who wants to consume it
+ #end def __init__
+
+ def Open(self):
+ """Initialize the URL."""
+ assert not self._url
+ self._url = URL()
+ #end def Open
+
+ def Close(self):
+ """Pass the URL to the consumer and reset it to None."""
+ assert self._url
+ self._consumer(self._url, False)
+ self._url = None
+ #end def Close
+
+ def Return(self, result):
+ """A value context has closed, absorb the data it gave us."""
+ assert self._url
+ if result:
+ self._url.TrySetAttribute(self._last_tag, result)
+ #end def Return
+ #end class _ContextUrl
+
+ class _ContextSitemapIndex(_ContextBase):
+
+ """Context handler for the document node in an index file."""
+
+ def __init__(self):
+ InputSitemap._ContextBase.__init__(self, ('sitemap',))
+ self._loclist = [] # List of accumulated Sitemap URLs
+ #end def __init__
+
+ def Open(self):
+ """Just a quick verify of state."""
+ assert not self._loclist
+ #end def Open
+
+ def Close(self):
+ """Return our list of accumulated URLs."""
+ if self._loclist:
+ temp = self._loclist
+ self._loclist = []
+ return temp
+ #end def Close
+
+ def Return(self, result):
+ """Getting a new loc URL, add it to the collection."""
+ if result:
+ self._loclist.append(result)
+ #end def Return
+ #end class _ContextSitemapIndex
+
+ class _ContextSitemap(_ContextBase):
+
+ """Context handler for a Sitemap entry in an index file."""
+
+ def __init__(self):
+ InputSitemap._ContextBase.__init__(self, ('loc', 'lastmod'))
+ self._loc = None # The URL to the Sitemap
+ #end def __init__
+
+ def Open(self):
+ """Just a quick verify of state."""
+ assert not self._loc
+ #end def Open
+
+ def Close(self):
+ """Return our URL to our parent."""
+ if self._loc:
+ temp = self._loc
+ self._loc = None
+ return temp
+ output.Warn('In the Sitemap index file, a "sitemap" entry had no "loc".')
+ #end def Close
+
+ def Return(self, result):
+ """A value has closed. If it was a 'loc', absorb it."""
+ if result and (self._last_tag == 'loc'):
+ self._loc = result
+ #end def Return
+ #end class _ContextSitemap
+
+ class _ContextValue(_ContextBase):
+
+ """Context handler for a single value. We return just the value. The
+ higher level context has to remember what tag led into us."""
+
+ def __init__(self):
+ InputSitemap._ContextBase.__init__(self, ())
+ self._text = None
+ #end def __init__
+
+ def AcceptText(self, text):
+ """Allow all text, adding it to our buffer."""
+ if self._text:
+ self._text = self._text + text
+ else:
+ self._text = text
+ return True
+ #end def AcceptText
+
+ def Open(self):
+ """Initialize our buffer."""
+ self._text = None
+ #end def Open
+
+ def Close(self):
+ """Return what's in our buffer."""
+ text = self._text
+ self._text = None
+ if text:
+ text = text.strip()
+ return text
+ #end def Close
+ #end class _ContextValue
+
+ def __init__(self, attributes):
+ """Initialize with a dictionary of attributes from our entry in the
+ config file."""
+ xml.sax.handler.ContentHandler.__init__(self)
+ self._pathlist = None # A list of files
+ self._current = -1 # Current context in _contexts
+ self._contexts = None # The stack of contexts we allow
+ self._contexts_idx = None # ...contexts for index files
+ self._contexts_stm = None # ...contexts for Sitemap files
+
+ if not ValidateAttributes('SITEMAP', attributes, ['path']):
+ return
+
+ # Init the first file path
+ path = attributes.get('path')
+ if path:
+ path = encoder.MaybeNarrowPath(path)
+ if os.path.isfile(path):
+ output.Log('Input: From SITEMAP "%s"' % path, 2)
+ self._pathlist = [path]
+ else:
+ output.Error('Can not locate file "%s"' % path)
+ else:
+ output.Error('Sitemap entries must have a "path" attribute.')
+ #end def __init__
+
+ def ProduceURLs(self, consumer):
+ """In general: Produces URLs from our data source, hand them to the
+ callable consumer.
+
+ In specific: Iterate over our list of paths and delegate the actual
+ processing to helper methods. This is a complexity no other data source
+ needs to suffer. We are unique in that we can have files that tell us
+ to bring in other files.
+
+ Note the decision to allow an index file or not is made in this method.
+ If we call our parser with (self._contexts == None) the parser will
+ grab whichever context stack can handle the file. IE: index is allowed.
+ If instead we set (self._contexts = ...) before parsing, the parser
+ will only use the stack we specify. IE: index not allowed.
+ """
+ # Set up two stacks of contexts
+ self._contexts_idx = [InputSitemap._ContextSitemapIndex(),
+ InputSitemap._ContextSitemap(),
+ InputSitemap._ContextValue()]
+
+ self._contexts_stm = [InputSitemap._ContextUrlSet(),
+ InputSitemap._ContextUrl(consumer),
+ InputSitemap._ContextValue()]
+
+ # Process the first file
+ assert self._pathlist
+ path = self._pathlist[0]
+ self._contexts = None # We allow an index file here
+ self._ProcessFile(path)
+
+ # Iterate over remaining files
+ self._contexts = self._contexts_stm # No index files allowed
+ for path in self._pathlist[1:]:
+ self._ProcessFile(path)
+ #end def ProduceURLs
+
+ def _ProcessFile(self, path):
+ """Do per-file reading/parsing/consuming for the file path passed in."""
+ assert path
+
+ # Open our file
+ (frame, file) = OpenFileForRead(path, 'SITEMAP')
+ if not file:
+ return
+
+ # Rev up the SAX engine
+ try:
+ self._current = -1
+ xml.sax.parse(file, self)
+ except SchemaError:
+ output.Error('An error in file "%s" made us abort reading the Sitemap.'
+ % path)
+ except IOError:
+ output.Error('Cannot read from file "%s"' % path)
+ except xml.sax._exceptions.SAXParseException, e:
+ output.Error('XML error in the file "%s" (line %d, column %d): %s' %
+ (path, e._linenum, e._colnum, e.getMessage()))
+
+ # Clean up
+ file.close()
+ if frame:
+ frame.close()
+ #end def _ProcessFile
+
+ def _MungeLocationListIntoFiles(self, urllist):
+ """Given a list of URLs, munge them into our self._pathlist property.
+ We do this by assuming all the files live in the same directory as
+ the first file in the existing pathlist. That is, we assume a
+ Sitemap index points to Sitemaps only in the same directory. This
+ is not true in general, but will be true for any output produced
+ by this script.
+ """
+ assert self._pathlist
+ path = self._pathlist[0]
+ path = os.path.normpath(path)
+ dir = os.path.dirname(path)
+ wide = False
+ if type(path) == types.UnicodeType:
+ wide = True
+
+ for url in urllist:
+ url = URL.Canonicalize(url)
+ output.Log('Index points to Sitemap file at: %s' % url, 2)
+ (scheme, netloc, path, query, frag) = urlparse.urlsplit(url)
+ file = os.path.basename(path)
+ file = urllib.unquote(file)
+ if wide:
+ file = encoder.WidenText(file)
+ if dir:
+ file = dir + os.sep + file
+ if file:
+ self._pathlist.append(file)
+ output.Log('Will attempt to read Sitemap file: %s' % file, 1)
+ #end def _MungeLocationListIntoFiles
+
+ def startElement(self, tag, attributes):
+ """SAX processing, called per node in the config stream.
+ As long as the new tag is legal in our current context, this
+ becomes an Open call on one context deeper.
+ """
+ # If this is the document node, we may have to look for a context stack
+ if (self._current < 0) and not self._contexts:
+ assert self._contexts_idx and self._contexts_stm
+ if tag == 'urlset':
+ self._contexts = self._contexts_stm
+ elif tag == 'sitemapindex':
+ self._contexts = self._contexts_idx
+ output.Log('File is a Sitemap index.', 2)
+ else:
+ output.Error('The document appears to be neither a Sitemap nor a '
+ 'Sitemap index.')
+ raise SchemaError
+
+ # Display a kinder error on a common mistake
+ if (self._current < 0) and (self._contexts == self._contexts_stm) and (
+ tag == 'sitemapindex'):
+ output.Error('A Sitemap index can not refer to another Sitemap index.')
+ raise SchemaError
+
+ # Verify no unexpected attributes
+ if attributes:
+ text = ''
+ for attr in attributes.keys():
+ # The document node will probably have namespaces
+ if self._current < 0:
+ if attr.find('xmlns') >= 0:
+ continue
+ if attr.find('xsi') >= 0:
+ continue
+ if text:
+ text = text + ', '
+ text = text + attr
+ if text:
+ output.Warn('Did not expect any attributes on any tag, instead tag '
+ '"%s" had attributes: %s' % (tag, text))
+
+ # Switch contexts
+ if (self._current < 0) or (self._contexts[self._current].AcceptTag(tag)):
+ self._current = self._current + 1
+ assert self._current < len(self._contexts)
+ self._contexts[self._current].Open()
+ else:
+ output.Error('Can not accept tag "%s" where it appears.' % tag)
+ raise SchemaError
+ #end def startElement
+
+ def endElement(self, tag):
+ """SAX processing, called per node in the config stream.
+ This becomes a call to Close on one context followed by a call
+ to Return on the previous.
+ """
+ tag = tag # Avoid warning on unused argument
+ assert self._current >= 0
+ retval = self._contexts[self._current].Close()
+ self._current = self._current - 1
+ if self._current >= 0:
+ self._contexts[self._current].Return(retval)
+ elif retval and (self._contexts == self._contexts_idx):
+ self._MungeLocationListIntoFiles(retval)
+ #end def endElement
+
+ def characters(self, text):
+ """SAX processing, called when text values are read. Important to
+ note that one single text value may be split across multiple calls
+ of this method.
+ """
+ if (self._current < 0) or (
+ not self._contexts[self._current].AcceptText(text)):
+ if text.strip():
+ output.Error('Can not accept text "%s" where it appears.' % text)
+ raise SchemaError
+ #end def characters
+#end class InputSitemap
+
+
+class FilePathGenerator:
+ """
+ This class generates filenames in a series, upon request.
+ You can request any iteration number at any time, you don't
+ have to go in order.
+
+ Example of iterations for '/path/foo.xml.gz':
+ 0 --> /path/foo.xml.gz
+ 1 --> /path/foo1.xml.gz
+ 2 --> /path/foo2.xml.gz
+ _index.xml --> /path/foo_index.xml
+ """
+
+ def __init__(self):
+ self.is_gzip = False # Is this a GZIP file?
+
+ self._path = None # '/path/'
+ self._prefix = None # 'foo'
+ self._suffix = None # '.xml.gz'
+ #end def __init__
+
+ def Preload(self, path):
+ """ Splits up a path into forms ready for recombination. """
+ path = encoder.MaybeNarrowPath(path)
+
+ # Get down to a base name
+ path = os.path.normpath(path)
+ base = os.path.basename(path).lower()
+ if not base:
+ output.Error('Couldn\'t parse the file path: %s' % path)
+ return False
+ lenbase = len(base)
+
+ # Recognize extension
+ lensuffix = 0
+ compare_suffix = ['.xml', '.xml.gz', '.gz']
+ for suffix in compare_suffix:
+ if base.endswith(suffix):
+ lensuffix = len(suffix)
+ break
+ if not lensuffix:
+ output.Error('The path "%s" doesn\'t end in a supported file '
+ 'extension.' % path)
+ return False
+ self.is_gzip = suffix.endswith('.gz')
+
+ # Split the original path
+ lenpath = len(path)
+ self._path = path[:lenpath-lenbase]
+ self._prefix = path[lenpath-lenbase:lenpath-lensuffix]
+ self._suffix = path[lenpath-lensuffix:]
+
+ return True
+ #end def Preload
+
+ def GeneratePath(self, instance):
+ """ Generates the iterations, as described above. """
+ prefix = self._path + self._prefix
+ if type(instance) == types.IntType:
+ if instance:
+ return '%s%d%s' % (prefix, instance, self._suffix)
+ return prefix + self._suffix
+ return prefix + instance
+ #end def GeneratePath
+
+ def GenerateURL(self, instance, root_url):
+ """ Generates iterations, but as a URL instead of a path. """
+ prefix = root_url + self._prefix
+ retval = None
+ if type(instance) == types.IntType:
+ if instance:
+ retval = '%s%d%s' % (prefix, instance, self._suffix)
+ else:
+ retval = prefix + self._suffix
+ else:
+ retval = prefix + instance
+ return URL.Canonicalize(retval)
+ #end def GenerateURL
+
+ def GenerateWildURL(self, root_url):
+ """ Generates a wildcard that should match all our iterations """
+ prefix = URL.Canonicalize(root_url + self._prefix)
+ temp = URL.Canonicalize(prefix + self._suffix)
+ suffix = temp[len(prefix):]
+ return prefix + '*' + suffix
+ #end def GenerateURL
+#end class FilePathGenerator
+
+
+class PerURLStatistics:
+ """ Keep track of some simple per-URL statistics, like file extension. """
+
+ def __init__(self):
+ self._extensions = {} # Count of extension instances
+ #end def __init__
+
+ def Consume(self, url):
+ """ Log some stats for the URL. At the moment, that means extension. """
+ if url and url.loc:
+ (scheme, netloc, path, query, frag) = urlparse.urlsplit(url.loc)
+ if not path:
+ return
+
+ # Recognize directories
+ if path.endswith('/'):
+ if self._extensions.has_key('/'):
+ self._extensions['/'] = self._extensions['/'] + 1
+ else:
+ self._extensions['/'] = 1
+ return
+
+ # Strip to a filename
+ i = path.rfind('/')
+ if i >= 0:
+ assert i < len(path)
+ path = path[i:]
+
+ # Find extension
+ i = path.rfind('.')
+ if i > 0:
+ assert i < len(path)
+ ext = path[i:].lower()
+ if self._extensions.has_key(ext):
+ self._extensions[ext] = self._extensions[ext] + 1
+ else:
+ self._extensions[ext] = 1
+ else:
+ if self._extensions.has_key('(no extension)'):
+ self._extensions['(no extension)'] = self._extensions[
+ '(no extension)'] + 1
+ else:
+ self._extensions['(no extension)'] = 1
+ #end def Consume
+
+ def Log(self):
+ """ Dump out stats to the output. """
+ if len(self._extensions):
+ output.Log('Count of file extensions on URLs:', 1)
+ set = self._extensions.keys()
+ set.sort()
+ for ext in set:
+ output.Log(' %7d %s' % (self._extensions[ext], ext), 1)
+ #end def Log
+
+class Sitemap(xml.sax.handler.ContentHandler):
+ """
+ This is the big workhorse class that processes your inputs and spits
+ out sitemap files. It is built as a SAX handler for set up purposes.
+ That is, it processes an XML stream to bring itself up.
+ """
+
+ def __init__(self, suppress_notify):
+ xml.sax.handler.ContentHandler.__init__(self)
+ self._filters = [] # Filter objects
+ self._inputs = [] # Input objects
+ self._urls = {} # Maps URLs to count of dups
+ self._set = [] # Current set of URLs
+ self._filegen = None # Path generator for output files
+ self._wildurl1 = None # Sitemap URLs to filter out
+ self._wildurl2 = None # Sitemap URLs to filter out
+ self._sitemaps = 0 # Number of output files
+ # We init _dup_max to 2 so the default priority is 0.5 instead of 1.0
+ self._dup_max = 2 # Max number of duplicate URLs
+ self._stat = PerURLStatistics() # Some simple stats
+ self._in_site = False # SAX: are we in a Site node?
+ self._in_Site_ever = False # SAX: were we ever in a Site?
+
+ self._default_enc = None # Best encoding to try on URLs
+ self._base_url = None # Prefix to all valid URLs
+ self._store_into = None # Output filepath
+ self._suppress = suppress_notify # Suppress notify of servers
+ #end def __init__
+
+ def ValidateBasicConfig(self):
+ """ Verifies (and cleans up) the basic user-configurable options. """
+ all_good = True
+
+ if self._default_enc:
+ encoder.SetUserEncoding(self._default_enc)
+
+ # Canonicalize the base_url
+ if all_good and not self._base_url:
+ output.Error('A site needs a "base_url" attribute.')
+ all_good = False
+ if all_good and not URL.IsAbsolute(self._base_url):
+ output.Error('The "base_url" must be absolute, not relative: %s' %
+ self._base_url)
+ all_good = False
+ if all_good:
+ self._base_url = URL.Canonicalize(self._base_url)
+ if not self._base_url.endswith('/'):
+ self._base_url = self._base_url + '/'
+ output.Log('BaseURL is set to: %s' % self._base_url, 2)
+
+ # Load store_into into a generator
+ if all_good:
+ if self._store_into:
+ self._filegen = FilePathGenerator()
+ if not self._filegen.Preload(self._store_into):
+ all_good = False
+ else:
+ output.Error('A site needs a "store_into" attribute.')
+ all_good = False
+
+ # Ask the generator for patterns on what its output will look like
+ if all_good:
+ self._wildurl1 = self._filegen.GenerateWildURL(self._base_url)
+ self._wildurl2 = self._filegen.GenerateURL(SITEINDEX_SUFFIX,
+ self._base_url)
+
+ # Unify various forms of False
+ if all_good:
+ if self._suppress:
+ if (type(self._suppress) == types.StringType) or (type(self._suppress)
+ == types.UnicodeType):
+ if (self._suppress == '0') or (self._suppress.lower() == 'false'):
+ self._suppress = False
+
+ # Done
+ if not all_good:
+ output.Log('See "example_config.xml" for more information.', 0)
+ return all_good
+ #end def ValidateBasicConfig
+
+ def Generate(self):
+ """ Run over all the Inputs and ask them to Produce """
+ # Run the inputs
+ for input in self._inputs:
+ input.ProduceURLs(self.ConsumeURL)
+
+ # Do last flushes
+ if len(self._set):
+ self.FlushSet()
+ if not self._sitemaps:
+ output.Warn('No URLs were recorded, writing an empty sitemap.')
+ self.FlushSet()
+
+ # Write an index as needed
+ if self._sitemaps > 1:
+ self.WriteIndex()
+
+ # Notify
+ self.NotifySearch()
+
+ # Dump stats
+ self._stat.Log()
+ #end def Generate
+
+ def ConsumeURL(self, url, allow_fragment):
+ """
+ All per-URL processing comes together here, regardless of Input.
+ Here we run filters, remove duplicates, spill to disk as needed, etc.
+ """
+ if not url:
+ return
+
+ # Validate
+ if not url.Validate(self._base_url, allow_fragment):
+ return
+
+ # Run filters
+ accept = None
+ for filter in self._filters:
+ accept = filter.Apply(url)
+ if accept != None:
+ break
+ if not (accept or (accept == None)):
+ url.Log(prefix='FILTERED', level=2)
+ return
+
+ # Ignore our out output URLs
+ if fnmatch.fnmatchcase(url.loc, self._wildurl1) or fnmatch.fnmatchcase(
+ url.loc, self._wildurl2):
+ url.Log(prefix='IGNORED (output file)', level=2)
+ return
+
+ # Note the sighting
+ hash = url.MakeHash()
+ if self._urls.has_key(hash):
+ dup = self._urls[hash]
+ if dup > 0:
+ dup = dup + 1
+ self._urls[hash] = dup
+ if self._dup_max < dup:
+ self._dup_max = dup
+ url.Log(prefix='DUPLICATE')
+ return
+
+ # Acceptance -- add to set
+ self._urls[hash] = 1
+ self._set.append(url)
+ self._stat.Consume(url)
+ url.Log()
+
+ # Flush the set if needed
+ if len(self._set) >= MAXURLS_PER_SITEMAP:
+ self.FlushSet()
+ #end def ConsumeURL
+
+ def FlushSet(self):
+ """
+ Flush the current set of URLs to the output. This is a little
+ slow because we like to sort them all and normalize the priorities
+ before dumping.
+ """
+
+ # Sort and normalize
+ output.Log('Sorting and normalizing collected URLs.', 1)
+ self._set.sort()
+ for url in self._set:
+ hash = url.MakeHash()
+ dup = self._urls[hash]
+ if dup > 0:
+ self._urls[hash] = -1
+ if not url.priority:
+ url.priority = '%.4f' % (float(dup) / float(self._dup_max))
+
+ # Get the filename we're going to write to
+ filename = self._filegen.GeneratePath(self._sitemaps)
+ if not filename:
+ output.Fatal('Unexpected: Couldn\'t generate output filename.')
+ self._sitemaps = self._sitemaps + 1
+ output.Log('Writing Sitemap file "%s" with %d URLs' %
+ (filename, len(self._set)), 1)
+
+ # Write to it
+ frame = None
+ file = None
+
+ try:
+ if self._filegen.is_gzip:
+ basename = os.path.basename(filename);
+ frame = open(filename, 'wb')
+ file = gzip.GzipFile(fileobj=frame, filename=basename, mode='wt')
+ else:
+ file = open(filename, 'wt')
+
+ file.write(SITEMAP_HEADER)
+ for url in self._set:
+ url.WriteXML(file)
+ file.write(SITEMAP_FOOTER)
+
+ file.close()
+ if frame:
+ frame.close()
+
+ frame = None
+ file = None
+ except IOError:
+ output.Fatal('Couldn\'t write out to file: %s' % filename)
+ os.chmod(filename, 0644)
+
+ # Flush
+ self._set = []
+ #end def FlushSet
+
+ def WriteIndex(self):
+ """ Write the master index of all Sitemap files """
+ # Make a filename
+ filename = self._filegen.GeneratePath(SITEINDEX_SUFFIX)
+ if not filename:
+ output.Fatal('Unexpected: Couldn\'t generate output index filename.')
+ output.Log('Writing index file "%s" with %d Sitemaps' %
+ (filename, self._sitemaps), 1)
+
+ # Make a lastmod time
+ lastmod = TimestampISO8601(time.time())
+
+ # Write to it
+ try:
+ fd = open(filename, 'wt')
+ fd.write(SITEINDEX_HEADER)
+
+ for mapnumber in range(0,self._sitemaps):
+ # Write the entry
+ mapurl = self._filegen.GenerateURL(mapnumber, self._base_url)
+ mapattributes = { 'loc' : mapurl, 'lastmod' : lastmod }
+ fd.write(SITEINDEX_ENTRY % mapattributes)
+
+ fd.write(SITEINDEX_FOOTER)
+
+ fd.close()
+ fd = None
+ except IOError:
+ output.Fatal('Couldn\'t write out to file: %s' % filename)
+ os.chmod(filename, 0644)
+ #end def WriteIndex
+
+ def NotifySearch(self):
+ """ Send notification of the new Sitemap(s) to the search engines. """
+ if self._suppress:
+ output.Log('Search engine notification is suppressed.', 1)
+ return
+
+ output.Log('Notifying search engines.', 1)
+
+ # Override the urllib's opener class with one that doesn't ignore 404s
+ class ExceptionURLopener(urllib.FancyURLopener):
+ def http_error_default(self, url, fp, errcode, errmsg, headers):
+ output.Log('HTTP error %d: %s' % (errcode, errmsg), 2)
+ raise IOError
+ #end def http_error_default
+ #end class ExceptionURLOpener
+ old_opener = urllib._urlopener
+ urllib._urlopener = ExceptionURLopener()
+
+ # Build the URL we want to send in
+ if self._sitemaps > 1:
+ url = self._filegen.GenerateURL(SITEINDEX_SUFFIX, self._base_url)
+ else:
+ url = self._filegen.GenerateURL(0, self._base_url)
+
+ # Test if we can hit it ourselves
+ try:
+ u = urllib.urlopen(url)
+ u.close()
+ except IOError:
+ output.Error('When attempting to access our generated Sitemap at the '
+ 'following URL:\n %s\n we failed to read it. Please '
+ 'verify the store_into path you specified in\n'
+ ' your configuration file is web-accessable. Consult '
+ 'the FAQ for more\n information.' % url)
+ output.Warn('Proceeding to notify with an unverifyable URL.')
+
+ # Cycle through notifications
+ # To understand this, see the comment near the NOTIFICATION_SITES comment
+ for ping in NOTIFICATION_SITES:
+ query_map = ping[3]
+ query_attr = ping[5]
+ query_map[query_attr] = url
+ query = urllib.urlencode(query_map)
+ notify = urlparse.urlunsplit((ping[0], ping[1], ping[2], query, ping[4]))
+
+ # Send the notification
+ output.Log('Notifying: %s' % ping[1], 1)
+ output.Log('Notification URL: %s' % notify, 2)
+ try:
+ u = urllib.urlopen(notify)
+ u.read()
+ u.close()
+ except IOError:
+ output.Warn('Cannot contact: %s' % ping[1])
+
+ if old_opener:
+ urllib._urlopener = old_opener
+ #end def NotifySearch
+
+ def startElement(self, tag, attributes):
+ """ SAX processing, called per node in the config stream. """
+
+ if tag == 'site':
+ if self._in_site:
+ output.Error('Can not nest Site entries in the configuration.')
+ else:
+ self._in_site = True
+
+ if not ValidateAttributes('SITE', attributes,
+ ('verbose', 'default_encoding', 'base_url', 'store_into',
+ 'suppress_search_engine_notify')):
+ return
+
+ verbose = attributes.get('verbose', 0)
+ if verbose:
+ output.SetVerbose(verbose)
+
+ self._default_enc = attributes.get('default_encoding')
+ self._base_url = attributes.get('base_url')
+ self._store_into = attributes.get('store_into')
+ if not self._suppress:
+ self._suppress = attributes.get('suppress_search_engine_notify',
+ False)
+ self.ValidateBasicConfig()
+
+ elif tag == 'filter':
+ self._filters.append(Filter(attributes))
+
+ elif tag == 'url':
+ self._inputs.append(InputURL(attributes))
+
+ elif tag == 'urllist':
+ for attributeset in ExpandPathAttribute(attributes, 'path'):
+ self._inputs.append(InputURLList(attributeset))
+
+ elif tag == 'directory':
+ self._inputs.append(InputDirectory(attributes, self._base_url))
+
+ elif tag == 'accesslog':
+ for attributeset in ExpandPathAttribute(attributes, 'path'):
+ self._inputs.append(InputAccessLog(attributeset))
+
+ elif tag == 'sitemap':
+ for attributeset in ExpandPathAttribute(attributes, 'path'):
+ self._inputs.append(InputSitemap(attributeset))
+
+ else:
+ output.Error('Unrecognized tag in the configuration: %s' % tag)
+ #end def startElement
+
+ def endElement(self, tag):
+ """ SAX processing, called per node in the config stream. """
+ if tag == 'site':
+ assert self._in_site
+ self._in_site = False
+ self._in_site_ever = True
+ #end def endElement
+
+ def endDocument(self):
+ """ End of SAX, verify we can proceed. """
+ if not self._in_site_ever:
+ output.Error('The configuration must specify a "site" element.')
+ else:
+ if not self._inputs:
+ output.Warn('There were no inputs to generate a sitemap from.')
+ #end def endDocument
+#end class Sitemap
+
+
+def ValidateAttributes(tag, attributes, goodattributes):
+ """ Makes sure 'attributes' does not contain any attribute not
+ listed in 'goodattributes' """
+ all_good = True
+ for attr in attributes.keys():
+ if not attr in goodattributes:
+ output.Error('Unknown %s attribute: %s' % (tag, attr))
+ all_good = False
+ return all_good
+#end def ValidateAttributes
+
+def ExpandPathAttribute(src, attrib):
+ """ Given a dictionary of attributes, return a list of dictionaries
+ with all the same attributes except for the one named attrib.
+ That one, we treat as a file path and expand into all its possible
+ variations. """
+ # Do the path expansion. On any error, just return the source dictionary.
+ path = src.get(attrib)
+ if not path:
+ return [src]
+ path = encoder.MaybeNarrowPath(path);
+ pathlist = glob.glob(path)
+ if not pathlist:
+ return [src]
+
+ # If this isn't actually a dictionary, make it one
+ if type(src) != types.DictionaryType:
+ tmp = {}
+ for key in src.keys():
+ tmp[key] = src[key]
+ src = tmp
+
+ # Create N new dictionaries
+ retval = []
+ for path in pathlist:
+ dst = src.copy()
+ dst[attrib] = path
+ retval.append(dst)
+
+ return retval
+#end def ExpandPathAttribute
+
+def OpenFileForRead(path, logtext):
+ """ Opens a text file, be it GZip or plain """
+
+ frame = None
+ file = None
+
+ if not path:
+ return (frame, file)
+
+ try:
+ if path.endswith('.gz'):
+ frame = open(path, 'rb')
+ file = gzip.GzipFile(fileobj=frame, mode='rt')
+ else:
+ file = open(path, 'rt')
+
+ if logtext:
+ output.Log('Opened %s file: %s' % (logtext, path), 1)
+ else:
+ output.Log('Opened file: %s' % path, 1)
+ except IOError:
+ output.Error('Can not open file: %s' % path)
+
+ return (frame, file)
+#end def OpenFileForRead
+
+def TimestampISO8601(t):
+ """Seconds since epoch (1970-01-01) --> ISO 8601 time string."""
+ return time.strftime('%Y-%m-%dT%H:%M:%SZ', time.gmtime(t))
+#end def TimestampISO8601
+
+def CreateSitemapFromFile(configpath, suppress_notify):
+ """ Sets up a new Sitemap object from the specified configuration file. """
+
+ # Remember error count on the way in
+ num_errors = output.num_errors
+
+ # Rev up SAX to parse the config
+ sitemap = Sitemap(suppress_notify)
+ try:
+ output.Log('Reading configuration file: %s' % configpath, 0)
+ xml.sax.parse(configpath, sitemap)
+ except IOError:
+ output.Error('Cannot read configuration file: %s' % configpath)
+ except xml.sax._exceptions.SAXParseException, e:
+ output.Error('XML error in the config file (line %d, column %d): %s' %
+ (e._linenum, e._colnum, e.getMessage()))
+ except xml.sax._exceptions.SAXReaderNotAvailable:
+ output.Error('Some installs of Python 2.2 did not include complete support'
+ ' for XML.\n Please try upgrading your version of Python'
+ ' and re-running the script.')
+
+ # If we added any errors, return no sitemap
+ if num_errors == output.num_errors:
+ return sitemap
+ return None
+#end def CreateSitemapFromFile
+
+def ProcessCommandFlags(args):
+ """
+ Parse command line flags per specified usage, pick off key, value pairs
+ All flags of type "--key=value" will be processed as __flags[key] = value,
+ "--option" will be processed as __flags[option] = option
+ """
+
+ flags = {}
+ rkeyval = '--(?P<key>\S*)[=](?P<value>\S*)' # --key=val
+ roption = '--(?P<option>\S*)' # --key
+ r = '(' + rkeyval + ')|(' + roption + ')'
+ rc = re.compile(r)
+ for a in args:
+ try:
+ rcg = rc.search(a).groupdict()
+ if rcg.has_key('key'):
+ flags[rcg['key']] = rcg['value']
+ if rcg.has_key('option'):
+ flags[rcg['option']] = rcg['option']
+ except AttributeError:
+ return None
+ return flags
+#end def ProcessCommandFlags
+
+
+#
+# __main__
+#
+
+if __name__ == '__main__':
+ flags = ProcessCommandFlags(sys.argv[1:])
+ if not flags or not flags.has_key('config') or flags.has_key('help'):
+ output.Log(__usage__, 0)
+ else:
+ suppress_notify = flags.has_key('testing')
+ sitemap = CreateSitemapFromFile(flags['config'], suppress_notify)
+ if not sitemap:
+ output.Log('Configuration file errors -- exiting.', 0)
+ else:
+ sitemap.Generate()
+ output.Log('Number of errors: %d' % output.num_errors, 1)
+ output.Log('Number of warnings: %d' % output.num_warns, 1)
diff --git a/nikola/utils.py b/nikola/utils.py
new file mode 100644
index 0000000..42e0c05
--- /dev/null
+++ b/nikola/utils.py
@@ -0,0 +1,466 @@
+"""Utility functions."""
+
+from collections import defaultdict
+import cPickle
+import datetime
+import hashlib
+import os
+import re
+import codecs
+import shutil
+import string
+import subprocess
+import sys
+from zipfile import ZipFile as zip
+
+from unidecode import unidecode
+
+import PyRSS2Gen as rss
+
+__all__ = ['get_theme_path', 'get_theme_chain', 'load_messages', 'copy_tree',
+ 'get_compile_html', 'get_template_module', 'generic_rss_renderer',
+ 'copy_file', 'slugify', 'unslugify', 'get_meta', 'to_datetime',
+ 'apply_filters', 'config_changed']
+
+
+class config_changed(object):
+ """ A copy of doit's but using pickle instead of serializing manually."""
+
+ def __init__(self, config):
+ self.config = config
+
+ def __call__(self, task, values):
+ config_digest = None
+ if isinstance(self.config, basestring):
+ config_digest = self.config
+ elif isinstance(self.config, dict):
+ data = cPickle.dumps(self.config)
+ config_digest = hashlib.md5(data).hexdigest()
+ else:
+ raise Exception(('Invalid type of config_changed parameter got %s'
+ + ', must be string or dict') % (type(self.config),))
+
+ def _save_config():
+ return {'_config_changed': config_digest}
+
+ task.insert_action(_save_config)
+ last_success = values.get('_config_changed')
+ if last_success is None:
+ return False
+ return (last_success == config_digest)
+
+
+def get_theme_path(theme):
+ """Given a theme name, returns the path where its files are located.
+
+ Looks in ./themes and in the place where themes go when installed.
+ """
+ dir_name = os.path.join('themes', theme)
+ if os.path.isdir(dir_name):
+ return dir_name
+ dir_name = os.path.join(os.path.dirname(__file__),
+ 'data', 'themes', theme)
+ if os.path.isdir(dir_name):
+ return dir_name
+ raise Exception(u"Can't find theme '%s'" % theme)
+
+
+def re_meta(line, match):
+ """ re.compile for meta"""
+ reStr = re.compile('^%s(.*)' % re.escape(match))
+ result = reStr.findall(line)
+ if result:
+ return result[0].strip()
+ else:
+ return ''
+
+
+def get_meta(source_path):
+ """get post's meta from source"""
+ with codecs.open(source_path, "r", "utf8") as meta_file:
+ meta_data = meta_file.readlines(15)
+ title = slug = date = tags = link = description = ''
+
+ re_md_title = re.compile(r'^%s([^%s].*)' %
+ (re.escape('#'), re.escape('#')))
+ re_rst_title = re.compile(r'^([^%s ].*)' % re.escape(string.punctuation))
+
+ for meta in meta_data:
+ if not title:
+ title = re_meta(meta, '.. title:')
+ if not title:
+ if re_rst_title.findall(meta):
+ title = re_rst_title.findall(meta)[0]
+ if not title:
+ if re_md_title.findall(meta):
+ title = re_md_title.findall(meta)[0]
+ if not slug:
+ slug = re_meta(meta, '.. slug:')
+ if not date:
+ date = re_meta(meta, '.. date:')
+ if not tags:
+ tags = re_meta(meta, '.. tags:')
+ if not link:
+ link = re_meta(meta, '.. link:')
+ if not description:
+ description = re_meta(meta, '.. description:')
+
+ # TODO: either enable or delete
+ #if not date:
+ #from datetime import datetime
+ #date = datetime.fromtimestamp(
+ # os.path.getmtime(source_path)).strftime('%Y/%m/%d %H:%M')
+
+ return (title, slug, date, tags, link, description)
+
+
+def get_template_engine(themes):
+ for theme_name in themes:
+ engine_path = os.path.join(get_theme_path(theme_name), 'engine')
+ if os.path.isfile(engine_path):
+ with open(engine_path) as fd:
+ return fd.readlines()[0].strip()
+ # default
+ return 'mako'
+
+def get_theme_bundles(themes):
+ """Given a theme chain, return the bundle definitions."""
+ bundles = {}
+ for theme_name in themes:
+ bundles_path = os.path.join(get_theme_path(theme_name), 'bundles')
+ if os.path.isfile(bundles_path):
+ with open(bundles_path) as fd:
+ for line in fd:
+ name, files = line.split('=')
+ files = [f.strip() for f in files.split(',')]
+ bundles[name.strip()] = files
+ break
+ return bundles
+
+def get_theme_chain(theme):
+ """Create the full theme inheritance chain."""
+ themes = [theme]
+
+ def get_parent(theme_name):
+ parent_path = os.path.join(get_theme_path(theme_name), 'parent')
+ if os.path.isfile(parent_path):
+ with open(parent_path) as fd:
+ return fd.readlines()[0].strip()
+ return None
+
+ while True:
+ parent = get_parent(themes[-1])
+ # Avoid silly loops
+ if parent is None or parent in themes:
+ break
+ themes.append(parent)
+ return themes
+
+
+def load_messages(themes, translations):
+ """ Load theme's messages into context.
+
+ All the messages from parent themes are loaded,
+ and "younger" themes have priority.
+ """
+ messages = defaultdict(dict)
+ for theme_name in themes[::-1]:
+ msg_folder = os.path.join(get_theme_path(theme_name), 'messages')
+ oldpath = sys.path
+ sys.path.insert(0, msg_folder)
+ for lang in translations.keys():
+ # If we don't do the reload, the module is cached
+ translation = __import__(lang)
+ reload(translation)
+ messages[lang].update(translation.MESSAGES)
+ del(translation)
+ sys.path = oldpath
+ return messages
+
+
+def copy_tree(src, dst, link_cutoff=None):
+ """Copy a src tree to the dst folder.
+
+ Example:
+
+ src = "themes/default/assets"
+ dst = "output/assets"
+
+ should copy "themes/defauts/assets/foo/bar" to
+ "output/assets/foo/bar"
+
+ if link_cutoff is set, then the links pointing at things
+ *inside* that folder will stay as links, and links
+ pointing *outside* that folder will be copied.
+ """
+ ignore = set(['.svn'])
+ base_len = len(src.split(os.sep))
+ for root, dirs, files in os.walk(src):
+ root_parts = root.split(os.sep)
+ if set(root_parts) & ignore:
+ continue
+ dst_dir = os.path.join(dst, *root_parts[base_len:])
+ if not os.path.isdir(dst_dir):
+ os.makedirs(dst_dir)
+ for src_name in files:
+ if src_name == '.DS_Store':
+ continue
+ dst_file = os.path.join(dst_dir, src_name)
+ src_file = os.path.join(root, src_name)
+ yield {
+ 'name': dst_file,
+ 'file_dep': [src_file],
+ 'targets': [dst_file],
+ 'actions': [(copy_file, (src_file, dst_file, link_cutoff))],
+ 'clean': True,
+ }
+
+
+def get_compile_html(input_format):
+ """Setup input format library."""
+ if input_format == "rest":
+ import rest
+ compile_html = rest.compile_html
+ elif input_format == "markdown":
+ import md
+ compile_html = md.compile_html
+ elif input_format == "html":
+ compile_html = copy_file
+ return compile_html
+
+
+class CompileHtmlGetter(object):
+ """Get the correct compile_html for a file, based on file extension.
+
+ This class exists to provide a closure for its `__call__` method.
+ """
+ def __init__(self, post_compilers):
+ """Store post_compilers for use by `__call__`.
+
+ See the structure of `post_compilers` in conf.py
+ """
+ self.post_compilers = post_compilers
+ self.inverse_post_compilers = {}
+
+ def __call__(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_post_compilers[ext]
+ except KeyError:
+ # Find the correct compiler for this files extension
+ langs = [lang for lang, exts in
+ self.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 = get_compile_html(lang)
+
+ self.inverse_post_compilers[ext] = compile_html
+
+ return compile_html
+
+
+def get_template_module(template_engine, themes):
+ """Setup templating library."""
+ templates_module = None
+ if template_engine == "mako":
+ import mako_templates
+ templates_module = mako_templates
+ elif template_engine == "jinja":
+ import jinja_templates
+ templates_module = jinja_templates
+ templates_module.lookup = \
+ templates_module.get_template_lookup(
+ [os.path.join(get_theme_path(name), "templates")
+ for name in themes])
+ return templates_module
+
+
+def generic_rss_renderer(lang, title, link, description,
+ timeline, output_path):
+ """Takes all necessary data, and renders a RSS feed in output_path."""
+ items = []
+ for post in timeline[:10]:
+ args = {
+ 'title': post.title(lang),
+ 'link': post.permalink(lang),
+ 'description': post.text(lang, teaser_only=True),
+ 'guid': post.permalink(lang),
+ 'pubDate': post.date,
+ }
+ items.append(rss.RSSItem(**args))
+ rss_obj = rss.RSS2(
+ title=title,
+ link=link,
+ description=description,
+ lastBuildDate=datetime.datetime.now(),
+ items=items,
+ generator='nikola',
+ )
+ dst_dir = os.path.dirname(output_path)
+ if not os.path.isdir(dst_dir):
+ os.makedirs(dst_dir)
+ with open(output_path, "wb+") as rss_file:
+ rss_obj.write_xml(rss_file)
+
+
+def copy_file(source, dest, cutoff=None):
+ dst_dir = os.path.dirname(dest)
+ if not os.path.isdir(dst_dir):
+ os.makedirs(dst_dir)
+ if os.path.islink(source):
+ link_target = os.path.relpath(
+ os.path.normpath(os.path.join(dst_dir, os.readlink(source))))
+ # Now we have to decide if we copy the link target or the
+ # link itself.
+ if cutoff is None or not link_target.startswith(cutoff):
+ # We copy
+ shutil.copy2(source, dest)
+ else:
+ # We link
+ if os.path.exists(dest) or os.path.islink(dest):
+ os.unlink(dest)
+ os.symlink(os.readlink(source), dest)
+ else:
+ shutil.copy2(source, dest)
+
+
+def remove_file(source):
+ if os.path.isdir(source):
+ shutil.rmtree(source)
+ elif os.path.isfile(source) or os.path.islink(source):
+ os.remove(source)
+
+# slugify is copied from
+# http://code.activestate.com/recipes/
+# 577257-slugify-make-a-string-usable-in-a-url-or-filename/
+_slugify_strip_re = re.compile(r'[^\w\s-]')
+_slugify_hyphenate_re = re.compile(r'[-\s]+')
+
+
+def slugify(value):
+ """
+ Normalizes string, converts to lowercase, removes non-alpha characters,
+ and converts spaces to hyphens.
+
+ From Django's "django/template/defaultfilters.py".
+ """
+ value = unidecode(value)
+ value = unicode(_slugify_strip_re.sub('', value).strip().lower())
+ return _slugify_hyphenate_re.sub('-', value)
+
+
+def unslugify(value):
+ """
+ Given a slug string (as a filename), return a human readable string
+ """
+ value = re.sub('^[0-9]', '', value)
+ value = re.sub('([_\-\.])', ' ', value)
+ value = value.strip().capitalize()
+ return value
+
+
+# A very slightly safer version of zip.extractall that works on
+# python < 2.6
+
+class UnsafeZipException(Exception):
+ pass
+
+
+def extract_all(zipfile):
+ pwd = os.getcwd()
+ os.chdir('themes')
+ z = zip(zipfile)
+ namelist = z.namelist()
+ for f in namelist:
+ if f.endswith('/') and '..' in f:
+ raise UnsafeZipException(
+ 'The zip file contains ".." and is not safe to expand.')
+ for f in namelist:
+ if f.endswith('/'):
+ if not os.path.isdir(f):
+ try:
+ os.makedirs(f)
+ except:
+ raise OSError("mkdir '%s' error!" % f)
+ else:
+ z.extract(f)
+ os.chdir(pwd)
+
+
+# From https://github.com/lepture/liquidluck/blob/develop/liquidluck/utils.py
+def to_datetime(value):
+ if isinstance(value, datetime.datetime):
+ return value
+ supported_formats = [
+ '%Y/%m/%d %H:%M',
+ '%Y/%m/%d %H:%M:%S',
+ '%Y/%m/%d %I:%M:%S %p',
+ '%a %b %d %H:%M:%S %Y',
+ '%Y-%m-%d %H:%M:%S',
+ '%Y-%m-%d %H:%M',
+ '%Y-%m-%dT%H:%M',
+ '%Y%m%d %H:%M:%S',
+ '%Y%m%d %H:%M',
+ '%Y-%m-%d',
+ '%Y%m%d',
+ ]
+ for format in supported_formats:
+ try:
+ return datetime.datetime.strptime(value, format)
+ except ValueError:
+ pass
+ raise ValueError('Unrecognized date/time: %r' % value)
+
+
+def apply_filters(task, filters):
+ """
+ Given a task, checks its targets.
+ If any of the targets has a filter that matches,
+ adds the filter commands to the commands of the task,
+ and the filter itself to the uptodate of the task.
+ """
+
+ def filter_matches(ext):
+ for key, value in filters.items():
+ if isinstance(key, (tuple, list)):
+ if ext in key:
+ return value
+ elif isinstance(key, (str, unicode)):
+ if ext == key:
+ return value
+ else:
+ assert False, key
+
+ for target in task['targets']:
+ ext = os.path.splitext(target)[-1].lower()
+ filter_ = filter_matches(ext)
+ if filter_:
+ for action in filter_:
+ def unlessLink(action, target):
+ if not os.path.islink(target):
+ if callable(action):
+ action(target)
+ else:
+ subprocess.check_call(action % target, shell=True)
+
+ task['actions'].append((unlessLink, (action, target)))
+ return task
diff --git a/nikola/wordpress.py b/nikola/wordpress.py
new file mode 100644
index 0000000..a04f19d
--- /dev/null
+++ b/nikola/wordpress.py
@@ -0,0 +1,134 @@
+import codecs
+import os
+import sys
+from urlparse import urlparse
+from urllib import urlopen
+
+from lxml import etree, html
+from mako.template import Template
+
+from nikola import utils
+
+links = {}
+
+def replacer(dst):
+ return links.get(dst, dst)
+
+def get_text_tag(tag, name, default):
+ t = tag.find(name)
+ if t is not None:
+ return t.text
+ else:
+ return default
+
+def import_attachment(item):
+ post_type = get_text_tag(item, '{http://wordpress.org/export/1.2/}post_type', 'post')
+ if post_type == 'attachment':
+ url = get_text_tag(item, '{http://wordpress.org/export/1.2/}attachment_url', 'foo')
+ link = get_text_tag(item, '{http://wordpress.org/export/1.2/}link', 'foo')
+ path = urlparse(url).path
+ dst_path = os.path.join(*(['new_site', 'files']+list(path.split('/'))))
+ dst_dir = os.path.dirname(dst_path)
+ if not os.path.isdir(dst_dir):
+ os.makedirs(dst_dir)
+ print "Downloading %s => %s" % (url, dst_path)
+ with open(dst_path, 'wb+') as fd:
+ fd.write(urlopen(url).read())
+ dst_url = '/'.join(dst_path.split(os.sep)[2:])
+ links[link] = '/'+dst_url
+ links[url] = '/'+dst_url
+ return
+
+
+def import_item(item):
+ """Takes an item from the feed and creates a post file."""
+ title = get_text_tag(item, 'title', 'NO TITLE')
+ # link is something like http://foo.com/2012/09/01/hello-world/
+ # So, take the path, utils.slugify it, and that's our slug
+ slug = utils.slugify(urlparse(get_text_tag(item, 'link', None)).path)
+ description = get_text_tag(item, 'description', '')
+ post_date = get_text_tag(item, '{http://wordpress.org/export/1.2/}post_date', None)
+ post_type = get_text_tag(item, '{http://wordpress.org/export/1.2/}post_type', 'post')
+ status = get_text_tag(item, '{http://wordpress.org/export/1.2/}status', 'publish')
+ content = get_text_tag(item, '{http://purl.org/rss/1.0/modules/content/}encoded', '')
+
+ tags = []
+ if status != 'publish':
+ tags.append('draft')
+ for tag in item.findall('category'):
+ text = tag.text
+ if text == 'Uncategorized':
+ continue
+ tags.append(text)
+
+ if post_type == 'attachment':
+ return
+ elif post_type == 'post':
+ out_folder = 'posts'
+ else:
+ out_folder = 'stories'
+ # Write metadata
+ with codecs.open(os.path.join('new_site', out_folder, slug+'.meta'), "w+", "utf8") as fd:
+ fd.write(u'%s\n' % title)
+ fd.write(u'%s\n' % slug)
+ fd.write(u'%s\n' % post_date)
+ fd.write(u'%s\n' % ','.join(tags))
+ fd.write(u'\n')
+ fd.write(u'%s\n' % description)
+ with open(os.path.join('new_site', out_folder, slug+'.wp'), "wb+") as fd:
+ if content.strip():
+ try:
+ doc = html.document_fromstring(content)
+ doc.rewrite_links(replacer)
+ fd.write(html.tostring(doc, encoding='utf8'))
+ except:
+ import pdb; pdb.set_trace()
+
+
+def process(fname):
+ # Parse the data
+ context = {}
+ with open(fname) as fd:
+ xml = []
+ for line in fd:
+ # These explode etree and are useless
+ if '<atom:link rel=' in line:
+ continue
+ xml.append(line)
+ xml = '\n'.join(xml)
+
+ tree = etree.fromstring(xml)
+ channel = tree.find('channel')
+
+ context['DEFAULT_LANG'] = get_text_tag(channel, 'language', 'en')
+ context['BLOG_TITLE'] = get_text_tag(channel, 'title', 'PUT TITLE HERE')
+ context['BLOG_DESCRIPTION'] = get_text_tag(channel, 'description', 'PUT DESCRIPTION HERE')
+ context['BLOG_URL'] = get_text_tag(channel, 'link', '#')
+ author = channel.find('{http://wordpress.org/export/1.2/}author')
+ context['BLOG_EMAIL'] = get_text_tag(author,
+ '{http://wordpress.org/export/1.2/}author_email', "joe@example.com")
+ context['BLOG_AUTHOR'] = get_text_tag(author,
+ '{http://wordpress.org/export/1.2/}author_display_name', "Joe Example")
+ context['POST_PAGES'] = '''(
+ ("posts/*.wp", "posts", "post.tmpl", True),
+ ("stories/*.wp", "stories", "story.tmpl", False),
+ )'''
+ context['POST_COMPILERS'] = '''{
+ "rest": ('.txt', '.rst'),
+ "markdown": ('.md', '.mdown', '.markdown', '.wp'),
+ "html": ('.html', '.htm')
+ }
+ '''
+
+ # Generate base site
+ os.system('nikola init new_site')
+ conf_template = Template(filename = os.path.join(
+ os.path.dirname(__file__), 'data', 'samplesite', 'conf.py.in'))
+ with codecs.open(os.path.join('new_site', 'conf.py'), 'w+', 'utf8') as fd:
+ fd.write(conf_template.render(**context))
+
+ # Import posts
+ for item in channel.findall('item'):
+ import_attachment(item)
+ for item in channel.findall('item'):
+ import_item(item)
diff --git a/nikola/youtube.py b/nikola/youtube.py
new file mode 100644
index 0000000..584160b
--- /dev/null
+++ b/nikola/youtube.py
@@ -0,0 +1,33 @@
+from docutils import nodes
+from docutils.parsers.rst import directives
+
+CODE = """\
+<iframe width="%(width)s"
+height="%(height)s"
+src="http://www.youtube.com/embed/%(yid)s?rel=0&amp;hd=1&amp;wmode=transparent">
+</iframe>
+"""
+
+
+def youtube(name, args, options, content, lineno,
+ contentOffset, blockText, state, stateMachine):
+ """ Restructured text extension for inserting youtube embedded videos """
+ if len(content) == 0:
+ return
+ string_vars = {
+ 'yid': content[0],
+ 'width': 425,
+ 'height': 344,
+ 'extra': ''
+ }
+ extra_args = content[1:] # Because content[0] is ID
+ extra_args = [ea.strip().split("=") for ea in extra_args] # key=value
+ extra_args = [ea for ea in extra_args if len(ea) == 2] # drop bad lines
+ extra_args = dict(extra_args)
+ if 'width' in extra_args:
+ string_vars['width'] = extra_args.pop('width')
+ if 'height' in extra_args:
+ string_vars['height'] = extra_args.pop('height')
+ return [nodes.raw('', CODE % (string_vars), format='html')]
+youtube.content = True
+directives.register_directive('youtube', youtube)