Passive Voice Day

2011-04-26

It has been decided that tomorrow, April 27, is Passive Voice Day. (It might be asked, “It has been decided by whom?” Exactly.) Passive voice should be written and spoken in by everybody. For one day only, active voice will be frowned upon. It might be considered silly by you, but it will be found to be fun by many.

The #passivevoiceday hashtag should not be forgotten when tweets are written, although it is doubted that passive voice sentences will be able to be fit into 140 characters.

ITS Tool Released

2011-04-26

Last October, I blogged about itstool, a tool I developed to translate XML documents with PO files using ITS rules. Today, I released version 1.0.0 of ITS Tool on the new ITS Tool web site. If you’ve used xml2po before, you’re familiar with the basic idea: PO messages are extracted from an XML files, and translated messages are merged with the source to produce localized XML files. If you’re not already translating your documents using a message-based format, you need to start. Your translators will thank you.

ITS Tool takes the same idea as tools like xml2po, but the implementation is done entirely in terms of rules from the W3C Internationalization Tag Set. You don’t have to patch it to create a mode for a new XML format. You just need to provide a standard ITS file. Better still, if you mix XML vocabularies in a single file, ITS Tool can apply the rules for all matching formats.

Translators will be happy to know that we can now mark things as untranslatable using the standard its:translate attribute, or using custom its:translateRule elements. This is a long-requested feature that will help cut down the amount of unnecessary cruft that translators have to look at.

In addition to the features we get from standard ITS data categories, ITS Tool provides some custom extension rules to support features like translator credits and external file tracking. There are a few more features I’d like to provide as well, such as adding extra Mallard link titles and specifying transliteration-only messages.

I’ll be working on the GNOME build tools to switch GNOME’s documentation over to itstool for 3.2. Most messages in the PO files will be the same as with xml2po, so it won’t introduce much extra work for translators.

But ITS Tool is not just a GNOME project. It’s free software, under the GPL 3. It’s built on Python and libxml2, and can be used by any project for their XML documents. If you use an XML format that isn’t handled by the built-in ITS rules, you can pass your own custom ITS rules. Or if it’s a common format, submit those rules upstream. I encourage everybody working with XML documents to try ITS Tool and let me know how well it works and what can be done to improve it.

Open Help Conference

GNOME 3

2011-04-07

I’ve never been more excited to be a GNOME developer. After years of hard work and planning, GNOME 3 was released yesterday. Check out the Introduction to GNOME from our brand-new help to learn all about it.

GNOME 3 shows the innovation that open source communities can bring. Hundreds of developers, designers, writers, testers, and translators worked hard to deliver an amazing new user experience. Among them are the fantastic people who helped create an all-new help system that rivals anything I’ve seen elsewhere. My release notes list Phil Bull, Jim Campbell, Tiffany Antapolski, Natalia Ruz Leiva, Shaun McCance, Paul Frields, Mike Hill, Aline Bessa, Marina Zhurakhinskaya, and Kelly Sinnott. If I missed anyone, I’m really sorry. It’s hard to keep track of so much awesome.

We tossed out the old manual (who reads those?) and started fresh with topic-oriented help, building on the dynamic Mallard language. The results are amazing. The initial release has 214 pages, carefully organized and cross-linked to help you find the information you need quickly and get back to your life. What is a workspace? Select files by pattern. Enter special characters.

Frederic Peters did amazing work on library.gnome.org so all the new help is available online. But remember that all of this is available from the Help application on your GNOME 3 desktop. The help viewer was completely rewritten for GNOME 3, and I think you’ll really like what we’ve done.

By the way, if you want to meet up and learn about GNOME’s fresh approach to help, you should come to the Open Help Conference this June. At least Phil, Jim, and I will be there, and we’ll be joined by documentation teams from other great open source projects.

We’re not done yet.

