A gag for a Ben Stiller movie

Pale bearded guy wants girl. His woman-savvy friend advises him to ditch the beard and get a suntan. “She’ll look at you in a totally new light, man. She’ll be all, who’s this guy?” Guy takes his advice and lays in the sun. Music montage, natch. Then we see him shaving his beard off, with the lather. With great satisfaction he wipes off the last lather and dries his face. We see him step out the door into the light. His face is evenly bronzed… except for the entire lower half, pitifully white, minus a small tanned crescent above his chin!1

  1. I had a mild form of this. I thought it was razor burn until I realized with horror what had happened. []

Mos Def

Is it bad that I really like this lyric? You kind of have to hear it. From Close Edge:

I’m Mos Definite, not think so
Flood ya city with the black ink flow
And my crew ain’t scared to let them things go
So, stop with the nonsense, like he conscious
I’m just alright dawg, I’m doin’ great dawg
I don’t play games so I don’t playa hate y’all
Get it straight or get the fuck up out my face dawg
I’m like the second plane that made the tower’s face off
That shit that let you know it’s really not a game dawg1

Your grind and my grind ain’t the same dawg
2

I think this is appropriate. While I was in Hawaii I went to the Pearl Harbor memorial, where you stand on a platform above the sunken USS Arizona with its 900+ interred corpses. Before you go out to the platform you watch a video that details the sequence of events. The tone of the video is mournful, but it never goes so far as to condemn the Japanese for the attack, only reminding the viewer that they were an Axis power. Yamamoto is treated as a figure like Robert E. Lee, personally against the war but determined to make the best military showing. Then when the attack happens, you feel a chill: so many ships in so little time, and there’s video of the Arizona exploding. Basically, because this was an attack on a military base, it’s possible to experience this event first objectively as awesome (deinos3), then parochially (and humanely) as dastardly, tragic, sad, etc. Anyways, I figure Mos Def’s figure is precise here. I remember on September 11, someone told me that a plane had crashed into one of the Twin Towers, and I assumed that he was talking about some freakish accident. It was only when I heard about the second plane, and then the collapses, that I knew it really wasn’t a game.

  1. The heavy repetition (e.g., dawg, dawg, dawg, dawg, and -ow for six couplets at the beginning) works IMO. []
  2. The other stuff going on in the song is interesting too. He’s saying that he’s not a gangsta rapper but he’s not going to criticize those who are. I like the poetic conflation in hip-hop. His “grind” is and isn’t the same as drug-dealing. It’s like the different accounts of gangsta on Damn, It Feels Good to be a Gangsta, one of which describes the President of the United States as a gangsta. Then, the main idea of Mos Def’s song is that he’s “close to the edge”, not of flipping out and killing someone, but he’s paying attention to what’s happening on the margin of society. []
  3. It’s a cranky prescriptivist cliche to remind people that something that is awesome, like an “awesome god”, is something that is terrifying to behold. []

Apple Cocoa Cavil

I’m going to try to sound more like Andy Rooney1 up here on this blog. Also, how about I indicate when the boooring technical notes begin and end with technical and interesting.

This is one of my favorite xkcd comics. It really speaks to my experience. Usually I can pull away before I’ve finished registering for comments. Sometimes I’m halfway through a closely reasoned argument when I realize how perfectly pointless and non-personal-goal-advancing my actions are. Then, in the worst case scenario, there I am mixing it up with the other comment-warriors. Here’s me windmilling my way through a post about dolphin killing on Japanprobe. This used to be the url for a pitched brawl in which I interjected a few uninformed comments. Etcetera.

Anyway, I thought I’d write this post at a more meta level to dissuade myself from commenting elsewhere. So here goes (technical):

Have you ever noticed that Objective-C is really, really weird? Like, they just took all the C- and C++- style conventions and changed them? Me too. And on top of that it’s compiled and you do memory management and the engineers make APIs that have objects called NSCamelCasedFactoryMethodObjectFacilitator2. Okay, so then someone makes a script-y dynamic thing for managing the Objective-C stuff, good idea. And when designing this scripting interface they make the following language syntax design decisions:

Finally, the instruction separator is a dot, like in English sentences:
myString := ‘hello’.

The following example shows how to send a message to an object:
myString class

See, this is funny, because it’s completely different from every other programming language3. That is all.

