Sunday, January 16, 2011

Ruby: Array of Hashes to 2-D Array

Quick Ruby trick.

It is often useful to convert an Array of Hashes (representing a table of objects) to an Array of column names (table header) and an Array of rows (table data). The canonical example would be taking the result of a DB query (ala Sequel) and displaying it in an HTML table (ala DataTables).

Without further ado, here is a proper map/inject one-liner:

array.inject( [data.first.keys, []] ) { |memo, row| memo[1] << memo[0].map { |key| row[key] } ; memo }


A 2-dimensional Array is returned. The first dimension contains the column names, and the second contains the row data (in the same order as the column names, which is the tricky part as Hash#keys can return the keys in any order).

The columns can be sorted (or otherwise ordered specifically) by manipulating data.first.keys when it is initially stored in memo[0].

Example of usage:

irb > data = [ { a: 1, b: 2, c: 3 }, { c: 9, b: 8, a: 7 } ]
irb >  cols, rows = data.inject( [data.first.keys, []] ) { |arr, row| arr[1] << arr[0].map { |key| row[key] } ; arr }
irb > cols
 => [:a, :b, :c]
irb > rows
 => [[1, 2, 3], [7, 8, 9]]

Thursday, January 6, 2011

QScintilla and getSelection()

The recent Ubuntu upgrade and subsequent Ruby woes were caused, of course, by the desire to install libqscintilla-ruby, a package only available on 10.10 (and even then, only for 1.8).

For the most part, QScintilla works fine in Ruby... until one encounters methods like this:

void getCursorPosition(int *line, int *index) 

void getSelection(int *lineFrom, int *indexFrom, int *lineTo, int *indexTo)


The docs provide some hint as to the problem:

If there is a selection, *lineFrom is set to the line number in which the selection begins and *lineTo is set to the line number in which the selection ends. (They could be the same.) *indexFrom is set to the index at which the selection begins within *lineFrom, and *indexTo is set to the index at which the selection ends within *lineTo. If there is no selection, *lineFrom, *indexFrom, *lineTo and *indexTo are all set to -1.

How does one pass an integer by reference in Ruby?

The answer: one doesn't. These functions take integer arguments and return nil, making them entirely useless in Ruby.

The Python guys did it right:

line_fro, idx_fro, line_to, idx_to = getSelection

The Ruby guys, of course, were lazy, and routed all calls directly to libqscintilla.so regardless of the sanity of their argument lists.


There is a way to make things work, however, thanks to ScintillaBase.


This, the base class of the Scintilla widget, provides the following method:

long SendScintilla(unsigned int msg, unsigned long wParam=0, long lParam=0)

At the top of  the base class documentation are a bunch of enums that look promising:

...
 SCI_SETSELECTIONSTART = 2142,   
 SCI_GETSELECTIONSTART = 2143,
 SCI_SETSELECTIONEND = 2144,  
 SCI_GETSELECTIONEND = 2145, 
...


Sure enough, these turn out to be the values for the msg parameter.


It makes for short work to add the following methods to the Scintilla object:


def get_sel_start
    # SCI_GETSELECTIONSTART
    self.SendScintilla(2143), 0, 0)
end

def get_sel_end
   # SCI_GETSELECTIONEND
    self.SendScintilla(2145), 0, 0)
end

def get_current_pos
    # SCI_GETCURRENTPOS
    self.SendScintilla(2008), 0, 0)
end


Yes, the messages have to be passed by number, as the symbols for the bulk of the messages have not been defined as constants in Ruby (lazy! bad! lazy!), as can be determined by examining Qsci::ScintilaBase.constants.

Monday, January 3, 2011

Ubuntu getting crappier and crappier

Stupidly made the decision to upgrade to 10.10 (for a single package!) on the main laptop (mbpro 5,5) and poof! No wireless!

Seriously, it's been three releases since an ethernet cable was required to do an upgrade.

Piece.
Of.
Shit.

Only question now is how many hours of productivity are going to be lost fixing what *was* a perfectly working system before the upgrade.

The sad fact is that os x and windows are just as unreliable for upgrades (while being less usable), and FreeBSD refuses to even glance at this hardware.

A decade ago, this shit used to *work*!

Running log of the fixes:

* apt-get install gnome-icon-theme to get NetworkManager running again. There is no fix for Wicd; it has apparently "stopped working".

* apt-get install gnome-alsa-mixer to un-mute the sound. Kmixer has been reduced from 7 or so channels to 1 (without a mute option).

* uninstall ruby 1.9 via apt-get. Download and install rvm (system-wide as this is a workstation), then do 'sudo rvm --default use 1.9.2' to make 1.9 the system-wide default (might roll that back later).

* reinstall all 1.9 gems.

* rebuild the passenger apache module ('sudo passenger-install-apache2-module') and update the config files to point to the new location.

* fix all rack-based webapps to include the line
    $: << File.dirname(__FILE__)
in config.ru and in each Sinatra::Base application file, as something got screwed in the ruby-passenger-rack environment, and PassengerRoot is no longer in the Ruby module path.

See? A smooth, seamless upgrade! You almost don't even notice that it happened!