Help needs to constantly improve and evolve as we learn more about what our users need. The GNOME documentation team is already hard at work on more pages and revisions to existing pages. We’ll be pushing updates to the help weekly. If you want to get involved, subscribe to our mailing list and send an email to gnome-doc-list@gnome.org.

We also welcome drive-by contributions. One of the really nice things about topic-oriented documents with Mallard is that it’s easy to just write up a page about something without worrying about making revisions to an entire book. If you love using GNOME, a great way to contribute is by writing a short page about an awesome time-saving trick. We’ll put these under Tips & Tricks, and users worldwide will learn something cool because of you.

I am GNOME

Air Canada is on my shit, or as I like to call it, my “Continental list”. (Guess which airline I hate the most.)

At this point, I’m seriously contemplating cancelling my upcoming conferences. I’m tired of the airline industry. I’m tired of their complete disregard for their customers. And I’m tired of shelling out over $200 because they can’t do their job. I can’t afford to do this anymore.

I was supposed to fly back from the GNOME documentation sprint last Wednesday. I had a 5:00 flight on Air Canada, direct from Toronto to Cincinnati. I got through customs and security without much incident. (I was misinformed about the customs procedure by an Air Canada employee, and I was selected for additional screening, but that’s all minor.) I got an overpriced chicken sandwich and sat at my gate. I pulled out my laptop and worked on the help.

It snowed in Toronto Wednesday. There was a dusting of snow on the ground when I woke up, and it really picked up as I headed to the airport. I grew up near Chicago. I understand crazy weather and lake-effect snow. I was fully expecting delays. So when they announced a delay, I sighed and kept working. When they changed gates, I sighed and walked to the new gate. When they announced they were down to one runway and there would be further delays, I sighed again. You have to deal with what nature hands you. As long as I could get home, I didn’t mind waiting.

Then they cancelled the flight. This was maybe two hours after when our flight was supposed to have left, which struck me as very early to cancel flights. They still had hours and hours to try to push flights out. Not only did they cancel the flight, they told us that there was no guarantee we’d be rebooked. Really. We all paid for a service, and they had no obligation to provide that service.

They sent us to a customer service counter behind security. Disheartened, we all shuffled down the concourse and stood in line. We waited. Then we were told that they couldn’t rebook us at that counter, and that we’d have to leave security and go through customs to rebook.

When you fly to the US from Toronto, you actually go through US customs in Canada. You’re in a special part of the airport that is, effectively, the USA. It’s usually very convenient. You land in a domestic gate and don’t have to deal with customs at your destination. But if you need to go back out of security, as we did, you have to go through Canadian customs. And they ask you questions like “Where are you coming from?” Um, “Gate 161”. “When’s the next flight?” Oh, hey, good question.

What this means is that, before you can get to the ticketing counter to rebook, you have to collect your bags. Another thing we had to wait for. When we got to the baggage carousal, there weren’t many people there. I think we were one of the first flights cancelled. But our bags didn’t come. We waited for nearly an hour and a half for our bags. Meanwhile, other flights were cancelled. Those passengers arrived, their bags came, and they went through customs. They were already out there filling up the rebooking line, while we were still waiting for our bags.

When we finally got through, we headed to the customer service desk, the one we were told to go to. There were surprisingly only about 20 people there. We waited. Airline employees told us we were in the right place to be rebooked. Then another employee told us we were in the wrong line. We had to go downstairs. More wasted time.

We went down to a mob of 300 people. I saw three employees working. If they can each process a customer in five minutes (and that’s being very generous, in my experience), that’s eight hours in line. And you just know they’re going to close when you get near the front of the line. (Yes, it’s happened. See paragraph 1 about my least favorite airline.) Eight hours to not even talk to anybody about the flight that they’ve already said they probably won’t even rebook? No thanks.