Umm, but there is a somewhat interesting take-away. Both Apple and Microsoft have designed really sucky APIs (in terms of intuitability rather than functionality) , compared to which GTK is fairly sane (it gets a bit clunky when dealing with “GtkIter” operations). But the MacOS developers follow Apple’s improvements of this API, cooing over the increased simplicity afforded by the new NSMakesYourToastRegistry. It’s the same with new C#/ Windows API developments. So (this is actually the interesting part) the lesson is that when people work within a “closed” development system, they lose their sense about good and bad design!4

Here’s the idea. Closed development systems don’t get good feedback and don’t have good change mechanisms, so even very good engineers (probably Apple’s are some of the best) end up working in the dark a little. It gets all culty, because there’s an elect that makes the design decisions and a laity that passively learns the new scripture. And everyone’s straining so hard to understand what the design class hath laid down that they’re no longer perceiving the design objectively. And proprietary lock-in helps, because it leads to fatalism (“what can I do, switch to Windows?”). There are all these weird little island communities where the natives are effectively locked-in to a platform because they’ve already invested the energy to understand its weird design. This isn’t even necessarily a proprietary vs. opensource thing. There are strange over-designed opensource projects that aren’t particularly open because of this class division (and most opensource projects rely on only 1-3 main contributors, it seems). All I’m saying is that bad APIs / development languages happen when designers aren’t being influenced in the right way by the end-user developers, and I’m speculating that this has to do with particular attitudes and processes associated with proprietary code and also a kind of design elitism. I mean, doesn’t Objective-C code (as code) suck?

  1. I include this link because I think this already marginal reference will become incomprehensible in ten years. []
  2. Yeah, I’ve got their number all right. []
  3. Actually, these are pretty interesting design decisions. The := assignment syntax is wack, but probably necessary for named arguments or something. The dot on the end is okay, but you’re moving the OO-messaging operator into the generally useless semicolon position. By using the space for messaging, you’re now saying “subject verbs(args)” instead of “subject.verb object, args” (in Ruby you can omit the parens for a function). []
  4. So I sort of believe that. Mainly I’m bitter because I can’t get some code to work on MacOS. []

Update

My life continues apace. At the moment, I’m looking for a job for the summer and onward1 , hopes fairly high. Going to Hawaii on Tuesday2.

Trends:

  • obsessive behavior
  • competence?
  • health?

An example of the first is the playing of this game. Probably I get in at least one game a day, usually more. A game will usually last an hour, but some games go much longer. Also, my computer is supposed to prevent me from typing/mousing for more than hour, and I will click to bypass its 3 minute break message for hours on end. You read me? Sometimes, say at periods like this when I’m trying to get on to the next thing, I “invest” my mind in the internet, just as during the semester I invested it in academic philosophy. That is, I try to catchup with the flow of information, trends, etc., at least in the domains I care about (mostly opensource software and some world affairs). What really happens is that my hands start hurting and I lose my attention span.

My sense of the second comes from a sudden improvement in my ability to “see” code. I made this portfolio site to show potential employers. It’s in PHP, which I don’t really know, but it’s logic, so I can hack out something like that with a little investigation of the syntax and standard libraries. I’ve also been writing a userspace driver for a usb device (the famous AgileLamp USB Lava Lamp!), which has been a really mysterious, frustrating process where I’ve ended up experimenting with several language bindings for the woefully under-documented libusb library. I ended up falling back to the original C library itself, and found that it’s no more or less understandable as logic than Ruby or Python (although as human-readable code C sucks). So it’s a sense of, if it’s code then it’s hackable. During the spring semester I got this way with writing philosophy papers, since these papers are all supposedly supposed to be publishable. I can take apart a topic and discuss it in a learned fashion for 15+ pages3. I’d like to get this way with electronics and maybe languages4.

By the last I only mean that I really enjoy riding around town on my bike. I have a route to school and can get to a couple interesting neighborhoods from my home. By health I do not mean not eating Jalapeno Cheetoses. Also, Amy and I found a very cheap pizza place that delivers, called Maximum Flavor. Their flavor truly cannot be surpassed. Attempts have been made, and all have failed, with tragic, often fatal, results. Pretty good pizza.

