pfritz

January 4, 2010

Color your diffs

Filed under: Uncategorized — pfritz @ 2:00

I’m using the command “svn diff” very often to control my already done changes for the subversion repo I’m working on. To be exactly I’m using “svn diff | colordiff” because it simplifies reading those diffs enormous. Since colordiff is a simple wrapper around the good old diff, I was wondering if it is possible to change the diff command used by subversion to something else. And yes it is! After some googling I found out the solution to define an alias to use colordiff, but an alias is not what I want. There must be a neater solution. Looking into the config file ‘.subversion/config’ I uncomment and changed the line starting with diff-cmd to ‘diff-cmd = colordiff’ and now the output of ‘svn diff’ is colored. Isn’t that nice? What I haven’t found out yet, however, is how you can change it back to normal diff if you want to prepare a normal patch without colors. I’ll report it to you if I find it out.

Advertisements

April 26, 2009

Setting up autofoo for gettext

Filed under: Uncategorized — pfritz @ 23:44
Tags:

Using gettext in your application or library is very easy and straight forward. The hard part is to setup autoconfig and automake to use gettext. Not because itis very difficult, but because there is not a good tutorial, at least I haven’t found one. The only resource is the gettext manual which is over 100 pages large and covers too many legacy cases to be simple. And the other resource is to look into existing project and take their setup as a base, either easy and you’ll find out that there are many ways of doing it, most of them are dated.

Since I added gettext support to EWL, I thought I can tell you the single steps, making your life easier, one you want to add i18n support to your application or library. I assume that you already have a autoconfig setup for your software.


First step: Setup the c code

Libraries need a different treatment here, because they cannot rely on global settings, else they would overwrite the catalog of the application that is using the library. Nevertheless the steps are very similar. I start here with the steps for the application.

Create a header file called intl.h or – if you prefer – mynamespace_intl.h it’s containing this code:

#ifndef INTL_H
#define INTL_H

#include <string.h>

#ifdef ENABLE_NLS
# include <libintl.h>
# include <locale.h>
#else
# define gettext(str) ((char*) (str))
#endif

#define _(str) gettext(str)
#define gettext_noop(str) str
#define N_(str) gettext_noop(str)
#define S_(str) sgettext(str)

static inline char *
sgettext(const char *msgid)
{
        char *msgval = gettext(msgid);
        if (msgval == msgid)
                msgval = strrchr(msgid, '|') + 1;
        return msgval;
}

#endif

This defines some useful macros, namely (), N() and S_(). Take a look into the gettext handbook to see how they work.

For libraries use this header:

#ifndef INTL_H
#define INTL_H

#include <string.h>

#ifdef ENABLE_NLS
# include <libintl.h>
# include <locale.h>
#else
# define dgettext(domain, str) ((char*) (str))
#endif

#define D_(str) dgettext(PACKAGE, str)
#define gettext_noop(str) str
#define N_(str) gettext_noop(str)
#define SD_(str) sdgettext(PACKAGE, str)

static inline char *
sdgettext(const char *domain, const char *msgid)
{
        char *msgval = dgettext(domain, msgid);
        if (msgval == msgid)
                msgval = strrchr(msgid, '|') + 1;
        return msgval;
}

#endif

Add this new header file to the (not installed) list of source files in your Makefile.am. You also need to add the following option to the AM_CPPFLAGS

-DPACKAGE_LOCALE_DIR=\"$(localedir)\"

We need this definition later.

To initialize the correct language and loading the language catalog we need to add some code, for apps add this code near the beginning of your main() function:

    /* Initialize NLS */
#ifdef ENABLE_NLS
    setlocale(LC_MESSAGES, "");
    bindtextdomain(PACKAGE, PACKAGE_LOCALE_DIR);
    bind_textdomain_codeset(PACKAGE, "UTF-8"); /* add this only if you want to
                                                  deal with UTF-8 strings
                                                  exclusivly. If you are using
                                                  evas, you most probably
                                                  want. */
    textdomain(PACKAGE);
#endif

For libraries add this:

    /* Initialize NLS */