So I found a couple of guys who decided to drive the next morning. They offered to let me tag along. (Dean and Tony, if you happen across this blog, you helped a stranger in need, and I thank you.) But we were driving at 5:00 in the morning, so I had to get a hotel room. The only hotel near the airport with rooms available was the Crowne Plaza. Five hours of sleep at the Crowne Plaza: $172 USD. (This blog post is already too long, so I won’t go into how badly they screwed up when I tried to get a quick bite to eat at the bar.)

Here’s the kicker: While driving to the hotel, there was no snow. It stopped. This was not a surprise. Every TV in the airport is showing CP24. All the passengers were looking at the radar on their smart phones. We all knew the snow was going to stop. I have a hard time believing Air Canada didn’t know as well. And we could see two planes coming in for a landing at the same time, which means they got more runways open.

Flights were getting out. My friend Phil got home to the UK, and he flew later than I did. The snow stopped. The runways opened. Why did they cancel our flights? Because we were a small flight without enough passengers to care about. It wasn’t profitable to try to get us home. We didn’t matter. And that’s what pisses me off the most.

Phil mentioned the future of Desktop Help, but he was sparse on details. Let me fill in the blanks. With Mallard, we worry mostly about structure and content, and we leave the rendering details to Yelp. Sometimes we want to influence the style, and for that we use Mallard’s style hints. But sometimes, just sometimes, for very special pages, we want to inject some pizzazz.

We can do that too. Watch:

This is using some custom style hints and a very small Mallard extension, which falls back gracefully thanks to Mallard’s well-defined rules for extensions and fallback.

This is still a work in progress. And with work continuing into the night at the help hackfest in Toronto, it’s very much in progress.

Have you submitted a talk proposal for the Open Help Conference yet? If not, here’s an extra incentive: XML Press is providing a free book to every speaker. You can choose between these three fantastic titles:

In light of the promotion, the proposal deadline has been extended to Martch 25. But why wait? Submit your talk proposal today.

The Open Help Conference focuses on how we can provide better help in open and collaborative communities. It’s not just about documentation; we’re also interested in participatory support for (and by) users. Please share your experiences, whether it’s about documentation, support, or communities.

Open Help Conference

I’ve been a window shader for a very long time. For those that don’t know, window shading causes the main body of a window to disappear, leaving just the titlebar in place. It’s an alternative to minimizing (GNOME 2, Windows), iconifying (OS X), and hiding (GNOME 3). I shade windows by double-clicking the titlebar. There’s a hidden option to enable this in GNOME 2.

Window shading has been around for a long time. It’s not even exclusive to *nix desktops. It was used in pre-X versions of MacOS. I seem to be one of the few remaining window shaders in the modern desktop world, so I want to share my experience. I’m not trying to debate what’s better, and I’m certainly not demanding that designers cater to my whims. I’m just sharing how I use my desktop.

I have no problems with overlapping windows. In fact, I rely on them. I often read something in one window while writing something in another. Shifting my gaze is much faster than changing windows in a window list. This is important to me, because I do it a lot. I have terrible short-term memory. I can tell you what lot we parked in at Disney World when I was 8, but I can’t tell you what I read five minutes ago. Consequently, I almost never maximize windows. I confess, I really don’t understand maximizing windows on large, wide-screen monitors. (Mine is 1680×1050.) It’s too wide to read. My windows are at their natural size, and that size depends on what’s in them.

But when you have lots of small, overlapping windows, it’s easy to lose track of them. I have a fairly static arrangement of windows. I know my web browser and other document windows cascade from the top left. I know my terminals are flush bottoom. I know my smaller utility windows are somewhere in the middle of the screen. I don’t really think about where the windows are, because I have years of habit ingrained in me. Habit helps me.

When I need to get behind a window, or when I don’t want to look at it, I shade it. I could just as easily minimize it to get it out of my way. There’s no practical difference in the hiding act. The difference comes in when I want that window back. And more often than not, I want it back soon.