Anyways5, I’ll try to write here more, generally on more limited, technical topics, because “I’m just not that disclosive”6. Look at the timestamp on this post and you’ll get a sense of where I’m at, psychophysically. My wrists hurt and my eyes are burning/droopy.

  1. I will learn the formal rule for when to use onward vs. onwards. []
  2. Because I am wealthy and carefree. []
  3. Which points to how silly “philosophy” is as a discrete academic domain. So just say this: all knowledge-endeavor is in the domain of philosophy, but not all endeavors are sufficiently philosophically reflective. This means: there shouldn’t be any philosophy grads, but other disciplines should be required to be much deeper about what they’re doing, e.g., law. []
  4. I know my best years for this are behind me. sniff []
  5. I will learn the formal rule for when to use anyway vs. anyways. []
  6. As Joe is wont to say about himself []

Setup for Alexandria Development: III

The following code requires Alexandria trunk. For more information see Cathal’s article. You can get this file here or as a full gem source package through svn:

svn co http://alexandria.rubyforge.org/svn/alexandria/trunk/readinglist .
$:.unshift File.dirname(__FILE__)
 
=begin
 
This is a sample (but useable) app for maintaining a reading list that takes
its reading options directly from Alexandria. At the moment it allows you to
move books to the reading list, remove them, mark them as read, and reorder
them. It's commented so as to be a kind of tutorial. The reader is encouraged
to play with it and try to add functionality. 
 
This code demonstrates some of the most common operations in a Gtk app:
creating menus, hooking up a treeview and syncing it to data, and widget
packing. Of these, working with the TreeView will probably be the most
confusing. The basic concept behind treeviews is that a treeview is the
graphical widget that administers a "store" or short-term database (usually
either a ListStore or a TreeStore) which in turn is concerned with managing
either a list-like or tree-like structure of TreeIters. In the case of a
ListStore, a TreeIter represents a row that you see in the TreeView, and the
indices of the TreeIter (like iter[0], etc.) return the values for the columns
in that row. One thing to know about GTK Iter objects is that they like to get
"invalidated" whenever the managing View changes, so for example if you get the
iter for the user's selection of the third row of a TreeView and store it for
later, you'll find that the iter itself (as opposed to its TreePath, or
"absolute" location) is no longer available. Clear? It's definitely recommended
that you take a look at this tutorial:
 
http://ruby-gnome2.sourceforge.jp/hiki.cgi?tut-treeview
 
=end
 
