Pelican
Pelican is a static site generator powered by Python and unlike most other static site generators, it uses reStructuredText instead of Markdown for authoring content. m.css provides a theme for it, together with a set of useful plugins. The theme is designed to fit both the use case of a simple blog consisting of just articles or a full product/project/portfolio website where the blog is only a side dish.
First steps with Pelican
Install Pelican either via pip
or using your system package manager.
# You may need sudo here
pip3 install pelican
Note that using the m.css theme or Pelican plugins may require additional dependencies than just Pelican — documentation of each lists the additional requirements, if any. Once you have Pelican installed, create a directory for your website and bootstrap it:
mkdir ~/my-cool-site/
cd ~/my-cool-site/
pelican-quickstart
This command will ask you a few questions. Leave most of the questions at their defaults, you can change them later. Once the quickstart script finishes, you can generate the website and spin up a auto-reload webserver for it with the following command:
pelican -Dlr
It will print quite some output about processing things and serving the data to
the console. Open your fresh website at http://localhost:8000. The site is now
empty, so let’s create a simple article and put it into content/
subdirectory with a .rst
extension. For this example that would be
~/my-cool-site/content/my-cool-article.rst
:
My cool article ############### :date: 2017-09-14 23:04 :category: Cool things :tags: cool, article, mine :summary: This article has a cool summary. This article has not only cool summary, but also has cool contents. Lorem? *Ipsum.* `Hi, google! <https://google.com>`_
If you did everything right, the auto-reload script should pick the file up and process it (check the console output). Then it’s just a matter of refreshing your browser to see it on the page.
That’s it! Congratulations, you successfully made your first steps with Pelican. Well, apart from the fact that the default theme is a bit underwhelming — so let’s fix that.
Basic theme setup
The easiest way to start is putting the whole Git repository of m.css into your project, for example as a submodule:
git submodule add https://github.com/mosra/m.css
The most minimal configuration to use the theme is the following. Basically you
need to tell Pelican where the theme resides (it’s in the pelican-theme/
subdir of your m.css submodule), then you tell it to put the static contents of
the theme into a static/
directory in the root of your webserver; the
M_CSS_FILES
variable is a list of CSS files that the theme needs. You can
put there any files you need, but there need to be at least the files mentioned
on the CSS themes page. The M_THEME_COLOR
specifies color used for the <meta name="theme-color" />
tag
corresponding to given theme; if not set, it’s simply not present. Lastly, the
theme uses some Jinja2 filters from the m.htmlsanity
plugin, so that plugin needs to be loaded as well.
THEME = 'm.css/pelican-theme' THEME_STATIC_DIR = 'static' DIRECT_TEMPLATES = ['index'] M_CSS_FILES = ['https://fonts.googleapis.com/css?family=Source+Sans+Pro:400,400i,600,600i%7CSource+Code+Pro:400,400i,600', '/static/m-dark.css'] M_THEME_COLOR = '#22272e' PLUGIN_PATHS = ['m.css/plugins'] PLUGINS = ['m.htmlsanity']
Here you can take advantage of the pelicanconf.py
and publishconf.py
distinction — use m-dark.css
for local development and override the
M_CSS_FILES
to use the smaller, faster and more compatible m-dark.compiled.css
for publishing.
If you would want to use the light theme instead, the configuration is this
(again with m-light.css
possibly replaced with m-light.compiled.css
):
M_CSS_FILES = ['https://fonts.googleapis.com/css?family=Libre+Baskerville:400,400i,700,700i%7CSource+Code+Pro:400,400i,600', '/static/m-light.css'] M_THEME_COLOR = '#cb4b16'
If you see something unexpected or not see something expected, check the Troubleshooting section below.
Configuration
Value of SITENAME
is used in the <title>
tag, separated with a
|
character from page title. If page title is the same as SITENAME
(for example on the index page), only the page title is shown. The static part
of the website with pages is treated differently from the “blog” part with
articles and there are two additional configuration options M_BLOG_URL
and
M_BLOG_NAME
that control how various parts of the theme link to the blog
and how blog pages are named in the <title>
element. The M_BLOG_URL
can be either absolute or relative to SITEURL
. If M_BLOG_NAME
/
M_BLOG_URL
are not set, the theme assumes they are the same as
SITENAME
/ SITEURL
.
SITENAME = 'Your Brand' SITEURL = '' M_BLOG_NAME = 'Your Brand Blog' M_BLOG_URL = 'blog/'
The M_FAVICON
setting, if present, is used to specify contents of the
<link rel="icon">
tag. It’s a tuple of (url, type)
where
url
is favicon URL and type
is its corresponding MIME type. If
M_BLOG_FAVICON
is specified, it’s overriding M_FAVICON
on blog-like
pages (articles, article listing… basically everything except pages). If
M_BLOG_FAVICON
is not specified, M_FAVICON
is used everywhere; if
neither is specified no <link>
tag is rendered. Example configuration:
M_FAVICON = ('favicon.ico', 'image/x-ico') M_BLOG_FAVICON = ('favicon-blog.png', 'image/png')
Arbitrary HTML content can be added at the end of the <head>
via the
M_HTML_HEADER
option.
Pages
Page content is simply put into <main>
, wrapped in an <article>
,
in the center 10 columns on large screens and spanning the full 12 columns
elsewhere; the container is marked as inflatable.
Page title is rendered in an <h1>
and there’s nothing else apart from
the page content.
Pages can override which menu item in the top navbar will be highlighted
by specifying the corresponding menu item slug in the :highlight:
field.
If the field is not present, page’s own slug is used instead.
Extending HTML <head>
The :css:
field can be used to link additional CSS files in page header.
Put one URL per line, internal link targets are expanded. Similarly :js:
can be used to link JavaScript files. Besides that, the :html_header:
field can be used to put arbitrary HTML at the end of the <head>
element. Indenting the lines is possible by putting an escaped space in front
(the backslash and the escaped space itself won’t get inserted). Example:
Showcase ######## :css: {static}/static/webgl.css {static}/static/canvas-controls.css :js: {static}/static/showcase.js :html_header: <script> \ function handleDrop(event) { \ event.preventDefault(); \ ... \ } </script>
Landing pages
It’s possible to override the default 10-column behavior for pages to make a
landing page with large
cover image spanning the whole window width. Put cover image URL into a
:cover:
field, the :landing:
field then contains
reST-processed content that appears on top of the
cover image. Contents of the :landing:
are put into a
<div class="m-container">
, you are expected to fully take care of rows
and columns in it. The :hide_navbar_brand:
field controls visibility of
the navbar brand link. Set it to True
to hide it, default (if not
present) is False
.
Example of a fully custom index page that overrides the default theme index page (which would just list all the articles) is below. Note the overridden save destination and URL.
Your Brand ########## :save_as: index.html :url: :cover: {static}/static/cover.jpg :hide_navbar_brand: True :landing: .. container:: m-row .. container:: m-col-m-6 m-push-m-5 .. raw:: html <h1>Your Brand</h1> *This is the brand you need.*
Pages with cover image
Besides full-blown landing pages that give you control over the whole layout,
you can add cover images to regular pages by just specifying the :cover:
field but omitting the :landing:
field. See corresponding section
in the CSS page layout docs
for details about how the cover image affects page layout.
News on index page
If you override the index page to a custom landing page, by default you lose
the list of latest articles. That might cause the website to appear stale when
you update just the blog. In order to fix that, it’s possible to show a block
with latest articles on the index page using the M_NEWS_ON_INDEX
setting.
It’s a tuple of (title, count)
where title
is the block header
title that acts as a link to M_BLOG_URL
and count
is the max number
of articles shown. Example configuration:
M_NEWS_ON_INDEX = ("Latest news on our blog", 3)
Articles
Compared to pages, articles have additional metadata like :date:
,
:author:
, :category:
and tags
that order them and divide
them into various sections. Besides that, there’s article :summary:
,
that, unlike with pages, is displayed in the article header; other metadata are
displayed in article footer. The article can also optionally have a
:modified:
date, which is shown as date of last update in the footer.
All article listing pages (archives, categories, tags, authors) are displaying just the article summary and the full article content is available only on the dedicated article page. An exception to this is the main index or archive page, where the first article is fully expanded so the users are greeted with some actual content instead of just a boring list of article summaries.
Article pages show a list of sections and tags in a right sidebar. By default,
list of authors is not displayed as there is usually just one author. If you
want to display the authors as well, enable it using the M_SHOW_AUTHOR_LIST
option in the configuration:
M_SHOW_AUTHOR_LIST = True
Jumbo articles
Jumbo articles are made
by including the :cover:
field containing URL of the cover image.
Besides that, if the title contains an em-dash (—), it gets split into a
title and subtitle that’s then rendered in a different font size. Example:
An article --- a jumbo one ########################## :cover: {static}/static/ship.jpg :summary: Article summary paragraph. Article contents.
Sidebar with tag, category and author list shown in the classic article layout
on the right is moved to the bottom for jumbo articles. In case you need to
invert text color on cover, add a :class:
field containing the
m-inverted
CSS class.
Archived articles
It’s possible to mark articles and archived by setting the :archived:
field to True
. In addition to that, you can display an arbitrary
formatted block on the article page on top of article contents right below the
summary. The content of the block is controlled by the
M_ARCHIVED_ARTICLE_BADGE
setting, containinig
reST-formatted markup. The {year}
placeholder,
if present, is replaced with the article year. If the setting is not present,
no block is rendered at all. Example setting:
M_ARCHIVED_ARTICLE_BADGE = """ .. container:: m-note m-warning This article is from {year}. **It's old.** Deal with it. """
Controlling article appearance
By default, the theme assumes that you provide an explicit :summary:
field for each article. The summary is then displayed on article listing page
and also prepended to fully expanded article. If your :summary:
is
automatically generated by Pelican or for any other reason repeats article
content, it might not be desirable to show it in combination with article
content. This can be configured via the following setting:
M_HIDE_ARTICLE_SUMMARY = True
There’s also a possibility to control this on a per-article basis by setting
:hide_summary:
to either True
or False
. If both global and
per-article setting is present, article-specific setting has a precedence.
Example:
An article without explicit summary ################################### :cover: {static}/static/ship.jpg :hide_summary: True Implicit article summary paragraph. Article contents.
As noted above, the first article is by default fully expanded on index and archive page. However, sometimes the article is maybe too long to be expanded or you might want to not expand any article at all. This can be controlled either globally using the following setting:
M_COLLAPSE_FIRST_ARTICLE = True
Or, again, on a per-article basis, by setting :collapse_first:
to either
True
or False
. If both global and per-article setting is present,
article-specific setting has a precedence.
Pre-defined pages
With the default configuration above the index page is just a list of articles
with the first being expanded; the archives page is basically the same. If you
want to have a custom index page (for example a landing page),
remove 'index'
from the DIRECT_TEMPLATES
setting and keep just
'archives'
for the blog front page. Also you may want to enable
pagination for the archives, as that’s not enabled by default:
# Defaults to ['index', 'categories', 'authors', 'archives'] DIRECT_TEMPLATES = ['archives'] # Defaults to {'index': None, 'tag': None, 'category': None, 'author': None} PAGINATED_TEMPLATES = {'archives': None, 'tag': None, 'category': None, 'author': None}
Every category, tag and author has its own page that lists corresponding articles in a way similar to the index or archives page, but without the first article expanded. On the top of the page there is a note stating what condition the articles are filtered with.
Index, archive and all category/tag/author pages are paginated based on the
DEFAULT_PAGINATION
setting — on the bottom of each page there are link
to prev and next page, besides that there’s <link rel="prev">
and
<link rel="next">
that provides the same as a hint to search engines.
Pass-through pages
Besides pages, articles and pre-defined pages explained above, where
the content is always wrapped with the navbar on top and the footer bottom,
it’s possible to have pages with fully custom markup — for example various
presentation slides, demos etc. To do that, set the :template:
metadata
to passthrough
. While it works with reST
sources, this is best combined with raw HTML input. Pelican will copy the
contents of the <body>
tag verbatim and use contents of the
<title>
element for a page title, put again in the <title>
(not as a <h1>
inside <body>
). Besides that, you can specify
additional metadata using the <meta name="key" content="value" />
tags:
<meta name="template" content="passthrough" />
needs to be always present in order to make Pelican use the passthrough template.<meta name="css" />
,<meta name="js" />
and<meta name="html_header" />
specify additional CSS files, JavaScript files and arbitrary HTML, similarly as with normal pages. Thecontent
can be multiple lines, empty lines are discarded for CSS and JS references. Be sure to properly escape everything.<meta name="class" />
can be used to add a CSS class to the top-level<html>
element- All usual Pelican metadata like
url
,slug
etc. work here as well.
Note that at the moment, the pass-through pages do not insert any of the (social) meta tags. Example of an input file for a pass-through page:
<!DOCTYPE html> <html lang="en"> <head> <title>WebGL Demo Page</title> <meta name="template" content="passthrough" /> <meta name="css" content=" m-dark.css https://fonts.googleapis.com/css?family=Source+Code+Pro:400,400i,600%7CSource+Sans+Pro:400,400i,600,600i " /> <meta name="js" content="webgl-demo.js" /> </head> <body> <!-- the actual page body --> </body> </html>
Theme properties
The theme markup is designed to have readable, nicely indented output. The code is valid HTML5 and should be parsable as XML.
Troubleshooting
Output is missing styling
If you are on Windows and don’t have Git symlinks enabled, empty CSS files
might get copied. The solution is either to reinstall Git with symlinks enabled
or manually copy all *.css
files from css/
to
pelican-theme/static/
, replacing the broken symlinks present there.
(Social) meta tags
The
M_BLOG_DESCRIPTION
setting, if available, is used to populate<meta name="description">
on the index / archive page, which can be then shown in search engine results. For sharing pages on Twitter, Facebook and elsewhere, it’s possible to configure site-wide Open Graph and Twitter Card<meta>
tags:og:site_name
is set toM_SOCIAL_SITE_NAME
, if availabletwitter:site
/twitter:site:id
is set toM_SOCIAL_TWITTER_SITE
/M_SOCIAL_TWITTER_SITE_ID`
, if availableog:title
/twitter:title
is set toM_BLOG_NAME
on index and archive pages and to category/author/tag name on particular filtering pages. This is overridden by particular pages and articles.og:url
is set toM_BLOG_URL
on index and archive pages and to category/author/tag URL on particular filtering pages. Pagination is not included in the URL. This is overridden by particular pages and articles.og:image
/twitter:image
is set to theM_SOCIAL_IMAGE
setting, if available. The image is expected to be smaller and square; Pelican internal linking capabilities are not supported in this setting. This can be overridden by particular pages and articles.twitter:card
is set tosummary
. This is further affected by metadata of particular pages and articles.og:description
/twitter:description
is set toM_SOCIAL_BLOG_SUMMARY
on index and archive pages.og:type
is set towebsite
. This is overridden by particular pages and articles.See (Social) meta tags for pages and (Social) meta tags for articles sections below for page- and article-specific
<meta>
tags.Example configuration to give sane defaults to all social meta tags:
It’s possible to disable rendering of all social meta tags (for example for testing purposes) by setting
M_DISABLE_SOCIAL_META_TAGS
toTrue
.