#ifdef ENABLE_NLS
    bindtextdomain(PACKAGE, PACKAGE_LOCALE_DIR);
    bind_textdomain_codeset(PACKAGE, "UTF-8"); /* add this only if you want to
                                                  deal with UTF-8 strings
                                                  exclusivly. If you are using
                                                  evas, you most probably
                                                  want. */
#endif

As you can see it is up to the library user to set the locale. Don’t forget to add the intl.h header file.

The only missing thing for the c-files is to mark translatable strings, with the defined macros. I will not explain here how to use them.


Second step: Setup autoconfig and automake

First add the following line in your autogen.sh or bootstrap.sh file, if you are using autoreconf you can jump to the next topic.

echo "Running autopoint..." ; autopoint -f || :

Before the line where aclocal is called.

Now execute autopoint -f in your package base dir. It will create the po/-directory and copy some files you will need later.

Add the following lines some where in your configure.ac file:

AM_GNU_GETTEXT([external])
AM_GNU_GETTEXT_VERSION([0.15])

And add po/Makefile.in (don’t forget the .in, this is not a typo!) into the AC_OUTPUT list. And add the po dir at the end of the subbdir list in the Makefile.am.

Now create the po/Makevars file, you will find there already a template for it. Simply copy it and adjust it for your needs. You may want to add the S_ or the D_ and SD_ macros to the xgettext options.

Create a new file called POTFILES.in. There you put every source file that contains a translatable string. The path should be relative to your base directory.

Well, if you are an English-speaker you are done know. Your project should now be translatable. If your not an English-speaker you can test your work and start a translation for your mother tongue.


Step three: The first translation

Run now ./autogen.sh, ./bootstrap.sh or autoreconf and the ./configure and make. After that you will find a pot file in the po/ directory. Use it to create a translation for your project and save the po file in the po/ dir.

Therefor that your translation will be converted into a binary form and later be installed add this file to a newly created LINGUAS file. There are all available translations listed. For example:

# Set of available languages.
de it cz

Call now make maintainer-clean and the next time you are going to build your project, your translation will be installed and used.

November 29, 2008

The Lists and their quirks

Filed under: Uncategorized — pfritz @ 22:24

When I started to write this article I planed to write about the API of the two major list implementations and their weak points. But since this article is already long enough, I only focusing on evas_list. Don’t worry, an article about ecore_list will follow.

Both API are widely used so you should assume that they are proved to be suited for daily use. And that is right in most of the cases where I have seen them to be used. But they have some weak points, which I want to show you here. Creating a list is in both cases very simple, where it gets hairy is removing items from the list. Evas_List (now Eina_List) doesn’t have a real destroy function, so you have to figure out the best way to free a complete list.

But this is an relative easy job and you’ll find thousands of examples in the efl. The real tricky point is if you do selective destruction, i.e. you remove a list item under some conditions.

Let’s say we have a list of filenames and want to remove the files that doesn’t have the extension “.jpg”. This shouldn’t be a hard job you think, shouldn’t it? Well it could be easier.

Let’s start with Eina_List. I mimic here the unaware new user, of course I know already how to do it right. We start with the normal approach. Iterate over all items and remove the items we don’t like.

#include <Eina.h>
#include <Ecore_Str.h>
#include <string.h>
#include <stdio.h>

int
main(void)
{
        Eina_List *list = NULL;
        Eina_List *l;
        const char *file;

        eina_init();

        list = eina_list_append(list, "dsc01.jpg");
        list = eina_list_append(list, "blah");
        list = eina_list_append(list, "dsc02.jpg");
        list = eina_list_append(list, "dsc03.jpg");
        list = eina_list_append(list, "dsc04.jpg");
        list = eina_list_append(list, "dsc05.jpg");
        list = eina_list_append(list, "foooo");
        list = eina_list_append(list, "dsc07.jpg");
        list = eina_list_append(list, "dsc08.jpg");
        list = eina_list_append(list, "dada");
        list = eina_list_append(list, "dsc10.jpg");

        EINA_LIST_FOREACH(list, l, file)
        {
                if (!ecore_str_has_extension(file, ".jpg"))
                        list = eina_list_remove(list, file);
        }

        /* and now print the rest */
        while (list)
        {
                file = eina_list_data_get(list);

                printf("%s\n", file);
                list = eina_list_remove(list, file);
        }

        eina_shutdown();
        return 0;
}

