Skip to main content

Ralsina.Me — Roberto Alsina's website

Posts about python (old posts, page 73)

Nikola 1.2 is out!

Ver­sion 1.2 of Niko­la, my stat­ic site gen­er­a­tor and the soft­ware be­hind this very site, is out!

Why build stat­ic sites? Be­cause they are light in re­sources, they are fu­ture-proof, be­cause it's easy, be­cause they are safe, and be­cause you avoid lockin.

New Fea­tures:

  • Im­age gallery (just drop pics on a fold­er)

  • Built-in web­serv­er for pre­views (doit -a serve)

  • Helper com­mands to cre­ate new posts (doit -a new_­­post)

  • Google Sitemap sup­­port

  • A Hand­­book!

  • Full de­­mo site in­­­clud­ed

  • Sup­port for au­to­mat­ic de­ploy­ment (doit -a de­­ploy)

  • Clien­t-­­side redi­rec­­tions

And of course the old fea­tures:

  • Write your posts in re­Struc­­tured text

  • Clean, cus­­tom­iz­a­ble page de­sign (via boot­s­trap)

  • Com­­ments via Dis­­qus

  • Sup­­port any an­a­­lyt­ics you want

  • Build blogs with tags, feed­s, feeds for your tags, in­­dex­es, and more

  • Works like a sim­­ple CMS for things out­­­side your blog

  • Clean cus­­tom­iz­a­ble tem­­plates us­ing Mako

  • Pure python, and not a lot of it (about 600 lines)

  • Smart builds (doit on­­ly re­builds changed pages)

  • Easy to ex­­tend and im­prove

  • Code dis­­­played with syn­­tax high­­­light­ing

Right now Niko­la does lit­er­al­ly ev­ery­thing I need, so if you try it and need some­thing else... it's a good time to ask!

More in­fo at http://niko­la-­gen­er­a­tor.­google­code.­com

Nikola 1.1 is out!

A sim­ple yet pow­er­ful and flex­i­ble stat­ic web­site and blog gen­er­a­tor, based on doit, mako, do­cu­tils and boot­strap.

I built this to pow­er this very site you are read­ing, but de­cid­ed it may be use­ful to oth­er­s. The main goals of Niko­la are:

  • Small code­base: be­­cause I don't want to main­­tain a big thing for my blog

  • Fast page gen­er­a­­tion: Adding a post should not take more that 5 sec­onds to build.

  • Stat­ic out­­put: De­­ploy­­ment us­ing rsync is smooth.

  • Flex­i­ble page gen­er­a­­tion: you can de­­cide where ev­ery­thing goes in the fi­­nal site.

  • Pow­er­­ful tem­­plates: Us­es Mako

  • Clean markup for post­s: Us­es Do­cu­tils

  • Don't do stupid build­s: Us­es doit

  • Clean HTML out­­put by de­­fault: Us­es boot­s­trap

  • Com­­ments out of the box: Us­es Dis­­qus

  • Tags, with their own RSS feeds

  • Easy way to do a blog

  • Stat­ic pages out­­­side the blog

  • Mul­ti­lin­gual blog sup­­port (my own blog is en­g­lish + span­ish)

I think this ini­tial ver­sion achieves all of those goal­s, but of course, it can be im­proved. Feed­back is very wel­come!

Niko­la's home page is cur­rent­ly http://niko­la-­gen­er­a­tor.­google­code.­com

Unicode in Python is Fun!

As I hope you know, if you get a string of bytes, and want the text in it, and that text may be non-asci­i, what you need to do is de­code the string us­ing the cor­rect en­cod­ing name:

>>> 'á'.decode('utf8')

How­ev­er, there is a gotcha there. You have to be ab­so­lute­ly sure that the thing you are de­cod­ing is a string of bytes, and not a uni­code ob­jec­t. Be­cause uni­code ob­jects al­so have a de­code method but it's an in­cred­i­bly use­less one, whose on­ly pur­pose in life is caus­ing this pe­cu­liar er­ror:

>>> u'á'.decode('utf8')
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
File "/usr/lib/python2.7/encodings/", line 16, in decode
    return codecs.utf_8_decode(input, errors, True)
UnicodeEncodeError: 'ascii' codec can't encode character u'\xe1'
in position 0: ordinal not in range(128)

Why pe­cu­liar? Be­cause it's an En­code er­ror. Caused by call­ing de­code. You see, on uni­code ob­ject­s, de­code does some­thing like this:

def decode(self, encoding):
    return self.encode('ascii').decode(encoding)

The us­er wants a uni­code ob­jec­t. He has a uni­code ob­jec­t. By def­i­ni­tion, there is no such thing as a way to ut­f-8-de­code a uni­code ob­jec­t. It just makes NO SENSE. It's like ask­ing for a way to comb a fish, or climb a lake.

What it should return is self! Also, it's annoying as all hell in that the only way to avoid it is to check for type, which is totally unpythonic.

Or even bet­ter, let's just not have a de­code method on uni­code ob­ject­s, which I think is the case in python 3, and I know we will nev­er get on python 2.

So, be aware of it, and good luck!

Nikola is Near

I man­aged to do some mi­nor work to­day on Niko­la, the stat­ic web­site gen­er­a­tor used to gen­er­ate ... well, this stat­ic web­site.

  • Im­­ple­­men­t­ed tags (in­­clud­ing per-­­tag RSS feed­s)

  • Sim­­pli­­fied tem­­plates

  • Sep­a­rat­ed code and con­­fig­u­ra­­tion.