module Readinglist
  require 'yaml'
  require 'alexandria'
  require 'gtk2'
 
  class ReadingListApp
    FILE_FORMAT = {:to_read => [], :have_read => []}
    READING_LIST_FILE = File.join(ENV["HOME"], ".reading_list.yml" )
 
    # I try to decompose methods as much as possible to show the procedural
    # skeleton of the program. I name the methods to read like sentences. 
 
    def initialize
      load_reading_list
      get_books
      setup_gui
      load_books_into_listview
    end
 
    # We use YAML as the "serialization" strategy. That is, we make sure a hash
    # is stored in a file in the user's home directory. We load it into memory,
    # manipulate it, and always make sure to sync it back to the file. This is
    # exactly how books are loaded in Alexandria. Alexandria even stores the
    # class instance to the file. 
 
    def load_reading_list
      unless File.exist?(READING_LIST_FILE)
        File.open(READING_LIST_FILE, 'w') do |file|
          file.write(FILE_FORMAT.to_yaml)
        end
      end
      database = YAML.load_file(File.join(ENV["HOME"], ".reading_list.yml" ))
      @reading_list = database[:to_read]
      @have_read = database[:have_read]
      puts @reading_list.inspect
    end
 
    # I have to make sure the listview and the data store are in sync, and I
    # want to make sure that when the program finishes the latest changes are
    # committed to file. This could get too expensive with enough books,
    # though. In Alexandria, some changes happen immediately (changes in book
    # attributes, covers) while others only occur on a clean program shutdown
    # (deleting, saving certain preferences).
 
    def save_to_yaml
      database = FILE_FORMAT
      database[:to_read] = @reading_list 
      database[:have_read] = @have_read 
      File.open(READING_LIST_FILE, 'w') do |file|
        file.write(database.to_yaml)
      end
    end
 
    # I initialize the Libraries simpleton (available through the above
    # require) and ask it to reload its list of libaries. Then the loadall class
    # method on Library gives me an array of arrays of books, which I then
    # flatten; that is, make into one long array. 
 
    def get_books
      libraries_simpleton = Alexandria::Libraries.instance
      libraries_simpleton.reload
      libraries = Alexandria::Library.loadall
      @books = libraries.flatten
    end
 
    # More declarative-style methods for erecting the GUI. This type of code
    # ends up being very mechanical to write, which is why people like to use
    # tools like Glade. In fact, keeping this gui layout code in the open is
    # probably a better idea for long-term maintenance. 
 
    def setup_gui
      setup_window
      setup_menus
      setup_current_reading_list
      setup_available_books_list 
      refresh_reading_list
      Alexandria::UI::Icons.init
      setup_callbacks
      @window.show_all # This needs to be called after widgets are packed. 
    end
 
    # For information on packing, see
    # http://ruby-gnome2.sourceforge.jp/hiki.cgi?tut-gtk2-packing-intro . Here
    # @window, which can only contain one child widget, gets a VBox. I'll put
    # several widgets inside @vbox, including more "container" widgets (like
    # HBox), in which I put yet more widgets.
 
    def setup_window
      @window = Gtk::Window.new("Reading List")
      @window.set_width_request(800)
      @window.set_height_request(600)
      @window.add(@vbox = Gtk::VBox.new)
    end
 
    # For information on callbacks in Gtk see
    # http://ruby-gnome2.sourceforge.jp/hiki.cgi?tut-gtk-signals . Ruby-gnome2
    # callbacks use the Ruby do/end block syntax. Since I don't like to define
    # the callback method inside the block, I use the method() function to turn
    # the method into a proc, and use the & syntax for passing in a "proc"
    # object to the block.
 
    def setup_callbacks
      @available_treeview.signal_connect("row-activated", &method(:on_row_activated))
      @window.signal_connect("delete-event", &method(:on_quit))      
    end
 
    # Pretty straight-forward, if wordy. The ImageMenuItems can use a
    # Gtk::Stock:: constant to save some work and get pretty icons. :expand =>
    # false is used to keep widgets from bulging out.
 
    def setup_menus
      @vbox.add(menubar = Gtk::MenuBar.new, :expand => false)
      menubar.append(file_menu = Gtk::MenuItem.new("_File"))
      menubar.append(edit_menu = Gtk::MenuItem.new("_Edit"))
      menubar.append(help_menu = Gtk::MenuItem.new("_Help"))
      file_menu.submenu = file_submenu = Gtk::Menu.new 
      edit_menu.submenu = edit_submenu = Gtk::Menu.new 
      help_menu.submenu = help_submenu = Gtk::Menu.new 
      file_submenu.add(Gtk::ImageMenuItem.new(Gtk::Stock::SAVE_AS))
      file_submenu.add(quit_item = Gtk::ImageMenuItem.new(Gtk::Stock::QUIT))
      quit_item.signal_connect("activate", &method(:on_quit))
      edit_submenu.add(Gtk::MenuItem.new("Mark selected _read"))
      help_submenu.add(about_submenu = Gtk::ImageMenuItem.new(Gtk::Stock::ABOUT))
    end
 
    # To setup the "current reading list" on top. The TreeView is connected
    # directly to the ListStore, but the TreeViewColumns and the CellRenderer*s
    # connected to them are responsible for telling the TreeView _how_ to
    # display the data within the ListStore. The argument :text => n is a
    # shorthand to tell the TreeViewColumn to associate with a position or
    # index in the TreeIter (row).
 
    def setup_current_reading_list
      @vbox.add(Gtk::Label.new("Books I am reading:"), :expand => false)
      @vbox.add(hbox = Gtk::HBox.new)
      hbox.add(scrolley1 = Gtk::ScrolledWindow.new)
      hbox.add(@button_vbox = Gtk::VButtonBox.new, :expand => false)
      setup_side_buttons
      scrolley1.add(@reading_treeview = Gtk::TreeView.new)
      @reading_treeview.model = @reading_store = Gtk::ListStore.new(Integer, String, String)
      renderer = Gtk::CellRendererText.new 
      reading_column1 = Gtk::TreeViewColumn.new("Order", renderer, :text => 0)
      @reading_treeview.append_column(reading_column1)
      reading_column2 = Gtk::TreeViewColumn.new("Title", renderer, :text => 1)
      @reading_treeview.append_column(reading_column2)
      reading_column3 = Gtk::TreeViewColumn.new("Author", renderer, :text => 2)
      @reading_treeview.append_column(reading_column3)
    end
 
    def setup_side_buttons
      @button_vbox.layout_style = Gtk::VButtonBox::START 
      @button_vbox.add(up_button = Gtk::Button.new(Gtk::Stock::GO_UP))
      @button_vbox.add(down_button = Gtk::Button.new(Gtk::Stock::GO_DOWN))
      @button_vbox.add(read_button = Gtk::Button.new("Read"))
      @button_vbox.add(remove_button = Gtk::Button.new(Gtk::Stock::REMOVE))
      up_button.signal_connect("clicked", &method(:on_click_up))
      down_button.signal_connect("clicked", &method(:on_click_down))
      remove_button.signal_connect("clicked", &method(:on_click_remove))
      read_button.signal_connect("clicked", &method(:on_click_read))
    end
 
    # Same as above, except with the added wrinkle that a TreeModelSort, using
    # a TreeModelFilter, is acting as a kind of proxy for the ListStore. This
    # is to support sorting of columns. This code is ripped off wholesale from
    # Alexandria.  
 
    def setup_available_books_list
      @vbox.add(Gtk::Label.new("Available books:"), :expand => false)
      @vbox.add(scrolley2 = Gtk::ScrolledWindow.new)
      scrolley2.add(@available_treeview = Gtk::TreeView.new)
 
      @list_store = Gtk::ListStore.new(Gdk::Pixbuf, String, String)
      @filter = Gtk::TreeModelFilter.new(@list_store)
      @available_treeview.model = @available_books_model = Gtk::TreeModelSort.new(@filter)
 
      setup_available_books_title_column
 
      renderer = Gtk::CellRendererText.new
      column2 = Gtk::TreeViewColumn.new("Author", renderer, :text => 2)
      column2.resizable = true
      column2.sort_column_id = 2
      @available_treeview.append_column(column2)
    end
 
    # This TreeViewColumn has two widgets, a CellRendererPixbuf for the book's
    # icon, and a regular CellRendererText for the book's Title. I have to tell
    # the CellRendererPixBuf how to display itself int the set_cell_data_func.
    # The convert_iter_to_child_iter is some kind of bookkeeping for the
    # TreeModelFilter.
 
    def setup_available_books_title_column
      column = Gtk::TreeViewColumn.new("Title")
 
      renderer = Gtk::CellRendererPixbuf.new 
      column.sizing = Gtk::TreeViewColumn::FIXED
      column.fixed_width = 200 
      column.widget = Gtk::Label.new("Title").show
      column.pack_start(renderer, expand = false)
      column.set_cell_data_func(renderer) do |column, cell, model, iter|
        iter = @available_treeview.model.convert_iter_to_child_iter(iter)
        iter = @filter.convert_iter_to_child_iter(iter)
        cell.pixbuf = iter[0]
      end
 
      renderer = Gtk::CellRendererText.new
      renderer.ellipsize = Pango::ELLIPSIZE_END if Pango.ellipsizable?
      column.pack_start(renderer, expand = true)
      column.add_attribute(renderer, :text, 1)
      column.sort_column_id = 1
      column.resizable = true
 
      @available_treeview.append_column(column)
    end
 
    # This supplies the actual data to @available_treeview. Icons.cover creates
    # a Gdk::PixBuf (image object) from cover files stored in the .alexandria
    # directory.
 
    def load_books_into_listview
      @books.each do |book|
        icon = Alexandria::UI::Icons.cover(book.library, book)
        icon = icon.scale(20,25) 
        iter= @list_store.append
        iter[0] = icon 
        iter[1] = book.title
        iter[2] = book.authors.join(" ")
      end
    end
 
    # Call this when you want to repopulate and reorder the reading_list. 
 
    def refresh_reading_list 
      @count = 0
      @reading_store.clear
      @reading_list.each do |item| 
        iter = @reading_store.append # Gets a new iter (row) to work with.
        iter[0] = @count += 1
        iter[1] = item[0]
        iter[2] = item[1]
      end
    end
 
    # @reading_treeview.selection.selected is the iter of the selected row. 
 
    def on_click_remove widget
      selection = @reading_treeview.selection.selected
      @reading_list.delete_at(selection[0].to_i - 1)
      save_to_yaml
      refresh_reading_list
    end
 
    def on_click_read widget
      selection = @reading_treeview.selection.selected
      @have_read << @reading_list.delete_at(selection[0].to_i - 1)
      save_to_yaml
      refresh_reading_list
    end
 
    # The idea is to swap the iters in the TreeView and mirror the swap in the
    # reading list. The iter's path is like its current map coordinates. Since
    # iters are always getting invalidated, it's a good plan to work with the
    # path. 
 
    def on_click_up widget
      selection = @reading_treeview.selection.selected
      position = selection[0].to_i - 1
      previous_path = selection.path
      previous_path.prev!
      previous = @reading_store.get_iter(previous_path)
      @reading_store.move_before(selection, previous)
      unless (position - 1) < 0
        @reading_list.insert(position - 1, @reading_list.delete_at(position))
        refresh_reading_list
      end
      @reading_treeview.selection.select_path(previous_path)
    end
 
    # on_click_up and on_click_down call for refactoring to reduce duplicated
    # code. Try it for yourself if you're interested.  
 
    def on_click_down widget
      selection = @reading_treeview.selection.selected
      position = selection[0].to_i - 1
      previous_path = selection.path
      after_path = selection.path 
      after_path.next!
      after = @reading_store.get_iter(after_path) 
      unless (position + 1) == @reading_list.length
        @reading_store.move_after(selection, after)
        @reading_list.insert(position + 1, @reading_list.delete_at(position))
        refresh_reading_list
        @reading_treeview.selection.select_path(after_path)
      end
    end
 
    # This is the callback for when a row in the lower available books listview
    # gets clicked.
 
    def on_row_activated widget, path, column
      iter = @available_treeview.model.get_iter(path)
      puts "#{iter[0]} #{iter[1]} #{iter[2]}"
      reading_list_item = []
      reading_iter = @reading_store.append 
      reading_iter[0] = @count += 1
      reading_iter[1] = iter[1]
      reading_list_item << iter[1]
      reading_iter[2] = iter[2]
      reading_list_item << iter[2]
      @reading_list << reading_list_item 
      save_to_yaml
    end
 
    # This method is called when the window is closed and when the quit menu
    # option is activated. Event is used for when the window connects to the
    # 'delete-event' signal and requires both parameters. Gtk.main_quit kills
    # the Gtk.main loop. 
 
    def on_quit widget, event = nil
      Gtk.main_quit
    end
  end
 
  def self.main
    ReadingListApp.new
    Gtk.main
  end