This doesn’t look that bad on the first look and if you test it does indeed work. But it is wrong! Let me first explain why it is wrong, I’ll explain you later why it does work nevertheless. Let’s take a look on what the EINA_LIST_FOREACH-macro does. Here is the same loop but the macro expanded:

        for (l = list, file = eina_list_data_get(l); l; l = eina_list_next(l),
                        file = eina_list_data_get(l))
        {
                if (!ecore_str_has_extension(file, ".jpg"))
                        list = eina_list_remove(list, file);
        }

eina_list_remove() will, remove the node where l is pointing to and frees the node. So with the following l = eina_list_next(l) we are accessing an already freed node. But why doesn’t it crash? Or why doesn’t valgrind show any error? Because eina uses an memory pool, to reduce the free()s and malloc()s, so the given node isn’t in fact freed. Ok, but if eina is working like that, why shouldn’t we take it as a hidden feature, since it does the job as we need it?

Imagine we want now not only remove the non-jpg-files, but want to put them in a second list:

#include <Eina.h>
#include <Ecore_Str.h>
#include <string.h>
#include <stdio.h>

int
main(void)
{
        Eina_List *list = NULL;
        Eina_List *list2 = NULL;
        Eina_List *l;
        const char *file;

        eina_init();

        list = eina_list_append(list, "dsc01.jpg");
        list = eina_list_append(list, "blah");
        list = eina_list_append(list, "dsc02.jpg");
        list = eina_list_append(list, "dsc03.jpg");
        list = eina_list_append(list, "dsc04.jpg");
        list = eina_list_append(list, "dsc05.jpg");
        list = eina_list_append(list, "foooo");
        list = eina_list_append(list, "dsc07.jpg");
        list = eina_list_append(list, "dsc08.jpg");
        list = eina_list_append(list, "dada");
        list = eina_list_append(list, "dsc10.jpg");

        for (l = list, file = eina_list_data_get(l); l; l = eina_list_next(l),
                        file = eina_list_data_get(l))
        {
                if (!ecore_str_has_extension(file, ".jpg"))
                {
                        list = eina_list_remove(list, file);
                        list2 = eina_list_append(list2, file);
                }
        }

        /* and now print the rest */
        printf("first list:\n");
        while (list)
        {
                const char *file = eina_list_data_get(list);

                printf("\t%s\n", file);
                list = eina_list_remove(list, file);
        }

        /* and now print the rest */
        printf("second list:\n");
        while (list2)
        {
                const char *file = eina_list_data_get(list2);

                printf("\t%s\n", file);
                list2 = eina_list_remove(list2, file);
        }

        eina_shutdown();
        return 0;
}

And suddenly it doesn’t work anymore. The reason is pretty simple the removed node is put into the memory pool, but then before we iterate to the next node it is appended to the second list. So in fact we are looping after the first remove over the second list.

So how can we do it right? We need to go to the next node before we are removing the node, i.e. we need to keep a reference of the node that is to be removed and do the next step:

        for (l = list; l;)
        {
                Eina_List *tmp = l;

                l = eina_list_next(l);
                file = eina_list_data_get(tmp);
                if (!ecore_str_has_extension(file, ".jpg"))
                {
                        list = eina_list_remove(list, file);
                        list2 = eina_list_append(list2, file);
                }
        }

Nice that works! So are we finally done? Not quite. We are using eina_list_remove(). Although there is nothing wrong with this function, you should avoid to use it, because it does a linear search to find the node to remove, i.e. the complexity is O(n). Better use eina_list_remove_list(). Unlike the name suggests, it doesn’t remove a list, but it removes a list node, actually exactly what we want.

        for (l = list; l;)
        {
                Eina_List *tmp = l;

                l = eina_list_next(l);
                file = eina_list_data_get(tmp);
                if (!ecore_str_has_extension(file, ".jpg"))
                {
                        list = eina_list_remove_list(list, tmp);
                        list2 = eina_list_append(list2, file);
                }
        }

Puh, that was harden then expected, but it was doable, and we even kept it to be an O(n) algorithim. The actually bad thing was that our first shot worked by accident. So keep your eyes open, if you have ever to do a selective remove with eina_list.

Blog at WordPress.com.