Python Tip: Conditional Expressions

I learned something new today about Python 2.5 and newer. I thought it was so nifty that I decided to write a blog article about it. Hopefully someone out there finds it as interestingly useful as I do.

One of the things I find myself doing quite often in my code (be it Python, PHP, Java, or what have you) is a simple conditional assignment like so:

if foo:
    bar = 'baz'
else:
    bar = 'qux'

In a lot of languages these days, you can use a ternary operator as follows to turn these four lines into a one-liner:

<?php
$bar = $foo ? 'baz' : 'qux';
?>

However, this ternary operator does not exist in Python. I have used various means in the past to accomplish the same task without using code like we saw in the first code block above, but they were all very hackish. Today I was happy to learn that there is an official way to do it in Python:

bar = ('baz' if foo else 'qux')

I'm not sure which one is more confusing to newbies: Python's conditional expressions or other languages' ternary operator. Personally, I prefer constructs like these to make my code more concise. I my mind, they also make the code more readable. I have heard some folks argue that ternary operators and the like obfuscate the code more than necessary, so they discourage the use of such tactics and recommend using the classic approach featured in the first example.

For those who are interested, I learned about it and a few other neat things in Python 2.5 and newer at http://www.python.org/doc/2.5/whatsnew/pep-308.html.

Slackware 12.2, Gmail Tasks and SMS

I just had to come up with a blog post about these newly released bits of awesomeness!!

  • Slackware 12.2 was announced this morning
  • Google has launched two new Labs features for Gmail: a simple task list and the ability to send SMS messages within Gmail itself. w00t

I wish I had more time to report other important releases, but I must study!!!

Syntax Highlighting, ReST, Pygments, and Django

Some of you regulars out there may have noticed an interesting change in the presentation of some of my articles: source code highlighting. I've been interested in doing this for quite some time, I just never really got around to implementing it until last night.

I found this implementation process to be a bit more complicatd than I had anticipated. For my own benefit as well as for anyone else who wants to do the same thing, I thought I'd document my findings in a thorough article for how to add syntax highlighting to an existing Django- and reStructuredText-powered Web site.

The power behind the syntax highlighting is:

Python is a huge player in this feature because reStructuredText (ReST) was built for Python, Pygments is the source highlighter (written in Python), and Django is written in Python (and my site is powered by Django). Some of you may recall that I converted all of my articles to ReST not too long ago because it suited my needs better than Textile, my previous markup processor. At the time, I was not aware that the conversion to ReST would make it all the easier for me to implement the syntax highlighting, but last night I figured out that that conversion probably saved me a lot of frustration. Cascading Stylesheets (CSS) are responsible for making the source code actually look good, while Pygments takes care of assigning classes to various parts of the designated source code and generating the CSS.

So, the first set of requirements, which I will not document in this article, are that you already have a Django site up and running and that you're familiar with ReST syntax. If you have the django.contrib.flatpages application installed already, you can type up some ReST documents there and apply the concepts discussed in this article.

Next, you should ensure that you have Pygments installed. There are a variety of ways to install this. Perhaps the easiest and most platform-independent method is to use easy_install:

$ easy_install pygments

This command should work essentially the same on Windows, Linux, and Macintosh computers. If you don't have it installed, you can get it from its website. If you're using a Debian-based distribution of Linux, such as Ubuntu, you could do something like this:

$ sudo apt-get install python-pygments

...and it should take care of downloading and installing Pygments. Alternatively, you can download it straight from the PyPI page and install it manually.

Now we need to install the Pygments ReST directive. A ReST directive is basically like a special command to the ReST processor. I think this part was the most difficult aspect of the implementation, simply because I didn't know where to find the Pygments directive or how to write my own. Eventually, I ended up downloading the Pygments-1.0.tar.gz file from PyPI, opening the Pygments-1.0/external/rst-directive.py file from the archive, and copying the stuff in there into a new file within my site.

For my own purposes, I made some small adjustments to the directive over what come with the Pygments distribution. I think it would save us all a lot of hassle if I just copied and pasted the directive, as I currently have it, so you can see it first-hand.