end
 
=begin
 
Some features that could be added: 
 
* connect up menu items 
* figure out how save to... works; does it export to one specific format or
  several?  
* Not a feature, but can the code be made cleaner, cleaner, more testable? 
* Something indescribably awesome...
 
A couple random tips: 
 
install ruby-debug gem to step through this code to see how it works
See http://cheat.errtheblog.com/s/rdebug/ for more information
 
install the utility_belt gem for colorized irb and add  
 
require 'rubygems'
require 'utility_belt'
 
to a file ~/.irbrc
 
=end
 
if __FILE__ == $0
  Readinglist.main
end

Setup for Alexandria Development: Part II

(…after too much grief today installing Mephisto and mucking with Apache virtualhosts; I’ll get Part I back from the ether eventually) Update: Done. Update: This is a post moved over from the short-lived Mephisto blog, and ported back in time.

First of all, the alexandria binary is just a ruby script that does a require ‘alexandria’ and runs Alexandria.main.

Alexandria.main is a method on the Alexandria ‘module’ that is used throughout the code (modules are ‘namespaces’ to avoid naming conflicts). This method is found in lib/alexandria.rb:

As you should be able to see, this method isn’t doing anything but setting up some global variables (like $DEBUG) and logging, and doing something weird with http_proxy. The real line is Alexandria::UI.main. That’s in lib/alexandria/ui.rb:

