Skip to main content

Ralsina.Me — Roberto Alsina's website

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')
u'\xe1'

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/utf_8.py", 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!

How it's done

I added a very mi­nor fea­ture to the site. Up here ^ you should be able to see a link that says "reSt". If you click on it, it will show you the "source code" for the page.

I did this for a few rea­son­s:

  1. Be­­cause a com­­ment seemed to sug­­gest it ;-)

  2. Be­­cause it seems like a nice thing to do. Since I so like reSt, I would like oth­­ers to use it, too. And show­ing how easy it is to write us­ing it, is cool.

  3. It's the "free soft­­ware-y" thing to do. I am pro­vid­ing you the pre­­ferred way to mod­­i­­fy my post­s.

  4. It was ridicu­lous­­ly easy to ad­d.

Al­so, if you see some­thing miss­ing, or some­thing you would like to have on the site, please com­men­t, I will try to add it.

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/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.
#

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

# 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",
    }

# Data about this site
BLOG_TITLE = "Lateral Opinion"
BLOG_URL = "//ralsina.me"
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."

# 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.
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 search form to search this site, for the sidebar. Has to be a <li>
# for the default template (base.tmpl).
SEARCH_FORM = """
    <!-- 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).
ANALYTICS = """
        <!-- 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.
GLOBAL_CONTEXT = {
    '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>',
        },
    }

execfile("nikola/nikola.py")

Contents © 2000-2023 Roberto Alsina