"""
    The Pygments reStructuredText directive
    ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

    This fragment is a Docutils_ 0.4 directive that renders source code
    (to HTML only, currently) via Pygments.

    To use it, adjust the options below and copy the code into a module
    that you import on initialization.  The code then automatically
    registers a ``code-block`` directive that you can use instead of
    normal code blocks like this::

    .. code:: python

            My code goes here.

    If you want to have different code styles, e.g. one with line numbers
    and one without, add formatters with their names in the VARIANTS dict
    below.  You can invoke them instead of the DEFAULT one by using a
    directive option::

    .. code:: python
       :number-lines:

            My code goes here.

    Look at the `directive documentation`_ to get all the gory details.

    .. _Docutils: http://docutils.sf.net/
    .. _directive documentation:
       http://docutils.sourceforge.net/docs/howto/rst-directives.html

    :copyright: 2007 by Georg Brandl.
    :license: BSD, see LICENSE for more details.
"""

# Options
# ~~~~~~~

# Set to True if you want inline CSS styles instead of classes
INLINESTYLES = False

from pygments.formatters import HtmlFormatter

# The default formatter
DEFAULT = HtmlFormatter(noclasses=INLINESTYLES)

# Add name -> formatter pairs for every variant you want to use
VARIANTS = {
    'linenos': HtmlFormatter(noclasses=INLINESTYLES, linenos=True),
}


from docutils import nodes
from docutils.parsers.rst import directives

from pygments import highlight
from pygments.lexers import get_lexer_by_name, TextLexer

def pygments_directive(name, arguments, options, content, lineno,
                       content_offset, block_text, state, state_machine):
    try:
        lexer = get_lexer_by_name(arguments[0])
    except ValueError:
        # no lexer found - use the text one instead of an exception
        lexer = TextLexer()
    # take an arbitrary option if more than one is given
    formatter = options and VARIANTS[options.keys()[0]] or DEFAULT
    parsed = highlight(u'\n'.join(content), lexer, formatter)
    parsed = '<div class="codeblock">%s</div>' % parsed
    return [nodes.raw('', parsed, format='html')]

pygments_directive.arguments = (1, 0, 1)
pygments_directive.content = 1
pygments_directive.options = dict([(key, directives.flag) for key in VARIANTS])

directives.register_directive('code-block', pygments_directive)

I won't explain what that code means, because, quite frankly, I'm still a little hazy on the inner workings of ReST directives myself. Suffice it to say that this snippet allows you to easily highlight blocks of code on ReST-powered pages.

The question now is: where do I put this snippet? As far as I'm aware, this code can be located anywhere so long as it is loaded at one point or another before you start your ReST processing. For the sake of simplicity, I just stuffed it in the __init__.py file of my Django site. This is the __init__.py file that lives in the same directory as manage.py and settings.py. Putting it in that file just makes sure it's loaded each time you start your Django site.

To make Pygments highlight a block of code, all you need to do is something like this:

.. code:: python

    print 'Hello world!'

...which would look like...

print 'Hello world!'

If you have a longer block of code and would like line numbers, use the :number-lines: option:

.. code:: python
    :number-lines:

    for i in range(100):
        print i

...which should look like this...

for i in range(100):
    print i

That's all fine and dandy, but it probably doesn't look like the code is highlighted at all just yet (on your site, not mine). It's just been marked up by Pygments to have some pretty CSS styles applied to it. But how do you know which styles mean what?

Luckily enough, Pygments takes care of generating the CSS files for you as well. There are several attractive styles that come with Pygments. I would recommend going to the Pygments demo to see which one suits you best. You can also roll your own styles, but I haven't braved that yet so I'll leave that for another day.

Once you choose a style (I chose native for Code Koala), you can run the following commands:

$ pygmentize -S native -f html > native.css
$ cp native.css /path/to/site/media/css

