Older blog entries for ralsina (starting at number 511)

Senses

While walking along the river before dawn I laid down on a bench and looked up, and saw the tree, clear and green against the orange clouds in the night sky, and thought, hey, that looks cool, and tried to take a picture.

The screen in my camera stayed obstinately black. I changed settings, moved ISOs, touched on different places trying to convince it to focus and set aperture for the darkest or the lightest areas of what I knew to be there.

And it remained black. And suddenly, I had a dissenting opinion, that there was not a clear green tree there, and that the sky was not full of orange clouds, but that it was all black, starless and empty, empty of tree, of cloud.

I placed my hand above the camera, hoping to catch a glimmer of it, and still, the display was a square of darkness separating my fingers from my arm, as empty as before, mocking me featureless.

Why was it so black, if I could see clearly. If there were lampposts giving light, and I could see clearly, and there was a tree. I knew the camera worked. What was I doing, by the river, at 4AM, on a tuesday, laying on a bench, looking up, with a camera?

You expect your senses to work. You expect to perceive what is there, and not perceive what is not. You expect to see reality, to not see irreality, to listen to things, to not listen to unthings, to touch truth, to smell shit.

What would happen if you had two sets of senses, two visions, and they disagreed, and you were not sure which one to trust, which one is right, which one is true? What would happen if the camera was right and my eyes were wrong, and I was actually not seeing, but imagining, and the truth was empty, and the tree was not there, and the sky was black.

Then I enabled flash, and the ugly picture convinced me to, someday, get a better camera, and never forget to take my gastritis medicine when going for trips on isolated locations.


Syndicated 2012-04-04 20:09:00 from Lateral Opinion

Nikola 1.1 is out!

A simple yet powerful and flexible static website and blog generator, based on doit, mako, docutils and bootstrap.

I built this to power this very site you are reading, but decided it may be useful to others. The main goals of Nikola are:

  • Small codebase: because I don't want to maintain a big thing for my blog
  • Fast page generation: Adding a post should not take more that 5 seconds to build.
  • Static output: Deployment using rsync is smooth.
  • Flexible page generation: you can decide where everything goes in the final site.
  • Powerful templates: Uses Mako
  • Clean markup for posts: Uses Docutils
  • Don't do stupid builds: Uses doit
  • Clean HTML output by default: Uses bootstrap
  • Comments out of the box: Uses Disqus
  • Tags, with their own RSS feeds
  • Easy way to do a blog
  • Static pages outside the blog
  • Multilingual blog support (my own blog is english + spanish)

I think this initial version achieves all of those goals, but of course, it can be improved. Feedback is very welcome!

Nikola's home page is currently http://nikola-generator.googlecode.com


Syndicated 2012-03-30 22:59:00 from Lateral Opinion

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-ascii, what you need to do is decode the string using the correct encoding name:

>>> 'á'.decode('utf8')
u'\xe1'

However, there is a gotcha there. You have to be absolutely sure that the thing you are decoding is a string of bytes, and not a unicode object. Because unicode objects also have a decode method but it's an incredibly useless one, whose only purpose in life is causing this peculiar error:

>>> 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 peculiar? Because it's an Encode error. Caused by calling decode. You see, on unicode objects, decode does something like this:

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

The user wants a unicode object. He has a unicode object. By definition, there is no such thing as a way to utf-8-decode a unicode object. It just makes NO SENSE. It's like asking 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 better, let's just not have a decode method on unicode objects, which I think is the case in python 3, ad I know we will never get on python 2.

So, be aware of it, and good luck!


Syndicated 2012-03-30 13:58:00 from Lateral Opinion

How it's done

I added a very minor feature 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 reasons:

  1. Because a comment seemed to suggest it ;-)
  2. Because it seems like a nice thing to do. Since I so like reSt, I would like others to use it, too. And showing how easy it is to write using it, is cool.
  3. It's the "free software-y" thing to do. I am providing you the preferred way to modify my posts.
  4. It was ridiculously easy to add.

