Felix Felicis (aka liquidluck) is a static website generator. The name of “Felix Felicis” comes from Harry Porter; It is a magic potion that gives the drinker luck.
If you want to contribute to this project, fork liquidluck and send a pull request for your changes. Have a glance at Development section for details. If you just want to use it, you should watch the project for updates.
If you are familiar with Python, it is strongly suggested that you install everything in virtualenv.
If you are a pythoner, and you have no idea about virtualenv, please do search the internet.
If you are on Linux or Mac OS X, you are the lucky one:
$ sudo pip -U install liquidluck
If no pip available, try easy_install:
$ sudo easy_install -U liquidluck
Sorry, I have no knowledge about Windows, but it really works on Windows. Cygwin and MinGW would make a better life with UNIX software on Windows.
If you prefer git, that is great. You can get the very latest code at GitHub:
$ git clone http://github.com/lepture/liquidluck.git
We use misaka (python wrapper for sundown) as the Markdown engine. It requires C compiler, which means you should install Xcode.
Then open Xcode’s preference (command + ,), select Downloads tab, and install Command Line Tools.
I strongly suggest that you install the Command Line Tools, even if you don’t use Felix Felicis. You will need it somewhere else.
It is strongly suggested that you use Cygwin as the environment.
You should install these packages:
Then head over to setuptools and install it, you will get easy_install.
This section assumes that you already have Felix Felicis (liquidluck) installed. If you do not, header over to the Installation section.
Now, you have Felix Felicis installed. Let’s create a website:
$ cd ~
$ mkdir website
$ cd website
$ liquidluck init
You will be asked some questions. If the question has a default value, we don’t change it, just hit Enter.
See what happens:
$ ls
content settings.yml
Create a post:
$ touch content/hello-world.md
Write with you favorite editor, for example vim:
$ vim content/hello-world.md
# Hello World
- date: 2012-12-12
- category: life
- tags: python, code
----------------
Hello World. This is a DEMO post.
Now that you have written a post, let’s create the website:
$ cd ~/website
$ liquidluck build -v
$ ls
content deploy settings.py
The website is created, you can test your website:
$ liquidluck server
Open your browser: http://127.0.0.1:8000
Felix Felicis provided a more powerful Preview Server, you should check it.
You can test more posts yourself.
Get all commands:
$ liquidluck -h
List all themes:
$ liquidluck search
Install a theme:
$ liquidlcuk install {{name}}
Felix Felicis (liquidluck) supports markup of Markdown and reStructuredText. It is suggested that you write in Markdown, it’s easier.
There are three parts in each post:
An example in markdown:
# Felix Felicis <-------- this is title
- date: 2012-12-12 12:12 <-------- this is meta
- tags: python, blog, web
Here is the description.
-------------------------- <-------- this seprate meta and content
Hello World! Welcome to the <-------- this is content
Felix Felicis World.
``` <-------- this is normal code
a {
color: black;
}
```
````css <-------- if code wrapped with 4 `, the code
a { will be injected to this page
color: black;
}
````
An example in reStructuredText:
Felix Felicis
================
:date: 2012-12-12 12:12
:tags: python, blog, web
Hello World! Welcome to the Felix Felicis World.
::
/* normal code */
a {
color: black;
}
.. sourcecode:: css
/* hightlight code */
a {
color: black;
}
Metadata that Felix Felicis supports natively:
which means you can access them in template with a shortcut, for example: {{post.tags}}.
Metadata that Felix Felicis created itself:
Page is the same as post, except that post contains date, page doesn’t, post follows permalink, page doesn’t.
A example of page in Markdown:
# Hello Page
- tags: python, web <----------- page has no date
----------------
Hello Page
```python
def hello():
print("Hello Page")
```
Page doesn’t have a date, but it may contain some metadata.
Where will the page be rendered? For example, the path of the page:
content/ <-------- source directory
page1.md
a_folder/
page2.md
and it will be rendered to:
deploy/ <-------- output directory
page1.html
a_folder/
page2.html
It will ignore the site.prefix, and therefore, if your settings:
site = {
'name': '...',
...
'prefix': 'blog',
}
and you want to you pages to be rendered to blog folder, you have to:
content/
blog/ <--------- place your pages under the prefix folder
page1.md
Any file without a valid markup suffix (e.g. .md, .rst, .mkd ...) is a File. It will be copied to the same path:
content/
robots.txt <--------- this is a file
media/
a_pic.jpg <--------- this is a file
And the output will be:
deploy/
robots.txt
media/
a_pic.jpg
Hence, I suggest that you have a folder named media, and you can leave your picture resources there:

Felix Felicis support yaml, python and json format config file. You can create the config file with:
$ liquidluck init
The default python format config file:
# -*- coding: utf-8 -*-
#: settings for liquidluck
#: site information
#: all variables can be accessed in template with ``site`` namespace.
#: for instance: {{site.name}}
site = {
"name": "Felix Felicis", # your site name
"url": "http://lab.lepture.com/liquidluck/", # your site url
# "prefix": "blog",
}
#: this config defined information of your site
#: 1. where the resources 2. how should the site be generated
config = {
"source": "content",
"output": "deploy",
"static": "deploy/static",
"static_prefix": "/static/",
"permalink": "{{date.year}}/{{filename}}.html",
"relative_url": False,
"perpage": 30,
"feedcount": 20,
"timezone": "+08:00",
}
author = {
"default": "nickname",
"vars": {}
}
#: active readers
reader = {
"active": [
"liquidluck.readers.markdown.MarkdownReader",
# uncomment to activate rST reader
# "liquidluck.readers.restructuredtext.RestructuredTextReader",
],
"vars": {}
}
#: active writers
writer = {
"active": [
"liquidluck.writers.core.PostWriter",
"liquidluck.writers.core.PageWriter",
"liquidluck.writers.core.ArchiveWriter",
"liquidluck.writers.core.ArchiveFeedWriter",
"liquidluck.writers.core.FileWriter",
"liquidluck.writers.core.StaticWriter",
"liquidluck.writers.core.YearWriter",
"liquidluck.writers.core.CategoryWriter",
# "liquidluck.writers.core.CategoryFeedWriter",
# "liquidluck.writers.core.TagWriter",
# "liquidluck.writers.core.TagCloudWriter",
],
"vars": {
# uncomment if you want to reset archive page
# "archive_output": "archive.html",
}
}
#: theme variables
theme = {
"name": "default",
# theme variables are defined by theme creator
# you can access theme in template with ``theme`` namespace
# for instance: {{theme.disqus}}
"vars": {
#"disqus": "your_short_name",
#"analytics": "UA-21475122-1",
}
}
#: template variables
template = {
"vars": {},
"filters": {},
}
Default permalink style is:
{{date.year}}/{{filename}}.html
# output example
tech/intro-of-liquidluck.html
There are other permalink styles you may like:
You can define other keywords in your post, and take them as a part of the permalink:
# Hello World
- date: 2012-12-12
- topic: life
----------
content here
And then you can set your permalink as: {{topic}}/{{filename}}.html. Learn more about Meta.
If you don’t like .html as a part of the permalink, you can set your permalink as:
{{category}}/{{filename}}
# or with a slash
{{category}}/{{filename}}/
# slash without server helper
{{category}}/{{filename}}/index.html
In this case, you need to make some config of your server, so that everything will be ok. A good example of nginx conf for slash style permalink: nginx.conf.
Issues about permalink:
If your site has multiple authors, you can add them to your settings:
author = {
'default': 'lepture',
'vars': {
'lepture': {
'name': 'Hsiaoming Yang',
'website': 'http://lepture.com',
'email': 'lepture@me.com',
},
'kitty': {
'name': 'Hello Kitty',
'website': 'http://hellokitty.com',
}
}
}
And when you write a post, the default author is ‘lepture’, but you can change it by:
# Hello World
- date: 2012-12-12
- author: kitty
--------
content here
Access the author information in template as {{post.author.name}} and {{post.author.website}}.
For more information on template or theme design, head over to Theme section.
The default theme doesn’t show any information of the author, it is designed for personal blogging.
There are two readers in Felix Felicis, one is Markdown, and the other is reStructuredText.
Issues that contain information on readers:
Issues that contain information on readers variables:
There are many writers in Felix Felicis, and you can add more. If you want to add your own writer to Felix Felics, head over to Development.
Every writer can define its own variable, for example the archive write, if you set:
writer = {
'vars': {
'archive_output': 'archive.html',
}
}
The archive page will be write to archive.html instead of index.html.
Available writers variables (but you won’t need to change them):
The design pattern of Felix Felicis contains one significant principle:
Don’t create anything.
That means we didn’t (and will not) add any invalid markup like Jekyll and others do. Everything should be valid in its markup language.
And hence there will be no plugin or extension like:
{% gist id %}
Just like the zen of Python:
Beautiful is better than ugly.
Nothing is better than everything.
Template engine of Felix Felicis (liquidluck) is Jinja. It would be great if you have a little knowledge on Jinja Template. The basic syntax is simple, you should know them.
You can learn how to design your own theme by demo:
Please create your repo at github with liquidluck-theme- prefix. Remember to submit your theme at Theme Gallery.
Get all themes:
$ liquidluck search
Install a theme:
$ liquidluck install moment
Install a theme to the global theme gallery:
$ liquidluck install moment -g
A glance of a theme:
your_theme/
settings.py <---- theme variables
filters.py <---- filters defined by theme
static/ <---- static files
style.css
...
templates/ <---- template files
archive.html
post.html
page.html
You don’t need to copy a feed.xml file. Only archive.html, post.html and page.html are required. But you can add more.
Sometimes, you don’t need to create a total new theme, you just want to make some changes.
For example, you are using the default theme, which means in your settings:
theme = {
'name': 'default'
}
You want to make some changes on the post page (like adding readability), in your blog directory, create a post.html template:
your_blog/
settings.py
content/
_templates/
post.html
And edit this post.html:
{% extends "layout.html" %}
{% block title %}{{post.title}} - {{site.name}}{% endblock %}
{% block main %}
<div class="hentry">
<h1 class="entry-title">{{post.title}}</h1>
<time class="updated" datetime="{{post.date|xmldatetime}}">{{post.date.strftime('%Y-%m-%d')}}</time>
{% if template.readability %}
<div class="rdbWrapper" data-show-read="1" data-show-send-to-kindle="1"></div>
<script type="text/javascript" src="http://www.readability.com/embed.js" async></script>
{% endif %}
<div class="entry-content">
{{post.content}}
</div>
<div class="entry-tags">
{% for tag in post.tags %}
<a href="{{ content_url(site.prefix, 'tag', tag, 'index.html') }}">{{tag}}</a>
{% endfor %}
</div>
{% if theme.disqus %}
<div id="disqus_thread"></div>
<script type="text/javascript">
var disqus_shortname = '{{theme.disqus}}';
var disqus_title = '{{post.title}}';
(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>
{% endif %}
</div>
{% endblock %}
And edit your settings, enable readability:
template = {
'vars': {
'readability': True,
}
}
There are two levels of variables, global and templatable. Global means that this variable can be accessed in every template, and templatable means that this variable can be accessed in specify template.
system, this is all about Felix Felicis:
{
'name': 'Felix Felicis',
'version': '....',
'homepage': '....',
'time': '....',
}
When you create your own theme, you should add copyright of Felix Felicis by:
Powered by <a href="{{system.homepage}}">{{system.name}}</a> {{system.version}}
{{system.time}} means current utc time.
site, you defined in your settings file:
site = {
'name': "Kitty's BLog",
'url': 'http://www.example.com',
}
theme, theme variable is defined by theme creator in the theme settings, and users can overwrite theme in blog settings theme_variables.
For example, in the default theme’s settings, we have:
navigation = [
{'title': 'Home', 'link': '/'},
{'title': 'About', 'link': '/about.html'},
]
Users can rewrite it in blog settings:
theme = {
'vars': {
'navigation': [
{'title': 'Home', 'link': '/'},
{'title': 'Life', 'link': '/life/'},
{'title': 'Work', 'link': '/work/'},
]
}
}
template, template variable is defined by users in settings with:
template = {
'vars': {
'readability': True,
}
}
And it can be access in template by {{template.readability}}, this is very useful.
writer, this variable tells you which writer is rendering this page now:
{
'class': 'ArchiveWriter',
'name': 'archive',
'filepath': 'path/to/file.html',
}
Templatable variables are only accessed in specify templates.
This variable is powerful, for example, {{resource.posts}} contains all your public posts. It is related to a writer.
Filter is an important concept in Jinja Template.
If you have designed a theme, you can submit it to the Theme Gallery
Want to contribute to this project? Have no idea about how to read the code? And this section will tell you how to understand it.
You should start here, it would be much easier to understand the whole things. Take a look at cli.py.
Gifts that makes Felix Felicis easy to use.
Preview your blog with:
$ liquidluck server
$ liquidluck server -p 8888
$ liquidluck server -p 8888 -s settings.py
Preview server now support livereload, when you are editing a post, it will auto compile and auto refresh the browser for you. Added in version 1.12
To enable livereload, your should install tornado:
$ pip install tornado
If you are using oh-my-zsh, there is a plugin for you.
https://github.com/lepture/liquidluck/tree/master/oh-my-zsh-plugins
Copy liquidluck to ~/.oh-my-zsh/plugins:
$ cp -r oh-my-zsh-plugins/liquidluck ~/.oh-my-zsh/plugins/
Then edit your zshrc file:
plugins=(git ... liquidluck)
Now you can tab complete every Felix Felicis command:
$ liquidluck b(tab)
$ liquidluck build (tab)
Felix Felicis supports webhook since v1.6. When you push to GitHub or BitBucket, your blog can generate itself.
First, you need to install Felix Felicis on your server:
$ pip install liquidluck
Second, your blog repo on your server:
$ git clone git://path/to/your/repo blog
Then, start webhook daemon in your blog:
$ cd blog
$ liquidluck webhook start -p 9876
Configure webhook on GitHub or BitBucket. We take GitHub as an example.
Head over to your blog source repo admin, select Service Hooks
If you prefer BitBucket, you should select the POST service:
If your server ip is 88.88.88.88, you can add a URL:
http://88.88.88.88:9876/webhook
And when you push to GitHub, your server will update the repo and generate the whole site.
All history since the new Felix Felicis are listed here:
Released on Nov 9th, 2012
Released on Nov 2nd, 2012
Released on Oct 31th, 2012
Released on Sep 20th, 2012
Released on Sep 7th, 2012
Released on Oct 23th, 2012
Released on Jul 9th, 2012
Released on Jun 20th, 2012
Released on Jul 17th, 2012
Released on Jul 4th, 2012
Released on Jun 29th, 2012
Released on Jun 29th, 2012
Released on Jun 28th, 2012.
Released on Jun 21th 2012.
Released on Jun 16th 2012. The new Felix Felicis.
We keep an issue tracker at GitHub, where you can report a bug by opening a new issue. If you want any help, please contact lepture@me.com; English and Chinese are accepted.