(obviously, you'd want to replace native with the name of the style you like the most) Finally, add a line to your HTML templates to load the newly created CSS file. In my case, it's something like this:

<link rel="stylesheet" type="text/css" href="/static/styles/native.css" />

Now you should be able to see nicely-formatted source code on your Web pages (assuming you've already got ReST processing your content).

If you haven't been using ReST to generate nicely-formatted pages, you should make sure a couple of things are in place. First, you must have the django.contrib.markup application installed. Second, your templates should be setup to process ReST markup into HTML. Here's a sample templates/flatpages/default.html:

{% extends 'base.html' %}
{% load markup %}

{% block title %}{{ flatpage.title }}{% endblock %}

{% block content %}
<h2>{{ flatpage.title }}</h2>

{{ flatpage.content|restructuredtext }}
{% endblock %}

So that short template should allow you to use ReST markup for your flatpages, and it should also take care of the magic behind the .. code:: python directive.

I should also note that Pygments can handle a TON of languages. Check out the Pygments demo for a list of languages it knows how to highlight.

I think that about does it. Hopefully this article will help some other poor chap who is currently in the same situation as I was last night, and hopefully it will save you a lot more time than it took me to figure out all this junk. If it looks like I've missed something, or maybe that something needs further clarification, please comment and I'll see what I can do.

Adding Captcha To Django's Built-in Comments

I recently had a good friend of mine ask for some help in adding a captcha field to his comments form. I gave him some pointers, but before he could put them into action he had to leave for a Thanksgiving roadtrip home. I didn't give much mind to the idea of putting captchas on my own site since it's not all that popular amongst spammers yet. When I woke up this morning, however, I found myself with a few spare minutes to see if my pointers were correct.

Some of the ideas I shared with my friend turned out to not work very well. As I tinkered about trying to get things to work on my own site, I think I came up with a relatively efficient way of doing things.

Installing The Captcha

The captcha field I use is quite simple and effective. I originally got it from http://django.agami.at/media/captcha/, but the project seems to be unmaintained now. Along the road to Django 1.0, some changes were made to the way form fields work, and there is a minor change required in the base code for this captcha field if you want it to work. Alternatively, you can use a copy of the field that I'm currently using.

All you need to do is extract the captcha directory somewhere on your PYTHONPATH. The author recommends putting it in django.contrib, but I usually just place it straight on the PYTHONPATH so all I need to do is from captcha import CaptchaField instead of from django.contrib.captcha import CaptchaField. Minor details...

Adding The Captcha

The first thing you'll want to do after installing the captcha field is add the field itself to your comments form. Instead of subclassing the built-in django.contrib.comments.forms.CommentForm form, I simply decorated the constructor of the form as such:

1
2
3
4
5
6
7
8
9
from django.contrib.comments.forms import CommentForm
from captcha import CaptchaField

def add_captcha(func):
    def wrapped(self, *args, **kwargs):
        func(self, *args, **kwargs)
        self.fields['security_code'] = CaptchaField()
    return wrapped
CommentForm.__init__ = add_captcha(CommentForm.__init__)

This adds a field called security_code to the CommentForm, and it works the same way as if you had done something like this:

1
2
3
4
5
6
7
from django import forms
from captcha import CaptchaField

class MyCommentForm(forms.Form):
    name = forms.CharField()
    ...
    security_code = CaptchaField()

You can put the decorating snippet from above anywhere you'd like so long as the module you put it in is loaded at some point in your project. I usually put this sort of magic in my main urls.py file so it's harder to forget about when I debug things.

Fixing the Form

The first problem with this little trick seems to be that the CaptchaField is rendered as unsafe HTML in the default form.html template in the built-in comments application. That just means that, instead of seeing the captcha, you will see the HTML necessary to render the CaptchaField directly on the page, like this:

<input type="hidden" name="security_code" value="captcha.caZ1SqQ" />
<img src="/static/captchas/caZ1SqQ/0656f09d3974850397dd4c4974f23a35.gif"
 alt="" /><br /><input type="text" name="security_code"
 id="id_security_code" />

To fix that, you can apply the safe filter to the field and make the template look something like this:

{% load comments i18n %}
<form action="{% comment_form_target %}" method="post">
<table>
{% for field in form %}
    {% if field.is_hidden %}
    {{ field }}
    {% else %}
    <tr{% ifequal field.name "honeypot" %} style="display:none;"{% endifequal %}>
        <th>{{ field.label_tag }}:</th>
        <td {% if field.errors %} class="error"{% endif %}>
            {{ field|safe }} &nbsp;
            {% if field.errors %}{{ field.errors|join:"; " }}{% endif %}
        </td>
    </tr>
    {% endif %}
{% endfor %}
</table>
<p class="submit">
    <input type="submit" name="post" class="submit-post"
     value="{% trans "Post" %}" />
    <input type="submit" name="preview" class="submit-preview"
     value="{% trans "Preview" %}" />

</form>

Notice the {{ field|safe }} in there. Also note that I prefer the table layout for the comment form over the default mode. If you change your form template as I have done, you should put the updated copy in your own project's template directory. It belongs in templates/comments/form.html, assuming that your templates directory is called templates. You'll probably also want to check out the preview.html template for the django.contrib.comments application. I changed mine to look like this:

{% extends "comments/base.html" %}
{% load i18n %}

{% block title %}{% trans "Preview your comment" %}{% endblock %}

{% block content %}
    {% load comments %}
    {% if form.errors %}
    <h1>{% blocktrans count form.errors|length as counter %}Please
     correct the error below{% plural %}Please correct the errors below
     {% endblocktrans %}</h1>
    {% else %}
    <h1>{% trans "Preview your comment" %}</h1>
        <blockquote>{{ comment|linebreaks }}</blockquote>

        {% trans "and" %} <input type="submit" name="submit"
         class="submit-post" value="{% trans "Post your comment" %}"
         id="submit" /> {% trans "or make changes" %}:

    {% endif %}

    {% include 'comments/form.html' %}
{% endblock %}

See how I just use the include tag to pull in the comments/form.html template I mentioned above? Saves a lot of typing and potential for problems... If you update the preview.html template, you should save your copy in templates/comments/preview.html, assuming your templates directory is called templates.

Testing It Out

At this point, you should be able to try out your newly installed captcha-fied comments. If it doesn't work, please comment on this article and perhaps we can figure out the problem!

Setup a favicon.ico in Django

Up until a couple weeks ago, I had never installed a FavIcon on any of my Django sites. I never really thought about it until one day I enabled the SEND_BROKEN_LINK_EMAILS setting for one of my sites. As soon as I did that, I was able to track down links to broken pages very quickly. It also notified me that I didn't have a favicon.ico file setup anywhere on my site, and there are a great many programs out there that look for this file automatically.

At first I tried to go through Apache to get this working, but I'm no Apache guru so I was less than successful in taking this route. A couple of days ago I figured out a little trick to make the missing favicon.ico file stop sending me "broken link" e-mails hundreds of times a day. The solution? Put a favicon on my site, of course!

The approach I took was to simple add some information to my main urls.py file. Here's the line straight from my URLconf:

(r'^favicon\.ico$', 'django.views.generic.simple.redirect_to', {'url': '/static/images/favicon.ico'}),

Hah! Simple isn't it? This way Apache is still handling the actual serving of the static image file--Django just handles the redirect. Ever since I added this line to my URLconf, I've not received one "broken link" e-mail pertaining to the missing favicon.ico file. That leads me to believe that most applications can understand the redirect and plug the actual image file where it belongs.

Oh, and for those of you who might be curious... My favicon.ico is actually just a PNG image that I renamed to favicon.ico. Again, most things seem to understand this (but I could be wrong).

See below for a more complete example of my URLconf

from django.conf.urls.defaults import *

from django.contrib import admin
admin.autodiscover()

urlpatterns = patterns('',
    (r'^admin/(.*)', admin.site.root),
    (r'^robots\.txt$', 'django.views.generic.simple.direct_to_template', {'template': 'robots.txt', 'mimetype': 'text/plain'}),
    (r'^favicon\.ico$', 'django.views.generic.simple.redirect_to', {'url': '/static/images/favicon.ico'}),
    (r'^$', 'django.views.generic.simple.direct_to_template', {'template': 'base_home.html'}),
)

Python 2.6 Is Available

I haven't been following the development of Python very closely, so I will rely on someone else's information in this post.

The major theme of Python 2.6 is preparing the migration path to Python 3.0, a major redesign of the language. Whenever possible, Python 2.6 incorporates new features and syntax from 3.0 while remaining compatible with existing code by not removing older features or syntax. When it's not possible to do that, Python 2.6 tries to do what it can, adding compatibility functions in a future_builtins module and a -3 switch to warn about usages that will become unsupported in 3.0.

Some significant new packages have been added to the standard library, such as the multiprocessing and json modules, but there aren't many new features that aren't related to Python 3.0 in some way.

Python 2.6 also sees a number of improvements and bugfixes throughout the source. A search through the change logs finds there were 259 patches applied and 612 bugs fixed between Python 2.5 and 2.6. Both figures are likely to be underestimates.

So there you have it! Rock on Python!

Django's New Comment System

There are a lot of exciting changes happening with Django right now. A lot. Some of these changes cause a lot of things to break across my sites. One such change was the integration of Thejaswi Puthraya's Summer of Code project: an improved comment system.

The first, and most obvious problem, was the change in the URLconf. This took me a while to track down for one reason or another. Here's the situation: originally, the django.contrib.comments application used a URLconf such as:

(r'^comments/', include('django.contrib.comments.urls.comments')),

This makes any comments-powered pages blow up. To solve this particular problem, just make it:

(r'^comments/', include('django.contrib.comments.urls')),

The next thing that caught me dealt with the templates for comments. Now there are actually some default ones, which is nice, but they might interfere with your own templates. I found that all I need in my templates/comments/ directory now is a single simple template called base.html:

{% extends 'base.html' %}

All of the other templates aren't needed unless you do some customized stuff (which I don't bother with).

Finally, and probably the most frustrating of all, getting an error such as:

NoReverseMatch: Reverse for '<function post_comment at 0xb504a1b4>' not found.

I'm not really sure why this problem has arisen, but my solution for it is to remove the entire django/contrib/comments/ directory and bring it back down from SVN. My guess is that some .pyc file lingering from the original comments application is interfering with the new comments application.

Feel free to post here if you have any other advice or problems!

Make Your Own iPod-Compatible Audio Books Using Linux

I like music a lot. I think I always have, and I probably always will. I like to be able to listen to good music wherever I go whenever I want. Thanks to the wonders of technology, we have a myriad of portable media devices to choose from. I personally chose an iPod nano. It's a wonderful little toy.

Anyway, as much as I like music, sometimes I feel that my time could be better used doing things more productive than just listening to music. Once I realized I felt this way, I began looking into ways to get my audio books onto my iPod. At first I simply transfered over the MP3s that came straight from the CDs. But I soon realized that this wasn't the most effective use of the iPod's audio book capabilities. So the hunt was on for some good Windows software to convert my MP3 audio books into M4B format for the iPod.

Now, I'm a pretty cheap guy when it comes to paying for software (which is probably one of the main reasons I started using Linux way back when). I found a bunch of different "free" tools that claimed to be able to convert my MP3's, but few of them actually worked well enough for me to stand using them. Eventually, I found a (very round-about) routine that allowed me to turn everything into something my iPod could understand as an audio book. I followed this routine to convert several audio books and transfer them to my iPod. I never actually finished listening to any of them completely.

Last night I started fooling around with converting my DVDs into a format my iPod could understand. When I finally got The Bourne Identity converted properly, I tried to throw it onto my iPod from my wife's Mac. It told me that I would have to erase everything (because I used my own PC to transfer my files before), and I said it was ok. I didn't have any of my original .m4b files around anymore, and so I began looking for ways of creating those audio books (in Linux this time).

It wasn't long before I stumbled upon a particularly interesting post on this exact topic. It requires the use of mp3wrap, mplayer, and faac. Pretty simple, really. Here's what you do:

# mp3wrap outputfilename *.mp3
# mplayer -vc null -vo null -ao pcm:nowaveheader:fast:file=outputfilename.pcm outputfilename_MP3WRAP.mp3
# faac -R 44100 -B 16 -C 2 -X -w -q 80 --artist "author" --album "title" --title "title" --track "1" --genre "Spoken Word" --year "year" -o outputfilename.m4b outputfilename.pcm

Nice and easy, huh? Now to decipher it all.

# mp3wrap outputfilename *.mp3

This command will stitch a bunch of MP3 files into a single MP3. This makes it easier to have a "real" audio book on your iPod.

# mplayer -vc null -vo null -ao pcm:nowaveheader:fast:file=outputfilename.pcm outputfilename_MP3WRAP.mp3

This command converts that one big MP3 file to PCM (uncompressed) format. Somewhere in the output of this command, you will see something like AO: [alsa] 44100Hz 2ch s16le (2 bytes per sample) which comes in handy for the next command:

# faac -R 44100 -B 16 -C 2 -X -w -q 80 --artist "author" --album "title" --title "title" --track "1" --genre "Spoken Word" --year "year" -o outputfilename.m4b outputfilename.pcm

Finally, this command turns the PCM file into an audio book (m4b) file. The 44100, 16, and 2 right after faac all come from that special line in the output of the mplayer command.

As much as I like the command line, I don't like having to remember all of those parameters and options. So I decided to create a utility script (written in Python, of course) to wrap all of these commands into one simple one:

# mp3s2m4b.py BookName mp3s_directory [--quality=0..100] [--artist="artist"] [--album="album"] [--title="title"] [--genre="genre"] [--year=year] [--track=number]

While this might still seem too complex for pleasure, it does reduce a lot of the typing involved with the other three commands. All of the thingies in square brackets (like [--quality=0..100]) are optional. My script runs the commands mentioned previously in order, and suppresses all of the scary output.

I've used my script 4 or 5 different times so far, and it seems to work great. You may download it here.

Flash & Silverlight - Possible Agenda Behind Them?

Being a web developer, I'm often presented with questions about what I prefer to use for building websites. I tend to build websites by hand using a plain old text editor. Obviously there are a lot of other tools that I use in the process of creating an entire website, but the fact that I use a text editor is sufficient information for the time being. Some of the people who ask me about my preferences often ask why I don't use Flash or similar technologies when they learn of my archaic habits. I have my personal reasons, but I recently read an article that presented many interesting thoughts concerning the potential future of Adobe's Flash and Microsoft's Silverlight that make me feel all the better about my decision to not use either one.

Many website owners do not feel that they can have a truly "professional" website without having at least one thing done with Flash or Silverlight. Developers often feel that technologies such as these save them massive amounts of time and offer increased functionality. I cannot argue with either of those points in a general sense (I actually used Flash a lot, long before Adobe bought Macromedia). Tristan Nitot, founder of Mozilla Europe, discussed the possibility of these two technologies becoming a problem for web developers in the future. As it stands right now, both companies freely release their respective plug-ins for use all over the world. But what happens if the companies decide to start charging for the plug-ins? Developers already have to fork out an arm and a leg just to be able to create Flash and Silverlight content. When will the consumers be forced to pay as well?

Nitot also feels that such technologies are limiting to the open nature of the Internet. He suggests that the next version of HTML will be capable of presenting video and audio files natively, requiring the user to download no plug-ins at all. This idea in and of itself reduces the usefulness of both Flash and Silverlight tremendously, as many websites simply use Flash applications for audio or video playback (think YouTube).

If you're a web monkey like myself, this article is worth a read. It definitely opened my mind to a few of the potential problems in the future of the Internet.

Super Computer

Ported From Blogger

The following post was ported from my old blogger account.

One of my good friends recently purchased a super nice computer system which should last him quite some time. He and I go back a couple years, and we devised a plan as to how I would set up his supercomputer when he got it a year and a half or so ago. This past weekend we carried out our plans. Saturday morning I woke up early and took my road trip up to Montana to take care of business.

I arrived at my friend's house around 10AM and work immediately commenced. Before I get too far into the details of the weekend, let me share a few of the vital specs of his computer:

  • Processor: AMD Athlon64 x2 4200+ (2.2Ghz) with liquid cooling
  • System Memory: 2GB DDR
  • Video: nVidia GeForce 7600 GS (512MB RAM)
  • Hard Drive: 2x 160GB SATA-II (320GB total)
  • Optical Media: 2x DVD+/-RW drives
  • Network: Wireless RaLink 2500 series
  • Monitor: 2x 19" Viewsonic LCD
  • Speakers: 5.1 Creative Surround Sound

Yeah, it's pretty sweet. I thoroughly enjoyed being able to work on it. I'll have to post some pictures of my friend's computer sometime. Ok, now on to the details of setting up his system.

My friend wanted to have both Windows and Linux on his system. We spent quite a bit of time around each other. Being the Linux nut that I am, he heard so much about Linux and wanted to get his fix. But he also wanted Windows for games and whatnot. Understandable. So we began the day trying to install Windows XP SP2 on his box. That was thoroughly painful, as usual. Installing a single driver, rebooting, installing another driver, rebooting, installing yet another driver, and once again rebooting. You'd think that the actual installation of Windows XP on a system such as his would be quite speedy. No, no... Microsoft never ceases to amaze me with the speed of Windows--or the lack thereof. It took at least an hour to get through all of the initial booting, setting up the partitions in a fashion that Windows could handle, installing drivers, and finally minimal essential software. Very rediculous. One of the best parts was that Windows somehow installed itself on the second hard drive...because of this (or some other unknown cause) Windows could not boot itself up. We had to use another bootCD in order to boot Windows. I assumed that GRUB (a Linux bootloader) would be able to circumvent this problem.

Once the first installation of Windows was complete and we had a backup of the installation, we proceeded to install SuSE 10.1 x86_64. This installation was painless. Everything worked extremely well. The hardest part about getting Linux to function properly was figuring out why his wireless adapter wouldn't connect to his router. It took a bit of time, but eventually we found the solution online (this solution also applied to my laptop, so I have great wireless in Linux now). As I was getting certain multimedia applications installed on Linux (since they're not included with SuSE for copyright reasons), we watched Hitch on the second monitor. It was great. Eventually Linux appeared to be set up and running perfectly. That was about the time we reboot Linux for the second time (once during installation, if I remember correctly).

Come to find out that not even GRUB could boot Windows. We were greatly frustrated, and my friend began to understand slightly more why I like Linux more than Windows. We decided to swap the hard drives around so that the drive with Windows already on it would be the primary master. I warned him that it would mean reinstalling Windows because it wouldn't know where to find itself after swapping the drives around. He was down with that, so that's what we did. We ran through the whole bloody process again. At least this time we knew what to expect when Windows complained about drivers--we'd already experienced it only hours before.

This installation of Windows went a bit more smoothly, but it also meant that we'd have to do something to get Linux back to an operational stage (firstly, get the GRUB boot menu back). I believe it was the first time I rebooted the computer after the second Windows installation that we got a "NTLDR is missing" error. Blasted Windows. I solved that problem, but then it started complaining about an invalid boot.ini file. Rubbish.

All we needed to do for Linux was pop the first install CD back in and run a rescue utility. It examined the existing installation and modified configuration files according to the drive swap. Linux was back up and running within 5 or 10 minutes. Windows, on the other hand, continues to complain at boot about the invalid boot.ini file.

And once again, my contempt for Windows has been reaffirmed. The only reasons I keep Windows around is for Adobe Creative Suite 2 and a game here or there. Even Google Earth runs natively on Linux (as of yesterday).