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!

Thursday, December 16, 2010

Apache + Passenger + Sinatra

OK, this seems like a pretty normal situation, and like most normal situations it apparently never occurs in nature (judging by the available docs).

An existing Apache webserver is to have a new webapp added to it, in a subdirectory of the DocumentRoot. Sinatra is the framework to be used, and Passenger is going to route requests from Apache to Sinatra without going through any mod_proxy or mod_rewrite business.

Assume the following: Apache2, an Ubuntu system, and all of the relevant gems have been apt-get installed. The web server root directory is /var/www, and the webapp will be in /var/www/timon.

First, update the Apache config file (/etc/apache2/apache2/conf):

<VirtualHost *:80>
  ServerName Apemantus
  DocumentRoot /var/www

  <Directory />
    Options -Indexes
    AllowOverride None
  </Directory>

  <Directory /var/www/>
    AllowOverride AuthConfig
    Order allow,deny
  </Directory>

  SetEnv RUBYLIB '/var/www/timon'
  PassengerEnabled on
  PassengerAppRoot /var/www/timon
  RackBaseURI /timon

</VirtualHost>

The bold lines indicate what must be added.
  • SetEnv : This just allows the code in subdirectories of the webapp to be easily required.
  • PassengerEnabled : Seems fairly obvious.
  • PassengerAppRoot : The full filesystem path to the webapp directory.
  • RackBaseURI : The relative (to DocumentRoot) path to the webapp directory.

Next, verify that the Passenger options (/etc/apache2/mods-enabled/passenger.conf) are correct:


<IfModule mod_passenger.c>

  PassengerRoot /usr
  PassengerRuby /usr/bin/ruby
  PassengerMaxPoolSize 10
  PassengerDefaultUser www-data


</IfModule mod_passenger.c>

These options are pretty straightforward. The only thing to note is that PassengerRuby can be set to a specific version of Ruby, e.g. jruby or ruby1.9.

Now, create an empty Rack-friendly directory structure for the webapp:

bash# cd /var/www
bash# mkdir timon
bash# mkdir timon/public
bash# mkdir timon/tmp

The use of public for static pages and tmp for restart.txt is well-documented.

Next, a Rack config file must be provided. This will be named config.ru (/var/www/timon/config.ru) and will have the following contents:

#!/usr/bin/env ruby
require 'rubygems'
require 'sinatra'

# Disable Sinatra's default Webrick instance
set :run, false

# Include timon.rb, the main webapp script
require 'timon'
run Sinatra::Application

Finally, the app itself must be provided. This will be named timon.rb (/var/www/timon/timon.rb) and will have the following contents:

#!/usr/bin/env ruby

require 'rubygems'
require 'sinatra'

get '/' do
  "<b>TIMON!</b>"
end

# Local 404 handler
not_found do
  "Timon NotFound exception"
end

# Local error handler
error do
  "Timon Error: " + env['sinatra_error'].name
end

Debugging can be made a bit more straightforward by adding some basic logging code to config.ru:


#!/usr/bin/env ruby
require 'rubygems'
require 'sinatra'

# Disable Sinatra's default Webrick instance
set :run, false

# Local logging
FileUtils.mkdir_p 'log' unless File.exists?('log')
log = File.new('log/sinatra.log', 'a')
$stdout.reopen(log)
$stderr.reopen(log)

# Include timon.rb, the main webapp script
require 'timon'
run Sinatra::Application

That's it.

Nothing much to it, really, but the lack of RackBaseURI in the relevant examples really makes debugging this kind of thing difficult.

Tuesday, November 30, 2010

Ruby CGI and Javascript

After a brief perusal of the Ruby CGI module documentation, it doesn't seem like there is a good way to generate Javascript from within it.

Due to the method_missing way that the CGI module handles HTML tags, however, it turns out to be quite simple: invoke cgi.script, passing all tag parameters as a hash, and pass the Javascript code as a string in the block:

  cgi.out {
    cgi.html {
      cgi.head {
        cgi.title {
          "Test"
        } +
        # Javascript library to include
        cgi.script( 'src' => 'dygraph-combined.js',
                    'type' => 'text/javascript') {
        }
      } +
      cgi.body {
        cgi.br +
        cgi.div( 'id' => 'graphdiv' ) +
        cgi.br +
        cgi.script( 'type' => 'text/javascript') {
          # Javascript to execute
          'g = new Dygraph(
                  document.getElementById("graphdiv"),
                  "Date,Temperature\n" +
                  "2008-05-07,75\n" +
                  "2008-05-08,70\n" +
                  "2008-05-09,80\n"
              );
          '
        }
      }
    }

Monday, October 18, 2010

internationalization regex

Just a quick vim regex that's useful when adding internationalization to a file:

:%s/\("\([^\\]\\"\|[^"]\)\+"\|'\([^\\]\\'\|[^']\)\+'\)/gettext(\1)/

Wraps all quoted strings in a gettext() call, preserving inner (and escaped) quotes. Doing this in vim instead of sed allows a cursory review of the changes in case any of them * shouldn't* be changed (e.g. log strings).

Monday, August 16, 2010

Indeed.

I was astonished to learn how accomplished both of my children are in programming. It is a skill that an entire generation of adolescents is learning underground, much the way we used to pick up dirty words.

-- S. Milgram, 1982