module Pango
  def self.ellipsizable?
    @ellipsizable ||= Pango.constants.include?('ELLIPSIZE_END')
  end
end
 
module Alexandria
  module UI
    def self.main
      Gnome::Program.new('alexandria', VERSION).app_datadir =
        Config::MAIN_DATA_DIR
      Icons.init
      MainApp.new
      Gtk.main
    end
  end
end

Gtk.main is the main loop of a gtk program. You set up your windows and widgets before running it, and it makes them all spin until you exit. So, after Icons.init runs (guess what that does), MainApp.new does all the work from now on.

The Pango code above this is interesting for seeing some Ruby syntax and features. Pango is a text-rendering and layout library inside gtk. The code is adding an elipsizable? “question” method (return true/false) to the Pango module. self.elipsizable? means that it’s defining a class method, a method on a class that doesn’t depend on instance data. ||= is a way of saying, “set the variable to this unless it’s already been set to something else (ie, it’s not nil)”.

Unfortunately, MainApp.new is in the massive MainApp class at lib/alexandria/ui/main_app.rb. This class does a lot (too much). The main thing it does is handle all the callbacks from the main window and its widgets. Let’s just take a look at the top:

 
module Alexandria
  module UI
    class MainApp < GladeBase
      attr_accessor :main_app, :actiongroup, :appbar
      include Logging
      include GetText
      GetText.bindtextdomain(Alexandria::TEXTDOMAIN, nil, nil, "UTF-8")
 
      module Columns
        COVER_LIST, COVER_ICON, TITLE, TITLE_REDUCED, AUTHORS,
        ISBN, PUBLISHER, PUBLISH_DATE, EDITION, RATING, IDENT,
        NOTES, REDD, OWN, WANT, TAGS = (0..16).to_a
      end
 
      # The maximum number of rating stars displayed.
      MAX_RATING_STARS = 5
 
      def initialize
        super("main_app.glade")
        @prefs = Preferences.instance
        load_libraries
        initialize_ui
        on_books_selection_changed
        restore_preferences
      end
    #... snip
    end
    # ... snip
  end