The last one was the trick­i­est. And as a teaser, here is the full con­fig­u­ra­tion file to cre­ate this site, ex­cept HTML bits for an­a­lyt­ic­s, google cus­tom search and what­ev­er that would make no sense on oth­er sites. I hope it's some­what clear.

# -*- coding: utf-8 -*-

# post_pages contains (wildcard, destination, template) 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/ and whatever/
# From those files, a set of HTML fragment files will be generated:
# whatever/thing.html (and maybe whatever/
# 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.

post_pages = (
    ("posts/*.txt", "weblog/posts", "post.tmpl"),
    ("stories/*.txt", "stories", "post.tmpl"),

# What is the default language?


# 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

    "en": "",
    "es": "tr/es",

# Data about this site
BLOG_TITLE = "Lateral Opinion"
BLOG_URL = "//"
BLOG_DESCRIPTION = "I write free software. I have an opinion on almost "\
    "everything. I write quickly. A weblog was inevitable."

# 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 HTML fragment describing the license, for the sidebar.
    <a rel="license" href="">
    <img alt="Creative Commons License" style="border-width:0; margin-bottom:12px;"

# A search form to search this site, for the sidebar. Has to be a <li>
# for the default template (base.tmpl).
    <!-- google custom search -->
    <!-- End of google custom search -->

# Google analytics or whatever else you use. Added to the bottom of <body>
# in the default template (base.tmpl).
        <!-- Start of StatCounter Code -->
        <!-- End of StatCounter Code -->
        <!-- Start of Google Analytics -->
        <!-- End of Google Analytics -->

# Put in global_context things you want available on all your templates.
# It can be anything, data, functions, modules, etc.
    'analytics': ANALYTICS,
    'blog_title': BLOG_TITLE,
    'blog_url': BLOG_URL,
    'translations': TRANSLATIONS,
    'license': LICENSE,
    'search_form': SEARCH_FORM,
    # Locale-dependent links
    'archives_link': {
        'es': '<a href="/tr/es/weblog/archive.html">Archivo</a>',
        'en': '<a href="/weblog/archive.html">Archives</a>',
    'tags_link': {
        'es': '<a href="/tr/es/categories/index.html">Tags</a>',
        'en': '<a href="/categories/index.html">Tags</a>',


Welcome To Nikola

If you see this, you may no­tice some changes in the site.

So, here is a short ex­pla­na­tion:

  • I changed the soft­­ware and the tem­­plates for this blog.

  • Yes, it's a work in progress.

  • The new soft­­ware is called Niko­la.

  • Yes, it's pret­­ty cool.

Why change?

Are you kid­ding? My pre­vi­ous blog-­gen­er­a­tor (Son of Bartle­Blog) was not in good shape. The ar­chives on­ly cov­ered 2000-2010, the "pre­vi­ous post­s" links were a lot­tery, and the span­ish ver­sion of the site was miss­ing whole sec­tion­s.

So, what's Nikola?

Niko­la is a stat­ic web­site gen­er­a­tor. One thing about this site is that it is, and has al­ways been, just HTM­L. Ev­ery "dy­nam­ic" thing you see in it, like com­ments, is a third par­ty ser­vice. This site is just a bunch of HTML files sit­ting in a fold­er.

So, how does Nikola work?

Niko­la takes a fold­er full of txt files writ­ten in re­struc­tured text, and gen­er­ates HTML frag­ments.

Those frag­ments plus some light meta­da­ta (ti­tle, tags, de­sired out­put file­name, ex­ter­nal links to sources) and Some Mako Tem­plates cre­ate HTML pages.

Those HTML pages use boot­strap to not look com­plete­ly bro­ken (hey, I nev­er claimed to be a de­sign­er).

To make sure I don't do use­less work, doit makes sure on­ly the re­quired files are recre­at­ed.

Why not use <whatever>?

Be­cause, for di­verse rea­son­s, I want­ed to keep the ex­act URLs I have been us­ing:

  • If I move a page, keep­­ing the Dis­­qus com­­ments at­­tached gets tricky

  • Some peo­­ple may have book­­marked them

Al­so, I want­ed:

  • Mako tem­­plates (be­­cause I like Mako)

  • Re­struc­­tured text (Be­­cause I have over 1000 posts writ­ten in it)

  • Python (so I could hack it)

  • Easy to hack (cur­ren­t­­ly Niko­la is un­der 600 LOC, and is al­­most fea­­ture com­­plete)

  • Sup­­port for a mul­ti­lin­gual blog like this one.

And of course:

  • It sound­ed like a fun, short pro­jec­t. I had the sus­pi­­cion that with a bit of glue, ex­ist­ing tools did 90% of the work. Looks like I was right, since I wrote it in a few days.

Are you going to maintain it?

Sure, since I am us­ing it.

Is it useful for other people?

Prob­a­bly not right now, be­cause it makes a ton of as­sump­tions for my site. I need to clean it up a bit be­fore it's re­al­ly nice.

Can other people use it?

Of course. It will be avail­able some­where soon.

Missing features?

No tags yet. Some oth­er mi­nor miss­ing things.

Contents © 2000-2023 Roberto Alsina