Window lists and zoom overviews have little to no spatial resemblence to how your windows are (or were) arranged. But shading leaves your windows where they were, just smaller. Because I have strong window placement habits, I don’t have to hunt for the window I want in some arbitrary list or grid. I go to where the window is. It’s in the same place as I left it. And as a result, I spend almost no time looking for windows.

I’ll probably have to change my habits soon, because modern desktops are moving away from supporting this kind of feature. But with all the innovation that’s happening, I’m hopeful that somebody will come up with a new way of bringing back hidden windows that actually takes advantage of spatial window placement habits.

There’s a lot of buzz in the tech world around the word community. Perhaps stemming from the wrench that social media has thrown into the works, business folks have started tossing around terms like community content, community relations, and community management. But do they know what community means?

“You keep using that word. I do not think it means what you think it means.”

I’ve been a community leader in the open source world for the last eight years. Before that, I was a community member. You cannot be a community leader without being a community member. To understand what a community leader does, you have to understand what a community is.

  1. A community is self-governing. Community members are empowered to decide the direction of the community. If you’re telling people what to do and how to do it, you don’t have a community. You have a workforce.
  2. A community is social. I don’t mean it’s on Facebook or Twitter. Community members might use Facebook or Twitter, but they might use something old-school like mailing lists or IRC. What’s important is that people are talking to each other, getting to know each other, and making decisions together. Without social interaction, you just have a random group of contributors.
  3. A community has a common interest. Community members will have differences on many topics, but there has to be something that brings and keeps them together.

Community leaders don’t set direction or dictate what gets done. That would work against the self-governing nature of communities. And they don’t keep projects or priorities secret. That would be anti-social. So what does a community leader do?

  1. The first job of a community leader is to welcome new members. People will come and go from your community. That’s the nature of things. A lot of people will just stop by because they’re curious. If you don’t welcome them and give them a way to get involved, they’ll be gone before they’ve ever started.
  2. A community leader keeps things running smoothly. If your community is trying to get things done (and when businesses talk about communities, they want those communities to do something), then someone needs to stay on top of those things. You can’t make community members do anything. But you can keep track of what they’ve decided to do and remind them. Leaders are like personal assistants to the entire community.
  3. A community leader keeps the big picture in mind. You have to have your finger on that common interest that’s keeping your community together. When people get bogged down with the trees, you have to remind them of the forest.

Every successful community I know of works this way. And every time a company tries to set up a community, but ignores the nature of communities, it eventually fails. So, do you have a real community, or are you just treating your users like employees?

I’m looking for a few talented hackers/writers to help us work on the developer documentation for the Gnome platform in Toronto March 17-22. We’re already hosting a hackfest for user help, and we’d like to colocate a developer documentation hackfest there. We’ll be focusing on the Platform Overview and the new Developer Demos. Time permitting, we can also put some extra polish into our (already mostly comprehensive) API references.

Here’s the skills we’re looking for:

  • Familiarity with the Gnome platform. You don’t have to be a Matthias Clasen or an Owen Taylor. In fact, knowing too much can sometimes cause you to overlook what new developers need to know. But you do have to have some experience building applications with Gnome.
  • Some experience writing XML markup. The Overview and Demos are in Mallard. The API references are done with gtk-doc, often with inline DocBook. You don’t need to be an expert on any of these. If you know XML and are comfortable writing it in a text editor, the details aren’t hard to pick up.
  • Patience and a willingness to learn. Effective writing is more than just dumping what you know into a text editor. We’ll have experienced technical writers on hand who will help you plan and organize your material.

If you’re interested, email me at shaunm@gnome.org.

The nice thing about Blip is that it’s completely open and insanely extensible. It tries very hard to find things like documents and translations wherever it can, but sometimes your content just doesn’t look like what it knows about.

But that’s OK. You can easily add plugins to find and display more information. In fact, everything you see in Blip—every bit of data collected and every page generated—comes from a plugin. Some of them are in the core blip package. Others are provided by the blip-gnome package, which has plugins to scan modules that look like typical Gnome modules.