end

A couple points here. MainApp inherits from GladeBase. The attr_accessor is a declaration that makes the @main_app, @actiongroup and @appbar instance variables publicly readable and settable. super(“main_app.glade”) calls the initialize method on GladeBase with the glade file that contains the definitions for all the widgets Alexandria uses. The names of the methods tell you about what they do (good!). Because these methods need to know about what the user’s preferences are, @prefs has been made available before they are called.

To understand what MainApp is doing, it seems like we need to understand what GladeBase is.

module Alexandria
  module UI
    class GladeBase
      def initialize(filename)
        file = File.join(Alexandria::Config::DATA_DIR, 'glade', filename)
        glade = GladeXML.new(file, nil, Alexandria::TEXTDOMAIN) { |handler| method(handler) }
        glade.widget_names.each do |name|
          begin
            instance_variable_set("@#{name}".intern, glade[name])
          rescue
          end
        end
      end
    end
  end
end

So GladeBase is using GladeXML to get the widgets out of the xml file and load them into memory. It then iterates through them, *adding them to MainApp (instance_variable_set is doing the work). So if there’s a widget called @main_menu, MainApp will get this variable to work with. These widgets work exactly as though they had been created “by hand”.

If you’ve been following, take a look at load_libraries and see if the code there makes sense. Here’s a short snippet:

      def load_libraries
        completion_models = CompletionModels.instance
        if @libraries
          @libraries.all_regular_libraries.each do |library|
            if library.is_a?(Library)
              library.delete_observer(self)
              completion_models.remove_source(library)
            end
          end
          @libraries.reload
        else
          #On start
 
          @libraries = Libraries.instance
          @libraries.reload
# ...

This is where things start to get confusing. load_libraries is also being used to reload libraries, so first it checks to see if @library has been defined already (refactoring opportunity). In the normal case, Libraries gets called by by invoking Libraries.instance. To understand this, you have to know that Libraries uses a factory class method to make sure that Libraries only gets created once (making the Libraries instance a “singleton”).

At the bottom of load_libraries is some interesting code:

# ...
        @libraries.all_regular_libraries.each do |library|
          library.add_observer(self)
          completion_models.add_source(library)
        end
# ...

This is telling each library in @libraries (the Libraries singleton) to add self as an “observer”. What does this mean? It means that class Library is “observable”. To see what that means you have to look at Library. First let’s look at Libraries, in lib/alexandria/library.rb:

  class Libraries
    attr_reader :all_libraries, :ruined_books
 
    include Observable
    include Singleton
 
# ... snip
 
    #######
    private
    #######
 
    def initialize
      @all_libraries = []
    end
 
    def notify(action, library)
      changed
      notify_observers(self, action, library)
    end
  end
end

Libraries is including the Observable and Singleton modules to give it special methods (in Python these are called “mixins”). Singleton gave it the instance method. Observable is giving it the notify_observers method. What this method does is “call up” all the observers of this instance by calling their update methods.

Libraries has many Librarys (it’s a little weird to give a class a plural name). Each library is an observer of Libraries. Library is also Observable:

 
  class Library < Array
    include Logging
# ...
    include Observable

As we saw above, MainApp adds itself as an observer to each library. If you look on MainApp you’ll see that it has an update method:

def update(*ary)
# ...
  end

*ary means that it accepts an array as its argument. This method gets called from many places in Library, like this:

        source_library.notify_observers(source_library,
                                        BOOK_REMOVED,
                                        book)

That’s all for now. To learn more about Observers read this.

Setup for Alexandria Development: Part I

This is the first in a series of brain-dumps of my knowledge about Alexandria and related development issues. Be warned, the approach I will take in these posts will be to discuss boring and perhaps obvious details as they occur to me. You are advised to skim.

Getting the code

First things first, you should be able to checkout a copy of Alexandria from subversion. You can find instructions here, but unless you want to pull down the entire tree this is the actual URL you want:

svn co svn+ssh://method@rubyforge.org/var/svn/alexandria/trunk/alexandria

Btw, this is worth looking at if you want to play around with code without committing to a central repository.

Initial setup

Let’s look at the directory structure of the checked out copy (called the working directory).

(alexandria root)
alexandria.desktop.in (Used to add Alexandria to the Gnome menu)
Rakefile                         (The `rake` command looks for this)
/spec                            (Specs go in here)
alexandria.xcodeproj        (MacOS XCode project file)
/data                            (Configuration files go here)
/lib                               (Alexandria code libraries are here)
tasks.rb                        (Rakefile uses this file)
/bin                              (Actual system-wide alexandria command goes here)
/debian                         (Contains templates needed to create debs)
/tests                           (For old 'test/unit' tests)
/doc                             (Docs go here)
/po                               (Language files go here)
/schemas                       (Used in gconf, configuration file like Windows registry)

You will need to get a copy of rubygems. For some reason, the Ubuntu packaged rubygem never seems to actually work, so you should just compile and install rubygems from here. On Ubuntu or Debian, you should run sudo apt-get install build-essential ruby1.8-dev because some gems will need to build “extensions”. You can use either your distro’s rake or install rake from gem. You install gems with:

sudo gem install (package)

You should install rake, rspec, rcov and zentest (autotest):

sudo gem install rake rspec rcov zentest

To work on the website you will also need staticmatic.

Rake and Testing

In the root of your working directory you should now be able to type rake -T and you will see a long list of rake “tasks” defined in the Rakefile and tasks.rb. The most important tasks for development purposes are sudo rake install to install to your system (it installs in /usr/lib/ so be careful) and rake spec, for running the test suite.

Rspec is super cool, but you’ll have to study the tutorials to learn how to use it. A great way to learn Ruby and Rspec at the same time is to ‘spec out’ basic Ruby types! For example, if you’re unsure about how an array method works, you can do this:

describe Array do
   it "should sort strings alphabetically" do
      ["b", "a", "c"].sort.first.should == "a"
   end
end

Just don’t get confused by the pattern of writing specs to cover code that’s already been written. The basic idea behind Behavior-Driven Development is that you write tests that show how your code will behave before writing the code. The only way to really learn how to do this is to force yourself to write some code this way.

Because BDD is supposed to happen before you write code, Alexandria has very poor test “coverage” at the moment, and its not easy to add specs to the code the way it is now. Still, it’s good practice to try and understand the behavior of a method on a class and write a spec for it. Take a look at the files in specs/alexandria for examples.

When a project has good test coverage it’s possible to work according to a very fast “red-to-green” development cycle. Autotest is a tool that will run ‘rake spec’ every time you change a file that’s being monitored. This is great because, again if the test suite is good, you can know the second you break the code! It’s even better if you use desktop notifications with Autotest. This is the version I use with Ubuntu Gutsy. One note: the file he links to is only good for Gentoo, you want this one.

That’s all for now. I’ll do another one tomorrow.

What’s this?

I just got this at the top of a search for “ruby rake” on Google.

Ruby — Rake: 4
According to http://jimweirich.umlcoop.net/index.cgi/Tech/Ruby - More sources »

The url under “More sources” goes here. All I can figure is that this is some kind of authority thing, or like the wtf feature on Technorati. jimweirich is a 4 or something. Maybe this is nothing, or maybe this is the beginning of semantic categorization on Google!!! ??? Why is this important? Well, if you search for Martin Luther King, one of the top links goes to a white supremacist hate page. It may be that Google is moving away from its raw algorithm, which can be gamed, and toward a trustweb system. Actually, it just occurred to me that that result could be from the Google search results tagging system that is already in place. So, is this old news?