Also, if you see something missing, or something you would like to have on the site, please comment, I will try to add it.


Syndicated 2012-03-29 18:07:00 from Lateral Opinion

Nikola is Near

I managed to do some minor work today on Nikola, the static website generator used to generate ... well, this static website.

  • Implemented tags (including per-tag RSS feeds)
  • Simplified templates
  • Separated code and configuration.

The last one was the trickiest. And as a teaser, here is the full configuration file to create this site, except HTML bits for analytics, google custom search and whatever that would make no sense on other sites. I hope it's somewhat 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 = "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."

# 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")


Syndicated 2012-03-28 22:59:00 from Lateral Opinion

Welcome To Nikola

If you see this, you may notice some changes in the site.

So, here is a short explanation:

  • I changed the software and the templates for this blog.
  • Yes, it's a work in progress.
  • The new software is called Nikola.
  • Yes, it's pretty cool.
Why change?
Are you kidding? My previous blog-generator (Son of BartleBlog) was not in good shape. The archives only covered 2000-2010, the "previous posts" links were a lottery, and the spanish version of the site was missing whole sections.
So, what's Nikola?
Nikola is a static website generator. One thing about this site is that it is, and has always been, just HTML. Every "dynamic" thing you see in it, like comments, is a third party service. This site is just a bunch of HTML files sitting in a folder.
So, how does Nikola work?

Nikola takes a folder full of txt files written in restructured text, and generates HTML fragments.

Those fragments plus some light metadata (title, tags, desired output filename, external links to sources) and Some Mako Templates create HTML pages.

Those HTML pages use bootstrap to not look completely broken (hey, I never claimed to be a designer).

To make sure I don't do useless work, doit makes sure only the required files are recreated.

Why not use <whatever>?

Because, for diverse reasons, I wanted to keep the exact URLs I have been using:

  • If I move a page, keeping the Disqus comments attached gets tricky
  • Some people may have bookmarked them

Also, I wanted:

  • Mako templates (because I like Mako)
  • Restructured text (Because I have over 1000 posts written in it)
  • Python (so I could hack it)
  • Easy to hack (currently Nikola is under 600 LOC, and is almost feature complete)
  • Support for a multilingual blog like this one.

And of course:

  • It sounded like a fun, short project. I had the suspicion that with a bit of glue, existing 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 using it.
Is it useful for other people?
Probably not right now, because it makes a ton of assumptions for my site. I need to clean it up a bit before it's really nice.
Can other people use it?
Of course. It will be available somewhere soon.
Missing features?
No tags yet. Some other minor missing things.


Syndicated 2012-03-27 23:19:00 from Lateral Opinion

Blog on hold

Men at work

I want to do a few things with this blog(or rather, with my "web presence"), and the time to do them has to come from somewhere, so it's coming from the time used to write the blog.

Will return to a more frequent schedule in a week.


Syndicated 2012-03-25 21:35:59 from Lateral Opinion

1463149

Up to the previous post, without counting the long-format posts, and the posts translated to spanish, this blog contained 1463149 (prime!) characters. It took me 4448 days to do that, which means I wrote 328 characters a day.

This is what I talk about, which means I am boring:

http://lateral.netmanagers.com.ar/static/wordcloud.png

BTW: this post is exactly 328 characters long now.


Syndicated 2012-03-13 03:59:27 from Lateral Opinion

Ubuntu One APIs by Example (part 1)

One of the nice things about working at Canonical is that we produce open source software. I, specifically, work in the team that does the desktop clients for Ubuntu One which is a really cool job, and a really cool piece of software. However, one thing not enough people know, is that we offer damn nice APIs for developers. We have to, since all our client code is open source, so we need those APIs for ourselves.

So, here is a small tutorial about using some of those APIs. I did it using Python and PyQt for several reasons:

  • Both are great tools for prototyping
  • Both have good support for the required stuff (DBus, HTTP, OAuth)
  • It's what I know and enjoy. Since I did this code on a sunday, I am not going to use other things.