If you want to deploy Blip for your project, you can create a package full of plugins to deal with the kind of data you actually care about. To show how easy this is, I’ll walk through a plugin I just added for the Evolution Quick Reference Card. This is a PDF document generated from a TeX file that lists common keyboard shortcuts. Obviously, this won’t be picked up by the plugins that handle DocBook, Mallard, and gtk-doc documents.

First, look at the report on the Evolution Quick Reference Card on Blip. Notice that Blip knows the title of the document, when it was modified, and what translations exist.

All of this comes from the evolutionquickref Blip plugin. This is less than 100 actual lines of code. Let’s break it down.

class QuickRefScanner (blip.plugins.modules.sweep.ModuleFileScanner):

In Blip, you create plugins by subclassing known extension points. In this case, there’s a module scanner (which is itself just a plugin). The module scanner reads the SCM history for a module, then walks the tree of files calling each file scanner.

A file scanner contains two methods. The process_file method is called for every file in the module. It’s expected to decide for itself whether it’s interested in the file. After all the files have been walked, the post_process method is called on each file scanner. Sometimes you need to find everything before you can fully process stuff.

    def process_file (self, dirname, basename):
        branch = self.scanner.branch
        is_quickref = False
        if branch.scm_server == 'git://git.gnome.org/' and branch.scm_module == 'evolution':
            if basename == 'Makefile.am':
                if os.path.join (self.scanner.repository.directory, 'help/quickref') == dirname:
                    is_quickref = True
        if not is_quickref:
            return

Here we’re just determining whether we care about the file. Unlike most scanners, we hardcode the SCM server and module to restrict it to Evolution. We then only look at help/quickref/Makefile.am. Why that file? Because we’re going to parse it for language information.

        with blip.db.Timestamp.stamped (filename, self.scanner.repository) as stamp:
            try:
                stamp.check (self.scanner.request.get_tool_option ('timestamps'))
            except:
                data = {'parent' : branch}
                scm_dir, scm_file = os.path.split (rel_ch)
                data['scm_dir'] = os.path.join (scm_dir, 'C')
                doc = blip.db.Branch.select_one (type=u'Document', **data)
                if doc is not None:
                    self.scanner.add_child (doc)
                    self.document = doc
                raise

Blip aggressively timestamps to avoid needless reprocessing. Trust me, in a production environment, you do not want it reprocessing every file from 100+ modules all the time.

Blip provides a convenient timestamp-checking class you can use with Python’s awesome with statement. When you call stamp.check, it checks the mtime of the file against what’s recorded in the database. If the file is newer, or if the database has no timestamp, it just returns and lets your code execute. Otherwise, it throws an exception that’s caught by the with block. If the entire block finishes without exception, a timestamp is added to the database automatically.

(By the way, Blip uses introspection to automatically attach a source function to each timestamp. So this timestamp won’t interfere with timestamps on the same file from another plugin.)

Sometimes you still want to do things when you’re skipping a timestamped file. In this case, we try to find a matching document in the database and add it to the module scanner. Why do we do this? The module scanner deletes child objects (documents, applications, translation domains, etc) from the database unless it’s told about them on each processing run. This is how we prevent stale information from lingering around in Blip. But that means we need to let the module scanner know about the document, even if we’re not going to process it.

            makefile = blip.parsers.get_parsed_file (blip.parsers.automake.Automake,
                                                     self.scanner.branch, filename)

Blip has some built-in parsers for the kinds of files it usually encounters. This one tries to extract some basic information from Makefile.am files. It can’t do everything; after all, an automake file is a template for a shell script. But it does a good job with most data.

            ident = u'/'.join(['/doc', bserver, bmodule, 'quickref', bbranch])
            document = blip.db.Branch.get_or_create (ident, u'Document')
            document.parent = branch
            for key in ('scm_type', 'scm_server', 'scm_module', 'scm_branch', 'scm_path'):
                setattr (document, key, getattr (branch, key))
            document.subtype = u'evolutionquickref'
            document.scm_dir = blip.utils.relative_path (os.path.join (dirname, u'C'),
                                                         self.scanner.repository.directory)
            document.scm_file = u'quickref.tex'
            self.scanner.add_child (document)
            self.document = document

We first create an ident. This is how just about everything is identified in Blip. In many cases, idents match the path of the URL you use to access objects. We use some information from the ident of the parent module. We then get or create an object in the database. If it’s not there already, Blip just makes it for us, and handles some extra administrative work.

After setting some information, we add the document to the module scanner. This is the same thing we did in the except clause above. We have to add it, or it will be deleted.

            translations = []
            for lang in makefile['SUBDIRS'].split():
                if lang == 'C':
                    continue
                lident = u'/l10n/' + lang + document.ident
                translation = blip.db.Branch.get_or_create (lident, u'Translation')
                translations.append (translation)
                for key in ('scm_type', 'scm_server', 'scm_module', 'scm_branch', 'scm_path'):
                    setattr (translation, key, getattr (document, key))
                translation.scm_dir = blip.utils.relative_path (os.path.join (dirname, lang),
                                                                self.scanner.repository.directory)
                translation.scm_file = u'quickref.tex'
                translation.parent = document
            document.set_children (u'Translation', translations)

We now find translations by looking at built subdirectories (except C, because that’s the source). We go through the same business of creating idents and getting or creating database objects.

Then we call set_children on the document. Remember that business of having to add children to the module scanner to keep them from being deleted? This is exactly what the module scanner calls on the module, using the objects that get added to it. This one line prevents us from having stale translations in the database if they’re removed from the module.

We’ll handle everything else in post_process.

        regexp = re.compile ('\\s*\\\\textbf{\\\\Huge{(.*)}}')
        filename = os.path.join (self.scanner.repository.directory,
                                 self.document.scm_dir, self.document.scm_file)
        with blip.db.Timestamp.stamped (filename, self.scanner.repository) as stamp:
            stamp.check (self.scanner.request.get_tool_option ('timestamps'))
            stamp.log ()
            for line in open(filename):
                match = regexp.match (line)
                if match:
                    self.document.name = blip.utils.utf8dec (match.group(1))
                    break

Here we process the quickref.tex file. (That’s what we set scm_file to above.) Again, we do this behind a timestamp so we don’t read a file if it hasn’t changed. We just loop over the lines in the file looking for one that matches our regexp, and use that to extract the title of the reference card. This is a one-off plugin, so we could have just hardcoded the title. But what’s the fun in that?

        rev = blip.db.Revision.get_last_revision (branch=self.document.parent,
                                                  files=[os.path.join (self.document.scm_dir,
                                                                       self.document.scm_file)])
        if rev is not None:
            self.document.mod_datetime = rev.datetime
            self.document.mod_person = rev.person
        self.document.updated = datetime.datetime.utcnow ()

Now we look up the most recent git revision to have touched quickref.tex and record that information. “But Shaun,” you say, “that information is already in the database. Normalize!” I’m not dogmatic about normalization. I put a lot of effort into fast page load times (and there’s still work to be done on some pages). This helps a lot.

We also set the updated field. This just says when Blip last looked at this particular thing. After that (code not reproduced here), we loop over the translations and do the same thing for them.

That’s it. A simple but non-trivial Blip plugin in about 100 lines of code. If you’re feeling adventurous, you could try to compute completion statistics for the translations. They’re not using PO files, but the TeX files are predictable and parseable, so you could put some sort of statistics together.

What kind of data do you have in your modules? As long as it’s parseable, it’s as simple is this to tell Blip about it. Try it yourself. And get in touch; I’m happy to help people build on Blip.

Open Help Conference