Having said that, there is nothing python-specific or Qt-specific in the code. Where I do a HTTP request using QtNetwork, you are free to use libsoup, or whatever.

So, on to the nuts and bolts. The main pieces of Ubuntu One, from a infrastructure perspective, are Ubuntu SSO Client, that handles user registration and login, and SyncDaemon, which handles file synchronization.

To interact with them, on Linux, they offer DBus interfaces. So, for example, this is a fragment of code showing a way to get the Ubuntu One credentials (this would normally be part of an object's __init__):

# Get the session bus
bus = dbus.SessionBus()

:
:
:

# Get the credentials proxy and interface
self.creds_proxy = bus.get_object("com.ubuntuone.Credentials",
                        "/credentials",
                        follow_name_owner_changes=True)

# Connect to signals so you get a call when something
# credential-related happens
self.creds_iface = dbus.Interface(self.creds_proxy,
    "com.ubuntuone.CredentialsManagement")
self.creds_proxy.connect_to_signal('CredentialsFound',
    self.creds_found)
self.creds_proxy.connect_to_signal('CredentialsNotFound',
    self.creds_not_found)
self.creds_proxy.connect_to_signal('CredentialsError',
    self.creds_error)

# Call for credentials
self._credentials = None
self.get_credentials()

You may have noticed that get_credentials doesn't actually return the credentials. What it does is, it tells SyncDaemon to fetch the credentials, and then, when/if they are there, one of the signals will be emitted, and one of the connected methods will be called. This is nice, because it means you don't have to worry about your app blocking while SyncDaemon is doing all this.

But what's in those methods we used? Not much, really!

def get_credentials(self):
    # Do we have them already? If not, get'em
    if not self._credentials:
        self.creds_proxy.find_credentials()
    # Return what we've got, could be None
    return self._credentials

def creds_found(self, data):
    # Received credentials, save them.
    print "creds_found", data
    self._credentials = data
    # Don't worry about get_quota yet ;-)
    if not self._quota_info:
        self.get_quota()

def creds_not_found(self, data):
    # No credentials, remove old ones.
    print "creds_not_found", data
    self._credentials = None

def creds_error(self, data):
    # No credentials, remove old ones.
    print "creds_error", data
    self._credentials = None

So, basically, self._credentials will hold a set of credentials, or None. Congratulations, we are now logged into Ubuntu One, so to speak.

So, let's do something useful! How about asking for how much free space there is in the account? For that, we can't use the local APIs, we have to connect to the servers, who are, after all, the ones who decide if you are over quota or not.

Access is controlled via OAuth. So, to access the API, we need to sign our requests. Here is how it's done. It's not particularly enlightening, and I did not write it, I just use it:

def sign_uri(self, uri, parameters=None):
    # Without credentials, return unsigned URL
    if not self._credentials:
        return uri
    if isinstance(uri, unicode):
        uri = bytes(iri2uri(uri))
    print "uri:", uri
    method = "GET"
    credentials = self._credentials
    consumer = oauth.OAuthConsumer(credentials["consumer_key"],
                                   credentials["consumer_secret"])
    token = oauth.OAuthToken(credentials["token"],
                             credentials["token_secret"])
    if not parameters:
        _, _, _, _, query, _ = urlparse(uri)
        parameters = dict(cgi.parse_qsl(query))
    request = oauth.OAuthRequest.from_consumer_and_token(
                                        http_url=uri,
                                        http_method=method,
                                        parameters=parameters,
                                        oauth_consumer=consumer,
                                        token=token)
    sig_method = oauth.OAuthSignatureMethod_HMAC_SHA1()
    request.sign_request(sig_method, consumer, token)
    print "SIGNED:", repr(request.to_url())
    return request.to_url()

And how do we ask for the quota usage? By accessing the https://one.ubuntu.com/api/quota/ entry point with the proper authorization, we would get a JSON dictionary with total and used space. So, here's a simple way to do it:

    # This is on __init__
    self.nam = QtNetwork.QNetworkAccessManager(self,
        finished=self.reply_finished)

:
:
:

def get_quota(self):
    """Launch quota info request."""
    uri = self.sign_uri(QUOTA_API)
    url = QtCore.QUrl()
    url.setEncodedUrl(uri)
    self.nam.get(QtNetwork.QNetworkRequest(url))

Again, see how get_quota doesn't return the quota? What happens is that get_quota will launch a HTTP request to the Ubuntu One servers, which will, eventually, reply with the data. You don't want your app to block while you do that. So, QNetworkAccessManager will call self.reply_finished when it gets the response:

def reply_finished(self, reply):
    if unicode(reply.url().path()) == u'/api/quota/':
        # Handle quota responses
        self._quota_info = json.loads(unicode(reply.readAll()))
        print "Got quota: ", self._quota_info
        # Again, don't worry about update_menu yet ;-)
        self.update_menu()

What else would be nice to have? How about getting a call whenever the status of syncdaemon changes? For example, when sync is up to date, or when you get disconnected? Again, those are DBus signals we are connecting in our __init__:

self.status_proxy = bus.get_object(
    'com.ubuntuone.SyncDaemon', '/status')
self.status_iface = dbus.Interface(self.status_proxy,
    dbus_interface='com.ubuntuone.SyncDaemon.Status')
self.status_iface.connect_to_signal(
    'StatusChanged', self.status_changed)

# Get the status as of right now
self._last_status = self.process_status(
    self.status_proxy.current_status())

And what's status_changed?

def status_changed(self, status):
    print "New status:", status
    self._last_status = self.process_status(status)
    self.update_menu()

The process_status function is boring code to convert the info from syncdaemon's status into a human-readable thing like "Sync is up-to-date". So we store that in self._last_status and update the menu.

What menu? Well, a QSystemTrayIcon's context menu! What you have read are the main pieces you need to create something useful: a Ubuntu One tray app you can use in KDE, XFCE or openbox. Or, if you are on unity and install sni-qt, a Ubuntu One app indicator!

http://ubuntuone.com/7iXTbysoMM9PIUS9Ai4TNn

My Ubuntu One indicator in action.

You can find the source code for the whole example app at my u1-toys project in launchpad and here is the full source code (missing some icon resources, just get the repo)

Coming soon(ish), more example apps, and cool things to do with our APIs!


Syndicated 2012-03-13 02:17:14 from Lateral Opinion

Dear Dr. Sheldon Cooper...

http://imgs.xkcd.com/comics/duty_calls.png

This post is a joke.

Dr. Cooper, I hope this letter fids you in good health. I couldn't avoid overhearing you talk with Dr. Hofstadter on our building's staircase the other day.

I am speaking about when you mused about how life would be if people evolved from reptiles. I am so disappointed in you.

First, you mention that lizards are cold-blooded. Which is true. And that when it's cold they get slower. Which is also true. But then you sayd something like "the weatherlizard would say 'it's slow outside' instead of 'it's cold'".

POPPYCOCK Dr. Cooper! If the lizard is slow because it's cold, it would perceive everything out there as fast, not slow, just like slow cars see faster cars as, you know... fast?

Also, the mention about suggesting the lizard should wear a sweater is a slap on the face of physics. Sweaters are an insulator, not an energy source. What makes the inside of the sweater warm is the human, Dr. Cold-blooded lizards would have no such effect beyond the tiny thermal inertia such an imperfect wool insulator would allow.

If you are interested on further argument in human-like reptile civilization and folklore I will be happy to indulge, but I must say I expected better of you.

Mrs. Vartabedian.

PS: things like this are the reason why I never ask you to come home for cereal.


Syndicated 2012-03-12 03:14:43 from Lateral Opinion

502 older entries...

New Advogato Features

New HTML Parser: The long-awaited libxml2 based HTML parser code is live. It needs further work but already handles most markup better than the original parser.

Keep up with the latest Advogato features by reading the Advogato status blog.

If you're a C programmer with some spare time, take a look at the mod_virgule project page and help us with one of the tasks on the ToDo list!