<?xml version='1.0' encoding='UTF-8'?><?xml-stylesheet href="http://www.blogger.com/styles/atom.css" type="text/css"?><feed xmlns='http://www.w3.org/2005/Atom' xmlns:openSearch='http://a9.com/-/spec/opensearchrss/1.0/' xmlns:georss='http://www.georss.org/georss' xmlns:gd='http://schemas.google.com/g/2005' xmlns:thr='http://purl.org/syndication/thread/1.0'><id>tag:blogger.com,1999:blog-8719896753162286533</id><updated>2011-12-04T18:55:10.848-08:00</updated><category term='linux iptables tacacs authorization'/><category term='RangeError'/><category term='Edge Rails'/><category term='Xen'/><category term='pspell'/><category term='display'/><category term='iterm'/><category term='inittab'/><category term='clojure'/><category term='hash'/><category term='pecl'/><category term='disk'/><category term='xterm'/><category term='ntpdate'/><category term='linux ubuntu stunnel mysql ssl tunnel'/><category term='rhel'/><category term='delegateclass'/><category term='finders'/><category term='unlambda'/><category term='SSHKeychain'/><category term='simpledelegator'/><category term='gem'/><category term='domxml'/><category term='nginx'/><category term='upstart'/><category term='rails'/><category term='rparsec'/><category term='disk image'/><category term='logwatch'/><category term='cakephp'/><category term='virtual'/><category term='pam'/><category term='rtfm'/><category term='cyrus'/><category term='macvim'/><category term='.htaccess'/><category term='msgpack'/><category term='aspell'/><category term='cacti'/><category term='crypt::blowfish'/><category term='AR-Delegation'/><category term='ExtJS'/><category term='xml'/><category term='GuessMethod'/><category term='aes'/><category term='wrapper'/><category term='mysql'/><category term='volatile-sloppy'/><category term='x11'/><category term='crypt::cbc'/><category term='fink'/><category term='vmware'/><category term='WWW:Mechanize'/><category term='Sequel'/><category term='os x snow leopard'/><category term='ssh-agent'/><category term='apt'/><category term='M-Net'/><category term='textmate'/><category term='rubygems'/><category term='lambda'/><category term='native'/><category term='pdf'/><category term='libxml2'/><category term='jquery-ui'/><category term='segfault'/><category term='editor'/><category term='ATSUI'/><category term='.irbrc'/><category term='phusion passenger'/><category term='controller'/><category term='functional-javascript'/><category term='Firefox'/><category term='screen scrape'/><category term='etch'/><category term='memcached'/><category term='DRb'/><category term='ruby rails mod_rails phusion passenger OS X'/><category term='pear'/><category term='Virtual CD-ROM Control Panel'/><category term='xinitrc'/><category term='libmemcached'/><category term='mcrypt'/><category term='cdata'/><category term='Event'/><category term='OpenSSH'/><category term='dialog plugin'/><category term='ruby'/><category term='autoconf'/><category term='dom'/><category term='javascript'/><category term='gdk'/><category term='smb'/><category term='perl'/><category term='variants'/><category term='ruby rubygems signed'/><category term='iso'/><category term='postfix'/><category term='acs_as_state_machine'/><category term='os x'/><category term='irb'/><category term='url rewriting'/><category term='delegation'/><category term='ghc'/><category term='linux debian ubuntu rpm deb'/><category term='event.d'/><category term='upstart-compat-sysv'/><category term='linux ubuntu ruby rubygems'/><category term='ssh tunnel'/><category term='Auto Vivification'/><category term='Exception Notifier'/><category term='debian'/><category term='windows'/><category term='saslauthd'/><category term='HPricot'/><category term='port'/><category term='sysvinit'/><category term='sarge'/><category term='linux'/><category term='apache'/><category term='init'/><category term='Wirble'/><category term='php'/><category term='macbook pro'/><category term='Core Text'/><category term='lambda calculus'/><category term='resizing'/><category term='mount'/><category term='linux pdftk pdf'/><category term='repl'/><category term='ssh'/><category term='Prototype'/><category term='Fireclipse'/><category term='deb'/><category term='sasl'/><category term='simplexml'/><category term='jquery'/><category term='openntpd'/><category term='TreePanel'/><category term='Firebug'/><category term='select_datetime'/><category term='Click'/><category term='functional programming'/><category term='ssh-add'/><category term='pattern'/><category term='composition'/><category term='xdebug'/><category term='IE'/><category term='Ubuntu'/><category term='model'/><category term='macports'/><category term='fusion'/><category term='HPricor'/><category term='password'/><category term='debian-volatile'/><title type='text'>Meta Thought</title><subtitle type='html'>(thinking about thinking)</subtitle><link rel='http://schemas.google.com/g/2005#feed' type='application/atom+xml' href='http://blog.ntrippy.net/feeds/posts/default'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/8719896753162286533/posts/default?max-results=100'/><link rel='alternate' type='text/html' href='http://blog.ntrippy.net/'/><link rel='hub' href='http://pubsubhubbub.appspot.com/'/><author><name>Charl</name><uri>http://www.blogger.com/profile/17742115469030093068</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='26' src='http://3.bp.blogspot.com/_4MLU7uPNEIY/TP2zTauOWAI/AAAAAAAAABs/OXj8i31AC0E/S220/blue%2B69.jpg'/></author><generator version='7.00' uri='http://www.blogger.com'>Blogger</generator><openSearch:totalResults>61</openSearch:totalResults><openSearch:startIndex>1</openSearch:startIndex><openSearch:itemsPerPage>100</openSearch:itemsPerPage><entry><id>tag:blogger.com,1999:blog-8719896753162286533.post-1120622932636855141</id><published>2011-03-21T13:15:00.000-07:00</published><updated>2011-03-21T13:18:26.273-07:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='editor'/><category scheme='http://www.blogger.com/atom/ns#' term='os x'/><category scheme='http://www.blogger.com/atom/ns#' term='pdf'/><title type='text'>PDF Forms Editing in OS X</title><content type='html'>I needed a simple PDF forms editor so that I can fill in and send forms via email and did not want to use the stone age process of printing the PDF out, filling it in, scanning it back in and then sending off the digitised copy.&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;&lt;a href="http://code.google.com/p/formulatepro/"&gt;FormulatePro&lt;/a&gt; fits the bill perfectly&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/8719896753162286533-1120622932636855141?l=blog.ntrippy.net' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://blog.ntrippy.net/feeds/1120622932636855141/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=8719896753162286533&amp;postID=1120622932636855141' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/8719896753162286533/posts/default/1120622932636855141'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/8719896753162286533/posts/default/1120622932636855141'/><link rel='alternate' type='text/html' href='http://blog.ntrippy.net/2011/03/pdf-forms-editing-in-os-x.html' title='PDF Forms Editing in OS X'/><author><name>Charl</name><uri>http://www.blogger.com/profile/17742115469030093068</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='26' src='http://3.bp.blogspot.com/_4MLU7uPNEIY/TP2zTauOWAI/AAAAAAAAABs/OXj8i31AC0E/S220/blue%2B69.jpg'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-8719896753162286533.post-5462914011192478763</id><published>2011-02-03T19:23:00.000-08:00</published><updated>2011-02-03T20:22:11.902-08:00</updated><title type='text'>Bundling jruby app with rawr:bundle:exe dies with 'private method `split' called for nil:NilClass' in</title><content type='html'>I have a little jruby SWT app that I'm trying to bundle up in its own little cocoon with &lt;a href="http://rawr.rubyforge.org/"&gt;rawr&lt;/a&gt; but I keep getting the following:&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;C:\nms-helper4.git&gt;rake rawr:bundle:exe&lt;br /&gt;(in C:/nms-helper4.git)&lt;br /&gt;mkdir -p package/classes/java&lt;br /&gt;javac -target 1.6 -cp lib/java/jruby-complete.jar;lib/java/swt_linux.jar;lib/jav a/swt_linux64.jar;lib/java/swt_osx.jar;lib/java/swt_osx64.jar;lib/java/swt_win32.jar;src -sourcepath src -d package/classes/java src/org/rubyforge/rawr/Main.java&lt;br /&gt;mkdir -p package/classes/ruby&lt;br /&gt;Compile src/main.rb into package/classes/ruby/main.class&lt;br /&gt;   compile_dirs has src_dirs = ["src"]&lt;br /&gt;   glob_ruby_files has directory 'src' glob ["src/main.rb", "src/swt_wrapper.rb"]&lt;br /&gt;files for src: 2&lt;br /&gt;   ruby_globs.each has glob_data = #&lt;OpenStruct files=["main.rb", "swt_wrapper.rb"], directory="src"&gt;&lt;br /&gt;    Go compile ["src/main.rb", "src/swt_wrapper.rb"]&lt;br /&gt;Compiling src/main.rb to class main&lt;br /&gt;Compiling src/swt_wrapper.rb to class swt_wrapper&lt;br /&gt;mkdir -p package/classes/META-INF&lt;br /&gt;mkdir -p package/jar&lt;br /&gt;=== Creating jar file: package/jar/nms-helper.jar&lt;br /&gt;cp lib/java/jruby-complete.jar package/jar/lib/java/jruby-complete.jar&lt;br /&gt;cp lib/java/swt_linux.jar package/jar/lib/java/swt_linux.jar&lt;br /&gt;cp lib/java/swt_linux64.jar package/jar/lib/java/swt_linux64.jar&lt;br /&gt;cp lib/java/swt_osx.jar package/jar/lib/java/swt_osx.jar&lt;br /&gt;cp lib/java/swt_osx64.jar package/jar/lib/java/swt_osx64.jar&lt;br /&gt;cp lib/java/swt_win32.jar package/jar/lib/java/swt_win32.jar&lt;br /&gt;mkdir -p package/windows&lt;br /&gt;Creating Windows application in package/jar/nms-helper.exe&lt;br /&gt;rake aborted!&lt;br /&gt;private method `split' called for nil:NilClass&lt;br /&gt;&lt;br /&gt;(See full trace by running task with --trace)&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;Taking the advice presented at the end there I run it again with:&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;C:\nms-helper4.git&gt;rake rawr:bundle:exe --trace&lt;br /&gt;(in C:/nms-helper4.git)&lt;br /&gt;** Invoke rawr:bundle:exe (first_time)&lt;br /&gt;** Invoke rawr:jar (first_time)&lt;br /&gt;** Invoke package/jar/nms-helper.jar (first_time, not_needed)&lt;br /&gt;** Invoke package/classes/java/org/rubyforge/rawr/Main.class (first_time, not_needed)&lt;br /&gt;** Invoke src/org/rubyforge/rawr/Main.java (first_time, not_needed)&lt;br /&gt;** Invoke package/classes/java (first_time, not_needed)&lt;br /&gt;** Invoke package/classes/ruby/main.class (first_time, not_needed)&lt;br /&gt;** Invoke src/main.rb (first_time, not_needed)&lt;br /&gt;** Invoke package/classes/ruby (first_time, not_needed)&lt;br /&gt;** Invoke package/classes/ruby/swt_wrapper.class (first_time, not_needed)&lt;br /&gt;** Invoke src/swt_wrapper.rb (first_time, not_needed)&lt;br /&gt;** Invoke package/classes/ruby (not_needed)&lt;br /&gt;** Invoke package/classes/META-INF (first_time, not_needed)&lt;br /&gt;** Invoke package/jar (first_time, not_needed)&lt;br /&gt;** Execute rawr:jar&lt;br /&gt;cp lib/java/jruby-complete.jar package/jar/lib/java/jruby-complete.jar&lt;br /&gt;cp lib/java/swt_linux.jar package/jar/lib/java/swt_linux.jar&lt;br /&gt;cp lib/java/swt_linux64.jar package/jar/lib/java/swt_linux64.jar&lt;br /&gt;cp lib/java/swt_osx.jar package/jar/lib/java/swt_osx.jar&lt;br /&gt;cp lib/java/swt_osx64.jar package/jar/lib/java/swt_osx64.jar&lt;br /&gt;cp lib/java/swt_win32.jar package/jar/lib/java/swt_win32.jar&lt;br /&gt;** Invoke package/windows (first_time, not_needed)&lt;br /&gt;** Execute rawr:bundle:exe&lt;br /&gt;Creating Windows application in package/jar/nms-helper.exe&lt;br /&gt;rake aborted!&lt;br /&gt;private method `split' called for nil:NilClass&lt;br /&gt;c:/jruby-1.6.0.RC1/lib/ruby/gems/1.8/gems/rawr-1.4.5/lib/exe_bundler.rb:92:in `deploy'&lt;br /&gt;c:/jruby-1.6.0.RC1/lib/ruby/gems/1.8/gems/rawr-1.4.5/lib/rawr.rb:225:in `(root)'&lt;br /&gt;&lt;br /&gt;org/jruby/RubyProc.java:276:in `call'&lt;br /&gt;org/jruby/RubyProc.java:236:in `call'&lt;br /&gt;c:/jruby-1.6.0.RC1/lib/ruby/gems/1.8/gems/rake-0.8.7/lib/rake.rb:636:in `execute'&lt;br /&gt;org/jruby/RubyArray.java:1671:in `each'&lt;br /&gt;c:/jruby-1.6.0.RC1/lib/ruby/gems/1.8/gems/rake-0.8.7/lib/rake.rb:631:in `execute'&lt;br /&gt;c:/jruby-1.6.0.RC1/lib/ruby/gems/1.8/gems/rake-0.8.7/lib/rake.rb:597:in `invoke_with_call_chain'&lt;br /&gt;c:/jruby-1.6.0.RC1/lib/ruby/1.8/monitor.rb:191:in `mon_synchronize'&lt;br /&gt;c:/jruby-1.6.0.RC1/lib/ruby/gems/1.8/gems/rake-0.8.7/lib/rake.rb:590:in `invoke_with_call_chain'&lt;br /&gt;c:/jruby-1.6.0.RC1/lib/ruby/gems/1.8/gems/rake-0.8.7/lib/rake.rb:583:in `invoke'&lt;br /&gt;&lt;br /&gt;c:/jruby-1.6.0.RC1/lib/ruby/gems/1.8/gems/rake-0.8.7/lib/rake.rb:2051:in `invoke_task'&lt;br /&gt;c:/jruby-1.6.0.RC1/lib/ruby/gems/1.8/gems/rake-0.8.7/lib/rake.rb:2029:in `top_level'&lt;br /&gt;org/jruby/RubyArray.java:1671:in `each'&lt;br /&gt;c:/jruby-1.6.0.RC1/lib/ruby/gems/1.8/gems/rake-0.8.7/lib/rake.rb:2029:in `top_level'&lt;br /&gt;c:/jruby-1.6.0.RC1/lib/ruby/gems/1.8/gems/rake-0.8.7/lib/rake.rb:2068:in `standard_exception_handling'&lt;br /&gt;c:/jruby-1.6.0.RC1/lib/ruby/gems/1.8/gems/rake-0.8.7/lib/rake.rb:2023:in `top_level'&lt;br /&gt;c:/jruby-1.6.0.RC1/lib/ruby/gems/1.8/gems/rake-0.8.7/lib/rake.rb:2001:in `run'&lt;br /&gt;c:/jruby-1.6.0.RC1/lib/ruby/gems/1.8/gems/rake-0.8.7/lib/rake.rb:2068:in `standard_exception_handling'&lt;br /&gt;c:/jruby-1.6.0.RC1/lib/ruby/gems/1.8/gems/rake-0.8.7/lib/rake.rb:1998:in `run'&lt;br /&gt;c:/jruby-1.6.0.RC1/lib/ruby/gems/1.8/gems/rake-0.8.7/bin/rake:31:in `(root)'&lt;br /&gt;org/jruby/RubyKernel.java:1066:in `load'&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;Aah, so things are coming undone in exe_bundler.rb:92:&lt;br /&gt;&lt;pre&gt;&lt;br /&gt; 80       if Platform.instance.using_windows?&lt;br /&gt; 81         # Check for FAT32 vs NTFS, the cacls command doesn't work on FAT32 nor is it required&lt;br /&gt; 82         output = `fsutil fsinfo volumeinfo #{file_dir_name.split(':')[0]}:\\`&lt;br /&gt; 83         # fsutil can only work with admin priviledges&lt;br /&gt; 84         raise output if output =~ /requires that you have administrative privileges/&lt;br /&gt; 85         # ===== Sample output of 'fsutil fsinfo volumeinfo c:\'&lt;br /&gt; 86         # Volume Name :&lt;br /&gt; 87         # Volume Serial Number : 0x80a5650a&lt;br /&gt; 88         # Max Component Length : 255&lt;br /&gt; 89         # File System Name : FAT32&lt;br /&gt; 90         # Preserves Case of filenames&lt;br /&gt; 91         # Supports Unicode in filenames&lt;br /&gt; 92         if 'NTFS' == output.split("\n")[3].split(':')[1].strip&lt;br /&gt; 93           sh "echo y | cacls \"#{file_dir_name}/launch4j/bin-win/windres.exe\" /G \"#{ENV['USERNAME']}\":F"&lt;br /&gt; 94           sh "echo y | cacls \"#{file_dir_name}/launch4j/bin-win/ld.exe\" /G \"#{ENV['USERNAME']}\":F"&lt;br /&gt; 95         end&lt;br /&gt; 96         link_launch4j_bin('win', file_dir_name)&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;Line 92 seems to rely on what happens in line 82 where we try and get some filesystem info based on the drive name. The bit that tries to determine the drive letter uses '\\' at the end which is the issue. If you change that to '\\\\' you get things to work as they should again:&lt;br /&gt;&lt;pre&gt;&lt;br /&gt; 82         output = `fsutil fsinfo volumeinfo #{file_dir_name.split(':')[0]}:\\\\`&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;Somehow the double backslash is not making it to the shell correctly from the backticks being called. Adding the extra set of backslashes saves the day:&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;C:\nms-helper4.git&gt;rake rawr:bundle:exe&lt;br /&gt;(in C:/nms-helper4.git)&lt;br /&gt;cp lib/java/jruby-complete.jar package/jar/lib/java/jruby-complete.jar&lt;br /&gt;cp lib/java/swt_linux.jar package/jar/lib/java/swt_linux.jar&lt;br /&gt;cp lib/java/swt_linux64.jar package/jar/lib/java/swt_linux64.jar&lt;br /&gt;cp lib/java/swt_osx.jar package/jar/lib/java/swt_osx.jar&lt;br /&gt;cp lib/java/swt_osx64.jar package/jar/lib/java/swt_osx64.jar&lt;br /&gt;cp lib/java/swt_win32.jar package/jar/lib/java/swt_win32.jar&lt;br /&gt;Creating Windows application in package/jar/nms-helper.exe&lt;br /&gt;echo y | cacls "c:/jruby-1.6.0.RC1/lib/ruby/gems/1.8/gems/rawr-1.4.5/lib/launch4j/bin-win/windres.exe" /G "cmatthee":F&lt;br /&gt;Are you sure (Y/N)?echo y | cacls "c:/jruby-1.6.0.RC1/lib/ruby/gems/1.8/gems/rawr-1.4.5/lib/launch4j/bin-win/ld.exe" /G "cmatthee":F&lt;br /&gt;Are you sure (Y/N)?call: java -jar "c:/jruby-1.6.0.RC1/lib/ruby/gems/1.8/gems/rawr-1.4.5/lib/launch4j/launch4j.jar" "package/windows/configuration.xml"&lt;br /&gt;java -jar "c:/jruby-1.6.0.RC1/lib/ruby/gems/1.8/gems/rawr-1.4.5/lib/launch4j/launch4j.jar" "package/windows/configuration.xml"&lt;br /&gt;launch4j: Compiling resources&lt;br /&gt;launch4j: Linking&lt;br /&gt;launch4j: Successfully created C:\nms-helper4.git\package\windows\nms-helper.exe&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;Reality restored.&lt;br /&gt;&lt;br /&gt;From what I can see I am running the latest rawr gem:&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;C:\nms-helper4.git&gt;jruby -S gem search -r rawr&lt;br /&gt;&lt;br /&gt;*** REMOTE GEMS ***&lt;br /&gt;&lt;br /&gt;drawr (1.0.1)&lt;br /&gt;rawr (1.4.5)&lt;br /&gt;&lt;br /&gt;C:\nms-helper4.git&gt;jruby -S gem list rawr&lt;br /&gt;&lt;br /&gt;*** LOCAL GEMS ***&lt;br /&gt;&lt;br /&gt;rawr (1.4.5)&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;Checking the &lt;a href="https://github.com/rawr/rawr/blob/master/lib/rawr/exe_bundler.rb"&gt;master branch&lt;/a&gt; on github though shows this file is no longer the same and the issue has been fixed:&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;81      if Platform.instance.using_windows?&lt;br /&gt;82        # Check for FAT32 vs NTFS, the cacls command doesn't work on FAT32 nor is it required&lt;br /&gt;83        volume = file_dir_name.split(":").first.upcase + ':'&lt;br /&gt;84        output = `fsutil fsinfo ntfsinfo #{volume}`&lt;br /&gt;85        # fsutil can only work with admin priviledges&lt;br /&gt;86        raise output if output =~ /requires that you have administrative privileges/&lt;br /&gt;87        raise output if output =~ /Error:/&lt;br /&gt;88        if 'NTFS' == output.split("\n")[0][0..3]&lt;br /&gt;89          sh "echo y | cacls \"#{file_dir_name}/launch4j/bin-win/windres.exe\" /G \"#{ENV['USERNAME']}\":F"&lt;br /&gt;90          sh "echo y | cacls \"#{file_dir_name}/launch4j/bin-win/ld.exe\" /G \"#{ENV['USERNAME']}\":F"&lt;br /&gt;91        end&lt;br /&gt;92        link_launch4j_bin('win', file_dir_name)&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;The &lt;a href="https://github.com/rawr/rawr/blob/master/Rakefile"&gt;Rakefile&lt;/a&gt; refers us to '&lt;a href="https://github.com/rawr/rawr/blob/master/lib/rawr/rawr_version.rb"&gt;rawr/rawr_version&lt;/a&gt;' that is:&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;1   module Rawr&lt;br /&gt;2     VERSION = "1.5.0"&lt;br /&gt;3   end&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;Looks like the latest version of the project on github has not made it into the wild as yet.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/8719896753162286533-5462914011192478763?l=blog.ntrippy.net' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://blog.ntrippy.net/feeds/5462914011192478763/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=8719896753162286533&amp;postID=5462914011192478763' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/8719896753162286533/posts/default/5462914011192478763'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/8719896753162286533/posts/default/5462914011192478763'/><link rel='alternate' type='text/html' href='http://blog.ntrippy.net/2011/02/bundling-jruby-app-with-rawrbundleexe.html' title='Bundling jruby app with rawr:bundle:exe dies with &apos;private method `split&apos; called for nil:NilClass&apos; in'/><author><name>Charl</name><uri>http://www.blogger.com/profile/17742115469030093068</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='26' src='http://3.bp.blogspot.com/_4MLU7uPNEIY/TP2zTauOWAI/AAAAAAAAABs/OXj8i31AC0E/S220/blue%2B69.jpg'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-8719896753162286533.post-3281510368842072836</id><published>2011-02-03T13:39:00.000-08:00</published><updated>2011-02-03T13:49:31.247-08:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='disk'/><category scheme='http://www.blogger.com/atom/ns#' term='resizing'/><category scheme='http://www.blogger.com/atom/ns#' term='virtual'/><category scheme='http://www.blogger.com/atom/ns#' term='fusion'/><category scheme='http://www.blogger.com/atom/ns#' term='vmware'/><title type='text'>Resizing your OS X VMWare Fusion Virtual Disk</title><content type='html'>&lt;div&gt;Assumptions:&lt;/div&gt;&lt;div&gt;&lt;ul&gt;&lt;li&gt;New disk size is 40GB.&lt;/li&gt;&lt;li&gt;Current virtual disk is foo.vdmk&lt;/li&gt;&lt;li&gt;Current VM image is at /Users/foo/Documents/Foo.vmwarevm&lt;/li&gt;&lt;/ul&gt;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;Quick recipe:&lt;div&gt;&lt;ul&gt;&lt;li&gt;Open up a teminal window&lt;/li&gt;&lt;li&gt;Run: cd /Users/foo/Documents/Foo.vmwarevm &amp;amp;&amp;amp; /Library/Application\ Support/VMware\ Fusion/vmware-vdiskmanager -x 40GB foo.vmdk&lt;/li&gt;&lt;li&gt;Resize the host OS with a third party tool. In my case I was resizing a NTFS partition with XP installed on it. &lt;a href="http://www.partition-tool.com/download.htm"&gt;Easeus Partition Manager home Edition&lt;/a&gt; did the trick for me.&lt;/li&gt;&lt;/ul&gt;&lt;/div&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/8719896753162286533-3281510368842072836?l=blog.ntrippy.net' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://blog.ntrippy.net/feeds/3281510368842072836/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=8719896753162286533&amp;postID=3281510368842072836' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/8719896753162286533/posts/default/3281510368842072836'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/8719896753162286533/posts/default/3281510368842072836'/><link rel='alternate' type='text/html' href='http://blog.ntrippy.net/2011/02/resizing-your-os-x-vmware-fusion.html' title='Resizing your OS X VMWare Fusion Virtual Disk'/><author><name>Charl</name><uri>http://www.blogger.com/profile/17742115469030093068</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='26' src='http://3.bp.blogspot.com/_4MLU7uPNEIY/TP2zTauOWAI/AAAAAAAAABs/OXj8i31AC0E/S220/blue%2B69.jpg'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-8719896753162286533.post-7238168196475483475</id><published>2010-10-21T20:40:00.000-07:00</published><updated>2010-10-21T20:54:35.593-07:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='linux iptables tacacs authorization'/><title type='text'>TACACS+, packet loss and 'Authorization failed' errors</title><content type='html'>I've been hunting a TACACS+ issue that a customer reported whereby they would be able to log into a network device but somewhere in the duration of the session they would try and run a command and get the dreaded '% Authorization failed' error.&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;After a couple of seconds they would be able to continue as if nothing was amiss.&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;The &lt;a href="http://www.pro-bono-publico.de/projects/tac_plus.html"&gt;tac_plus&lt;/a&gt; (I am using the Event-Driven version) logs were not showing anything out of the ordinary so this was a rather perplexing issue. The error seemed to be popping up rather randomly (and always when I did not have a tcpdump running).&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;While observing several devices over a larger time period I noticed that they were experiencing intermittent packet loss and this got me to wonder of the packet loss events I was noticing were not coinciding with the authorization failures.&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;Lucky for me tac_plus is running on a Linux host and so I thought I'd simulate packet loss between one of my own devices and the tac_plus service to see what that would result in. To implement the simulated packet loss I simply put the following rule in place:&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;iptables -A INPUT -m static -mode random -probability 0.5 -s &lt;ip&gt;/32 -j DROP&lt;/ip&gt;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;With a probability of 0.5 (this can be between 0 and 1) I was dropping around 40-50% of the packets. &lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;Lo and behold, '% Authorization failure', scientifically reproducible.&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/8719896753162286533-7238168196475483475?l=blog.ntrippy.net' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://blog.ntrippy.net/feeds/7238168196475483475/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=8719896753162286533&amp;postID=7238168196475483475' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/8719896753162286533/posts/default/7238168196475483475'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/8719896753162286533/posts/default/7238168196475483475'/><link rel='alternate' type='text/html' href='http://blog.ntrippy.net/2010/10/tacacs-packet-loss-and-autjorization.html' title='TACACS+, packet loss and &apos;Authorization failed&apos; errors'/><author><name>Charl</name><uri>http://www.blogger.com/profile/17742115469030093068</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='26' src='http://3.bp.blogspot.com/_4MLU7uPNEIY/TP2zTauOWAI/AAAAAAAAABs/OXj8i31AC0E/S220/blue%2B69.jpg'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-8719896753162286533.post-1095036705297157410</id><published>2010-09-01T16:42:00.000-07:00</published><updated>2010-09-06T15:39:52.384-07:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='ruby'/><category scheme='http://www.blogger.com/atom/ns#' term='msgpack'/><category scheme='http://www.blogger.com/atom/ns#' term='memcached'/><category scheme='http://www.blogger.com/atom/ns#' term='rails'/><category scheme='http://www.blogger.com/atom/ns#' term='perl'/><title type='text'>Sharing is caring: Ruby, Perl, Memcached and MsgPack</title><content type='html'>Do you need to share data structures between ruby and perl ... FAST?&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;I recently saw &lt;a href="http://github.com/msgpack/msgpack"&gt;MsgPack&lt;/a&gt; bubble through my RSS feeds and tagged it to go have another look. It provides very &lt;a href="http://github.com/frsyuki/serializer-speed-test"&gt;fast&lt;/a&gt; &lt;a href="http://redmine.msgpack.org/projects/msgpack/wiki"&gt;multi-language bindings&lt;/a&gt; for serialisation/de-serialisation.&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;A few quick experiments with the data I share (via memcached) between ruby and perl showed a write (serialisation + write to memcached) speed increase from 20s to 1.8s. Read (read from memcached + de-serialisation) performance showed similar performance increases.&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;All initial testing was done ruby -&gt; memcached -&gt; ruby but as soon as I switched to reading from memcached via perl I started getting 'extra bytes' errors from the perl side. I then tried perl -&gt; memcached -&gt; perl and everything was fine.&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;Weird.&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;A closer look at the data written to memcached and then read from perl showed that the data serialised with MsgPack on the ruby end was not the same as the data read by perl from memcached (validating the 'extra bytes' error).&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;Testing the write -&gt; read process from perl to ruby yielded the following error:&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;i&gt;/opt/local/lib/ruby/gems/1.8/gems/memcached-0.19.5/lib/memcached/memcached.rb:514:in `load': incompatible marshal file format (can't be read) (TypeError)&lt;/i&gt;&lt;/div&gt;&lt;div&gt;&lt;i&gt;        format version 4.8 required; 147.1 given&lt;/i&gt;&lt;/div&gt;&lt;div&gt;&lt;i&gt;        from /opt/local/lib/ruby/gems/1.8/gems/memcached-0.19.5/lib/memcached/memcached.rb:514:in `get'&lt;/i&gt;&lt;/div&gt;&lt;div&gt;&lt;i&gt;        from ./t_msgpack.rb:35:in `read_test'&lt;/i&gt;&lt;/div&gt;&lt;div&gt;&lt;i&gt;        from ./t_msgpack.rb:49&lt;/i&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;i&gt;&lt;br /&gt;&lt;/i&gt;&lt;/div&gt;&lt;div&gt;Now why on earth would I be getting a 'incompatible marshal file format' error as I am not using the ruby &lt;a href="http://ruby-doc.org/core/classes/Marshal.html"&gt;marshalling&lt;/a&gt; lib at all?&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;Turns out the &lt;a href="http://github.com/fauna/memcached"&gt;memcached lib&lt;/a&gt; I use turns marshalling of ruby data on by default when you write to/read from memcached. This is most likely the best option for most cases where you don't want to use some other form of serialisation/de-serialisation but was really biting me here.&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;The solution is to simply stop the default behaviour of the memcached lib by using the following forms of get and set that turns on the 'raw' data handling switch for the memcached lib:&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;get KEY, false&lt;/div&gt;&lt;div&gt;set KEY, VALUE, TTL, false&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;The 'false' parameter at the end of those overrides the default behaviour turning default serialisation/de-serialisation via &lt;a href="http://ruby-doc.org/core/classes/Marshal.html"&gt;Marshall&lt;/a&gt; off.&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;Reality restored.&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/8719896753162286533-1095036705297157410?l=blog.ntrippy.net' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://blog.ntrippy.net/feeds/1095036705297157410/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=8719896753162286533&amp;postID=1095036705297157410' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/8719896753162286533/posts/default/1095036705297157410'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/8719896753162286533/posts/default/1095036705297157410'/><link rel='alternate' type='text/html' href='http://blog.ntrippy.net/2010/09/sharing-is-caring-ruby-perl-memcached.html' title='Sharing is caring: Ruby, Perl, Memcached and MsgPack'/><author><name>Charl</name><uri>http://www.blogger.com/profile/17742115469030093068</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='26' src='http://3.bp.blogspot.com/_4MLU7uPNEIY/TP2zTauOWAI/AAAAAAAAABs/OXj8i31AC0E/S220/blue%2B69.jpg'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-8719896753162286533.post-1134635837146299505</id><published>2010-07-01T19:50:00.000-07:00</published><updated>2010-07-01T19:58:52.890-07:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='repl'/><category scheme='http://www.blogger.com/atom/ns#' term='clojure'/><title type='text'>How do you make your Clojure REPL suck less?</title><content type='html'>Simple, rely on the venerable rlwrap that provides you with a readline wrapper around your existing REPL.&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;My sucky REPL looked like this:&lt;/div&gt;&lt;br /&gt;&lt;div&gt;&lt;code&gt;#!/bin/sh&lt;br /&gt;java -cp PATH_TO/clojure.jar clojure.main $1&lt;br /&gt;&lt;span class="Apple-style-span"   style="font-family:Georgia, serif;font-size:130%;"&gt;&lt;span class="Apple-style-span"  style="font-size:16px;"&gt;&lt;br /&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/div&gt;&lt;div&gt;Simply install the rlwrap package using your favorite package manager and change your REPL script (clj) to the following:&lt;/div&gt;&lt;br /&gt;&lt;div&gt;&lt;code&gt;#!/bin/sh&lt;br /&gt;rlwrap java -cp PATH_TO/clojure.jar clojure.main $1&lt;br /&gt;&lt;/code&gt;&lt;br /&gt;&lt;/div&gt;Major suckiness averted. The added boon of this approach is that you now get all the readline goodness (history traversal, inline editing, etc.) you've come to depend on in other REPLs.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/8719896753162286533-1134635837146299505?l=blog.ntrippy.net' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://blog.ntrippy.net/feeds/1134635837146299505/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=8719896753162286533&amp;postID=1134635837146299505' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/8719896753162286533/posts/default/1134635837146299505'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/8719896753162286533/posts/default/1134635837146299505'/><link rel='alternate' type='text/html' href='http://blog.ntrippy.net/2010/07/how-do-you-make-your-clojure-repl-suck.html' title='How do you make your Clojure REPL suck less?'/><author><name>Charl</name><uri>http://www.blogger.com/profile/17742115469030093068</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='26' src='http://3.bp.blogspot.com/_4MLU7uPNEIY/TP2zTauOWAI/AAAAAAAAABs/OXj8i31AC0E/S220/blue%2B69.jpg'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-8719896753162286533.post-2357460210890691785</id><published>2009-10-12T22:20:00.000-07:00</published><updated>2009-10-12T22:45:37.987-07:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='os x snow leopard'/><category scheme='http://www.blogger.com/atom/ns#' term='macports'/><title type='text'>B0rked ports on Snow Leopard</title><content type='html'>If you are using &lt;a href="http://www.macports.org/"&gt;MacPort&lt;/a&gt; to manage &lt;a href="http://www.google.com.au/url?q=http://en.wiktionary.org/wiki/OSS&amp;amp;ei=IxPUSpqyHZHclAfA6tGpCg&amp;amp;sa=X&amp;amp;oi=define&amp;amp;ct=&amp;amp;cd=1&amp;amp;ved=0CB0QpAMoBQ&amp;amp;usg=AFQjCNE-MNhVPfzxeU-kXy5kbIB8sUEr_A"&gt;OSS&lt;/a&gt; on your box and you recently upgraded to Snow Leopard you will find error messages like this when trying to use the ports system:&lt;br /&gt;&lt;pre&gt;$ port selfupdate&lt;br /&gt;dlopen(/Library/Tcl/macports1.0/MacPorts.dylib, 10): no suitable image&lt;br /&gt;found.  Did find:&lt;br /&gt;/Library/Tcl/macports1.0/MacPorts.dylib: mach-o, but wrong architecture&lt;br /&gt;while executing&lt;br /&gt;"load /Library/Tcl/macports1.0/MacPorts.dylib"&lt;br /&gt;("package ifneeded macports 1.0" script)&lt;br /&gt;invoked from within&lt;br /&gt;"package require macports"&lt;br /&gt;(file "/opt/local/bin/port" line 39)&lt;br /&gt;&lt;/pre&gt;The issue is simply that you have all your libs and binaries managed via MacPorts compiled for the i386 architecture and not x86-64 (as required for Snow Leopard).&lt;br /&gt;&lt;br /&gt;The solution is simple, but, arduous. You need to install the new version of MacPorts for Snow Leopard, make a backup list of the installed ports, delete them and reinstall the ones you still want.&lt;br /&gt;&lt;br /&gt;You may want to pay close attention to the variants of the ports that you had previously installed when reinstalling them.&lt;br /&gt;&lt;br /&gt;Also, you need the latest version of &lt;a href="http://developer.apple.com/TOOLS/Xcode/"&gt;Xcode&lt;/a&gt; (a version greater than v3.0 will do) installed.&lt;br /&gt;&lt;br /&gt;Fun.&lt;br /&gt;&lt;br /&gt;See the following two URLs for more info:&lt;br /&gt;&lt;ul&gt;&lt;li&gt;&lt;a href="http://www.macports.org/install.php"&gt;Installing MacPorts&lt;/a&gt;&lt;/li&gt;&lt;li&gt;&lt;a href="http://trac.macports.org/wiki/Migration"&gt;Migrating a MacPorts install to a new major OS version or CPU architecture&lt;/a&gt;&lt;/li&gt;&lt;/ul&gt;Happy recompiling!&lt;br /&gt;&lt;br /&gt;&lt;pre&gt;&lt;/pre&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/8719896753162286533-2357460210890691785?l=blog.ntrippy.net' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://blog.ntrippy.net/feeds/2357460210890691785/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=8719896753162286533&amp;postID=2357460210890691785' title='1 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/8719896753162286533/posts/default/2357460210890691785'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/8719896753162286533/posts/default/2357460210890691785'/><link rel='alternate' type='text/html' href='http://blog.ntrippy.net/2009/10/b0rked-ports-on-snow-leopard.html' title='B0rked ports on Snow Leopard'/><author><name>Charl</name><uri>http://www.blogger.com/profile/17742115469030093068</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='26' src='http://3.bp.blogspot.com/_4MLU7uPNEIY/TP2zTauOWAI/AAAAAAAAABs/OXj8i31AC0E/S220/blue%2B69.jpg'/></author><thr:total>1</thr:total></entry><entry><id>tag:blogger.com,1999:blog-8719896753162286533.post-2258077924069522273</id><published>2009-09-22T22:02:00.001-07:00</published><updated>2009-09-22T22:20:17.828-07:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='os x snow leopard'/><category scheme='http://www.blogger.com/atom/ns#' term='Core Text'/><category scheme='http://www.blogger.com/atom/ns#' term='ATSUI'/><category scheme='http://www.blogger.com/atom/ns#' term='macvim'/><title type='text'>OS X Snow Leopard and broken scrolling in MacVim</title><content type='html'>My upgrade to &lt;a href="http://www.apple.com/macosx/"&gt;Snow Leopard&lt;/a&gt; has been pretty smooth sailing bar one annoying hitch. After the upgrade &lt;a href="http://code.google.com/p/macvim/"&gt;MacVim&lt;/a&gt; stopped scrolling properly.&lt;br /&gt;&lt;br /&gt;When you scroll down only the last few lines on the screen update and scrolling up only the first few lines. This requires you CTRL-L to redraw the window every time you've finished scrolling.&lt;br /&gt;&lt;br /&gt;Yuck!&lt;br /&gt;&lt;br /&gt;Looks like this is caused by MacVim's support for &lt;a href="http://developer.apple.com/legacy/mac/library/documentation/Carbon/Conceptual/ATSUI_Concepts/atsui_chap 1/atsui_intro.html"&gt;ATSUI&lt;/a&gt; which has been deprecated in favour of &lt;a href="http://developer.apple.com/mac/library/documentation/Carbon/Conceptual/CoreText_Programming/Introduction/Introduction.html"&gt;Core Text&lt;/a&gt; in Snow Leopard.&lt;br /&gt;&lt;br /&gt;If you are experiencing this you can simply turn the ATSUI renderer off by unchecking MacVim -&gt; Preferences -&gt; Advanced -&gt; Use ATSUI renderer.&lt;br /&gt;&lt;br /&gt;This is switched off by default so most people won't be affected by this. At the time of this post there was no real indication from the MacVim project if they'd be switching to Core Text in the future in addition to using the ATSUI renderer.&lt;br /&gt;&lt;br /&gt;&lt;pre&gt;&lt;/pre&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/8719896753162286533-2258077924069522273?l=blog.ntrippy.net' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://blog.ntrippy.net/feeds/2258077924069522273/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=8719896753162286533&amp;postID=2258077924069522273' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/8719896753162286533/posts/default/2258077924069522273'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/8719896753162286533/posts/default/2258077924069522273'/><link rel='alternate' type='text/html' href='http://blog.ntrippy.net/2009/09/os-x-snow-leopard-and-broken-scrolling.html' title='OS X Snow Leopard and broken scrolling in MacVim'/><author><name>Charl</name><uri>http://www.blogger.com/profile/17742115469030093068</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='26' src='http://3.bp.blogspot.com/_4MLU7uPNEIY/TP2zTauOWAI/AAAAAAAAABs/OXj8i31AC0E/S220/blue%2B69.jpg'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-8719896753162286533.post-5488621770177403452</id><published>2009-09-13T16:03:00.000-07:00</published><updated>2009-09-13T16:40:01.666-07:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='iterm'/><category scheme='http://www.blogger.com/atom/ns#' term='x11'/><category scheme='http://www.blogger.com/atom/ns#' term='display'/><category scheme='http://www.blogger.com/atom/ns#' term='xinitrc'/><category scheme='http://www.blogger.com/atom/ns#' term='xterm'/><title type='text'>iTerm, X11.app and the case of the missing DISPLAY</title><content type='html'>If you're running X11.app as your X server on OS X you generally need to use the xterm window it launches for you as part of the system or your specific xinitrc to connect to remote hosts via SSH when you want to enable X11 forwarding (and the propagation of your DISPLAY variable).&lt;br /&gt;&lt;br /&gt;I prefer &lt;a href="http://iterm.sourceforge.net/"&gt;iTerm&lt;/a&gt; and luckily the solutions to switch to using it instead of the xterm launched by X11 is pretty easy to implement.&lt;br /&gt;&lt;br /&gt;The first option is to make a user specific copy of xinitrc and modify the line that launches xterm to rather launch iTerm. If you don't have a user specific xinitrc (~/.xinitrc) you can grab a copy from /etc/X11/xinit/xinitrc or /usr/X11R6/lib/X11/xinit/xinitrc.&lt;br /&gt;&lt;br /&gt;Near the bottom of the xinitrc you'll see a few lines that reference xterm that you can comment our and substitute with:&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;/usr/bin/open /Applications/iTerm.app&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;If you access remote X applications very infrequently it may simply be easier to ensure that X11 is running and then export your DISPLAY variable manually in iTerm:&lt;br /&gt;&lt;pre&gt;&lt;code&gt;&lt;br /&gt;$ export DISPLAY=:0&lt;/code&gt;&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;The final solution is to make all terms aware of your DISPLAY variable by editing your local environment plist (~/.MacOSX/environment.plist):&lt;br /&gt;&lt;code&gt;&lt;br /&gt;$ open ~/.MacOSX/environment.plist&lt;br /&gt;&lt;/code&gt;&lt;br /&gt;This will open the plist in the Property List Editor where you can add a new child (DISPLAY) under the root node with a value of ':0'. For this to take effect you need to log out/in. You could also have dropped 'export DISPLAY=:0' into your local ~/.bash_profile to achieve the same results.&lt;br /&gt;&lt;br /&gt;&lt;pre&gt;&lt;/pre&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/8719896753162286533-5488621770177403452?l=blog.ntrippy.net' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://blog.ntrippy.net/feeds/5488621770177403452/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=8719896753162286533&amp;postID=5488621770177403452' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/8719896753162286533/posts/default/5488621770177403452'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/8719896753162286533/posts/default/5488621770177403452'/><link rel='alternate' type='text/html' href='http://blog.ntrippy.net/2009/09/iterm-x11app-and-case-of-missinf.html' title='iTerm, X11.app and the case of the missing DISPLAY'/><author><name>Charl</name><uri>http://www.blogger.com/profile/17742115469030093068</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='26' src='http://3.bp.blogspot.com/_4MLU7uPNEIY/TP2zTauOWAI/AAAAAAAAABs/OXj8i31AC0E/S220/blue%2B69.jpg'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-8719896753162286533.post-2673240439727256037</id><published>2009-09-13T15:48:00.000-07:00</published><updated>2010-07-11T22:56:15.307-07:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='x11'/><category scheme='http://www.blogger.com/atom/ns#' term='ssh'/><category scheme='http://www.blogger.com/atom/ns#' term='display'/><category scheme='http://www.blogger.com/atom/ns#' term='gdk'/><title type='text'>Untrusted SSH clients or Gdk-WARNING **: Connection to display localhost:10.0 appears to be untrusted</title><content type='html'>While connecting to a local RH system via 'ssh -X' I was seeing errors like:&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;Connection to display localhost:10.0 appears to be untrusted. Pointer and &lt;/pre&gt;&lt;pre&gt;keyboard grabs and inter-client communication may not work as expected.&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;a href="http://www.openssh.org/"&gt;OpenSSH&lt;/a&gt; implements the X11 &lt;i&gt;SECURITY&lt;/i&gt; extension that provides two clients types: &lt;i&gt;trusted&lt;/i&gt; &amp;amp; untrusted.&lt;br /&gt;&lt;br /&gt;Trusted clients can do anything with the display while &lt;i&gt;untrusted&lt;/i&gt; clients cannot inject synthetic events (mouse movement, keypresses, etc.) or read data from other windows (e.g. take screenshots).&lt;br /&gt;&lt;br /&gt;In theory you should be able to run most applications via the untrusted category but it seems that a fair amount of client apps still do not implement this correctly so YMMV.&lt;br /&gt;&lt;br /&gt;If you want to initialize a SSH connection with X11 forwarding support and using the trusted security type you simply replace -X with -Y:&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;$ ssh -Y USER@HOST&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;TIP: Add a '-C' to your ssh connections to speed things up. Nothings for free but I am sure the CPU/speed trade-off will suit most users.&lt;br /&gt;&lt;br /&gt;&lt;pre&gt;&lt;/pre&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/8719896753162286533-2673240439727256037?l=blog.ntrippy.net' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://blog.ntrippy.net/feeds/2673240439727256037/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=8719896753162286533&amp;postID=2673240439727256037' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/8719896753162286533/posts/default/2673240439727256037'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/8719896753162286533/posts/default/2673240439727256037'/><link rel='alternate' type='text/html' href='http://blog.ntrippy.net/2009/09/untrusted-ssh-clients-or-gdk-warning.html' title='Untrusted SSH clients or Gdk-WARNING **: Connection to display localhost:10.0 appears to be untrusted'/><author><name>Charl</name><uri>http://www.blogger.com/profile/17742115469030093068</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='26' src='http://3.bp.blogspot.com/_4MLU7uPNEIY/TP2zTauOWAI/AAAAAAAAABs/OXj8i31AC0E/S220/blue%2B69.jpg'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-8719896753162286533.post-5399480664588144905</id><published>2009-09-11T21:46:00.000-07:00</published><updated>2010-07-11T23:01:05.482-07:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='macports'/><category scheme='http://www.blogger.com/atom/ns#' term='ghc'/><category scheme='http://www.blogger.com/atom/ns#' term='os x'/><category scheme='http://www.blogger.com/atom/ns#' term='autoconf'/><title type='text'>Installing ghc on OS X and getting "./configure: line 11082: syntax error: unexpected end of file"</title><content type='html'>While trying to install &lt;a href="http://trac.macports.org/browser/trunk/dports/lang/ghc/Portfile"&gt;ghc&lt;/a&gt; from &lt;a href="http://www.macports.org/"&gt;MacPorts&lt;/a&gt; I kept getting this:&lt;br /&gt;&lt;pre&gt;[...]&lt;br /&gt;---&gt;  Fetching ghc&lt;br /&gt;---&gt;  Attempting to fetch ghc-6.10.3-src.tar.bz2 from &lt;/pre&gt;&lt;pre&gt;http://distfiles.macports.org/ghc&lt;br /&gt;---&gt;  Attempting to fetch ghc-6.10.3-src-extralibs.tar.bz2 from &lt;/pre&gt;&lt;pre&gt;http://distfiles.macports.org/ghc&lt;br /&gt;---&gt;  Attempting to fetch testsuite-6.10.3.tar.bz2 from &lt;/pre&gt;&lt;pre&gt;http://distfiles.macports.org/ghc&lt;br /&gt;---&gt;  Attempting to fetch ghc-6.8.2-darwin-i386-leopard-bootstrap.tar.bz2 from &lt;/pre&gt;&lt;pre&gt;http://distfiles.macports.org/ghc&lt;br /&gt;---&gt;  Verifying checksum(s) for ghc&lt;br /&gt;---&gt;  Extracting ghc&lt;br /&gt;---&gt;  Applying patches to ghc&lt;br /&gt;---&gt;  Configuring ghc&lt;br /&gt;Error: Target org.macports.configure returned: configure failure: shell command &lt;/pre&gt;&lt;pre&gt;" cd "/opt/local/var/macports/build/_opt_local_var_macports_sources_rsync.&lt;/pre&gt;&lt;pre&gt;macports.org_release_ports_lang_ghc/work/ghc-6.10.3" &amp;amp;&amp;amp; ./configure &lt;/pre&gt;&lt;pre&gt;--prefix=/opt/local &lt;/pre&gt;&lt;pre&gt;--prefix=/opt/local/var/macports/build/_opt_local_var_macports_sources_rsync.&lt;/pre&gt;&lt;pre&gt;macports.org_release_ports_lang_ghc/work/destroot/opt/local &lt;/pre&gt;&lt;pre&gt;--datadir=/opt/local/var/macports/build/_opt_local_var_macports_sources_rsync.&lt;/pre&gt;&lt;pre&gt;macports.org_release_ports_lang_ghc/work/destroot/opt/local/share/ghc-6.10.3 &lt;/pre&gt;&lt;pre&gt;--with-gmp-includes=/opt/local/include --with-gmp-libraries=/opt/local/lib &lt;/pre&gt;&lt;pre&gt;--with-ghc='/opt/local/var/macports/build/&lt;/pre&gt;&lt;pre&gt;_opt_local_var_macports_sources_rsync.macports.org_release_ports_lang_ghc/&lt;/pre&gt;&lt;pre&gt;work/ghc-bootstrap/bin/ghc' --with-gcc=/usr/bin/gcc-4.0 " returned error 2&lt;/pre&gt;&lt;pre&gt;Command output: checking build system type... i386-apple-darwin9.7.0&lt;br /&gt;checking host system type... i386-apple-darwin9.7.0&lt;br /&gt;checking target system type... i386-apple-darwin9.7.0&lt;br /&gt;Canonicalised to: i386-apple-darwin&lt;br /&gt;checking version of ghc... 6.8.2&lt;br /&gt;checking for nhc... no&lt;br /&gt;checking for nhc98... no&lt;br /&gt;checking for hbc... no&lt;br /&gt;checking for ld... /usr/bin/ld&lt;br /&gt;./configure: line 11082: syntax error: unexpected end of file&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;Turns out there's some old cruft in the ghc autoconf macros that were causing the break. The fix was a simple:&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;$ sudo port selfupdate &amp;amp;&amp;amp; sudo port clean ghc &amp;amp;&amp;amp; sudo port install ghc&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;You can see the filed &lt;a href="http://trac.macports.org/ticket/20468"&gt;bug&lt;/a&gt; and &lt;a href="http://trac.macports.org/changeset/56583"&gt;changeset&lt;/a&gt; at MacPorts for more information.&lt;br /&gt;&lt;br /&gt;&lt;pre&gt;&lt;/pre&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/8719896753162286533-5399480664588144905?l=blog.ntrippy.net' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://blog.ntrippy.net/feeds/5399480664588144905/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=8719896753162286533&amp;postID=5399480664588144905' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/8719896753162286533/posts/default/5399480664588144905'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/8719896753162286533/posts/default/5399480664588144905'/><link rel='alternate' type='text/html' href='http://blog.ntrippy.net/2009/09/installing-ghc-on-os-x-and-getting.html' title='Installing ghc on OS X and getting &quot;./configure: line 11082: syntax error: unexpected end of file&quot;'/><author><name>Charl</name><uri>http://www.blogger.com/profile/17742115469030093068</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='26' src='http://3.bp.blogspot.com/_4MLU7uPNEIY/TP2zTauOWAI/AAAAAAAAABs/OXj8i31AC0E/S220/blue%2B69.jpg'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-8719896753162286533.post-7115580183460302372</id><published>2009-09-01T15:56:00.000-07:00</published><updated>2009-09-01T16:08:59.340-07:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='ruby rails mod_rails phusion passenger OS X'/><title type='text'>Update: Home made nginx + phusion passenger OS X port</title><content type='html'>The new &lt;a href="http://blog.phusion.nl/2009/09/01/phusion-passenger-2-2-5-released/"&gt;v2.2.5&lt;/a&gt; version of Phusion Passenger has been released.&lt;br /&gt;&lt;br /&gt;If you want to roll this into OS X without having to use their installer you can simply follow the &lt;a href="http://blog.ntrippy.net/2009/07/home-made-nginx-phusion-passenger-dep_01.html"&gt;previous&lt;/a&gt; article to get this done.&lt;br /&gt;&lt;br /&gt;Important things to note is that wherever you used /opt/local/lib/ruby/gems/1.8/gems/passenger-2.2.4* you would now use /opt/local/lib/ruby/gems/1.8/gems/passenger-2.2.5* (the Portfile and the nginx.conf come to mind).&lt;br /&gt;&lt;br /&gt;&lt;pre&gt;&lt;/pre&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/8719896753162286533-7115580183460302372?l=blog.ntrippy.net' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://blog.ntrippy.net/feeds/7115580183460302372/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=8719896753162286533&amp;postID=7115580183460302372' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/8719896753162286533/posts/default/7115580183460302372'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/8719896753162286533/posts/default/7115580183460302372'/><link rel='alternate' type='text/html' href='http://blog.ntrippy.net/2009/09/update-home-made-nginx-phusion.html' title='Update: Home made nginx + phusion passenger OS X port'/><author><name>Charl</name><uri>http://www.blogger.com/profile/17742115469030093068</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='26' src='http://3.bp.blogspot.com/_4MLU7uPNEIY/TP2zTauOWAI/AAAAAAAAABs/OXj8i31AC0E/S220/blue%2B69.jpg'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-8719896753162286533.post-7879487803251930743</id><published>2009-07-24T16:03:00.000-07:00</published><updated>2009-07-24T16:20:35.548-07:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='windows'/><category scheme='http://www.blogger.com/atom/ns#' term='mount'/><category scheme='http://www.blogger.com/atom/ns#' term='disk image'/><category scheme='http://www.blogger.com/atom/ns#' term='iso'/><category scheme='http://www.blogger.com/atom/ns#' term='Virtual CD-ROM Control Panel'/><title type='text'>Mounting a disk image with Windows tools only</title><content type='html'>Coming from the *NIX/Mac world I frequently find myself mounting ISO images as local disks for software installation, etc.&lt;br /&gt;&lt;br /&gt;The same requirement recently came up for me on a Windows platform and I was a little stumped as I did not want to spend any money on this (as I see this as an intrinsic part of the the OS) by buying &lt;a href="http://www.alcohol-software.com/"&gt;Alcohol&lt;/a&gt; or &lt;a href="http://www.daemon-tools.cc/eng/products"&gt;Daemon Tools&lt;/a&gt;.&lt;br /&gt;&lt;br /&gt;To my surprise Microsoft actually has a (unsupported) tool (&lt;a href="http://download.microsoft.com/download/7/b/6/7b6abd84-7841-4978-96f5-bd58df02efa2/winxpvirtualcdcontrolpanel_21.exe"&gt;Virtual CD-ROM Control Panel&lt;/a&gt;) and driver that provides virtual CD-ROM services.&lt;br /&gt;&lt;br /&gt;Unfortunately the solution is XP-only. Download the executable above and install the service. Its readme is quite short and to the point so getting this going is pretty easy.&lt;br /&gt;&lt;br /&gt;Have fun!&lt;br /&gt;&lt;br /&gt;&lt;p&gt;&lt;/p&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/8719896753162286533-7879487803251930743?l=blog.ntrippy.net' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://blog.ntrippy.net/feeds/7879487803251930743/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=8719896753162286533&amp;postID=7879487803251930743' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/8719896753162286533/posts/default/7879487803251930743'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/8719896753162286533/posts/default/7879487803251930743'/><link rel='alternate' type='text/html' href='http://blog.ntrippy.net/2009/07/mounting-disk-image-with-windows-tools.html' title='Mounting a disk image with Windows tools only'/><author><name>Charl</name><uri>http://www.blogger.com/profile/17742115469030093068</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='26' src='http://3.bp.blogspot.com/_4MLU7uPNEIY/TP2zTauOWAI/AAAAAAAAABs/OXj8i31AC0E/S220/blue%2B69.jpg'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-8719896753162286533.post-3370674974232395437</id><published>2009-07-08T17:59:00.000-07:00</published><updated>2009-07-08T19:33:02.877-07:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='javascript'/><category scheme='http://www.blogger.com/atom/ns#' term='jquery'/><category scheme='http://www.blogger.com/atom/ns#' term='dialog plugin'/><category scheme='http://www.blogger.com/atom/ns#' term='jquery-ui'/><title type='text'>Auto-sizing jQuery-UI Dialog</title><content type='html'>I recently upgraded all my &lt;a href="http://docs.jquery.com/"&gt;jquery&lt;/a&gt; and &lt;a href="http://jqueryui.com/demos"&gt;jquery-ui&lt;/a&gt; (and some supporting) libs. Part of this upgrade was also to get the latest version of &lt;a href="http://nyromodal.nyrodev.com/"&gt;nyroModal&lt;/a&gt; working.&lt;br /&gt;&lt;br /&gt;After several hours of chasing issues with the lib I decided to rip it out of my app and rather use the &lt;a href="http://jqueryui.com/demos/dialog"&gt;jquery-ui dialog plugin&lt;/a&gt; that ships with the native lib.&lt;br /&gt;&lt;br /&gt;A large bugbear with nyroModal has always been that I could never get the dialogs to flow around the different content lengths that I was populating them with properly. After retrofitting the jquery-ui dialog plugin I ran into the similar issues I had with nyroModal.&lt;br /&gt;&lt;br /&gt;Whenever the content of my dialog was greater than the distance from the top of the dialog window to the bottom of the viewport, the dialog would simply disappear off the viewport (regardless of whether it had a scrollbar or not).&lt;br /&gt;&lt;br /&gt;What I wanted was for the dialog to automagically adjust its vertical height based on the size of the content it contains relative to the top of the dialog position and the bottom of the viewport.&lt;br /&gt;&lt;br /&gt;The fact that I am using &lt;a href="http://fabrizioballiano.net/jquery-border-layout/"&gt;FBBorderLayout&lt;/a&gt; (yep, I am aware this project has been superseded by the &lt;a href="http://layout.jquery-dev.net/"&gt;jQuery UI plugin&lt;/a&gt; but I am taking baby steps here) to set up a nice layout that stops the page from scrolling down and contributes to the dialog sizing off the viewport.&lt;br /&gt;&lt;br /&gt;&lt;span style="font-weight:bold;"&gt;The scene&lt;/span&gt;&lt;br /&gt;Based on the documentation on the jquery-ui dialog plugin page I created something like &lt;a href="http://pastie.org/539536"&gt;this&lt;/a&gt;.&lt;br /&gt;&lt;br /&gt;As you can see from this page the dialog stretched off the screen with no scrollbar or way to scroll down to see the content of the dialog.&lt;br /&gt;&lt;br /&gt;&lt;span style="font-weight:bold;"&gt;Styling&lt;/span&gt;&lt;br /&gt;Ok, not quite what we wanted so I added the following &lt;a href="http://pastie.org/539540"&gt;style code&lt;/a&gt; to the head to try and get the dialog container to automatically flow and add a scrollbar if the content was too big.&lt;br /&gt;&lt;br /&gt;And? It had no effect whatsoever. I tried all possible styling that I could find to try and get the dialog container to play nice but to no avail.&lt;br /&gt;&lt;br /&gt;&lt;span style="font-weight:bold;"&gt;Unlikely rescuer&lt;/span&gt;&lt;br /&gt;I scoured the web for a possible solution. All I found were a lot of questions about this phenomenon but no real usable solutions or suggestions (beyond the styling I'd already tried).&lt;br /&gt;&lt;br /&gt;I then thought of trying to calculate the height &lt;a href="http://pastie.org/539543"&gt;programatically&lt;/a&gt; and set the height of the dialog contents prior to displaying it.&lt;br /&gt;&lt;br /&gt;You'll notice I stopped the dialog from displaying automatically by adding the 'autoOpen: false' option to the dialog initialization. This is required so that I am able to calculate the height before displaying the dialog.&lt;br /&gt;&lt;br /&gt;&lt;span style="font-weight:bold;"&gt;Finally&lt;/span&gt;&lt;br /&gt;Here's the &lt;a href="http://pastie.org/539534"&gt;complete source file&lt;/a&gt; for posterity.&lt;br /&gt;&lt;br /&gt;Enjoy!&lt;br /&gt;&lt;br /&gt;&lt;pre&gt;&lt;/pre&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/8719896753162286533-3370674974232395437?l=blog.ntrippy.net' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://blog.ntrippy.net/feeds/3370674974232395437/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=8719896753162286533&amp;postID=3370674974232395437' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/8719896753162286533/posts/default/3370674974232395437'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/8719896753162286533/posts/default/3370674974232395437'/><link rel='alternate' type='text/html' href='http://blog.ntrippy.net/2009/07/auto-sizing-jqury-ui-dialog.html' title='Auto-sizing jQuery-UI Dialog'/><author><name>Charl</name><uri>http://www.blogger.com/profile/17742115469030093068</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='26' src='http://3.bp.blogspot.com/_4MLU7uPNEIY/TP2zTauOWAI/AAAAAAAAABs/OXj8i31AC0E/S220/blue%2B69.jpg'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-8719896753162286533.post-1011375643131846634</id><published>2009-07-05T18:58:00.001-07:00</published><updated>2009-07-05T19:31:56.120-07:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='ruby'/><category scheme='http://www.blogger.com/atom/ns#' term='mysql'/><category scheme='http://www.blogger.com/atom/ns#' term='os x'/><category scheme='http://www.blogger.com/atom/ns#' term='rails'/><category scheme='http://www.blogger.com/atom/ns#' term='native'/><category scheme='http://www.blogger.com/atom/ns#' term='gem'/><title type='text'>Installing the MySQL gem on OS X</title><content type='html'>If you manage your system packages via the &lt;a href="http://www.macports.org/"&gt;MacPorts&lt;/a&gt; system you may run into some problems when trying to install the native MySQL driver gem on OS X.&lt;br /&gt;&lt;br /&gt;Typically, you'd see output like this when trying to install the gem:&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;$ sudo gem install mysql&lt;br /&gt;Building native extensions.  This could take a while...&lt;br /&gt;ERROR:  Error installing mysql:&lt;br /&gt;        ERROR: Failed to build gem native extension.&lt;br /&gt;&lt;br /&gt;/opt/local/bin/ruby extconf.rb&lt;br /&gt;checking for mysql_query() in -lmysqlclient... no&lt;br /&gt;checking for main() in -lm... yes&lt;br /&gt;checking for mysql_query() in -lmysqlclient... no&lt;br /&gt;checking for main() in -lz... yes&lt;br /&gt;checking for mysql_query() in -lmysqlclient... no&lt;br /&gt;checking for main() in -lsocket... no&lt;br /&gt;checking for mysql_query() in -lmysqlclient... no&lt;br /&gt;checking for main() in -lnsl... no&lt;br /&gt;checking for mysql_query() in -lmysqlclient... no&lt;br /&gt;*** extconf.rb failed ***&lt;br /&gt;Could not create Makefile due to some reason, probably lack of&lt;br /&gt;necessary libraries and/or headers.  Check the mkmf.log file for more&lt;br /&gt;details.  You may need configuration options.&lt;br /&gt;&lt;br /&gt;Provided configuration options:&lt;br /&gt;        --with-opt-dir&lt;br /&gt;        --without-opt-dir&lt;br /&gt;        --with-opt-include&lt;br /&gt;        --without-opt-include=${opt-dir}/include&lt;br /&gt;        --with-opt-lib&lt;br /&gt;        --without-opt-lib=${opt-dir}/lib&lt;br /&gt;        --with-make-prog&lt;br /&gt;        --without-make-prog&lt;br /&gt;        --srcdir=.&lt;br /&gt;        --curdir&lt;br /&gt;        --ruby=/opt/local/bin/ruby&lt;br /&gt;        --with-mysql-config&lt;br /&gt;        --without-mysql-config&lt;br /&gt;        --with-mysql-dir&lt;br /&gt;        --without-mysql-dir&lt;br /&gt;        --with-mysql-include&lt;br /&gt;        --without-mysql-include=${mysql-dir}/include&lt;br /&gt;        --with-mysql-lib&lt;br /&gt;        --without-mysql-lib=${mysql-dir}/lib&lt;br /&gt;        --with-mysqlclientlib&lt;br /&gt;        --without-mysqlclientlib&lt;br /&gt;        --with-mlib&lt;br /&gt;        --without-mlib&lt;br /&gt;        --with-mysqlclientlib&lt;br /&gt;        --without-mysqlclientlib&lt;br /&gt;        --with-zlib&lt;br /&gt;        --without-zlib&lt;br /&gt;        --with-mysqlclientlib&lt;br /&gt;        --without-mysqlclientlib&lt;br /&gt;        --with-socketlib&lt;br /&gt;        --without-socketlib&lt;br /&gt;        --with-mysqlclientlib&lt;br /&gt;        --without-mysqlclientlib&lt;br /&gt;        --with-nsllib&lt;br /&gt;        --without-nsllib&lt;br /&gt;        --with-mysqlclientlib&lt;br /&gt;        --without-mysqlclientlib&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;Gem files will remain installed in /opt/local/lib/ruby/gems/1.8/gems/mysql-2.7 for inspection.&lt;br /&gt;Results logged to /opt/local/lib/ruby/gems/1.8/gems/mysql-2.7/gem_make.out&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;Based on those errors the configure process seems to be failing when looking for MySQL libs. The default path to the libs based on extconf.rb seems to be /usr/local which is not where the ports system installs MySQL.&lt;br /&gt;&lt;br /&gt;All you need to do is point to the correct mysql_config and let the gem install command line know of this to get things going:&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;$ sudo gem install mysql -- --with-mysql-config=/opt/local/bin/mysql_config5  &lt;br /&gt;Building native extensions.  This could take a while...&lt;br /&gt;Successfully installed mysql-2.7&lt;br /&gt;1 gem installed&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;All's well that ends well.&lt;br /&gt;&lt;br /&gt;&lt;p&gt;&lt;/p&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/8719896753162286533-1011375643131846634?l=blog.ntrippy.net' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://blog.ntrippy.net/feeds/1011375643131846634/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=8719896753162286533&amp;postID=1011375643131846634' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/8719896753162286533/posts/default/1011375643131846634'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/8719896753162286533/posts/default/1011375643131846634'/><link rel='alternate' type='text/html' href='http://blog.ntrippy.net/2009/07/installing-mysql-gem-on-os-x.html' title='Installing the MySQL gem on OS X'/><author><name>Charl</name><uri>http://www.blogger.com/profile/17742115469030093068</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='26' src='http://3.bp.blogspot.com/_4MLU7uPNEIY/TP2zTauOWAI/AAAAAAAAABs/OXj8i31AC0E/S220/blue%2B69.jpg'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-8719896753162286533.post-577291606317169182</id><published>2009-07-01T20:41:00.000-07:00</published><updated>2009-07-01T20:57:03.556-07:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='phusion passenger'/><category scheme='http://www.blogger.com/atom/ns#' term='nginx'/><category scheme='http://www.blogger.com/atom/ns#' term='RangeError'/><title type='text'>nginx/passenger: Exception RangeError in PhusionPassenger::Railz::ApplicationSpawner (bignum too big to convert into `long')</title><content type='html'>Nasty.&lt;br /&gt;&lt;br /&gt;I am running nginx+passenger on my OS X laptop and after getting the passenger module installed I got the following stacktrace in /opt/local/var/log/nginx/error.log:&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;*** Exception RangeError in PhusionPassenger::Railz::ApplicationSpawner (bignum too big to convert into `long') (process&lt;br /&gt; 56051):&lt;br /&gt;        from /opt/local/lib/ruby/gems/1.8/gems/passenger-2.2.4/lib/phusion_passenger/utils.rb:363:in `setgid'&lt;br /&gt;        from /opt/local/lib/ruby/gems/1.8/gems/passenger-2.2.4/lib/phusion_passenger/utils.rb:363:in `switch_to_user'&lt;br /&gt;        from /opt/local/lib/ruby/gems/1.8/gems/passenger-2.2.4/lib/phusion_passenger/utils.rb:328:in `lower_privilege'&lt;br /&gt;        from /opt/local/lib/ruby/gems/1.8/gems/passenger-2.2.4/lib/phusion_passenger/railz/application_spawner.rb:250:in&lt;br /&gt; `initialize_server'&lt;br /&gt;        from /opt/local/lib/ruby/gems/1.8/gems/passenger-2.2.4/lib/phusion_passenger/utils.rb:230:in `report_app_init_st&lt;br /&gt;atus'&lt;br /&gt;        from /opt/local/lib/ruby/gems/1.8/gems/passenger-2.2.4/lib/phusion_passenger/railz/application_spawner.rb:237:in&lt;br /&gt; `initialize_server'&lt;br /&gt;        from /opt/local/lib/ruby/gems/1.8/gems/passenger-2.2.4/lib/phusion_passenger/abstract_server.rb:193:in `start_sy&lt;br /&gt;nchronously'&lt;br /&gt;        from /opt/local/lib/ruby/gems/1.8/gems/passenger-2.2.4/lib/phusion_passenger/abstract_server.rb:162:in `start'&lt;br /&gt;        from /opt/local/lib/ruby/gems/1.8/gems/passenger-2.2.4/lib/phusion_passenger/railz/application_spawner.rb:213:in&lt;br /&gt; `start'&lt;br /&gt;        from /opt/local/lib/ruby/gems/1.8/gems/passenger-2.2.4/lib/phusion_passenger/spawn_manager.rb:261:in `spawn_rail&lt;br /&gt;s_application'&lt;br /&gt;        from /opt/local/lib/ruby/gems/1.8/gems/passenger-2.2.4/lib/phusion_passenger/abstract_server_collection.rb:126:i&lt;br /&gt;n `lookup_or_add'&lt;br /&gt;        from /opt/local/lib/ruby/gems/1.8/gems/passenger-2.2.4/lib/phusion_passenger/spawn_manager.rb:255:in `spawn_rail&lt;br /&gt;s_application'&lt;br /&gt;        from /opt/local/lib/ruby/gems/1.8/gems/passenger-2.2.4/lib/phusion_passenger/abstract_server_collection.rb:80:in&lt;br /&gt; `synchronize'&lt;br /&gt;        from /opt/local/lib/ruby/gems/1.8/gems/passenger-2.2.4/lib/phusion_passenger/abstract_server_collection.rb:79:in&lt;br /&gt; `synchronize'&lt;br /&gt;        from /opt/local/lib/ruby/gems/1.8/gems/passenger-2.2.4/lib/phusion_passenger/spawn_manager.rb:254:in `spawn_rail&lt;br /&gt;s_application'&lt;br /&gt;        from /opt/local/lib/ruby/gems/1.8/gems/passenger-2.2.4/lib/phusion_passenger/spawn_manager.rb:153:in `spawn_appl&lt;br /&gt;ication'&lt;br /&gt;        from /opt/local/lib/ruby/gems/1.8/gems/passenger-2.2.4/lib/phusion_passenger/spawn_manager.rb:286:in `handle_spa&lt;br /&gt;wn_application'&lt;br /&gt;        from /opt/local/lib/ruby/gems/1.8/gems/passenger-2.2.4/lib/phusion_passenger/abstract_server.rb:351:in `__send__&lt;br /&gt;'&lt;br /&gt;        from /opt/local/lib/ruby/gems/1.8/gems/passenger-2.2.4/lib/phusion_passenger/abstract_server.rb:351:in `main_loo&lt;br /&gt;p'&lt;br /&gt;        from /opt/local/lib/ruby/gems/1.8/gems/passenger-2.2.4/lib/phusion_passenger/abstract_server.rb:195:in `start_sy&lt;br /&gt;nchronously'&lt;br /&gt;        from /opt/local/lib/ruby/gems/1.8/gems/passenger-2.2.4/bin/passenger-spawn-server:61&lt;/pre&gt;&lt;br /&gt;So, what's up?&lt;br /&gt;&lt;br /&gt;&lt;span style="font-weight:bold;"&gt;The old switcheroo&lt;/span&gt;&lt;br /&gt;Passenger implements &lt;a href="http://www.modrails.com/documentation/Users%20guide%20Apache.html#user_switching"&gt;User Switching&lt;/a&gt; which works around the age old problem that plagues most other scripting languages where they run in the same context as the web server they are accessed through.&lt;br /&gt;&lt;br /&gt;The default behaviour is for rails apps to be started as the owner of config/environment.rb. There are however some points that should be noted:&lt;br /&gt;&lt;ul&gt;&lt;br /&gt;&lt;li&gt;The owner of environment.rb must have read access to the rails application folder, and read/write access to the rails application logs folder.&lt;/li&gt;&lt;br /&gt;&lt;li&gt;This feature is only available if nginx is started by root. This is the case on most nginx installations.&lt;/li&gt;&lt;br /&gt;&lt;li&gt;Under no circumstances will applications be run as root. If environment.rb is owned as root or by an unknown user, then the rails application will run as the user specified by passenger_default_user.&lt;/li&gt;&lt;br /&gt;&lt;/ul&gt;&lt;br /&gt;&lt;br /&gt;In my case the project was owned by root so changing that to a local user fixed the problem.&lt;br /&gt;&lt;br /&gt;&lt;pre&gt;&lt;/pre&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/8719896753162286533-577291606317169182?l=blog.ntrippy.net' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://blog.ntrippy.net/feeds/577291606317169182/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=8719896753162286533&amp;postID=577291606317169182' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/8719896753162286533/posts/default/577291606317169182'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/8719896753162286533/posts/default/577291606317169182'/><link rel='alternate' type='text/html' href='http://blog.ntrippy.net/2009/07/nginxpassenger-exception-rangeerror-in.html' title='nginx/passenger: Exception RangeError in PhusionPassenger::Railz::ApplicationSpawner (bignum too big to convert into `long&apos;)'/><author><name>Charl</name><uri>http://www.blogger.com/profile/17742115469030093068</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='26' src='http://3.bp.blogspot.com/_4MLU7uPNEIY/TP2zTauOWAI/AAAAAAAAABs/OXj8i31AC0E/S220/blue%2B69.jpg'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-8719896753162286533.post-757146427560525798</id><published>2009-07-01T19:06:00.000-07:00</published><updated>2009-07-01T20:14:58.954-07:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='phusion passenger'/><category scheme='http://www.blogger.com/atom/ns#' term='port'/><category scheme='http://www.blogger.com/atom/ns#' term='os x'/><category scheme='http://www.blogger.com/atom/ns#' term='rails'/><category scheme='http://www.blogger.com/atom/ns#' term='nginx'/><title type='text'>Home made nginx + phusion passenger OS X port</title><content type='html'>The instructions to do this are similar to those in my &lt;a href="http://blog.ntrippy.net/2009/07/home-made-nginx-phusion-passenger-dep.html"&gt;previous&lt;/a&gt; article.&lt;br /&gt;&lt;br /&gt;&lt;span style="font-weight: bold;"&gt;Install passenger gem&lt;/span&gt;&lt;br /&gt;The first step is to install the &lt;a href="http://www.modrails.com/"&gt;Phusion Passenger&lt;/a&gt; gem:&lt;br /&gt;&lt;pre&gt;$ sudo gem install -V -r passenger&lt;br /&gt;[...]&lt;br /&gt;Installing RDoc documentation for passenger-2.2.4...&lt;/pre&gt;&lt;br /&gt;Take note of where the passenger gem installed the nginx module code (ext/nginx/*) that will be required further down. In this case the install directory will be /opt/local/lib/ruby/gems/1.8/gems/passenger-2.2.4/ext/.&lt;br /&gt;&lt;br /&gt;&lt;span style="font-weight: bold;"&gt;Compile nginx passenger module&lt;/span&gt;&lt;br /&gt;You also need to compile the passenger nginx module if it was not already compiled:&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;$ cd /opt/local/lib/ruby/gems/1.8/gems/passenger-2.2.4/ext/&lt;br /&gt;$ sudo rake nginx&lt;br /&gt;[...]&lt;br /&gt;g++ ext/nginx/HelperServer.cpp -o ext/nginx/HelperServer -Iext -Iext/common -D_REENTRANT -I/usr/local/include -Wall -g -DPASSENGER_DEBUG -DBOOST_DISABLE_ASSERTS  ext/nginx/libpassenger_common.a ext/nginx/libboost_oxt.a -lpthread&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;&lt;span style="font-weight:bold;"&gt;Modify the nginx Portfile&lt;/span&gt;&lt;br /&gt;Ensure you have nginx port installed. Your Portfile will most likely be in /opt/local/var/macports/sources/rsync.macports.org/release/ports/www/nginx/Portfile.&lt;br /&gt;&lt;br /&gt;Add the following variant block to the end of the file:&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;variant passenger description {Enable Phusion Passenger (mod_rails) support} {&lt;br /&gt;    configure.args-append   --add-module=/opt/local/lib/ruby/gems/1.8/gems/passenger-2.2.4/ext/nginx&lt;br /&gt;}&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;&lt;span style="font-weight:bold;"&gt;Rebuild/install nginx&lt;/span&gt;&lt;br /&gt;When you try and reinstall nginx now it will use the updated Portfile and add the passenger module to the mix:&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;$ sudo port uninstall nginx &amp;&amp; sudo port clean nginx&lt;br /&gt;$ sudo port -vvvvv install nginx +passenger&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;&lt;span style="font-weight:bold;"&gt;Nginx passenger configuration&lt;/span&gt;&lt;br /&gt;To enable and take advantage of the passenger nginx module you need to edit the /opt/local/etc/nginx/nginx.conf config. Here's an example:&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;#user  nobody;&lt;br /&gt;worker_processes  1;&lt;br /&gt;&lt;br /&gt;#error_log  logs/error.log;&lt;br /&gt;#error_log  logs/error.log  notice;&lt;br /&gt;#error_log  logs/error.log  info;&lt;br /&gt;&lt;br /&gt;#pid        logs/nginx.pid;&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;events {&lt;br /&gt;    worker_connections  1024;&lt;br /&gt;}&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;http {&lt;br /&gt;  include       mime.types;&lt;br /&gt;  default_type  application/octet-stream;&lt;br /&gt;&lt;br /&gt;  #log_format  main  '$remote_addr - $remote_user [$time_local] $request '&lt;br /&gt;  #                  '"$status" $body_bytes_sent "$http_referer" '&lt;br /&gt;  #                  '"$http_user_agent" "$http_x_forwarded_for"';&lt;br /&gt;&lt;br /&gt;  #access_log  logs/access.log  main;&lt;br /&gt;&lt;br /&gt;  sendfile        on;&lt;br /&gt;  #tcp_nopush     on;&lt;br /&gt;&lt;br /&gt;  #keepalive_timeout  0;&lt;br /&gt;  keepalive_timeout  65;&lt;br /&gt;&lt;br /&gt;  gzip  on;&lt;br /&gt;&lt;br /&gt;  # Enable Phusion Passenger nginx module&lt;br /&gt;  passenger_root /opt/local/lib/ruby/gems/1.8/gems/passenger-2.2.4;&lt;br /&gt;  passenger_ruby /opt/local/bin/ruby;&lt;br /&gt;&lt;br /&gt;  server {&lt;br /&gt;    listen 80;&lt;br /&gt;    server_name foo.example.com;&lt;br /&gt;    root /var/www/foo/public;   # &lt;--- be sure to point to 'public'!&lt;br /&gt;    passenger_enabled on;&lt;br /&gt;  }&lt;br /&gt;}&lt;/pre&gt;&lt;br /&gt;Ensure that the port, server_name and root variables have valid values for your environment. &lt;br /&gt;&lt;br /&gt;If you want to use a non-production rails environment you can add the following to the block after passenger_enabled:&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;      rails_env development;&lt;/pre&gt;&lt;br /&gt;Once done you can restart nginx and you should see the following processes that confirm things are running:&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;$ ps axw | egrep "nginx|passenger"&lt;br /&gt;55935   ??  Ss     0:00.00 nginx: master process nginx&lt;br /&gt;55936   ??  S      0:00.00 nginx: worker process&lt;br /&gt;55925 s001  S      0:00.01 PassengerNginxHelperServer /opt/local/lib/ruby/gems/1.8/gems/passenger-2.2.4 /opt/local/bin/ruby 3 4 0 6 0 300 1 nobody 4294967294 4294967294 /tmp/passenger.55923&lt;br /&gt;55938 s001  R+     0:00.00 egrep nginx|passenger&lt;/pre&gt;&lt;br /&gt;The final test to see if everything is working is to open a browser and to browse to the server_name and port defined above. Errors will be reported in /opt/local/var/log/nginx/error.log as well as the development/production logs of the rails project you're running.&lt;br /&gt;&lt;br /&gt;&lt;pre&gt;&lt;/pre&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/8719896753162286533-757146427560525798?l=blog.ntrippy.net' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://blog.ntrippy.net/feeds/757146427560525798/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=8719896753162286533&amp;postID=757146427560525798' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/8719896753162286533/posts/default/757146427560525798'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/8719896753162286533/posts/default/757146427560525798'/><link rel='alternate' type='text/html' href='http://blog.ntrippy.net/2009/07/home-made-nginx-phusion-passenger-dep_01.html' title='Home made nginx + phusion passenger OS X port'/><author><name>Charl</name><uri>http://www.blogger.com/profile/17742115469030093068</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='26' src='http://3.bp.blogspot.com/_4MLU7uPNEIY/TP2zTauOWAI/AAAAAAAAABs/OXj8i31AC0E/S220/blue%2B69.jpg'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-8719896753162286533.post-9055569551476030893</id><published>2009-07-01T17:37:00.000-07:00</published><updated>2009-07-01T20:11:51.526-07:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='phusion passenger'/><category scheme='http://www.blogger.com/atom/ns#' term='linux'/><category scheme='http://www.blogger.com/atom/ns#' term='debian'/><category scheme='http://www.blogger.com/atom/ns#' term='deb'/><category scheme='http://www.blogger.com/atom/ns#' term='rails'/><category scheme='http://www.blogger.com/atom/ns#' term='nginx'/><category scheme='http://www.blogger.com/atom/ns#' term='Ubuntu'/><title type='text'>Home made nginx + phusion passenger Debian/Ubuntu dep</title><content type='html'>If you are using Ubuntu Hardy then you could try using Brightbox's &lt;a href="http://apt.brightbox.net/"&gt;repo&lt;/a&gt; instead of building your own deb.&lt;br /&gt;&lt;br /&gt;If you are however running some other distro then the following (non-definitive) instructions are for you.&lt;br /&gt;&lt;br /&gt;&lt;span style="font-style:italic;"&gt;Warning: it may be safer to install nginx + passenger via the passenger-install-nginx-module script as it will match passenger with a tested version of nginx.&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;The instructions below simply add passenger support to the latest nginx deb in your distro of choice.&lt;br /&gt;&lt;br /&gt;&lt;span style="font-weight: bold;"&gt;Install passenger gem&lt;/span&gt;&lt;br /&gt;The first step is to install the &lt;a href="http://www.modrails.com/"&gt;Phusion Passenger&lt;/a&gt; gem:&lt;br /&gt;&lt;pre&gt;$ sudo gem install -V -r passenger&lt;br /&gt;[...]&lt;br /&gt;Installing RDoc documentation for passenger-2.2.4...&lt;/pre&gt;&lt;br /&gt;Take note of where the passenger gem installed the nginx module code that will be required further down:&lt;br /&gt;&lt;pre&gt;$ gem contents passenger | egrep '/ext/nginx/' | head -n 1&lt;br /&gt;/var/lib/gems/1.8/gems/passenger-2.2.4/ext/nginx/Configuration.c&lt;/pre&gt;&lt;br /&gt;In this case the install directory will be /var/lib/gems/1.8/gems/passenger-2.2.4/ext/nginx.&lt;br /&gt;&lt;br /&gt;&lt;span style="font-weight: bold;"&gt;Compile nginx passenger module&lt;/span&gt;&lt;br /&gt;You also need to compile the passenger nginx module if it was not already compiled:&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;$ cd /var/lib/gems/1.8/gems/passenger-2.2.4/ext/nginx&lt;br /&gt;$ sudo rake nginx&lt;br /&gt;&lt;br /&gt;(in /var/lib/gems/1.8/gems/passenger-2.2.4)&lt;br /&gt;mkdir -p ext/nginx/libboost_oxt/boost&lt;br /&gt;g++ -Iext  -D_REENTRANT -I/usr/local/include -Wall -g -DPASSENGER_DEBUG -DBOOST_DISABLE_ASSERTS -o ext/nginx/libboost_oxt/boost/exceptions.o -c ext/boost/src/pthread/exceptions.cpp&lt;br /&gt;[...]&lt;br /&gt;g++ ext/nginx/HelperServer.cpp -o ext/nginx/HelperServer -Iext -Iext/common -D_REENTRANT -I/usr/local/include -Wall -g -DPASSENGER_DEBUG -DBOOST_DISABLE_ASSERTS  ext/nginx/libpassenger_common.a ext/nginx/libboost_oxt.a -lpthread&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;&lt;span style="font-weight:bold;"&gt;Install build packages&lt;/span&gt;&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;$ sudo aptitude install fakeroot debhelper dpkg-dev autotools-dev libpcre3-dev libssl-dev&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;&lt;span style="font-weight:bold;"&gt;Modify APT sources.list&lt;/span&gt;&lt;br /&gt;Open /etc/apt/sources.list and ensure you add deb-src lines for each of the universe repos. The easiest way to do this is to simply copy the universe repo lines that exist and change the starting deb to deb-src:&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;deb http://au.archive.ubuntu.com/ubuntu/ jaunty universe&lt;br /&gt;deb-src http://au.archive.ubuntu.com/ubuntu/ jaunty universe&lt;br /&gt;deb http://au.archive.ubuntu.com/ubuntu/ jaunty-updates universe&lt;br /&gt;deb-src http://au.archive.ubuntu.com/ubuntu/ jaunty-updates universe&lt;/pre&gt;&lt;br /&gt;Replace 'au.archive.ubuntu.com' with the repo closest to you and also replace 'jaunty' with your distro. Ensure you do a 'sudo aptitude update' to load the latest package indexes.&lt;br /&gt;&lt;br /&gt;&lt;span style="font-weight:bold;"&gt;Set up a packaging environment&lt;/span&gt;&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;$ mkdir -p /var/tmp/nginx-passenger &amp;&amp; cd /var/tmp/nginx-passenger&lt;br /&gt;$ apt-get source nginx&lt;br /&gt;Reading package lists... Done&lt;br /&gt;Building dependency tree      &lt;br /&gt;Reading state information... Done&lt;br /&gt;NOTICE: 'nginx' packaging is maintained in the 'Svn' version control system at:&lt;br /&gt;svn://svn.debian.org/svn/collab-maint/deb-maint/nginx/trunk&lt;br /&gt;Need to get 537kB of source archives.&lt;br /&gt;Get:1 http://au.archive.ubuntu.com jaunty/universe nginx 0.6.35-0ubuntu1 (dsc) [1305B]&lt;br /&gt;Get:2 http://au.archive.ubuntu.com jaunty/universe nginx 0.6.35-0ubuntu1 (tar) [525kB]&lt;br /&gt;Get:3 http://au.archive.ubuntu.com jaunty/universe nginx 0.6.35-0ubuntu1 (diff) [11.0kB]&lt;br /&gt;Fetched 537kB in 0s (1504kB/s)&lt;br /&gt;gpg: Signature made Sat 21 Feb 2009 07:02:34 AM EST using DSA key ID 3FE63E00&lt;br /&gt;gpg: Can't check signature: public key not found&lt;br /&gt;dpkg-source: extracting nginx in nginx-0.6.35&lt;br /&gt;dpkg-source: info: unpacking nginx_0.6.35.orig.tar.gz&lt;br /&gt;dpkg-source: info: applying nginx_0.6.35-0ubuntu1.diff.gz&lt;br /&gt;$ ls -la&lt;br /&gt;total 548&lt;br /&gt;drwxr-xr-x 3 root root   4096 2009-06-29 08:55 .&lt;br /&gt;drwxrwxrwt 3 root root   4096 2009-06-29 08:39 ..&lt;br /&gt;drwxr-xr-x 8 root root   4096 2009-06-29 08:55 nginx-0.6.35&lt;br /&gt;-rw-r--r-- 1 root root  11018 2009-02-21 08:04 nginx_0.6.35-0ubuntu1.diff.gz&lt;br /&gt;-rw-r--r-- 1 root root   1305 2009-02-21 08:04 nginx_0.6.35-0ubuntu1.dsc&lt;br /&gt;-rw-r--r-- 1 root root 524987 2009-02-21 08:04 nginx_0.6.35.orig.tar.gz&lt;br /&gt;$ rm -f nginx_*&lt;br /&gt;$ mv nginx-0.6.35 nginx-passenger-0.6.35&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;&lt;span style="font-weight:bold;"&gt;Package customizations&lt;/span&gt;&lt;br /&gt;In order to build our nginx-passenger custom package we need to modify the following packaging resources:&lt;br /&gt;&lt;ol&gt;&lt;br /&gt;&lt;li&gt;control file&lt;/li&gt;&lt;br /&gt;&lt;li&gt;changelog file&lt;/li&gt;&lt;br /&gt;&lt;li&gt;rules file&lt;/li&gt;&lt;br /&gt;&lt;li&gt;nginx.install file&lt;/li&gt;&lt;br /&gt;&lt;li&gt;nginx.logrotate file&lt;/li&gt;&lt;br /&gt;&lt;li&gt;passenger.conf file&lt;/li&gt;&lt;br /&gt;&lt;li&gt;postinst file&lt;/li&gt;&lt;br /&gt;&lt;/ol&gt;&lt;br /&gt;&lt;br /&gt;&lt;span style="font-weight:bold;"&gt;Control file&lt;/span&gt;&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;$ cd nginx-passenger-0.6.35/debian&lt;/pre&gt;&lt;br /&gt;Edit the control file and change the Source, Maintainer, Uploaders, Package, Provides, Conflicts and Description fields:&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;Source: nginx-passenger&lt;br /&gt;Maintainer: Charl Matthee&lt;br /&gt;Uploaders: Charl Matthee&lt;br /&gt;Package: nginx-passenger&lt;br /&gt;Provides: httpd, nginx&lt;br /&gt;Conflicts: nginx&lt;br /&gt;Description: small, but very powerful and efficient web server&lt;br /&gt; Nginx (engine x) is a web server created by Igor Sysoev and kindly provided&lt;br /&gt; to the open-source community. This server can be used as standalone HTTP&lt;br /&gt; server and as a reverse proxy server before some Apache or another big&lt;br /&gt; server to reduce load to backend servers by many concurrent HTTP-sessions.&lt;br /&gt; .&lt;br /&gt; This is a custom nginx package that includes Phusion Passenger&lt;br /&gt; support for nginx.&lt;/pre&gt;&lt;br /&gt;&lt;span style="font-style:italic;"&gt;Note: spacing in the control file is significant.&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;More information on the control file internals can be found &lt;a href="http://www.debian.org/doc/maint-guide/ch-dreq.en.html#s-control"&gt;here&lt;/a&gt;.&lt;br /&gt;&lt;br /&gt;&lt;span style="font-weight:bold;"&gt;Changelog&lt;/span&gt;&lt;br /&gt;Add a new changelog entry in the changelog file:&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;nginx-passenger (0.6.35-0custom1) jaunty; urgency=low&lt;br /&gt;&lt;br /&gt;  * New custom Passenger Phusion nginx module addition&lt;br /&gt;&lt;br /&gt; -- Charl Matthee   Mon, 29 Jun 2009 09:08:36 +0100&lt;br /&gt;&lt;br /&gt;nginx (0.6.35-0ubuntu1) jaunty; urgency=low&lt;br /&gt;&lt;br /&gt;  * New upstream bugfix release&lt;br /&gt;&lt;br /&gt; -- Daniel Hahler   Fri, 20 Feb 2009 21:01:23 +0100&lt;br /&gt;[...]&lt;/pre&gt;&lt;br /&gt;More information on the changelog file internals can be found &lt;a href="http://www.debian.org/doc/maint-guide/ch-dreq.en.html#s-changelog"&gt;here&lt;/a&gt;.&lt;br /&gt;&lt;br /&gt;&lt;span style="font-weight:bold;"&gt;Rules&lt;/span&gt;&lt;br /&gt;Add the passenger module inclusion to the configure script options when nginx is built by modifying the './configure $(CONFIGURE_OPTS)' block in the rules file like this:&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;        ./configure $(CONFIGURE_OPTS) --conf-path=/etc/nginx/nginx.conf \&lt;br /&gt;        --error-log-path=/var/log/nginx/error.log --pid-path=/var/run/nginx.pid \&lt;br /&gt;        --lock-path=/var/lock/nginx.lock   --http-log-path=/var/log/nginx/access.log \&lt;br /&gt;        --http-client-body-temp-path=/var/lib/nginx/body --http-proxy-temp-path=/var/lib/nginx/proxy \&lt;br /&gt;        --http-fastcgi-temp-path=/var/lib/nginx/fastcgi --with-http_stub_status_module \&lt;br /&gt;        --with-http_flv_module --with-http_ssl_module --with-http_dav_module \&lt;br /&gt;        --with-http_realip_module --add-module=PASSENGER_LIB_DIR&lt;/pre&gt;&lt;br /&gt;Where PASSENGER_LIB_DIR is the path you noted above when you installed the passenger gem. So, the updated block looks like this:&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;        ./configure $(CONFIGURE_OPTS) --conf-path=/etc/nginx/nginx.conf \&lt;br /&gt;        --error-log-path=/var/log/nginx/error.log --pid-path=/var/run/nginx.pid \&lt;br /&gt;        --lock-path=/var/lock/nginx.lock   --http-log-path=/var/log/nginx/access.log \&lt;br /&gt;        --http-client-body-temp-path=/var/lib/nginx/body --http-proxy-temp-path=/var/lib/nginx/proxy \&lt;br /&gt;        --http-fastcgi-temp-path=/var/lib/nginx/fastcgi --with-http_stub_status_module \&lt;br /&gt;        --with-http_flv_module --with-http_ssl_module --with-http_dav_module \&lt;br /&gt;        --with-http_realip_module --add-module=/var/lib/gems/1.8/gems/passenger-2.2.4/ext/nginx/&lt;/pre&gt;&lt;br /&gt;More infomration on the changelog file internals can be found &lt;a href="http://www.debian.org/doc/maint-guide/ch-dreq.en.html#s-rules"&gt;here&lt;/a&gt;.&lt;br /&gt;&lt;br /&gt;&lt;span style="font-weight:bold;"&gt;nginx.install&lt;/span&gt;&lt;br /&gt;Move this file to the correct name based on the package name and add the installation instructions for the default nginx configuration that is required to turn passenger support on:&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;$ mv nginx.install nginx-passenger.install&lt;br /&gt;$ cat nginx-passenger.install&lt;br /&gt;objs/nginx usr/sbin&lt;br /&gt;html/* var/www/nginx-default&lt;br /&gt;debian/conf/* etc/nginx&lt;br /&gt;debian/passenger.conf etc/nginx/conf.d/&lt;br /&gt;debian/NEWS.Debian usr/share/doc/nginx&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;&lt;span style="font-weight:bold;"&gt;nginx.logrotate&lt;/span&gt;&lt;br /&gt;Move this file to the correct name based on the package name:&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;$ mv nginx.logrotate nginx-passenger.logrotate&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;&lt;span style="font-weight:bold;"&gt;passenger.conf&lt;/span&gt;&lt;br /&gt;Create the following file in the debian directory:&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;    passenger_root /var/lib/gems/1.8/gems/passenger-2.2.4;&lt;br /&gt;    passenger_ruby /usr/bin/ruby1.8;&lt;/pre&gt;&lt;br /&gt;Ensure those paths point to the correct passenger isntall and ruby directories.&lt;br /&gt;&lt;br /&gt;&lt;span style="font-weight:bold;"&gt;postinst&lt;/span&gt;&lt;br /&gt;Add the following steps to the postinst script after the '#DEBHELPER#' token:&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;# Phusion Passenger module settings for nginx&lt;br /&gt;echo&lt;br /&gt;echo "***********************************************************************"&lt;br /&gt;echo "For each virtual host you want to add Phusion Passenger support for you"&lt;br /&gt;echo "need to create a config like this in /etc/nginx/site-available:"&lt;br /&gt;echo&lt;br /&gt;echo "    server {&lt;br /&gt;      listen 80;&lt;br /&gt;      server_name DOMAIN_NAME;&lt;br /&gt;      root /var/www/RAILS_PROJECT/public; #&lt;--- be sure to point to 'public'!&lt;br /&gt;      passenger_enabled on;&lt;br /&gt;    }"&lt;br /&gt;echo&lt;br /&gt;echo "Don't forget to add a symlink from this file(s) to"&lt;br /&gt;echo "/etc/nginx/site-enabled to enable the configuration in nginx."&lt;br /&gt;echo "***********************************************************************"&lt;br /&gt;echo&lt;/pre&gt;&lt;br /&gt;This will warn administrators that install this package that they still need to do some manual work to get a virtual host set up to take advantage of the passenger module.&lt;br /&gt;&lt;br /&gt;&lt;span style="font-weight:bold;"&gt;Package rebuild&lt;/span&gt;&lt;br /&gt;The package can then be rebuilt just like any other custom deb:&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;$ cd /var/tmp/nginx-passenger/nginx-passenger-0.6.35&lt;br /&gt;$ dpkg-buildpackage -rfakeroot&lt;br /&gt;dpkg-buildpackage: set CFLAGS to default value: -g -O2&lt;br /&gt;[...]&lt;br /&gt;dpkg-deb: building package `nginx-passenger' in `../nginx-passenger_0.6.35-0custom1_i386.deb'.&lt;br /&gt;dpkg-deb: ignoring 1 warnings about the control file(s)&lt;br /&gt; signfile nginx-passenger_0.6.35-0custom1.dsc&lt;br /&gt;gpg: skipped "Charl Matthee ": secret key not available&lt;br /&gt;gpg: [stdin]: clearsign failed: secret key not available&lt;br /&gt;&lt;br /&gt;dpkg-genchanges  &gt;../nginx-passenger_0.6.35-0custom1_i386.changes&lt;br /&gt;dpkg-genchanges: warning: the current version (0.6.35-0custom1) is smaller than the previous one (0.6.35-0ubuntu1)&lt;br /&gt;dpkg-genchanges: including full source code in upload&lt;br /&gt;dpkg-buildpackage: full upload; Debian-native package (full source is included)&lt;br /&gt;dpkg-buildpackage: warning: Failed to sign .dsc and .changes file&lt;/pre&gt;&lt;br /&gt;Those signing errors were triggered because I have did not have the required key infrastructure installed. They can be ignored for the moment.&lt;br /&gt;&lt;br /&gt;Install the new package:&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;$ sudo dpkg -i ../nginx-passenger_0.6.35-0custom1_i386.deb&lt;br /&gt;[...]&lt;br /&gt;Setting up nginx-passenger (0.6.35-0custom1) ...&lt;br /&gt;&lt;br /&gt;***********************************************************************&lt;br /&gt;Create a config like this for each virtual host you want to add Phusion&lt;br /&gt;Passenger support to. They should be created in&lt;br /&gt;/etc/nginx/site-available:&lt;br /&gt;&lt;br /&gt;    server {&lt;br /&gt;      listen 80;&lt;br /&gt;      server_name DOMAIN_NAME;&lt;br /&gt;      root /var/www/RAILS/public;     # &lt;--- be sure to point to 'public'!&lt;br /&gt;      passenger_enabled on;&lt;br /&gt;    }&lt;br /&gt;&lt;br /&gt;Don't forget to add a symlink from this file(s) to&lt;br /&gt;/etc/nginx/site-enabled to enable the configuration in nginx.&lt;br /&gt;***********************************************************************&lt;br /&gt;[...]&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;&lt;span style="font-weight:bold;"&gt;Nginx passenger configuration&lt;/span&gt;&lt;br /&gt;To enable and take advantage of the passenger nginx module you need to create a /etc/nginx/conf.d/passenger.conf config:&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;    passenger_root /var/lib/gems/1.8/gems/passenger-2.2.4;&lt;br /&gt;    passenger_ruby /usr/bin/ruby1.8;&lt;/pre&gt;&lt;br /&gt;This will of course have been done when we installed our custom package.&lt;br /&gt;&lt;br /&gt;Next, add a virtual host for each of the rails applications you want to support by dropping a file like 'foo' into /etc/nginx/sites-available/:&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;    server {&lt;br /&gt;      listen 80;&lt;br /&gt;      server_name foo.example.com;&lt;br /&gt;      root /var/www/foo/public;   # &lt;--- be sure to point to 'public'!&lt;br /&gt;      passenger_enabled on;&lt;br /&gt;    }&lt;/pre&gt;&lt;br /&gt;Ensure that the port, server_name and root variables have valid values for your environment. &lt;br /&gt;&lt;br /&gt;If you want to use a non-production rails environment you can add the following to the block after passenger_enabled:&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;      rails_env development;&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;To enable the virtual host(s) you need to ensure that there's a symlink linking the virtual host in sites-available to sites-enabled:&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;$ sudo ln -s /etc/nginx/sites-available/foo /etc/nginx/sites-enabled/foo&lt;br /&gt;$ ls -la /etc/nginx/sites-enabled&lt;br /&gt;total 8&lt;br /&gt;drwxr-xr-x 2 root root 4096 2009-06-29 12:33 .&lt;br /&gt;drwxr-xr-x 5 root root 4096 2009-06-29 10:51 ..&lt;br /&gt;lrwxrwxrwx 1 root root   34 2009-06-02 07:25 default -&gt; /etc/nginx/sites-available/default&lt;br /&gt;lrwxrwxrwx 1 root root   30 2009-06-29 12:33 foo -&gt; /etc/nginx/sites-available/foo&lt;/pre&gt;&lt;br /&gt;Once done you can restart nginx and you should see the following processes that confirm things are running:&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;$ ps axf | egrep "nginx|passenger"&lt;br /&gt;28608 pts/1    S+     0:00                      \_ egrep nginx|passenger&lt;br /&gt;28560 pts/1    Sl     0:00 PassengerNginxHelperServer /var/lib/gems/1.8/gems/passenger-2.2.4 /usr/bin/ruby1.8 3 4 0 6 0 300 1 nobody 33 33 /tmp/passenger.28557&lt;br /&gt;28580 ?        Ss     0:00 nginx: master process /usr/sbin/nginx&lt;br /&gt;28581 ?        S      0:00  \_ nginx: worker process&lt;/pre&gt;&lt;br /&gt;The final test to see if everything is working is to open a browser and to browse to the server_name and port defined above. Errors will be reported in /var/log/nginx/error.log as well as the development/production logs of the rails project you're running.&lt;br /&gt;&lt;br /&gt;&lt;pre&gt;&lt;/pre&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/8719896753162286533-9055569551476030893?l=blog.ntrippy.net' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://blog.ntrippy.net/feeds/9055569551476030893/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=8719896753162286533&amp;postID=9055569551476030893' title='3 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/8719896753162286533/posts/default/9055569551476030893'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/8719896753162286533/posts/default/9055569551476030893'/><link rel='alternate' type='text/html' href='http://blog.ntrippy.net/2009/07/home-made-nginx-phusion-passenger-dep.html' title='Home made nginx + phusion passenger Debian/Ubuntu dep'/><author><name>Charl</name><uri>http://www.blogger.com/profile/17742115469030093068</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='26' src='http://3.bp.blogspot.com/_4MLU7uPNEIY/TP2zTauOWAI/AAAAAAAAABs/OXj8i31AC0E/S220/blue%2B69.jpg'/></author><thr:total>3</thr:total></entry><entry><id>tag:blogger.com,1999:blog-8719896753162286533.post-675691500574548076</id><published>2009-06-11T18:07:00.001-07:00</published><updated>2009-06-11T19:28:49.319-07:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='linux pdftk pdf'/><title type='text'>Commandline PDF Manipulation</title><content type='html'>Want to do any of the following from a *NIX based system commandline with PDF documents:&lt;br /&gt;&lt;ul&gt;&lt;li&gt;&lt;p&gt;Merge PDF Documents&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;Split PDF Pages into a New Document&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;Rotate PDF Pages or Documents&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;Decrypt Input as Necessary (Password Required)&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;Encrypt Output as Desired&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;Fill PDF Forms with FDF Data or XFDF Data and/or Flatten Forms&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;Apply a Background Watermark or a Foreground Stamp&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;Report on PDF Metrics such as Metadata, Bookmarks, and Page Labels&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;Update PDF Metadata&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;Attach Files to PDF Pages or the PDF Document&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;Unpack PDF Attachments&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;Burst a PDF Document into Single Pages&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;Uncompress and Re-Compress Page Streams&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;Repair Corrupted PDF&lt;/p&gt;&lt;/li&gt;&lt;/ul&gt;One way to accomplish any of these would be to use &lt;a href="http://www.accesspdf.com/pdftk/"&gt;pdftk&lt;/a&gt; (PDF Toolkit).&lt;br /&gt;&lt;br /&gt;Something I need to do quite often is concatenate (join) several PDF documents into one document. This can quite trivially be achieved with the following from the commandline:&lt;br /&gt;&lt;br /&gt;$ pdftk 01.pdf 02.pdf cat output all_together_now.pdf&lt;br /&gt;&lt;br /&gt;Above we concatenated 01.pdf and 02.pdf to form the new combined all_together_now.pdf document.&lt;br /&gt;&lt;br /&gt;&lt;pre&gt;&lt;/pre&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/8719896753162286533-675691500574548076?l=blog.ntrippy.net' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://blog.ntrippy.net/feeds/675691500574548076/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=8719896753162286533&amp;postID=675691500574548076' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/8719896753162286533/posts/default/675691500574548076'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/8719896753162286533/posts/default/675691500574548076'/><link rel='alternate' type='text/html' href='http://blog.ntrippy.net/2009/06/commandline-pdf-manipulation.html' title='Commandline PDF Manipulation'/><author><name>Charl</name><uri>http://www.blogger.com/profile/17742115469030093068</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='26' src='http://3.bp.blogspot.com/_4MLU7uPNEIY/TP2zTauOWAI/AAAAAAAAABs/OXj8i31AC0E/S220/blue%2B69.jpg'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-8719896753162286533.post-8787316750741717164</id><published>2009-04-22T00:19:00.000-07:00</published><updated>2009-06-11T19:22:01.735-07:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='rubygems'/><category scheme='http://www.blogger.com/atom/ns#' term='ruby'/><category scheme='http://www.blogger.com/atom/ns#' term='libmemcached'/><category scheme='http://www.blogger.com/atom/ns#' term='linux'/><category scheme='http://www.blogger.com/atom/ns#' term='debian'/><category scheme='http://www.blogger.com/atom/ns#' term='memcached'/><category scheme='http://www.blogger.com/atom/ns#' term='Ubuntu'/><title type='text'>Broken memcached gem/libmemchached deb on Ubuntu</title><content type='html'>This post follows on a &lt;a href="http://blog.ntrippy.net/2009/02/broken-memcached-gemlibmemchached-port.html"&gt;previous&lt;/a&gt; post that was OS X centric.&lt;br /&gt;&lt;br /&gt;To fix this issue follow the same steps (starting with the download of libmemcached-0.25.14.tar.gz) detailed &lt;a href="http://blog.ntrippy.net/2009/02/broken-memcached-gemlibmemchached-port.html"&gt;here&lt;/a&gt;.&lt;br /&gt;&lt;br /&gt;Because the source installation of libmemcached dropped the libs in a non-standard place (from a Debian/Ubuntu viewpoint) we need a little more magic before things will work.&lt;br /&gt;&lt;br /&gt;As root, create /etc/ld.so.conf.d/libmemcached.conf:&lt;br /&gt;&lt;pre&gt;# Manual installation of libmemcached dropped the libs in a non-standard place&lt;br /&gt;/usr/local/lib&lt;br /&gt;&lt;/pre&gt;Run ldconfig to load the new config and test to ensure your script can now access libmemcached properly through the memcached gem.&lt;br /&gt;&lt;br /&gt;&lt;pre&gt;&lt;/pre&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/8719896753162286533-8787316750741717164?l=blog.ntrippy.net' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://blog.ntrippy.net/feeds/8787316750741717164/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=8719896753162286533&amp;postID=8787316750741717164' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/8719896753162286533/posts/default/8787316750741717164'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/8719896753162286533/posts/default/8787316750741717164'/><link rel='alternate' type='text/html' href='http://blog.ntrippy.net/2009/04/broken-memcached-gemlibmemchached-port.html' title='Broken memcached gem/libmemchached deb on Ubuntu'/><author><name>Charl</name><uri>http://www.blogger.com/profile/17742115469030093068</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='26' src='http://3.bp.blogspot.com/_4MLU7uPNEIY/TP2zTauOWAI/AAAAAAAAABs/OXj8i31AC0E/S220/blue%2B69.jpg'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-8719896753162286533.post-6183087447740225121</id><published>2009-03-22T18:35:00.000-07:00</published><updated>2009-06-11T19:22:39.573-07:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='linux debian ubuntu rpm deb'/><title type='text'>Unpacking a RPM on Ubuntu (or Debian)</title><content type='html'>&lt;pre&gt;&lt;br /&gt;# alien --scripts PACKAGE.rpm&lt;br /&gt;# ar -x PACKAGE.deb&lt;br /&gt;&lt;/pre&gt;You now have two tarballs control.tar.gz and data.tar.gz.&lt;br /&gt;&lt;br /&gt;The first is the information dpkg needs to do a proper installation and configuration of the package, the second is contains the binaries and data files.&lt;br /&gt;&lt;br /&gt;&lt;pre&gt;&lt;/pre&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/8719896753162286533-6183087447740225121?l=blog.ntrippy.net' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://blog.ntrippy.net/feeds/6183087447740225121/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=8719896753162286533&amp;postID=6183087447740225121' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/8719896753162286533/posts/default/6183087447740225121'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/8719896753162286533/posts/default/6183087447740225121'/><link rel='alternate' type='text/html' href='http://blog.ntrippy.net/2009/03/unpacking-rpm-on-ubuntu-or-debian.html' title='Unpacking a RPM on Ubuntu (or Debian)'/><author><name>Charl</name><uri>http://www.blogger.com/profile/17742115469030093068</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='26' src='http://3.bp.blogspot.com/_4MLU7uPNEIY/TP2zTauOWAI/AAAAAAAAABs/OXj8i31AC0E/S220/blue%2B69.jpg'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-8719896753162286533.post-6057621335369759225</id><published>2009-02-23T15:13:00.000-08:00</published><updated>2009-06-11T19:22:53.112-07:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='rubygems'/><category scheme='http://www.blogger.com/atom/ns#' term='ruby'/><category scheme='http://www.blogger.com/atom/ns#' term='libmemcached'/><category scheme='http://www.blogger.com/atom/ns#' term='os x'/><category scheme='http://www.blogger.com/atom/ns#' term='memcached'/><title type='text'>Broken memcached gem/libmemchached port on OS X</title><content type='html'>&lt;span style="font-weight:bold;"&gt;Update (2009/04/22):&lt;/span&gt;&lt;br /&gt;This post was originally only targeted at OS X but I have since noticed Ubuntu also having this same issue. Jump to my &lt;a href="http://blog.ntrippy.net/2009/04/broken-memcached-gemlibmemchached-port.html"&gt;additional&lt;/a&gt; instructions for Ubuntu.&lt;br /&gt;&lt;br /&gt;Recipe for disaster:&lt;br /&gt;&lt;ol&gt;&lt;li&gt;Use MacPorts for your package management&lt;/li&gt; &lt;li&gt;Use the ruby memcached gem&lt;/li&gt; &lt;li&gt;Use the libmemcached port as a dependency on the gem above&lt;/li&gt; &lt;li&gt;Upgrade your ports&lt;/li&gt;&lt;/ol&gt;&lt;b&gt;Synopses&lt;/b&gt;&lt;br /&gt;I upgraded all my ports on my MBP recently and one of the libs that was upgraded in the process was libmemcached (upgraded from v0.25 to v0.26). Unfortunately this has broken my access to memcached on my system (the script dies when trying to connect).&lt;br /&gt;&lt;br /&gt;Not knowing what the problem may be I also upgraded my gem to v0.14 which then could not build. A quick search for the issue found &lt;a href="http://rubyforge.org/forum/forum.php?thread_id=31257&amp;amp;forum_id=20894"&gt;this&lt;/a&gt; entry on the &lt;a href="http://rubyforge.org/forum/forum.php?forum_id=20894"&gt;memcached&lt;/a&gt; discussion forum.&lt;br /&gt;&lt;br /&gt;&lt;b&gt;How now brown cow?&lt;/b&gt;&lt;br /&gt;&lt;a href="http://blog.evanweaver.com/about/"&gt;Evan Weaver&lt;/a&gt;, the maintainer of the &lt;a href="http://blog.evanweaver.com/files/doc/fauna/memcached"&gt;memcached&lt;/a&gt; project, mentioned that libmemcached v0.26 lacks some critical patch and suggested people may have to wait for v0.27 to rectify things.&lt;br /&gt;&lt;br /&gt;For those who cannot wait and have inconsistent, broken systems, Evan goes on to mention that you should currently use the libmemcached lib and memcached gem pair from &lt;a href="http://blog.evanweaver.com/articles/2009/01/24/secret-codes/"&gt;here&lt;/a&gt;.&lt;br /&gt;&lt;br /&gt;&lt;a href="http://blog.evanweaver.com/files/doc/fauna/memcached/files/COMPATIBILITY.html"&gt;Here&lt;/a&gt; he also provides a convenient compatibility matrix so you can see how to align the different versions of libmemcached and the memcached gem.&lt;br /&gt;&lt;br /&gt;&lt;b&gt;Fix&lt;/b&gt;&lt;br /&gt;Unfortunately this messes with things because libmemcached now needs to be installed as a source package in a system managed with MacPorts.&lt;br /&gt;&lt;br /&gt;Yuck!&lt;br /&gt;&lt;br /&gt;Unfortunately the choices are limited as the versions of libmemcached that ships as a port are either v0.26 (the latest, but broken for our purposes) or v0.25 (which aligns with the older v0.13 of the memcached gem).&lt;br /&gt;&lt;br /&gt;First off remove any libmemcached ports you may have installed:&lt;br /&gt;&lt;pre&gt;$ sudo port uninstall libmemcached&lt;br /&gt;&lt;/pre&gt;Download the &lt;a href="http://blog.evanweaver.com/files/libmemcached-0.25.14.tar.gz"&gt;libmemcached source archive&lt;/a&gt;, extract, build and install it:&lt;br /&gt;&lt;pre&gt;ibm-99tvvxc:msp charl$ cd /var/tmp/&lt;br /&gt;$ wget http://blog.evanweaver.com/files/libmemcached-0.25.14.tar.gz&lt;br /&gt;--2009-02-24 10:50:40--  http://blog.evanweaver.com/files/libmemcached-0.25.14.tar.gz&lt;br /&gt;Resolving blog.evanweaver.com... 208.78.102.192&lt;br /&gt;Connecting to blog.evanweaver.com|208.78.102.192|:80... connected.&lt;br /&gt;HTTP request sent, awaiting response... 200 OK&lt;br /&gt;Length: 428047 (418K) [application/x-gzip]&lt;br /&gt;Saving to: `libmemcached-0.25.14.tar.gz'&lt;br /&gt;&lt;br /&gt;100%[==========================================================&gt;] 428,047      135K/s   in 3.1s &lt;br /&gt;&lt;br /&gt;2009-02-24 10:50:49 (135 KB/s) - `libmemcached-0.25.14.tar.gz' saved [428047/428047]&lt;br /&gt;&lt;br /&gt;$ tar zxvf libmemcached-0.25.14.tar.gz&lt;br /&gt;libmemcached-0.25.14/&lt;br /&gt;[...]&lt;br /&gt;$ cd libmemcached-0.25.14&lt;br /&gt;$ ./configure&lt;br /&gt;checking build system type... i386-apple-darwin9.6.0&lt;br /&gt;checking host system type... i386-apple-darwin9.6.0&lt;br /&gt;checking target system type... i386-apple-darwin9.6.0&lt;br /&gt;checking for a BSD-compatible install... /usr/bin/install -c&lt;br /&gt;[...]&lt;br /&gt;$ make&lt;br /&gt;Making all in docs&lt;br /&gt;/usr/bin/pod2man -c "libmemcached" -r "" -s 3 libmemcached.pod &gt; libmemcached.3&lt;br /&gt;/usr/bin/pod2man -c "libmemcached" -r "" -s 3 libmemcached_examples.pod &gt; libmemcached_examples.3&lt;br /&gt;[...]&lt;br /&gt;$ sudo make install&lt;br /&gt;Password:&lt;br /&gt;Making install in docs&lt;br /&gt;make[2]: Nothing to be done for `install-exec-am'.&lt;br /&gt;test -z "/usr/local/share/man/man1" || ../config/install-sh -c -d "/usr/local/share/man/man1"&lt;br /&gt;/usr/bin/install -c -m 644 './memcat.1' '/usr/local/share/man/man1/memcat.1'&lt;br /&gt;[...]&lt;br /&gt;&lt;/pre&gt;You may want to keep the source directory around so that you can easily uninstall (run sudo make uninstall in the source directory) the source installation later when the relevant aligned packaged versions are available for installation.&lt;br /&gt;&lt;br /&gt;The final step is to install the gem:&lt;br /&gt;&lt;pre&gt;$ sudo env ARCHFLAGS="-arch i386" gem install -r -V memcached --no-rdoc --no-ri&lt;br /&gt;GET 200 OK: http://gems.rubyforge.org/latest_specs.4.8.gz&lt;br /&gt;GET 200 OK: http://gems.github.com/latest_specs.4.8.gz&lt;br /&gt;connection reset after 2 requests, retrying&lt;br /&gt;GET 200 OK: http://gems.rubyforge.org/quick/Marshal.4.8/memcached-0.14.gemspec.rz&lt;br /&gt;Installing gem memcached-0.14&lt;br /&gt;/opt/local/lib/ruby/gems/1.8/gems/memcached-0.14/BENCHMARKS&lt;br /&gt;/opt/local/lib/ruby/gems/1.8/gems/memcached-0.14/CHANGELOG&lt;br /&gt;[...]&lt;br /&gt;Successfully installed memcached-0.14&lt;br /&gt;1 gem installed&lt;br /&gt;&lt;/pre&gt;Success!&lt;br /&gt;&lt;br /&gt;We now have libmemcached v0.25.14 and the memcached v0.14 gem installed and we can go on with our lives.&lt;br /&gt;&lt;br /&gt;&lt;pre&gt;&lt;/pre&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/8719896753162286533-6057621335369759225?l=blog.ntrippy.net' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://blog.ntrippy.net/feeds/6057621335369759225/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=8719896753162286533&amp;postID=6057621335369759225' title='1 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/8719896753162286533/posts/default/6057621335369759225'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/8719896753162286533/posts/default/6057621335369759225'/><link rel='alternate' type='text/html' href='http://blog.ntrippy.net/2009/02/broken-memcached-gemlibmemchached-port.html' title='Broken memcached gem/libmemchached port on OS X'/><author><name>Charl</name><uri>http://www.blogger.com/profile/17742115469030093068</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='26' src='http://3.bp.blogspot.com/_4MLU7uPNEIY/TP2zTauOWAI/AAAAAAAAABs/OXj8i31AC0E/S220/blue%2B69.jpg'/></author><thr:total>1</thr:total></entry><entry><id>tag:blogger.com,1999:blog-8719896753162286533.post-2129690085681340554</id><published>2009-02-22T12:18:00.000-08:00</published><updated>2009-06-11T19:23:06.562-07:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='os x'/><category scheme='http://www.blogger.com/atom/ns#' term='rails'/><title type='text'>Silly Rails Environment Optimisation</title><content type='html'>Do you find supplying the ./script base directory for any of the commands you want to run in it annoying?&lt;br /&gt;&lt;br /&gt;Why not practice some &lt;a href="http://en.wikipedia.org/wiki/Ruby_on_Rails"&gt;Convention over Configuration&lt;/a&gt; and assume these commands will always be run from within a valid rails project directory.&lt;br /&gt;&lt;br /&gt;Based on this assumption you can drop the following into your .bash_profile (OS X) or some system-wide profile to facilitate some laziness:&lt;br /&gt;&lt;pre&gt;# Rails aliases&lt;br /&gt;alias about="./script/about"&lt;br /&gt;alias console="./script/console"&lt;br /&gt;alias dbconsole="./script/dbconsole"&lt;br /&gt;alias destroy="./script/destroy"&lt;br /&gt;alias generate="./script/generate"&lt;br /&gt;alias performance="./script/performance"&lt;br /&gt;alias plugin="./script/plugin"&lt;br /&gt;alias process="./script/process"&lt;br /&gt;alias runner="./script/runner"&lt;br /&gt;alias server="./script/server"&lt;br /&gt;&lt;/pre&gt;Don't forget to load it into you current shell with:&lt;br /&gt;&lt;pre&gt;$ . ~/.bash_profile&lt;br /&gt;&lt;/pre&gt;Now you can simply run 'console', 'dbconsole', 'server', etc. when you're in a valid rails project directory without having to append that annoying base directory.&lt;br /&gt;&lt;br /&gt;&lt;span style="font-weight: bold;"&gt;Caveat: &lt;/span&gt;If similar commands exist on your system or are introduced by a non-rails package a conflict will arise and the alias version of the command will win. Also, TAB-completion now no longer works for these commands and you'll have to type them out in their entirety.&lt;br /&gt;&lt;br /&gt;&lt;pre&gt;&lt;/pre&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/8719896753162286533-2129690085681340554?l=blog.ntrippy.net' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://blog.ntrippy.net/feeds/2129690085681340554/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=8719896753162286533&amp;postID=2129690085681340554' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/8719896753162286533/posts/default/2129690085681340554'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/8719896753162286533/posts/default/2129690085681340554'/><link rel='alternate' type='text/html' href='http://blog.ntrippy.net/2009/02/silly-rails-environment-optimisation.html' title='Silly Rails Environment Optimisation'/><author><name>Charl</name><uri>http://www.blogger.com/profile/17742115469030093068</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='26' src='http://3.bp.blogspot.com/_4MLU7uPNEIY/TP2zTauOWAI/AAAAAAAAABs/OXj8i31AC0E/S220/blue%2B69.jpg'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-8719896753162286533.post-3316351361529249489</id><published>2009-02-20T18:04:00.000-08:00</published><updated>2009-06-11T19:23:21.558-07:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='ruby'/><category scheme='http://www.blogger.com/atom/ns#' term='rails'/><category scheme='http://www.blogger.com/atom/ns#' term='acs_as_state_machine'/><title type='text'>What's next with acts_as_state_machine?</title><content type='html'>I've been using &lt;a href="http://elitists.textdriven.com/svn/plugins/acts_as_state_machine/trunk/"&gt;acts_as_state_machine&lt;/a&gt; and there seems to  be at least two convenience methods missing that I find quite useful:&lt;br /&gt;&lt;ol&gt;&lt;li&gt;next_state - what state will we be transitioning to&lt;/li&gt; &lt;li&gt;next_states - list of states that we still need to transition to&lt;/li&gt;&lt;/ol&gt;Drop the following in a suitable place (your model using acts_as_state_machine will do) and abuse it to your hearts content:&lt;br /&gt;&lt;pre&gt;# Monkey patch acts_as_state_machine to allow us to see what the next states are&lt;br /&gt;# for the current state&lt;br /&gt;module ScottBarron&lt;br /&gt; module Acts&lt;br /&gt;   module StateMachine&lt;br /&gt;     module InstanceMethods&lt;br /&gt;         def next_state(state=nil)&lt;br /&gt;           state ||= current_state()&lt;br /&gt;           self.class.read_inheritable_attribute(:transition_table).each_value do |event|&lt;br /&gt;             event.each do |transition|&lt;br /&gt;               return transition.to if transition.from == state &lt;br /&gt;             end&lt;br /&gt;           end&lt;br /&gt;           nil&lt;br /&gt;         end&lt;br /&gt;        &lt;br /&gt;         def next_states(state=nil)&lt;br /&gt;           state ||= current_state()&lt;br /&gt;           states = [] &lt;br /&gt;           while state = next_state(state)&lt;br /&gt;             states &lt;&lt; state            &lt;br /&gt;           end            &lt;br /&gt;           states          &lt;br /&gt;        end       &lt;br /&gt;      end    &lt;br /&gt;    end  &lt;br /&gt;  end&lt;br /&gt;end &lt;/pre&gt;Thanks to &lt;a href="http://blog.methodmissing.com/2006/11/16/beyond-callbacks-for-complex-model-lifecycles/"&gt;Lourens Naude's'&lt;/a&gt; post on acts_as_state_machine for providing me with some ideas and to &lt;a href="http://www.workingwithrails.com/person/5415-scott-barron"&gt;Scott Barron&lt;/a&gt; for the great acts_as_state_machine plugin.&lt;br /&gt;&lt;br /&gt;&lt;pre&gt;&lt;/pre&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/8719896753162286533-3316351361529249489?l=blog.ntrippy.net' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://blog.ntrippy.net/feeds/3316351361529249489/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=8719896753162286533&amp;postID=3316351361529249489' title='7 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/8719896753162286533/posts/default/3316351361529249489'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/8719896753162286533/posts/default/3316351361529249489'/><link rel='alternate' type='text/html' href='http://blog.ntrippy.net/2009/02/whats-next-with-actsasstatemachine.html' title='What&apos;s next with acts_as_state_machine?'/><author><name>Charl</name><uri>http://www.blogger.com/profile/17742115469030093068</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='26' src='http://3.bp.blogspot.com/_4MLU7uPNEIY/TP2zTauOWAI/AAAAAAAAABs/OXj8i31AC0E/S220/blue%2B69.jpg'/></author><thr:total>7</thr:total></entry><entry><id>tag:blogger.com,1999:blog-8719896753162286533.post-2610726024875899454</id><published>2009-02-11T17:18:00.000-08:00</published><updated>2009-06-11T19:23:38.098-07:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='ruby rubygems signed'/><title type='text'>Signing your own gems</title><content type='html'>&lt;a href="http://docs.rubygems.org/read/chapter/21"&gt;Chapter 9 (Signing Your Gems)&lt;/a&gt; of the &lt;a href="http://docs.rubygems.org/read/book/1"&gt;RubyGems User Guide&lt;/a&gt; contains everything you need to know to do this.&lt;br /&gt;&lt;br /&gt;Here's a quick overview of the steps required.&lt;br /&gt;&lt;br /&gt;&lt;b&gt;Build a certificate and private key pair&lt;/b&gt;&lt;br /&gt;&lt;pre&gt;$ cd /to/where/you/wish/to/keep/your/cert_and_key&lt;br /&gt;$ gem cert --build gemmaster@example.com&lt;br /&gt;&lt;/pre&gt;Two files are generated from this: gem-private_key.pem &amp;amp; gem-public_cert.pem.&lt;br /&gt;&lt;br /&gt;Ensure you keep gem-private_key.pem somewhere safe that only you have access to.&lt;br /&gt;&lt;br /&gt;&lt;span style="font-weight: bold;"&gt;Modify your build specification&lt;/span&gt;&lt;br /&gt;Add the following spec to your existing gem's gemspec or to the relevant Rakefile ("s" is your Gem::Specification instance):&lt;br /&gt;&lt;pre&gt;$ s.signing_key = 'gem-private_key.pem'&lt;br /&gt;$ s.cert_chain  = ['gem-public_cert.pem']&lt;br /&gt;&lt;/pre&gt;Rebuild your package and you're done!&lt;br /&gt;&lt;br /&gt;&lt;pre&gt;&lt;/pre&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/8719896753162286533-2610726024875899454?l=blog.ntrippy.net' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://blog.ntrippy.net/feeds/2610726024875899454/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=8719896753162286533&amp;postID=2610726024875899454' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/8719896753162286533/posts/default/2610726024875899454'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/8719896753162286533/posts/default/2610726024875899454'/><link rel='alternate' type='text/html' href='http://blog.ntrippy.net/2009/02/signing-your-own-gems.html' title='Signing your own gems'/><author><name>Charl</name><uri>http://www.blogger.com/profile/17742115469030093068</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='26' src='http://3.bp.blogspot.com/_4MLU7uPNEIY/TP2zTauOWAI/AAAAAAAAABs/OXj8i31AC0E/S220/blue%2B69.jpg'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-8719896753162286533.post-2570583426540414371</id><published>2009-02-11T17:05:00.000-08:00</published><updated>2009-06-11T19:23:50.938-07:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='linux ubuntu ruby rubygems'/><title type='text'>Your own gem repository</title><content type='html'>Setting up your own repository is quite simple. All you need is the following:&lt;br /&gt; &lt;ul&gt;&lt;li&gt;Web server&lt;/li&gt;  &lt;li&gt;Builder gem&lt;/li&gt;    &lt;li&gt;A repository&lt;/li&gt;    &lt;li&gt;Client setup&lt;/li&gt;&lt;/ul&gt;&lt;b&gt;Web server&lt;/b&gt;&lt;br /&gt;I used nginx but you should be able to tailor this to your needs without too much effort.&lt;br /&gt;&lt;br /&gt;Create a directory to contain your local gem repository:&lt;br /&gt;&lt;pre&gt;$ sudo mkdir -p /var/www/gems-repo/gems&lt;br /&gt;&lt;/pre&gt;Note: to get everything working correctly you need to ensure you have that /gems subdirectory off your base directory configured in nginx. You can find more info on this at the &lt;a href="http://docs.rubygems.org/read/chapter/18#page80"&gt;RubyGems FAQ&lt;/a&gt;.&lt;br /&gt;&lt;br /&gt;Add a virtual host config in /etc/nginx/sites-available for your gems repository (I'm going to assume gems.example.com):&lt;br /&gt;&lt;pre&gt;$ cat /etc/nginx/sites-available/gems.example.com&lt;br /&gt;server {&lt;br /&gt; listen   80;&lt;br /&gt; server_name  gems.example.com;&lt;br /&gt; access_log  /var/log/nginx/gems.example.com.access.log;&lt;br /&gt; root /var/www/gems-repo;&lt;br /&gt;}&lt;br /&gt;&lt;/pre&gt;Ensure you drop a symlink in /etc/nginx/sites-enabled/ for your virtual host and restart nginx.&lt;br /&gt;&lt;br /&gt;&lt;b&gt;Install the builder gem&lt;/b&gt;&lt;br /&gt;&lt;pre&gt;$ sudo gem install builder&lt;span style="font-family: Georgia,serif;"&gt;&lt;br /&gt;&lt;/span&gt;&lt;/pre&gt;&lt;b&gt;Build your repository&lt;/b&gt;&lt;br /&gt;Jump into the base directory defined above (/var/www/gems-repo). Copy all your own gems into /var/www/gems-repo/gems/. Then generate the relevant resource files for rubygems:&lt;br /&gt;&lt;pre&gt;$ sudo gem generate_index -d /var/www/gems-repo&lt;br /&gt;Loading 1 gems from /var/www/gems-repo&lt;br /&gt;.&lt;br /&gt;Loaded all gems&lt;br /&gt;Generating quick index gemspecs for 1 gems&lt;br /&gt;.&lt;br /&gt;Complete&lt;br /&gt;Generating specs index&lt;br /&gt;Generating latest specs index&lt;br /&gt;Generating quick index&lt;br /&gt;Generating latest index&lt;br /&gt;Generating Marshal master index&lt;br /&gt;Generating YAML master index for 1 gems (this may take a while)&lt;br /&gt;.&lt;br /&gt;Complete&lt;br /&gt;Compressing indicies&lt;br /&gt;&lt;/pre&gt;You would re-run this every time you add to or make changes to the repository.&lt;br /&gt;&lt;br /&gt;&lt;b&gt;Client setup&lt;/b&gt;&lt;br /&gt;Finally run the following on all local clients that need access to the repository:&lt;br /&gt;&lt;pre&gt;$ sudo gem sources -a http://gems.example.com/&lt;br /&gt;&lt;/pre&gt;You can ensure the extra source was added by running 'gem sources'. If you now do a search for you gem that you added above you should see it listed as a target.&lt;br /&gt;&lt;br /&gt;&lt;pre&gt;&lt;/pre&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/8719896753162286533-2570583426540414371?l=blog.ntrippy.net' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://blog.ntrippy.net/feeds/2570583426540414371/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=8719896753162286533&amp;postID=2570583426540414371' title='5 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/8719896753162286533/posts/default/2570583426540414371'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/8719896753162286533/posts/default/2570583426540414371'/><link rel='alternate' type='text/html' href='http://blog.ntrippy.net/2009/02/your-own-gem-repository.html' title='Your own gem repository'/><author><name>Charl</name><uri>http://www.blogger.com/profile/17742115469030093068</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='26' src='http://3.bp.blogspot.com/_4MLU7uPNEIY/TP2zTauOWAI/AAAAAAAAABs/OXj8i31AC0E/S220/blue%2B69.jpg'/></author><thr:total>5</thr:total></entry><entry><id>tag:blogger.com,1999:blog-8719896753162286533.post-9006555855229582422</id><published>2009-01-27T15:00:00.000-08:00</published><updated>2009-06-11T19:24:07.334-07:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='linux ubuntu stunnel mysql ssl tunnel'/><title type='text'>Stunneling (SSL Tunnels) MySQL on Ubuntu</title><content type='html'>We have really restrictive IT policies at my current employer which dictates that no client/server communication can happen in the clear. This has some interesting implications as most software servers do not support encryption or encrypted tunnels in any way.&lt;br /&gt;&lt;br /&gt;&lt;b&gt;What are the options?&lt;/b&gt;&lt;br /&gt;&lt;ul&gt;&lt;li&gt;Write our own software that supports client/server encryption&lt;/li&gt; &lt;li&gt;Only use existing applications that support client/server encryption&lt;/li&gt; &lt;li&gt;Treat the encryption layer as an abstraction that the client/server is not aware of&lt;/li&gt;&lt;br /&gt;&lt;/ul&gt;The first two options have obvious drawbacks in terms of time and choice. The third seems to be the best way to transparently insert encryption into an existing architecture without requiring massive changes.&lt;br /&gt;&lt;br /&gt;This approach can be done by:&lt;br /&gt;&lt;br /&gt;&lt;ul&gt;&lt;li&gt;SSH tunnels&lt;/li&gt; &lt;li&gt;OpenVPN tunnels&lt;/li&gt; &lt;li&gt;Stunnel&lt;/li&gt;&lt;br /&gt;&lt;/ul&gt;There may be other options but these are the main ones I have had experience with. &lt;a href="http://www.openssh.com/"&gt;SSH&lt;/a&gt; tunnels are very convenient but tend to be unstable requiring restarts every now and again.&lt;br /&gt;&lt;br /&gt;&lt;a href="http://openvpn.net/"&gt;OpenVPN&lt;/a&gt; has also provided very little stability requiring frequent restarts.&lt;br /&gt;&lt;br /&gt;Lets explore the setup of the third option then, &lt;a href="http://www.stunnel.org/"&gt;stunnel&lt;/a&gt;.&lt;br /&gt;&lt;br /&gt;&lt;b&gt;Stunneling&lt;/b&gt;&lt;br /&gt;The &lt;a href="http://www.stunnel.org/"&gt;stunnel&lt;/a&gt; home page describes stunnel as:&lt;br /&gt;&lt;br /&gt;&lt;pre&gt;Stunnel is a program that allows you to encrypt arbitrary TCP connections inside&lt;br /&gt;SSL (Secure Sockets Layer) available on both Unix and Windows. Stunnel can allow&lt;br /&gt;you to secure non-SSL aware daemons and protocols (like POP, IMAP, LDAP, etc) by&lt;br /&gt;having Stunnel provide the encryption, requiring no changes to the daemon's code.&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;Sounds promising. They have a really comprehensive &lt;a href="http://www.stunnel.org/examples/"&gt;examples&lt;/a&gt; section on their site which I highly recommend you have a look at.&lt;br /&gt;&lt;br /&gt;In my case I needed to get a MySQL master/slave pair to replicate via a secure channel. MySQL seems to support SSL but the package requires a rebuild with the required options which would mean I need a non-standard MySQL package which makes me itchy. I also needed a similar solution for several other applications that need secure communications channels so I've decided on this solution throughout my architecture.&lt;br /&gt;&lt;br /&gt;See &lt;a href="http://ubuntuforums.org/showthread.php?t=377945"&gt;this&lt;/a&gt; Ubuntu thread for more info on rebuilding the MySQL package with SSL support if you're so inclined.&lt;br /&gt;&lt;br /&gt;Our installation targets are an Ubuntu servers and will therefore be flavored accordingly but you should be able to easily adapt anything mentioned here for your platform of choice.&lt;br /&gt;&lt;br /&gt;&lt;b&gt;Preamble&lt;/b&gt;&lt;br /&gt;If you are going to be using this for the same purposes I am ensure that you have a working MySQL master/slave setup. You don't want to troubleshoot multiple applications if something is not playing nice.&lt;br /&gt;&lt;br /&gt;&lt;b&gt;Installation&lt;/b&gt;&lt;br /&gt;A simple aptitude install stunnel4 on the master and slave should do the trick.&lt;br /&gt;&lt;br /&gt;Now, keep in mind that if one of the nodes you have stunnel installed on is acting as both a stunnel client and stunnel server you need to have at least one stunnel server running for the client entries in a config file and one for the server entries in a config file.&lt;br /&gt;&lt;br /&gt;Not heading this warning will result in you seeing errors like this on the client:&lt;br /&gt;&lt;br /&gt;&lt;pre&gt;2009.01.28 07:02:16 LOG7[11508:3082910608]: SSL state (connect): before/connect initialization&lt;br /&gt;2009.01.28 07:02:16 LOG7[11508:3082910608]: SSL state (connect): SSLv3 write client hello A&lt;br /&gt;2009.01.28 07:02:16 LOG7[11508:3082910608]: SSL alert (write): fatal: handshake failure&lt;br /&gt;2009.01.28 07:02:16 LOG3[11508:3082910608]: SSL_connect: 1408F10B: error:1408F10B:SSL routines:SSL3_GET_RECORD:wrong version number&lt;br /&gt;2009.01.28 07:02:16 LOG5[11508:3082910608]: Connection reset: 0 bytes sent to SSL, 0 bytes sent to socket&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;And this on the server:&lt;br /&gt;&lt;br /&gt;&lt;pre&gt;2009.01.28 07:10:06 LOG7[13426:3082861456]: SSL state (connect): before/connect initialization&lt;br /&gt;2009.01.28 07:10:06 LOG7[13426:3082861456]: SSL state (connect): SSLv3 write client hello A&lt;br /&gt;2009.01.28 07:10:06 LOG7[13426:3082861456]: SSL alert (write): fatal: handshake failure&lt;br /&gt;2009.01.28 07:10:06 LOG3[13426:3082861456]: SSL_connect: 1408F10B: error:1408F10B:SSL routines:SSL3_GET_RECORD:wrong version number&lt;br /&gt;2009.01.28 07:10:06 LOG5[13426:3082861456]: Connection reset: 0 bytes sent to SSL, 0 bytes sent to socket&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;My convention is to use the default config file (/etc/stunnel/stunnel.conf) that ships with the stunnel4 package for the client configuration and a modified copy of that as a config (/etc/stunnel/mysql-master.conf, in this specific case) for the server.&lt;br /&gt;&lt;br /&gt;If you will not be using the MySQL master server as a stunnel client then you can simply ensure that your /etc/stunnel/stunnel.conf is the same as my /etc/stunnel/mysql-master.conf.&lt;br /&gt;&lt;br /&gt;Keep in mind that multiple config files in /etc/stunnel/ will cause multiple stunnel servers to be run with each of the files found there.&lt;br /&gt;&lt;br /&gt;&lt;b&gt;Server&lt;/b&gt;&lt;br /&gt;First off, let's generate our own key (/etc/ssl/master.pem) that we'll be using for encryption. This key must be treated as a secure file that should have the relevant access controls placed on it.&lt;br /&gt;&lt;br /&gt;&lt;pre&gt;root@master:~# cd /etc/ssl/&lt;br /&gt;root@master:~# openssl req -new -x509 -nodes -days 730 -out master.pem -keyout master.pem&lt;br /&gt;root@master:~# chmod 600 master.pem&lt;br /&gt;root@master:~# dd if=/dev/urandom of=temp_file count=2&lt;br /&gt;root@master:~# openssl dhparam -rand temp_file 512 &gt;&gt; master.pem&lt;br /&gt;root@master:~# ln -sf master.pem `openssl x509 -noout -hash &amp;lt; coretools01.pem`.0&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;Now, change the following lines in /etc/stunnel/mysql-master.conf:&lt;br /&gt;&lt;br /&gt;&lt;pre&gt;cert = /etc/ssl/master.pem&lt;br /&gt;;client = yes&lt;br /&gt;debug = 7&lt;br /&gt;output = /var/log/stunnel4/stunnel.log&lt;br /&gt;&lt;br /&gt;[mysql-master]&lt;br /&gt;accept  = 443&lt;br /&gt;connect = 127.0.0.1:3306&lt;br /&gt;TIMEOUTclose = 0&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;This config will ensure we're running as a stunnel server, listening for incoming SSL connections on 0.0.0.0:443 and shunting the unencrypted connection to 127.0.0.1:3306 (ensure your MySQl master is listening for connections here).&lt;br /&gt;&lt;br /&gt;Fire stunnel up on the master with /etc/init.d/stunnel4 start &amp;amp;&amp;amp; tail -f /var/log/stunnel4/stunnel.log.&lt;br /&gt;&lt;br /&gt;&lt;b&gt;Client&lt;/b&gt;&lt;br /&gt;Just modify the default /etc/stunnel/stunnel.conf that was installed via the package by changing the lines below:&lt;br /&gt;&lt;br /&gt;&lt;pre&gt;;cert = /etc/stunnel/mail.pem&lt;br /&gt;;key = /etc/stunnel/mail.pem&lt;br /&gt;debug = 7&lt;br /&gt;output = /var/log/stunnel4/stunnel.log&lt;br /&gt;client = yes&lt;br /&gt;&lt;br /&gt;[mysql-master]&lt;br /&gt;accept  = 127.0.0.1:3307&lt;br /&gt;connect = master.example.com:443&lt;br /&gt;TIMEOUTclose = 0&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;This sets up the MySQL slave machine as a stunnel client, forwarding all unencrypted connections on 127.0.0.1:3307 via SSL to master.example.com:443. Just replace master.example.com with the correct &lt;a href="http://en.wikipedia.org/wiki/FQDN"&gt;FQDN&lt;/a&gt; for your master server. You also need to ensure that your MySQL slave's config points to 127.0.0.1:3307 for its replication requirements.&lt;br /&gt;&lt;br /&gt;Fire stunnel up on the slave with /etc/init.d/stunnel4 start &amp;amp;&amp;amp; tail -f /var/log/stunnel4/stunnel.log.&lt;br /&gt;&lt;br /&gt;&lt;b&gt;Testing&lt;/b&gt;&lt;br /&gt;An initial test can be done from the slave with the mysql command line utility in a separate terminal session by connecting to a suitable user/db configured on the master with access from 'localhost' or '127.0.0.1':&lt;br /&gt;&lt;br /&gt;&lt;pre&gt;root@slave:~# mysql -u replication -h 127.0.0.1 -P 3307 -p test&lt;br /&gt;Enter password:&lt;br /&gt;Reading table information for completion of table and column names&lt;br /&gt;You can turn off this feature to get a quicker startup with -A&lt;br /&gt;&lt;br /&gt;Welcome to the MySQL monitor.  Commands end with ; or \g.&lt;br /&gt;Your MySQL connection id is 21&lt;br /&gt;Server version: 5.0.51a-3ubuntu5.4-log (Ubuntu)&lt;br /&gt;&lt;br /&gt;Type 'help;' or '\h' for help. Type '\c' to clear the buffer.&lt;br /&gt;&lt;br /&gt;mysql&gt;&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;Yay!&lt;br /&gt;&lt;br /&gt;Now, apply your general method for determining if your master and slave is in sync. You can use the logs at /var/log/stunnel4/stunnel.log if you hit any snags. Once you're happy with everything ensure you disable the debugging in the stunnel config files.&lt;/pre&gt;&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;&lt;pre&gt;&lt;/pre&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/8719896753162286533-9006555855229582422?l=blog.ntrippy.net' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://blog.ntrippy.net/feeds/9006555855229582422/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=8719896753162286533&amp;postID=9006555855229582422' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/8719896753162286533/posts/default/9006555855229582422'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/8719896753162286533/posts/default/9006555855229582422'/><link rel='alternate' type='text/html' href='http://blog.ntrippy.net/2009/01/stunneling-ssl-tunnels-mysql-on-ubuntu.html' title='Stunneling (SSL Tunnels) MySQL on Ubuntu'/><author><name>Charl</name><uri>http://www.blogger.com/profile/17742115469030093068</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='26' src='http://3.bp.blogspot.com/_4MLU7uPNEIY/TP2zTauOWAI/AAAAAAAAABs/OXj8i31AC0E/S220/blue%2B69.jpg'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-8719896753162286533.post-1113606597360638103</id><published>2008-12-15T17:07:00.000-08:00</published><updated>2009-06-11T19:24:24.558-07:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='ruby'/><category scheme='http://www.blogger.com/atom/ns#' term='DRb'/><title type='text'>drb.rb:852:in `initialize': getaddrinfo: nodename nor servname provided, or not known (SocketError) (aka DRb TCPServer.open(0) failure on OS X)</title><content type='html'>&lt;span style="font-weight: bold;"&gt;&lt;br /&gt;Update (2009/01/01):&lt;/span&gt; Problem TT moved to &lt;a href="http://redmine.ruby-lang.org/issues/show/963"&gt;redmine&lt;/a&gt;.&lt;br /&gt;&lt;span style="font-weight: bold;"&gt;Update (2009/05/13):&lt;/span&gt; TT &lt;a href="http://redmine.ruby-lang.org/issues/show/963"&gt;resolved&lt;/a&gt;.&lt;br /&gt;&lt;br /&gt;I am currently busy building an priority queue server in ruby and I have chosen to use &lt;a href="http://www.ruby-doc.org/core/files/lib/drb/drb_rb.html"&gt;DRb&lt;/a&gt; as my communications platform.&lt;br /&gt;&lt;br /&gt;While experimenting the simple examples from the Net (see &lt;a href="http://advent2008.hackruby.com/past/2008/12/8/a_simple_distributed_queue_in_ruby/"&gt;here&lt;/a&gt; and &lt;a href="http://segment7.net/projects/ruby/drb/introduction.html"&gt;here&lt;/a&gt;) I was consistently getting the same error from inside drb.rb (/opt/local/lib/ruby/1.8/drb/drb.rb:852):&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;/opt/local/lib/ruby/1.8/drb/drb.rb:852:in `initialize': getaddrinfo: nodename nor servname provided, or not known (SocketError)&lt;br /&gt;     from /opt/local/lib/ruby/1.8/drb/drb.rb:852:in `open'&lt;br /&gt;     from /opt/local/lib/ruby/1.8/drb/drb.rb:852:in `open_server_inaddr_any'&lt;br /&gt;     from /opt/local/lib/ruby/1.8/drb/drb.rb:864:in `open_server'&lt;br /&gt;     from /opt/local/lib/ruby/1.8/drb/drb.rb:759:in `open_server'&lt;br /&gt;     from /opt/local/lib/ruby/1.8/drb/drb.rb:757:in `each'&lt;br /&gt;     from /opt/local/lib/ruby/1.8/drb/drb.rb:757:in `open_server'&lt;br /&gt;     from /opt/local/lib/ruby/1.8/drb/drb.rb:1346:in `initialize'&lt;br /&gt;     from /opt/local/lib/ruby/1.8/drb/drb.rb:1634:in `new'&lt;br /&gt;     from /opt/local/lib/ruby/1.8/drb/drb.rb:1634:in `start_service'&lt;br /&gt;     from ./queue-provider.rb:32&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;b&gt;What's up?&lt;/b&gt;&lt;br /&gt;After poking drb.rb::self.open_server_inaddr_any(host, port) with a stick a few times two issues came to light:&lt;br /&gt;&lt;ol&gt;&lt;br /&gt;&lt;li&gt;Multiple network address families are not catered for properly in the the code.&lt;/li&gt;&lt;br /&gt;&lt;li&gt;&lt;i&gt;TCPServer.open(port)&lt;/i&gt; where port == 0 fails under OS X but not Linux&lt;/li&gt;&lt;br /&gt;&lt;/ol&gt;&lt;b&gt;Multiple Address Families&lt;/b&gt;&lt;br /&gt;The code in question looks like this:&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;def self.open_server_inaddr_any(host, port)&lt;br /&gt;infos = Socket::getaddrinfo(host, nil,&lt;br /&gt;                           Socket::AF_UNSPEC,&lt;br /&gt;                           Socket::SOCK_STREAM,                                  0,&lt;br /&gt;                           Socket::AI_PASSIVE)&lt;br /&gt;family = infos.collect { |af, *_| af }.uniq&lt;br /&gt;case family&lt;br /&gt;when ['AF_INET']&lt;br /&gt; return TCPServer.open('0.0.0.0', port)&lt;br /&gt;when ['AF_INET6']&lt;br /&gt; return TCPServer.open('::', port)&lt;br /&gt;else&lt;br /&gt; return TCPServer.open(port)&lt;br /&gt;end&lt;br /&gt;end&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;From that we can see that we only seem to expect one network address family which is a little naive. &lt;i&gt;Socket::getaddrinfo()&lt;/i&gt; on my MacBook Pro has the following to say (where host == 'localhost'):&lt;br /&gt;&lt;br /&gt;&lt;pre&gt;$ irb&lt;br /&gt;irb(main):006:0&gt; require "socket"&lt;br /&gt;=&gt; true&lt;br /&gt;irb(main):007:0&gt; host = 'localhost'&lt;br /&gt;=&gt; "localhost"&lt;br /&gt;irb(main):008:0&gt; Socket::getaddrinfo(host, nil,&lt;br /&gt;irb(main):009:1* Socket::AF_UNSPEC,&lt;br /&gt;irb(main):010:1* Socket::SOCK_STREAM,                                  0,&lt;br /&gt;irb(main):011:1* Socket::AI_PASSIVE)&lt;br /&gt;=&gt; [["AF_INET6", 0, "localhost", "::1", 30, 1, 6], ["AF_INET6", 0, "localhost", "fe80::1%lo0", 30, 1, 6], ["AF_INET", 0, "localhost", "127.0.0.1", 2, 1, 6]]&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;When you take this as your input you'll see that we don't end up matching either 'AF_INET' or 'AF_INET6' and we fall through to &lt;i&gt;return TCPServer.open(port)&lt;/i&gt; because the case block expects a match against an array with one element.&lt;br /&gt;&lt;br /&gt;&lt;b&gt;TCPServer.open(0) OS X Weirdness&lt;/b&gt;&lt;br /&gt;I have used DRb on both Linux and Windblowns in the past without a hitch so I was rather surprised to run into something like this which is a show stopper on OS X. I though I'd see if I was having the same problems on Linux to have something to compare with:&lt;br /&gt;&lt;br /&gt;&lt;pre&gt;$ irb&lt;br /&gt;irb(main):001:0&gt; require "socket"&lt;br /&gt;=&gt; true&lt;br /&gt;irb(main):002:0&gt; port = 0&lt;br /&gt;=&gt; 0&lt;br /&gt;irb(main):003:0&gt; TCPServer.open(port)&lt;br /&gt;=&gt; #&lt;tcpserver:0xb7c37f78&gt;&lt;br /&gt;&lt;/tcpserver:0xb7c37f78&gt;&lt;/pre&gt;&lt;br /&gt;Works a treat! Let's try that on OS X:&lt;br /&gt;&lt;br /&gt;&lt;pre&gt;$ irb&lt;br /&gt;irb(main):001:0&gt; require "socket"&lt;br /&gt;=&gt; true&lt;br /&gt;irb(main):002:0&gt; port = 0&lt;br /&gt;=&gt; 0&lt;br /&gt;irb(main):003:0&gt; TCPServer.open(port)&lt;br /&gt;SocketError: getaddrinfo: nodename nor servname provided, or not known&lt;br /&gt;     from (irb):3:in `initialize'&lt;br /&gt;     from (irb):3:in `open'&lt;br /&gt;     from (irb):3&lt;br /&gt;     from :0&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;CRASH! BOOM! BANG!&lt;br /&gt;&lt;br /&gt;&lt;b&gt;DRb Quilt&lt;/b&gt;&lt;br /&gt;The first issue is rather trivial to fix:&lt;br /&gt;&lt;br /&gt;&lt;pre&gt;def self.open_server_inaddr_any(host, port)&lt;br /&gt;infos = Socket::getaddrinfo(host, nil,&lt;br /&gt;                            Socket::AF_UNSPEC,&lt;br /&gt;                            Socket::SOCK_STREAM,&lt;br /&gt;                            0,&lt;br /&gt;                            Socket::AI_PASSIVE)&lt;br /&gt;families = Hash[*infos.collect { |af, *_| af }.uniq.zip([]).flatten]&lt;br /&gt;return TCPServer.open('0.0.0.0', port) if families.has_key?('AF_INET')&lt;br /&gt;return TCPServer.open('::', port) if families.has_key?('AF_INET6')&lt;br /&gt;return TCPServer.open(port)&lt;br /&gt;end&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;The code now rather assumes we're dealing with an array of one or more network address families and tries the IPv4 and IPv6 families first and then falls though to &lt;i&gt;TCPServer.open(port)&lt;/i&gt;.&lt;br /&gt;&lt;br /&gt;I have opened a &lt;a href="http://rubyforge.org/tracker/index.php?func=detail&amp;amp;aid=23245&amp;amp;group_id=426&amp;amp;atid=1698"&gt;TT&lt;/a&gt; on &lt;a href="http://rubyforge.org/"&gt;RubyForge&lt;/a&gt; for this that contains a patch from me to fix the first issue.&lt;br /&gt;&lt;br /&gt;What is required to fix the second issue? Dunno just yet, I'll keep looking and see if anything interesting pops up in the &lt;a href="http://rubyforge.org/tracker/index.php?func=detail&amp;amp;aid=23245&amp;amp;group_id=426&amp;amp;atid=1698"&gt;TT&lt;/a&gt;.&lt;br /&gt;&lt;br /&gt;&lt;a href="http://redmine.ruby-lang.org/issues/show/963"&gt;&lt;/a&gt;&lt;br /&gt;&lt;br /&gt;&lt;pre&gt;&lt;/pre&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/8719896753162286533-1113606597360638103?l=blog.ntrippy.net' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://blog.ntrippy.net/feeds/1113606597360638103/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=8719896753162286533&amp;postID=1113606597360638103' title='1 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/8719896753162286533/posts/default/1113606597360638103'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/8719896753162286533/posts/default/1113606597360638103'/><link rel='alternate' type='text/html' href='http://blog.ntrippy.net/2008/12/drbrb852in-initialize-getaddrinfo.html' title='drb.rb:852:in `initialize&apos;: getaddrinfo: nodename nor servname provided, or not known (SocketError) (aka DRb TCPServer.open(0) failure on OS X)'/><author><name>Charl</name><uri>http://www.blogger.com/profile/17742115469030093068</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='26' src='http://3.bp.blogspot.com/_4MLU7uPNEIY/TP2zTauOWAI/AAAAAAAAABs/OXj8i31AC0E/S220/blue%2B69.jpg'/></author><thr:total>1</thr:total></entry><entry><id>tag:blogger.com,1999:blog-8719896753162286533.post-6680957580893361058</id><published>2008-11-26T02:29:00.000-08:00</published><updated>2009-06-11T19:24:37.821-07:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='ruby'/><category scheme='http://www.blogger.com/atom/ns#' term='rails'/><title type='text'>Massaging Rails Models (with a happy ending)</title><content type='html'>How do you alter data in a model so that the data which is stored to and gathered from the database is first filtered/transformed?&lt;br /&gt;&lt;br /&gt;Two ways come to mind:&lt;br /&gt;&lt;br /&gt;&lt;ul&gt;&lt;li&gt;Insert the required behavior into your model's before_save, before_create and after_initialize callbacks.&lt;/li&gt;&lt;li&gt;Manually modify your attribute accessors for the attributes in question to do the magic for you.&lt;/li&gt;&lt;/ul&gt;&lt;br /&gt;We'll use the following contrived Model as our example:&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;class Gogga &amp;lt; ActiveRecord::Base&lt;br /&gt;end&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;pre&gt;CREATE TABLE  `foo`.`goggas` (&lt;br /&gt;`id` int(11) NOT NULL auto_increment,&lt;br /&gt;`secret` varchar(255) default NULL,&lt;br /&gt;PRIMARY KEY  (`id`)&lt;br /&gt;) ENGINE=InnoDB AUTO_INCREMENT=3 DEFAULT CHARSET=latin1&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;A Gogga has an id and a secret field. Let's pretend we need to keep Gogga.secret encrypted (using the super secret ROT13 algorithm) in our db and we would like the fact that it is encrypted to be transparent to our rails app. We need to therefore handle en/decryption of the secret data transparently from the rest of the app within the model.&lt;br /&gt;&lt;br /&gt;&lt;b&gt;Callbacks&lt;/b&gt;&lt;br /&gt;The before_save, before_create and after_initialize callbacks are well documented in the &lt;a href="http://api.rubyonrails.org/classes/ActiveRecord/Callbacks.html"&gt;Callbacks API&lt;/a&gt; documentation.&lt;br /&gt;&lt;br /&gt;The strategy behind using the callbacks is to simply insert the behavior we want at the relevant stage of the object's life cycle. Here's one way to accomplish this using the mentioned callbacks:&lt;br /&gt;&lt;br /&gt;&lt;pre&gt;class Gogga &amp;lt; ActiveRecord::Base&lt;br /&gt;  def before_save&lt;br /&gt;    self.secret = rot13(self.secret)&lt;br /&gt;  end&lt;br /&gt;&lt;br /&gt;  def before_create&lt;br /&gt;    self.secret = rot13(self.secret)&lt;br /&gt;  end&lt;br /&gt;&lt;br /&gt;  def after_initialize&lt;br /&gt;    self.secret = rot13(self.secret)&lt;br /&gt;  end&lt;br /&gt;&lt;br /&gt;protected&lt;br /&gt;  def rot13(corpus)&lt;br /&gt;    return corpus.tr!("A-Za-z", "N-ZA-Mn-za-m")&lt;br /&gt;  end&lt;br /&gt;end&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;If everything works as advertised our secret attribute should now be encoded when you call create or save on its model and decoded when you call a new on its model. The major drawback of this strategy is that if you are manipulating a large list of Goggas you will be post/pre-processing each of those instances.&lt;br /&gt;&lt;br /&gt;&lt;b&gt;The Lazy Way&lt;/b&gt;&lt;br /&gt;An alternative would be to override the default behavior of the model to auto-generate attribute accessors via method_missing in the mystical black guts of ActiveRecord::Base. There's some info on this in the API docs as well in the &lt;a href="http://api.rubyonrails.org/classes/ActiveRecord/Base.html"&gt;Overwriting default accessors&lt;/a&gt; section.&lt;br /&gt;&lt;br /&gt;This would look something like this:&lt;br /&gt;&lt;br /&gt;&lt;pre&gt;class Gogga &amp;lt; ActiveRecord::Base&lt;br /&gt;  def secret&lt;br /&gt;    return rot13(read_attribute("secret"))&lt;br /&gt;  end&lt;br /&gt;&lt;br /&gt;  def secret=(value)&lt;br /&gt;    write_attribute("secret", rot13(value))&lt;br /&gt;  end&lt;br /&gt;&lt;br /&gt;protected&lt;br /&gt;  def rot13(corpus)&lt;br /&gt;    return corpus.tr!("A-Za-z", "N-ZA-Mn-za-m")&lt;br /&gt;  end&lt;br /&gt;end&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;This technique has the advantage that you never really mess with the internals of the model (as the view you have of it from outside is tinted by the accessor transformations) and of course work only gets done when you need to read/write the specific attribute.&lt;br /&gt;&lt;br /&gt;Now, when you have one or two attributes that need to be protected writing out two accessors for each is not the end of the world. However, when you have several things become messy, tedious and downright boring.&lt;br /&gt;&lt;br /&gt;Maybe we can look at some meta-magic to DRY things up a bit in another article.&lt;br /&gt;&lt;br /&gt;&lt;pre&gt;&lt;/pre&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/8719896753162286533-6680957580893361058?l=blog.ntrippy.net' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://blog.ntrippy.net/feeds/6680957580893361058/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=8719896753162286533&amp;postID=6680957580893361058' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/8719896753162286533/posts/default/6680957580893361058'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/8719896753162286533/posts/default/6680957580893361058'/><link rel='alternate' type='text/html' href='http://blog.ntrippy.net/2008/11/massaging-rails-models-with-happy.html' title='Massaging Rails Models (with a happy ending)'/><author><name>Charl</name><uri>http://www.blogger.com/profile/17742115469030093068</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='26' src='http://3.bp.blogspot.com/_4MLU7uPNEIY/TP2zTauOWAI/AAAAAAAAABs/OXj8i31AC0E/S220/blue%2B69.jpg'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-8719896753162286533.post-1772616263930367994</id><published>2008-08-21T13:56:00.000-07:00</published><updated>2009-06-11T19:24:50.106-07:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='TreePanel'/><category scheme='http://www.blogger.com/atom/ns#' term='javascript'/><category scheme='http://www.blogger.com/atom/ns#' term='Click'/><category scheme='http://www.blogger.com/atom/ns#' term='Event'/><category scheme='http://www.blogger.com/atom/ns#' term='ExtJS'/><title type='text'>Oh ExtJS TreePanel Click, wherefore art thou?</title><content type='html'>&lt;a href="http://extjs.com/deploy/ext/docs/"&gt;ExtJS&lt;/a&gt; is a high quality JS UI library that really eases the cross-platform blues when it comes to designing online apps. Unfortunately I find the documentation can be a be a little obtuse from time to time.&lt;br /&gt;&lt;br /&gt;Even the trusty Google librarian cannot answer some of the questions that pop up right off the bat and a fair amount of searching is required to get some info.&lt;br /&gt;&lt;br /&gt;&lt;b&gt;TreePanel Click&lt;/b&gt;&lt;br /&gt;I have a TreePanel that I populate through lazy loading to save the amount of data I need to send off to the client in any one request. Unfortunately for the life of me I could not work out how to determine which node in the tree was clicked (so that I could fire off an Ajax request to populate another panel with data related to the clicked node).&lt;br /&gt;&lt;br /&gt;&lt;b&gt;Attaching Events&lt;/b&gt;&lt;br /&gt;The first thing to do would obviously be to attach a click event listener somewhere so that we can pick up when were being prodded. If you're not familiar to JavaScript and ExtJS specifically you'd be inclined to attach an event to each of the nodes.&lt;br /&gt;&lt;br /&gt;This is wasteful in the browser context as you should be taking advantage of &lt;a href="http://www.quirksmode.org/js/events_order.html"&gt;event bubbling&lt;/a&gt;. So we simply attach a listener to the whole tree component:&lt;pre&gt;  Ext.onReady(function(){&lt;br /&gt;   // Create initial root node&lt;br /&gt;   root = new Ext.tree.AsyncTreeNode({&lt;br /&gt;     text: 'Invisible Root',&lt;br /&gt;     id:'0'&lt;br /&gt;   });&lt;br /&gt;  &lt;br /&gt;   // Create the tree&lt;br /&gt;   tree = new Ext.tree.TreePanel({&lt;br /&gt;     loader: new Ext.tree.TreeLoader({&lt;br /&gt;       url:'/companies',&lt;br /&gt;       requestMethod:'GET',&lt;br /&gt;       baseParams:{format:'json'}&lt;br /&gt;     }),&lt;br /&gt;     renderTo:'company-tree',&lt;br /&gt;     root: root,&lt;br /&gt;     rootVisible:false&lt;br /&gt;   });&lt;br /&gt;  &lt;br /&gt;   // Expand invisible root node to trigger load of the first level of actual data&lt;br /&gt;   root.expand();&lt;br /&gt;  &lt;br /&gt;   // Listen for mouse clicks&lt;br /&gt;   Ext.get('company-tree').on("click", function(){&lt;br /&gt;     // Code to determine which node was clicked ...&lt;br /&gt;   });&lt;br /&gt; });&lt;/pre&gt;&lt;b&gt;Where did that click go?&lt;/b&gt;&lt;br /&gt;Never having used the TreePanel control before I had no real inkling how I was going to reap the relevant id from the node that was clicked. I tried several approaches but nothing was bearing any fruit till I found &lt;a href="http://extjs.com/forum/archive/index.php/t-5686.html"&gt;this&lt;/a&gt; article on the ExtJS forums discussing something similar.&lt;br /&gt;&lt;br /&gt;Armed with a new weapon I was able to refactor the listener:&lt;br /&gt;&lt;pre&gt;  // Listen for mouse clicks&lt;br /&gt; Ext.get('company-tree').on("click", function(){&lt;br /&gt;   node = tree.getSelectionModel().getSelectedNode();&lt;br /&gt;   console.log("You clicked on", node.id);&lt;br /&gt; });&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;That'll do.&lt;br /&gt;&lt;br /&gt;&lt;pre&gt;&lt;/pre&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/8719896753162286533-1772616263930367994?l=blog.ntrippy.net' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://blog.ntrippy.net/feeds/1772616263930367994/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=8719896753162286533&amp;postID=1772616263930367994' title='2 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/8719896753162286533/posts/default/1772616263930367994'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/8719896753162286533/posts/default/1772616263930367994'/><link rel='alternate' type='text/html' href='http://blog.ntrippy.net/2008/08/oh-extjs-treepanel-click-wherefore-art.html' title='Oh ExtJS TreePanel Click, wherefore art thou?'/><author><name>Charl</name><uri>http://www.blogger.com/profile/17742115469030093068</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='26' src='http://3.bp.blogspot.com/_4MLU7uPNEIY/TP2zTauOWAI/AAAAAAAAABs/OXj8i31AC0E/S220/blue%2B69.jpg'/></author><thr:total>2</thr:total></entry><entry><id>tag:blogger.com,1999:blog-8719896753162286533.post-3728232535990632597</id><published>2008-06-17T16:08:00.000-07:00</published><updated>2009-06-11T19:25:04.818-07:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='linux'/><category scheme='http://www.blogger.com/atom/ns#' term='pecl'/><category scheme='http://www.blogger.com/atom/ns#' term='rhel'/><category scheme='http://www.blogger.com/atom/ns#' term='xdebug'/><category scheme='http://www.blogger.com/atom/ns#' term='php'/><category scheme='http://www.blogger.com/atom/ns#' term='pear'/><title type='text'>Installing PECL/PEAR PHP modules on a RHEL box</title><content type='html'>The default RHEL 5.2 installation does not come with &lt;a href="http://xdebug.org/"&gt;xdebug&lt;/a&gt; as part of any of the php RPMs. A quick look around the Net also provided no real RPM candidates that I could use on this system so I had to fall back to using the package management tools (&lt;a href="http://pecl.php.net/"&gt;pecl&lt;/a&gt; and &lt;a href="http://pear.php.net/"&gt;pear&lt;/a&gt;) provided by php.&lt;br /&gt;&lt;br /&gt;&lt;b&gt;PECL's Odyssey&lt;/b&gt;&lt;br /&gt;&lt;a href="http://www.redhat.com/rhel/"&gt;RHEL&lt;/a&gt; is a general PITA for me already so I just sigh and get on with it:&lt;pre&gt;$ sudo pecl install xdebug&lt;br /&gt;downloading xdebug-2.0.3.tgz ...&lt;br /&gt;Starting to download xdebug-2.0.3.tgz (286,325 bytes)&lt;br /&gt;...........................................................done: 286,325 bytes&lt;br /&gt;66 source files, building&lt;br /&gt;running: phpize&lt;br /&gt;Configuring for:&lt;br /&gt;PHP Api Version:         20041225&lt;br /&gt;Zend Module Api No:      20060613&lt;br /&gt;Zend Extension Api No:   220060519&lt;br /&gt;/usr/bin/phpize: /tmp/pear/download/xdebug-2.0.3/build/shtool: /bin/sh: bad interpreter: Permission denied&lt;br /&gt;Cannot find autoconf. Please check your autoconf installation and the $PHP_AUTOCONF&lt;br /&gt;environment variable is set correctly and then rerun this script.&lt;br /&gt;&lt;br /&gt;ERROR: `phpize' failed&lt;/pre&gt;Yep, loving it already!&lt;br /&gt;&lt;br /&gt;Why on earth am I not able to invoke /bin/sh (as can be seen by the '/bin/sh: bad interpreter: Permission denied' error above)? Let's see if root can actually run the shell interpreter:&lt;pre&gt;$ sudo /bin/sh&lt;br /&gt;sh-3.2# exit&lt;/pre&gt;OK, everything looks good. Why is it breaking when we're trying to run the interpreter from /tmp/pear/download/xdebug-2.0.3/build/shtool?&lt;br /&gt;&lt;br /&gt;&lt;b&gt;Back to basics&lt;/b&gt;&lt;br /&gt;Perhaps this has something to do with where we're trying to run it from and the user we're doing the installation as (root) seems to be capable of running the interpreter but not from the shtool script for some reason.&lt;pre&gt;$ ls -ld /tmp/&lt;br /&gt;drwxrwxrwt 17 root root 4096 Jun 18 07:41 /tmp/&lt;/pre&gt;Obviously _not_ a permissions issue.&lt;pre&gt;$ grep tmp /etc/fstab&lt;br /&gt;/dev/sda2     /tmp    ext3    defaults,nosuid,nodev,noexec    1 2&lt;/pre&gt;Ah, there you are! /tmp is mounted with a 'noexec' flag so that's what's causing the execution to fail when we try to install xdebug via pecl. No problem, I'll just set pecl to use /var/tmp instead ... oh, wait, on RHEL systems /var/tmp is just a symlink to /tmp.&lt;br /&gt;&lt;br /&gt;*sigh*&lt;br /&gt;&lt;br /&gt;&lt;b&gt;Hand me half a brick&lt;/b&gt;&lt;br /&gt;Time to work around the issue. Let's go find those directories pear expects to be somehow related to /tmp or /var/tmp:&lt;pre&gt;$ pear config-show | grep tmp&lt;br /&gt;PEAR Installer download        download_dir     /tmp/pear/download&lt;br /&gt;PEAR Installer temp directory  temp_dir         /var/tmp&lt;/pre&gt;I updated these to temporarily point elsewhere:&lt;pre&gt;$ pecl config-show| grep tmp&lt;br /&gt;PEAR Installer download        download_dir     /root/tmp/pear/download&lt;br /&gt;PEAR Installer temp directory  temp_dir         /root/tmp&lt;br /&gt;$ sudo mkdir -p /root/tmp/pear/download&lt;/pre&gt;&lt;b&gt;Second verse, same as the first&lt;/b&gt;&lt;br /&gt;Let's give that installation another whirl and see where we are:&lt;pre&gt;$ sudo pecl install xdebug&lt;br /&gt;downloading xdebug-2.0.3.tgz ...&lt;br /&gt;Starting to download xdebug-2.0.3.tgz (286,325 bytes)&lt;br /&gt;.....................................done: 286,325 bytes&lt;br /&gt;66 source files, building&lt;br /&gt;running: phpize&lt;br /&gt;Configuring for:&lt;br /&gt;PHP Api Version:         20041225&lt;br /&gt;Zend Module Api No:      20060613&lt;br /&gt;Zend Extension Api No:   220060519&lt;br /&gt;building in /var/tmp/pear-build-root/xdebug-2.0.3&lt;br /&gt;running: /root/tmp/pear/download/xdebug-2.0.3/configure&lt;br /&gt;checking for egrep... grep -E&lt;br /&gt;checking for a sed that does not truncate output... /bin/sed&lt;br /&gt;checking for gcc... gcc&lt;br /&gt;checking for C compiler default output file name... a.out&lt;br /&gt;checking whether the C compiler works... configure: error: cannot run C compiled programs.&lt;br /&gt;If you meant to cross compile, use `--host'.&lt;br /&gt;See `config.log' for more details.&lt;br /&gt;ERROR: `/root/tmp/pear/download/xdebug-2.0.3/configure' failed&lt;/pre&gt;A quick look at /root/tmp/pear/download/xdebug-2.0.3/configure shows no obvious reasons why we're failing so out comes the &lt;a href="http://www.urbandictionary.com/define.php?term=cluebat"&gt;cluebat &lt;/a&gt;:&lt;pre&gt;$ sudo strace pecl insstall xdebug 2&gt;&amp;amp;1&lt;br /&gt;access("/etc/ld.so.preload", R_OK)      = -1 ENOENT (No such file or directory)&lt;br /&gt;execve("/usr/bin/pecl", ["pecl", "install", "xdebug"], [/* 16 vars */]) = 0&lt;br /&gt;brk(0)                                  = 0x8c3d000&lt;br /&gt;&lt;br /&gt;[... truncated ...]&lt;br /&gt;&lt;br /&gt;flock(3, LOCK_UN)                       = 0&lt;br /&gt;close(3)                                = 0&lt;br /&gt;write(1, "ERROR: `/root/tmp/pear/download/"..., 63ERROR: `/root/tmp/pear/download/xdebug-2.0.3/configure' failed&lt;br /&gt;) = 63&lt;br /&gt;stat64("/var/tmp/pear-build-root/xdebug-2.0.3", {st_mode=S_IFDIR|0755, st_size=4096, ...}) = 0&lt;br /&gt;lstat64("/var/tmp/pear-build-root/xdebug-2.0.3", {st_mode=S_IFDIR|0755, st_size=4096, ...}) = 0&lt;br /&gt;open("/var/tmp/pear-build-root/xdebug-2.0.3", O_RDONLY|O_NONBLOCK|O_LARGEFILE|O_DIRECTORY) = 3&lt;br /&gt;fstat64(3, {st_mode=S_IFDIR|0755, st_size=4096, ...}) = 0&lt;br /&gt;fcntl64(3, F_SETFD, FD_CLOEXEC)         = 0&lt;br /&gt;time(NULL)                              = 1213740767&lt;br /&gt;lstat64("/var", {st_mode=S_IFDIR|0755, st_size=4096, ...}) = 0&lt;br /&gt;lstat64("/var/tmp", {st_mode=S_IFLNK|0777, st_size=4, ...}) = 0&lt;br /&gt;readlink("/var/tmp", "/tmp", 4096)      = 4&lt;br /&gt;lstat64("/tmp", {st_mode=S_IFDIR|S_ISVTX|0777, st_size=4096, ...}) = 0&lt;br /&gt;lstat64("/tmp/pear-build-root", {st_mode=S_IFDIR|0755, st_size=4096, ...}) = 0&lt;br /&gt;lstat64("/tmp/pear-build-root/xdebug-2.0.3", {st_mode=S_IFDIR|0755, st_size=4096, ...}) = 0&lt;br /&gt;getdents(3, /* 4 entries */, 4096)      = 80&lt;br /&gt;getdents(3, /* 0 entries */, 4096)      = 0&lt;br /&gt;close(3)                                = 0&lt;br /&gt;stat64("/tmp/pear-build-root/xdebug-2.0.3/config.log", {st_mode=S_IFREG|0644, st_size=4948, ...}) = 0&lt;br /&gt;stat64("/tmp/pear-build-root/xdebug-2.0.3/config.nice", {st_mode=S_IFREG|0755, st_size=93, ...}) = 0&lt;br /&gt;unlink("/tmp/pear-build-root/xdebug-2.0.3/config.log") = 0&lt;br /&gt;unlink("/tmp/pear-build-root/xdebug-2.0.3/config.nice") = 0&lt;br /&gt;rmdir("/tmp/pear-build-root/xdebug-2.0.3") = 0&lt;br /&gt;stat64("/var/tmp/pear-build-root/install-xdebug-2.0.3", {st_mode=S_IFDIR|0755, st_size=4096, ...}) = 0&lt;br /&gt;lstat64("/var/tmp/pear-build-root/install-xdebug-2.0.3", {st_mode=S_IFDIR|0755, st_size=4096, ...}) = 0&lt;br /&gt;open("/var/tmp/pear-build-root/install-xdebug-2.0.3", O_RDONLY|O_NONBLOCK|O_LARGEFILE|O_DIRECTORY) = 3&lt;br /&gt;fstat64(3, {st_mode=S_IFDIR|0755, st_size=4096, ...}) = 0&lt;br /&gt;fcntl64(3, F_SETFD, FD_CLOEXEC)         = 0&lt;br /&gt;time(NULL)                              = 1213740767&lt;br /&gt;lstat64("/var", {st_mode=S_IFDIR|0755, st_size=4096, ...}) = 0&lt;br /&gt;lstat64("/var/tmp", {st_mode=S_IFLNK|0777, st_size=4, ...}) = 0&lt;br /&gt;readlink("/var/tmp", "/tmp", 4096)      = 4&lt;br /&gt;lstat64("/tmp", {st_mode=S_IFDIR|S_ISVTX|0777, st_size=4096, ...}) = 0&lt;br /&gt;lstat64("/tmp/pear-build-root", {st_mode=S_IFDIR|0755, st_size=4096, ...}) = 0&lt;br /&gt;lstat64("/tmp/pear-build-root/install-xdebug-2.0.3", {st_mode=S_IFDIR|0755, st_size=4096, ...}) = 0&lt;br /&gt;getdents(3, /* 2 entries */, 4096)      = 32&lt;br /&gt;getdents(3, /* 0 entries */, 4096)      = 0&lt;br /&gt;close(3)                                = 0&lt;br /&gt;rmdir("/tmp/pear-build-root/install-xdebug-2.0.3") = 0&lt;br /&gt;stat64("/tmp/glibctestUP8dWe", 0xbfa97ca0) = -1 ENOENT (No such file or directory)&lt;br /&gt;unlink("/tmp/glibctestUP8dWe")          = -1 ENOENT (No such file or directory)&lt;br /&gt;umask(022)                              = 022&lt;br /&gt;close(2)                                = 0&lt;br /&gt;close(1)                                = 0&lt;br /&gt;close(0)                                = 0&lt;br /&gt;munmap(0xb7fd6000, 4096)                = 0&lt;br /&gt;setitimer(ITIMER_PROF, {it_interval={0, 0}, it_value={0, 0}}, NULL) = 0&lt;br /&gt;brk(0xa3ce000)                          = 0xa3ce000&lt;br /&gt;setitimer(ITIMER_PROF, {it_interval={0, 0}, it_value={0, 0}}, NULL) = 0&lt;br /&gt;brk(0xa18a000)                          = 0xa18a000&lt;br /&gt;exit_group(0)&lt;/pre&gt;After all that pecl (aka pear) still tried to muck with /tmp by trying to run things in /var/tmp/pear-build-root/xdebug-2.0.3.&lt;br /&gt;&lt;br /&gt;Fine. Be that way.&lt;pre&gt;$ sudo cd /tmp &amp;amp;&amp;amp; rm -fr pear pear-build-root&lt;br /&gt;$ sudo ln -s /root/tmp/pear-build-root .&lt;/pre&gt;One more time please Sam:&lt;pre&gt;$ sudo pecl install xdebug&lt;br /&gt;downloading xdebug-2.0.3.tgz ...&lt;br /&gt;Starting to download xdebug-2.0.3.tgz (286,325 bytes)&lt;br /&gt;.....................................................done: 286,325 bytes&lt;br /&gt;66 source files, building&lt;br /&gt;running: phpize&lt;br /&gt;Configuring for:&lt;br /&gt;PHP Api Version:         20041225&lt;br /&gt;Zend Module Api No:      20060613&lt;br /&gt;Zend Extension Api No:   220060519&lt;br /&gt;building in /var/tmp/pear-build-root/xdebug-2.0.3&lt;br /&gt;running: /root/tmp/pear/download/xdebug-2.0.3/configure&lt;br /&gt;checking for egrep... grep -E&lt;br /&gt;checking for a sed that does not truncate output... /bin/sed&lt;br /&gt;checking for gcc... gcc&lt;br /&gt;checking for C compiler default output file name... a.out&lt;br /&gt;checking whether the C compiler works... yes&lt;br /&gt;&lt;br /&gt;[... truncated ...]&lt;br /&gt;&lt;br /&gt;Build complete.&lt;br /&gt;Don't forget to run 'make test'.&lt;br /&gt;&lt;br /&gt;running: make INSTALL_ROOT="/var/tmp/pear-build-root/install-xdebug-2.0.3" install&lt;br /&gt;Installing shared extensions:     /var/tmp/pear-build-root/install-xdebug-2.0.3/usr/lib/php/modules/&lt;br /&gt;running: find "/var/tmp/pear-build-root/install-xdebug-2.0.3" -ls&lt;br /&gt;36962402    4 drwxr-xr-x   3 root     root         4096 Jun 18 08:20 /var/tmp/pear-build-root/install-xdebug-2.0.3&lt;br /&gt;36962465    4 drwxr-xr-x   3 root     root         4096 Jun 18 08:20 /var/tmp/pear-build-root/install-xdebug-2.0.3/usr&lt;br /&gt;36962466    4 drwxr-xr-x   3 root     root         4096 Jun 18 08:20 /var/tmp/pear-build-root/install-xdebug-2.0.3/usr/lib&lt;br /&gt;36962467    4 drwxr-xr-x   3 root     root         4096 Jun 18 08:20 /var/tmp/pear-build-root/install-xdebug-2.0.3/usr/lib/php&lt;br /&gt;36962468    4 drwxr-xr-x   2 root     root         4096 Jun 18 08:20 /var/tmp/pear-build-root/install-xdebug-2.0.3/usr/lib/php/modules&lt;br /&gt;36962464  608 -rwxr-xr-x   1 root     root       618210 Jun 18 08:20 /var/tmp/pear-build-root/install-xdebug-2.0.3/usr/lib/php/modules/xdebug.so&lt;br /&gt;&lt;br /&gt;Build process completed successfully&lt;br /&gt;Installing '/usr/lib/php/modules/xdebug.so'&lt;br /&gt;install ok: channel://pecl.php.net/xdebug-2.0.3&lt;br /&gt;configuration option "php_ini" is not set to php.ini location&lt;br /&gt;You should add "extension=xdebug.so" to php.ini&lt;/pre&gt;Smell that? It's sweet success!&lt;br /&gt;&lt;br /&gt;&lt;b&gt;No more crawling, it's time to walk!&lt;/b&gt;&lt;br /&gt;Let's finish the install off for brevity's sake:&lt;pre&gt;$ sudo echo 'zend_extension="/usr/lib/php/modules/xdebug.so"' &gt; /etc/php.d/xdebug.ini&lt;br /&gt;$ php -i | grep xdebug&lt;br /&gt;/etc/php.d/xdebug.ini,&lt;br /&gt;xdebug&lt;br /&gt;xdebug support =&gt; enabled&lt;br /&gt;xdebug.auto_trace =&gt; Off =&gt; Off&lt;br /&gt;xdebug.collect_includes =&gt; On =&gt; On&lt;br /&gt;xdebug.collect_params =&gt; 0 =&gt; 0&lt;br /&gt;xdebug.collect_return =&gt; Off =&gt; Off&lt;br /&gt;xdebug.collect_vars =&gt; Off =&gt; Off&lt;br /&gt;xdebug.default_enable =&gt; On =&gt; On&lt;br /&gt;xdebug.dump.COOKIE =&gt; no value =&gt; no value&lt;br /&gt;xdebug.dump.ENV =&gt; no value =&gt; no value&lt;br /&gt;xdebug.dump.FILES =&gt; no value =&gt; no value&lt;br /&gt;xdebug.dump.GET =&gt; no value =&gt; no value&lt;br /&gt;xdebug.dump.POST =&gt; no value =&gt; no value&lt;br /&gt;xdebug.dump.REQUEST =&gt; no value =&gt; no value&lt;br /&gt;xdebug.dump.SERVER =&gt; no value =&gt; no value&lt;br /&gt;xdebug.dump.SESSION =&gt; no value =&gt; no value&lt;br /&gt;xdebug.dump_globals =&gt; On =&gt; On&lt;br /&gt;xdebug.dump_once =&gt; On =&gt; On&lt;br /&gt;xdebug.dump_undefined =&gt; Off =&gt; Off&lt;br /&gt;xdebug.extended_info =&gt; On =&gt; On&lt;br /&gt;xdebug.idekey =&gt; root =&gt; no value&lt;br /&gt;xdebug.manual_url =&gt; http://www.php.net =&gt; http://www.php.net&lt;br /&gt;xdebug.max_nesting_level =&gt; 100 =&gt; 100&lt;br /&gt;xdebug.profiler_aggregate =&gt; Off =&gt; Off&lt;br /&gt;xdebug.profiler_append =&gt; Off =&gt; Off&lt;br /&gt;xdebug.profiler_enable =&gt; Off =&gt; Off&lt;br /&gt;xdebug.profiler_enable_trigger =&gt; Off =&gt; Off&lt;br /&gt;xdebug.profiler_output_dir =&gt; /tmp =&gt; /tmp&lt;br /&gt;xdebug.profiler_output_name =&gt; cachegrind.out.%p =&gt; cachegrind.out.%p&lt;br /&gt;xdebug.remote_autostart =&gt; Off =&gt; Off&lt;br /&gt;xdebug.remote_enable =&gt; Off =&gt; Off&lt;br /&gt;xdebug.remote_handler =&gt; dbgp =&gt; dbgp&lt;br /&gt;xdebug.remote_host =&gt; localhost =&gt; localhost&lt;br /&gt;xdebug.remote_log =&gt; no value =&gt; no value&lt;br /&gt;xdebug.remote_mode =&gt; req =&gt; req&lt;br /&gt;xdebug.remote_port =&gt; 9000 =&gt; 9000&lt;br /&gt;xdebug.show_exception_trace =&gt; Off =&gt; Off&lt;br /&gt;xdebug.show_local_vars =&gt; Off =&gt; Off&lt;br /&gt;xdebug.show_mem_delta =&gt; Off =&gt; Off&lt;br /&gt;xdebug.trace_format =&gt; 0 =&gt; 0&lt;br /&gt;xdebug.trace_options =&gt; 0 =&gt; 0&lt;br /&gt;xdebug.trace_output_dir =&gt; /tmp =&gt; /tmp&lt;br /&gt;xdebug.trace_output_name =&gt; trace.%c =&gt; trace.%c&lt;br /&gt;xdebug.var_display_max_children =&gt; 128 =&gt; 128&lt;br /&gt;xdebug.var_display_max_data =&gt; 512 =&gt; 512&lt;br /&gt;xdebug.var_display_max_depth =&gt; 3 =&gt; 3&lt;/pre&gt;I can now delete those temporary directories I created in /root/tmp and get back to twitching in the corner, or ...&lt;br /&gt;&lt;br /&gt;&lt;b&gt;Easy alternative - just add remount&lt;/b&gt;&lt;br /&gt;Instead of the hoops I jumped through to get the pecl bits to stop addressing /tmp I could simply have remounted the /tmp filesystem to allow execution:&lt;pre&gt;$ sudo mount -o remount,exec /tmp&lt;br /&gt;$ sudo pecl install xdebug&lt;br /&gt;$ sudo mount -o remount,defaults,nosuid,nodev,noexec /tmp&lt;/pre&gt;This however would have compromised the security that was put in place to stop the possible malicious execution of bits (especially root kits) in /tmp (or /var/tmp).&lt;br /&gt;&lt;br /&gt;You have the power (and the knowledge now) so wield it to your benefit.&lt;br /&gt;&lt;br /&gt;&lt;pre&gt;&lt;/pre&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/8719896753162286533-3728232535990632597?l=blog.ntrippy.net' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://blog.ntrippy.net/feeds/3728232535990632597/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=8719896753162286533&amp;postID=3728232535990632597' title='7 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/8719896753162286533/posts/default/3728232535990632597'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/8719896753162286533/posts/default/3728232535990632597'/><link rel='alternate' type='text/html' href='http://blog.ntrippy.net/2008/06/installing-peclpear-php-modules-on-rhel.html' title='Installing PECL/PEAR PHP modules on a RHEL box'/><author><name>Charl</name><uri>http://www.blogger.com/profile/17742115469030093068</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='26' src='http://3.bp.blogspot.com/_4MLU7uPNEIY/TP2zTauOWAI/AAAAAAAAABs/OXj8i31AC0E/S220/blue%2B69.jpg'/></author><thr:total>7</thr:total></entry><entry><id>tag:blogger.com,1999:blog-8719896753162286533.post-5461132512429055466</id><published>2008-05-30T13:36:00.000-07:00</published><updated>2009-06-11T19:25:17.478-07:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='saslauthd'/><category scheme='http://www.blogger.com/atom/ns#' term='cyrus'/><category scheme='http://www.blogger.com/atom/ns#' term='debian'/><category scheme='http://www.blogger.com/atom/ns#' term='sasl'/><category scheme='http://www.blogger.com/atom/ns#' term='Ubuntu'/><title type='text'>warning: SASL authentication failure: cannot connect to saslauthd server: No such file or directory</title><content type='html'>There's nothing quite like being in a complete coding frenzy, communicating with your customers to get feedback on critical bugs, and your mail server going to SMTP heaven on you.&lt;br /&gt;&lt;br /&gt;&lt;b&gt;The Setup&lt;/b&gt;&lt;br /&gt;My MTA (&lt;a href="http://www.postfix.org/"&gt;postfix&lt;/a&gt;) is set up to do secure SMTP-AUTH and TLS via Cyrus SASL library (a.k.a. saslauthd via the &lt;a href="http://packages.ubuntu.com/hardy/sasl2-bin"&gt;sasl2-bin&lt;/a&gt; package) on an Ubuntu box.&lt;br /&gt;&lt;br /&gt;Postfix SASL support (&lt;a href="http://tools.ietf.org/html/rfc4954"&gt;RFC 4954&lt;/a&gt;, formerly &lt;a href="http://www.faqs.org/rfcs/rfc2554.html"&gt;RFC 2554&lt;/a&gt;) is used to authenticate remote SMTP clients to the MTA and the Postfix SMTP client to a remote SMTP server.&lt;br /&gt;&lt;br /&gt;&lt;b&gt;The Error&lt;/b&gt;&lt;br /&gt;I originally set things up via the &lt;a href="http://www.projektfarm.com/en/support/howto/postfix_smtp_auth_tls.html"&gt;Postfix-SMTP-AUTH-TLS-Howto&lt;/a&gt; and everything was working fine until earlier today when I started seeing the following log entries when trying to send mail vi the MTA:&lt;pre&gt;May 30 03:03:36 pyxidis postfix/smtpd[2840]: connect from unknown[x.x.x.x]&lt;br /&gt;May 30 03:03:37 pyxidis postfix/smtpd[2840]: setting up TLS connection from unknown[x.x.x.x]&lt;br /&gt;May 30 03:03:40 pyxidis postfix/smtpd[2840]: Anonymous TLS connection established from unknown[x.x.x.x]: TLSv1 with cipher AES128-SHA (128/128 bits)&lt;br /&gt;May 30 03:03:40 pyxidis postfix/smtpd[2840]: warning: SASL authentication failure: cannot connect to saslauthd server: No such file or directory&lt;br /&gt;May 30 03:03:40 pyxidis postfix/smtpd[2840]: warning: SASL authentication failure: Password verification failed&lt;br /&gt;May 30 03:03:40 pyxidis postfix/smtpd[2840]: warning: unknown[x.x.x.x]: SASL PLAIN authentication failed: generic failure&lt;br /&gt;May 30 03:03:46 pyxidis postfix/smtpd[2840]: lost connection after AUTH from unknown[x.x.x.x]&lt;br /&gt;May 30 03:03:46 pyxidis postfix/smtpd[2840]: disconnect from unknown[x.x.x.x]&lt;br /&gt;&lt;/pre&gt;&lt;b&gt;The Solution&lt;/b&gt;&lt;br /&gt;I checked and the saslauthd process was happily running. Next up I had a peek in /var/spool/postfix/var/run/saslauthd/ (which I had previously created as per the HOWTO above) but there were no *mux* files to be seen as there should have been.&lt;br /&gt;&lt;br /&gt;I then dawned on me that postfix runs in a chrooted jail and that saslauthd for some reason had stopped writing the required info to the chrooted jail where postfix was running. A quick look at the saslauthd rc script and its default file showed that it no longer had the required config to do this properly.&lt;br /&gt;&lt;br /&gt;Why? Dunno. I'll have to go do some snooping a little later.&lt;br /&gt;&lt;br /&gt;For now thought the fix was as simple as modifying the OPTIONS variable in the /etc/defaults/saslauthd config file to be something like this:&lt;pre&gt;OPTIONS="-c -m /var/spool/postfix/var/run/saslauthd"&lt;/pre&gt;Restart saslauthd and things start appearing where they should and mail is back in business.&lt;br /&gt;&lt;br /&gt;&lt;pre&gt;&lt;/pre&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/8719896753162286533-5461132512429055466?l=blog.ntrippy.net' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://blog.ntrippy.net/feeds/5461132512429055466/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=8719896753162286533&amp;postID=5461132512429055466' title='3 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/8719896753162286533/posts/default/5461132512429055466'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/8719896753162286533/posts/default/5461132512429055466'/><link rel='alternate' type='text/html' href='http://blog.ntrippy.net/2008/05/warning-sasl-authentication-failure.html' title='warning: SASL authentication failure: cannot connect to saslauthd server: No such file or directory'/><author><name>Charl</name><uri>http://www.blogger.com/profile/17742115469030093068</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='26' src='http://3.bp.blogspot.com/_4MLU7uPNEIY/TP2zTauOWAI/AAAAAAAAABs/OXj8i31AC0E/S220/blue%2B69.jpg'/></author><thr:total>3</thr:total></entry><entry><id>tag:blogger.com,1999:blog-8719896753162286533.post-3037852168346317727</id><published>2008-05-30T13:31:00.000-07:00</published><updated>2009-06-11T19:25:36.645-07:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='segfault'/><category scheme='http://www.blogger.com/atom/ns#' term='mysql'/><category scheme='http://www.blogger.com/atom/ns#' term='cacti'/><title type='text'>Cacti Segfaults</title><content type='html'>I recently did a cacti installation for a customer and ended up skipping step #3 from the &lt;a href="http://www.cacti.net/downloads/docs/html/unix_configure_cacti.html"&gt;Install and Configure Cacti&lt;/a&gt; guide.&lt;br /&gt;&lt;br /&gt;Step in question:&lt;pre&gt;shell&gt; mysql cacti &lt;/pre&gt;Why would I have skipped this very crucial step you might say?&lt;br /&gt;&lt;br /&gt;Well, the installation was done in two sessions with enough time having elapsed between the initial and final sessions that I had gotten hazy on what was and wasn't done. I had created the database but just never took the next step.&lt;br /&gt;&lt;br /&gt;After completing the rest of the configuration I fired Cacti but via my browser and php promptly did a segfault and lay there on the ground haemorrhaging. From my recent experience php has a propensity to do this in two specific cases:&lt;ul&gt; &lt;li&gt;Something went awry with a database connection or using a database resource&lt;/li&gt;&lt;li&gt;You're pushing the php boundaries with recursive regexps in a pre_match*() function&lt;/li&gt;&lt;/ul&gt;Once I sat down and went through each of the steps required to set the beastie up I realized I simply needed to import the db schema and initial data to get things going.&lt;br /&gt;&lt;br /&gt;Presto!&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;br /&gt;&lt;br /&gt;&lt;pre&gt;&lt;/pre&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/8719896753162286533-3037852168346317727?l=blog.ntrippy.net' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://blog.ntrippy.net/feeds/3037852168346317727/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=8719896753162286533&amp;postID=3037852168346317727' title='2 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/8719896753162286533/posts/default/3037852168346317727'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/8719896753162286533/posts/default/3037852168346317727'/><link rel='alternate' type='text/html' href='http://blog.ntrippy.net/2008/05/cacti-segfaults.html' title='Cacti Segfaults'/><author><name>Charl</name><uri>http://www.blogger.com/profile/17742115469030093068</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='26' src='http://3.bp.blogspot.com/_4MLU7uPNEIY/TP2zTauOWAI/AAAAAAAAABs/OXj8i31AC0E/S220/blue%2B69.jpg'/></author><thr:total>2</thr:total></entry><entry><id>tag:blogger.com,1999:blog-8719896753162286533.post-3662611246400466131</id><published>2008-05-24T18:12:00.000-07:00</published><updated>2009-06-11T19:25:50.071-07:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='crypt::cbc'/><category scheme='http://www.blogger.com/atom/ns#' term='crypt::blowfish'/><category scheme='http://www.blogger.com/atom/ns#' term='mysql'/><category scheme='http://www.blogger.com/atom/ns#' term='php'/><category scheme='http://www.blogger.com/atom/ns#' term='mcrypt'/><category scheme='http://www.blogger.com/atom/ns#' term='perl'/><category scheme='http://www.blogger.com/atom/ns#' term='aes'/><title type='text'>Tales from the (PHP and Perl) Crypt - AES Encryption in MySQL</title><content type='html'>I was looking for a way to share encrypted information between two systems where a table in MySQL was the integration point.&lt;br /&gt;&lt;br /&gt;The one system is based on php while the other component is a perl daemon.&lt;br /&gt;&lt;br /&gt;&lt;b&gt;Let's get cryptic&lt;span class="Apple-style-span" style="font-weight: normal;"&gt;&lt;/span&gt;&lt;/b&gt;&lt;div&gt;&lt;b&gt;&lt;span class="Apple-style-span" style="font-weight: normal;"&gt;My first stab at this was a perl based solution using the &lt;/span&gt;&lt;a href="http://search.cpan.org/author/LDS/Crypt-CBC-2.29/CBC.pm"&gt;&lt;span class="Apple-style-span" style="font-weight: normal;"&gt;Crypt::CBC&lt;/span&gt;&lt;/a&gt;&lt;span class="Apple-style-span" style="font-weight: normal;"&gt; and &lt;/span&gt;&lt;a href="http://search.cpan.org/author/DPARIS/Crypt-Blowfish-2.10/Blowfish.pm"&gt;&lt;span class="Apple-style-span" style="font-weight: normal;"&gt;Crypt::Blowfish&lt;/span&gt;&lt;/a&gt;&lt;span class="Apple-style-span" style="font-weight: normal;"&gt; libraries plus a shared secret/key. This meant I had to develop a perl script which I called from php to do the encryption which is a rather inelegant solution.&lt;br /&gt;&lt;br /&gt;At first I could not find the right libs in php to get this done but later stumbled upon the &lt;/span&gt;&lt;a href="http://www.php.net/mcrypt"&gt;&lt;span class="Apple-style-span" style="font-weight: normal;"&gt;Mcrypt&lt;/span&gt;&lt;/a&gt;&lt;span class="Apple-style-span" style="font-weight: normal;"&gt; suit off php and &lt;/span&gt;&lt;a href="http://search.cpan.org/~fkuo/MCrypt-0.92/MCrypt.pm"&gt;&lt;span class="Apple-style-span" style="font-weight: normal;"&gt;MCrypt&lt;/span&gt;&lt;/a&gt;&lt;span class="Apple-style-span" style="font-weight: normal;"&gt; perl functions that allow you to do encryption between the two different subsystems.&lt;br /&gt;&lt;br /&gt;Unfortunately this means you have double the amount of hassle when it comes to updates and ensuring things Just Work™.&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;b&gt;Move it back to the source&lt;/b&gt;&lt;br /&gt;&lt;span class="Apple-style-span" style="font-weight: normal;"&gt;Some more checking brought me to the MySQL &lt;/span&gt;&lt;a href="http://dev.mysql.com/doc/refman/5.0/en/encryption-functions.html"&gt;&lt;span class="Apple-style-span" style="font-weight: normal;"&gt;AES&lt;/span&gt;&lt;/a&gt;&lt;span class="Apple-style-span" style="font-weight: normal;"&gt; encryption functions that are built into MySQL. They provide the best cryptographic algorithms MySQL currently has to offer and are pretty &lt;a href="http://en.wikipedia.org/wiki/Advanced_Encryption_Standard"&gt;respectable&lt;/a&gt; from a &lt;a href="http://www.networkworld.com/community/node/16348"&gt;academic&lt;/a&gt; encryption perspective.&lt;br /&gt;&lt;br /&gt;This means en/decryption is dealt with at one integration point across all languages involved which is much more elegant.&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;b&gt;Tales from the Crypt&lt;/b&gt;&lt;br /&gt;&lt;span class="Apple-style-span" style="font-weight: normal;"&gt;The MySQL AES encryption functions allow you to en/decrypt data quite easily. To encrypt a string you simply issue the following, assuming your shared secret is &lt;/span&gt;&lt;i&gt;&lt;span class="Apple-style-span" style="font-weight: normal;"&gt;lesser-spotted-mountain-squid&lt;/span&gt;&lt;/i&gt;&lt;span class="Apple-style-span" style="font-weight: normal;"&gt;:&lt;/span&gt;&lt;pre&gt;&lt;span class="Apple-style-span" style="font-weight: normal;"&gt;mysql&gt; INSERT INTO test_table (test_column) VALUES(AES_ENCRYPT('this is a super-secret message', 'lesser-spotted-mountain-squid'));&lt;br /&gt;Query OK, 1 row affected (0.09 sec)&lt;br /&gt;&lt;br /&gt;mysql&gt; SELECT * FROM test_table;&lt;br /&gt;+----------------------------------+&lt;br /&gt;| test_column                      |&lt;br /&gt;+----------------------------------+&lt;br /&gt;| Aÿ„1&lt;br /&gt;ý#ôärO™é=:Žï   ¼Ñ†kWA |&lt;br /&gt;+----------------------------------+&lt;br /&gt;1 row in set (0.00 sec)&lt;/span&gt;&lt;/pre&gt;&lt;span class="Apple-style-span" style="font-weight: normal;"&gt;Et voila!&lt;br /&gt;&lt;br /&gt;One thing you need to keep in mind is that the field you want to store your encrypted data in must be a MySQL &lt;/span&gt;&lt;a href="http://dev.mysql.com/doc/refman/5.0/en/blob.html"&gt;&lt;span class="Apple-style-span" style="font-weight: normal;"&gt;BLOB&lt;/span&gt;&lt;/a&gt;&lt;span class="Apple-style-span" style="font-weight: normal;"&gt; data type.&lt;br /&gt;&lt;br /&gt;Sucking our super secret string back out into a usable form is as simple as:&lt;/span&gt;&lt;pre&gt;&lt;span class="Apple-style-span" style="font-weight: normal;"&gt;mysql&gt; SELECT AES_DECRYPT(test_column, 'lesser-spotted-mountain-squid') AS top_secret FROM test_table;&lt;br /&gt;+--------------------------------+&lt;br /&gt;| top_secret                     |&lt;br /&gt;+--------------------------------+&lt;br /&gt;| this is a super-secret message |&lt;br /&gt;+--------------------------------+&lt;br /&gt;1 row in set (0.00 sec)&lt;/span&gt;&lt;/pre&gt;&lt;b&gt;The security lesson&lt;/b&gt;&lt;br /&gt;&lt;span class="Apple-style-span" style="font-weight: normal;"&gt;This is rather obvious but your security is only as strong as the weakest link in the chain. In this specific case I did not want to have clear text data in the db and achieved that amicably.&lt;br /&gt;&lt;br /&gt;Because my secret is in clear text in two different systems I am rather exposed if those systems are not as secure as they could be. Lucky for my they are pretty much locked away from daylight so I'm not too concerned.&lt;br /&gt;&lt;/span&gt;&lt;br /&gt;&lt;/b&gt;&lt;/div&gt;&lt;br /&gt;&lt;br /&gt;&lt;pre&gt;&lt;/pre&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/8719896753162286533-3662611246400466131?l=blog.ntrippy.net' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://blog.ntrippy.net/feeds/3662611246400466131/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=8719896753162286533&amp;postID=3662611246400466131' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/8719896753162286533/posts/default/3662611246400466131'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/8719896753162286533/posts/default/3662611246400466131'/><link rel='alternate' type='text/html' href='http://blog.ntrippy.net/2008/05/tales-from-php-and-perl-crypt-aka-aes.html' title='Tales from the (PHP and Perl) Crypt - AES Encryption in MySQL'/><author><name>Charl</name><uri>http://www.blogger.com/profile/17742115469030093068</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='26' src='http://3.bp.blogspot.com/_4MLU7uPNEIY/TP2zTauOWAI/AAAAAAAAABs/OXj8i31AC0E/S220/blue%2B69.jpg'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-8719896753162286533.post-6103349797676138815</id><published>2008-05-06T16:39:00.000-07:00</published><updated>2009-06-11T19:26:04.966-07:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='password'/><category scheme='http://www.blogger.com/atom/ns#' term='linux'/><category scheme='http://www.blogger.com/atom/ns#' term='Ubuntu'/><category scheme='http://www.blogger.com/atom/ns#' term='smb'/><category scheme='http://www.blogger.com/atom/ns#' term='pam'/><title type='text'>Ubuntu 8.04 and PAM SMB Password</title><content type='html'>For those of you who have taken the plunge to Ubuntu v8.04 (Hardy Heron) you may have noticed that your auth.log is being filled with the following:&lt;br /&gt;&lt;pre&gt;May  4 03:17:01 example CRON[10796]: PAM adding faulty module: /lib/security/pam_smbpass.so&lt;br /&gt;May  4 04:17:01 example CRON[10799]: PAM unable to dlopen(/lib/security/pam_smbpass.so)&lt;br /&gt;May  4 04:17:01 example CRON[10799]: PAM [error: /lib/security/pam_smbpass.so: cannot open shared object file: No such file or directory]&lt;br /&gt;&lt;/pre&gt;&lt;b&gt;What's up?&lt;/b&gt;&lt;br /&gt;For some reason the Ubuntu gods have decided by default to include PAM configuration for the PAM SMB password module without actually installing the PAM SMB password module.&lt;br /&gt;&lt;br /&gt;Hence the complaints in your logs.&lt;br /&gt;&lt;br /&gt;&lt;b&gt;Make it go away!&lt;/b&gt;&lt;br /&gt;Sure, simply install the &lt;a href="http://packages.ubuntu.com/hardy/libpam-smbpass"&gt;libpam-smbpass&lt;/a&gt; package or edit two config files on your system like this:&lt;br /&gt;&lt;per&gt;&lt;br /&gt;&lt;/per&gt;&lt;div&gt;&lt;per&gt;$ perl -p -i -e 's/(password\s+optional\s+pam_smbpass.so nullok use_authtok use_first_pass)/#$1/' /etc/pam.d/common-password&lt;br /&gt;$ perl -p -i -e 's/(auth\s+optional\s+pam_smbpass.so migrate)/#$1/' /etc/pam.d/common-auth&lt;br /&gt;&lt;/per&gt;&lt;br /&gt;You can find some more info on this boog &lt;a href="https://bugs.launchpad.net/ubuntu/+bug/216990"&gt;here&lt;/a&gt;.&lt;br /&gt;&lt;br /&gt;&lt;/div&gt;&lt;br /&gt;&lt;br /&gt;&lt;pre&gt;&lt;/pre&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/8719896753162286533-6103349797676138815?l=blog.ntrippy.net' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://blog.ntrippy.net/feeds/6103349797676138815/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=8719896753162286533&amp;postID=6103349797676138815' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/8719896753162286533/posts/default/6103349797676138815'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/8719896753162286533/posts/default/6103349797676138815'/><link rel='alternate' type='text/html' href='http://blog.ntrippy.net/2008/05/ubuntu-804-and-pam-smb-password.html' title='Ubuntu 8.04 and PAM SMB Password'/><author><name>Charl</name><uri>http://www.blogger.com/profile/17742115469030093068</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='26' src='http://3.bp.blogspot.com/_4MLU7uPNEIY/TP2zTauOWAI/AAAAAAAAABs/OXj8i31AC0E/S220/blue%2B69.jpg'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-8719896753162286533.post-8675999031831163029</id><published>2008-04-19T16:23:00.000-07:00</published><updated>2009-06-11T19:26:19.455-07:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='cakephp'/><category scheme='http://www.blogger.com/atom/ns#' term='nginx'/><category scheme='http://www.blogger.com/atom/ns#' term='.htaccess'/><category scheme='http://www.blogger.com/atom/ns#' term='php'/><category scheme='http://www.blogger.com/atom/ns#' term='apache'/><category scheme='http://www.blogger.com/atom/ns#' term='url rewriting'/><title type='text'>Baking CakePHP with nginx</title><content type='html'>I've switched all my web server related infrastructure to &lt;a href="http://nginx.net/"&gt;nginx&lt;/a&gt; which is a high performance HTTP server (amongst other things) which blows apache out of the water (IMNSHO).&lt;br /&gt;&lt;br /&gt;If you're a &lt;a href="http://cakephp.org/"&gt;CakePHP&lt;/a&gt; user you'll know that it has some special rewriting requirements which can be found in $ROOT/.htaccess:&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;$ cat .htaccess&lt;br /&gt;&lt;ifmodule c=""&gt;&lt;br /&gt;RewriteEngine on&lt;br /&gt;RewriteRule    ^$ app/webroot/    [L]&lt;br /&gt;RewriteRule    (.*) app/webroot/$1 [L]&lt;br /&gt;&lt;/ifmodule&gt;&lt;/pre&gt;&lt;br /&gt;Unfortunately this &lt;a href="http://httpd.apache.org/docs/2.0/howto/htaccess.html"&gt;.htaccess&lt;/a&gt; file is an &lt;a href="http://httpd.apache.org/"&gt;apache&lt;/a&gt; mechanism to achieve the &lt;a href="http://httpd.apache.org/docs/1.3/mod/mod_rewrite.html"&gt;URL rewriting&lt;/a&gt; that is required to get CakePHP to play nice. The apache rewrite rules do not translate directly to something nginx can use though so some work is required to get things going.&lt;br /&gt;&lt;br /&gt;Being lazy (and believing that no problem I discover is unique to me) I had a look a round and found &lt;a href="http://www.littlehart.net/atthekeyboard/2007/09/14/configuring-cakephp-to-work-with-nginx/#comment-9126"&gt;this&lt;/a&gt; article by &lt;a href="http://www.littlehart.net/atthekeyboard/about/"&gt;Chris Hartjes&lt;/a&gt; which needed a some mods for it to work for my setup:&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;# CakePHP rewrite rules&lt;br /&gt;location / {&lt;br /&gt;  root /opt/local/html/live_site;&lt;br /&gt;  index index.php;&lt;br /&gt;&lt;br /&gt;  # Serve static page immediately&lt;br /&gt;    if (-f $request_filename) {&lt;br /&gt;    break;&lt;br /&gt;  }&lt;br /&gt;&lt;br /&gt;  if (!-f $request_filename) {&lt;br /&gt;    rewrite ^/(.+)$ /index.php?url=$1 last;&lt;br /&gt;    break;&lt;br /&gt;  }&lt;br /&gt;}&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;Here's my complete nginx.conf to give you an idea of how everything fits together:&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;&lt;br /&gt;user  nobody;&lt;br /&gt;worker_processes  1;&lt;br /&gt;&lt;br /&gt;#error_log  logs/error.log;&lt;br /&gt;#error_log  logs/error.log  notice;&lt;br /&gt;#error_log  logs/error.log  info;&lt;br /&gt;&lt;br /&gt;#pid        logs/nginx.pid;&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;events {&lt;br /&gt;  worker_connections  1024;&lt;br /&gt;}&lt;br /&gt;&lt;br /&gt;http {&lt;br /&gt;  include       etc/nginx/mime.types;&lt;br /&gt;  default_type  application/octet-stream;&lt;br /&gt;  server_names_hash_bucket_size  128;&lt;br /&gt;&lt;br /&gt;  sendfile           on;&lt;br /&gt;  keepalive_timeout  20;&lt;br /&gt;  tcp_nodelay        on;&lt;br /&gt;&lt;br /&gt;  server {&lt;br /&gt;    listen       80;&lt;br /&gt;    server_name  localhost;&lt;br /&gt;    rewrite_log on;&lt;br /&gt;&lt;br /&gt;    #error_page  404              /404.html;&lt;br /&gt;    # redirect server error pages to the static page /50x.html&lt;br /&gt;    #&lt;br /&gt;    error_page   500 502 503 504  /50x.html;&lt;br /&gt;    location = /50x.html {&lt;br /&gt;    root   share/nginx/html;&lt;br /&gt;    }&lt;br /&gt;&lt;br /&gt;    # Serve static content directly with some caching goodness&lt;br /&gt;    location ~* ^.+.(jpg|jpeg|gif|css|png|js|ico)$ {&lt;br /&gt;      root /opt/local/html/live_site/app/webroot;&lt;br /&gt;      access_log        off;&lt;br /&gt;      expires           1d;&lt;br /&gt;    }&lt;br /&gt;&lt;br /&gt;    # CakePHP rewrite rules&lt;br /&gt;    location / {&lt;br /&gt;      root /opt/local/html/live_site;&lt;br /&gt;      index index.php;&lt;br /&gt;&lt;br /&gt;      # Serve static pages immediately&lt;br /&gt;      if (-f $request_filename) {&lt;br /&gt;        break;&lt;br /&gt;      }&lt;br /&gt;      &lt;br /&gt;      # Rewrite all other URLs&lt;br /&gt;      if (!-f $request_filename) {&lt;br /&gt;        rewrite ^/(.+)$ /index.php?url=$1 last;&lt;br /&gt;        break;&lt;br /&gt;      }&lt;br /&gt;    }&lt;br /&gt;&lt;br /&gt;    # Pass PHP scripts to FastCGI server listening on 127.0.0.1:9000&lt;br /&gt;    #&lt;br /&gt;    location ~ \.php$ {&lt;br /&gt;    root /opt/local/html/live_site;&lt;br /&gt;    fastcgi_pass   127.0.0.1:9000;&lt;br /&gt;    fastcgi_index  index.php;&lt;br /&gt;    include        /opt/local/etc/nginx/fastcgi_params;&lt;br /&gt;    }&lt;br /&gt;  }&lt;br /&gt;}&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;&lt;pre&gt;&lt;/pre&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/8719896753162286533-8675999031831163029?l=blog.ntrippy.net' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://blog.ntrippy.net/feeds/8675999031831163029/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=8719896753162286533&amp;postID=8675999031831163029' title='3 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/8719896753162286533/posts/default/8675999031831163029'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/8719896753162286533/posts/default/8675999031831163029'/><link rel='alternate' type='text/html' href='http://blog.ntrippy.net/2008/04/ive-switched-all-my-web-server-related.html' title='Baking CakePHP with nginx'/><author><name>Charl</name><uri>http://www.blogger.com/profile/17742115469030093068</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='26' src='http://3.bp.blogspot.com/_4MLU7uPNEIY/TP2zTauOWAI/AAAAAAAAABs/OXj8i31AC0E/S220/blue%2B69.jpg'/></author><thr:total>3</thr:total></entry><entry><id>tag:blogger.com,1999:blog-8719896753162286533.post-5798835115063392309</id><published>2008-04-18T19:41:00.000-07:00</published><updated>2009-06-11T19:26:31.682-07:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='libxml2'/><category scheme='http://www.blogger.com/atom/ns#' term='dom'/><category scheme='http://www.blogger.com/atom/ns#' term='xml'/><category scheme='http://www.blogger.com/atom/ns#' term='php'/><category scheme='http://www.blogger.com/atom/ns#' term='domxml'/><category scheme='http://www.blogger.com/atom/ns#' term='cdata'/><category scheme='http://www.blogger.com/atom/ns#' term='simplexml'/><title type='text'>PHP5 SimpleXML and CDATA</title><content type='html'>I am in the process of porting a rather large PHP4 application to PHP5 (just in time for PHP6, yes, yes, I know) for one of my customers. Most of the application is pure imperative programming so the switch has been rather painless.&lt;br /&gt;&lt;br /&gt;Unfortunately (for me) they have made rather judicious use of the PHP4 &lt;a href="http://www.php.net/manual/ref.domxml.php"&gt;domxml extension&lt;/a&gt; libraries which no longer exist in PHP5 (where they have been replaced with the &lt;a href="http://www.php.net/manual/ref.dom.php"&gt;dom extension&lt;/a&gt;).&lt;br /&gt;&lt;br /&gt;Moving from domxml to dom was straight forward (looping over tags, tags, attributes and text inside tags are addresses differently) so I chose to simply re-write the code to use the new dom extensions instead of opting for a &lt;a href="http://alexandre.alapetite.net/doc-alex/domxml-php4-php5/index.en.html"&gt;translation library&lt;/a&gt; like the one provided by &lt;a href="http://alexandre.alapetite.net/cv/alexandre-alapetite.en.html"&gt;Alexandre Alapetite&lt;/a&gt;.&lt;br /&gt;&lt;br /&gt;One exception to this was the use of &lt; !--[CDATA[...]]--&gt; blocks which SimpleXML simply seemed to discard when creating a new object.&lt;br /&gt;&lt;br /&gt;A quick look around Google (&lt;a href="http://maisonbisson.com/blog/post/11257"&gt;here&lt;/a&gt;, &lt;a href="http://blog.agoraproduction.com/index.php?/archives/53-Quick-Tip-PHP,-SimpleXML-and-CDATA.html"&gt;here&lt;/a&gt; and &lt;a href="http://www.blogger.com/post-create.g?blogID=8719896753162286533"&gt;here&lt;/a&gt;) and I found what  needed to be done to address SimpleXML's ignorant behaviour.&lt;br /&gt;&lt;br /&gt;The SimpleXML constructor allows you to pass in &lt;a href="http://us3.php.net/manual/en/libxml.constants.php"&gt;extra libxml2 parameters&lt;/a&gt; which allow you to get further functionality out of the library. The one I was interested was of course:&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;LIBXML_NOCDATA  (integer)&lt;br /&gt;Merge CDATA as text nodes&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;So, simply changing my constructor from:&lt;br /&gt;&lt;pre&gt;$xml = new SimpleXMLElement($text)&lt;br /&gt;&lt;/pre&gt;to:&lt;br /&gt;&lt;pre&gt;$xml = new SimpleXMLElement($text, LIBXML_NOCDATA)&lt;br /&gt;&lt;/pre&gt;was all that was required for me to gain access to those &lt; !--[CDATA[...]]--&gt; structures.&lt;br /&gt;&lt;br /&gt;&lt;pre&gt;&lt;/pre&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/8719896753162286533-5798835115063392309?l=blog.ntrippy.net' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://blog.ntrippy.net/feeds/5798835115063392309/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=8719896753162286533&amp;postID=5798835115063392309' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/8719896753162286533/posts/default/5798835115063392309'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/8719896753162286533/posts/default/5798835115063392309'/><link rel='alternate' type='text/html' href='http://blog.ntrippy.net/2008/04/php5-simplexml-and-cdata.html' title='PHP5 SimpleXML and CDATA'/><author><name>Charl</name><uri>http://www.blogger.com/profile/17742115469030093068</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='26' src='http://3.bp.blogspot.com/_4MLU7uPNEIY/TP2zTauOWAI/AAAAAAAAABs/OXj8i31AC0E/S220/blue%2B69.jpg'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-8719896753162286533.post-7276151461168893177</id><published>2008-04-01T19:46:00.000-07:00</published><updated>2009-06-11T19:26:47.967-07:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='macports'/><category scheme='http://www.blogger.com/atom/ns#' term='variants'/><category scheme='http://www.blogger.com/atom/ns#' term='os x'/><category scheme='http://www.blogger.com/atom/ns#' term='aspell'/><category scheme='http://www.blogger.com/atom/ns#' term='pspell'/><category scheme='http://www.blogger.com/atom/ns#' term='php'/><title type='text'>Aspell, PSPELL, PHP and OS X</title><content type='html'>Until quite &lt;a href="http://trac.macosforge.org/projects/macports/ticket/14595#comment:2"&gt;recently&lt;/a&gt; there was no way to use Aspell (via the &lt;a href="http://www.php.net/pspell"&gt;PSPELL&lt;/a&gt; PHP libs) on an OS X host that was using the &lt;a href="http://www.macports.org/"&gt;MacPorts&lt;/a&gt; system for package management.&lt;br /&gt;&lt;br /&gt;This was simply because of the lack of a &lt;i&gt;pspell&lt;/i&gt; variant for the php{45} packages.&lt;br /&gt;&lt;br /&gt;&lt;b&gt;Port of call&lt;/b&gt;&lt;br /&gt;The ports system has the notion of a &lt;a href="http://guide.macports.org/#reference.variants"&gt;variant&lt;/a&gt; for packages that are conditional modifications of port installation behaviour.&lt;br /&gt;&lt;br /&gt;There are two types of variants: user-selected variants and platform variants.&lt;br /&gt;&lt;br /&gt;User-selected variants are options selected by a user when a port is installed while platform variants are selected automatically by MacPorts' base according to the OS or hardware platform (Darwin, FreeBSD, Linux, i386, PPC, etc.).&lt;br /&gt;&lt;br /&gt;&lt;b&gt;More stuff and less fluff&lt;/b&gt;&lt;br /&gt;There are several ways of working this. The first would be to log a ticket at the &lt;a href="http://trac.macosforge.org/projects/macports"&gt;MacPorts trac&lt;/a&gt; with a request to add the variant.&lt;br /&gt;&lt;br /&gt;Depending on the package maintainer's load you may be a response pretty quickly. I've generally gotten something back within days of logging the ticket.&lt;br /&gt;&lt;br /&gt;In the meantime your universe cannot come to a halt waiting for someone else to add the next greatest thing as a variant to your favourite package. So here's some manual steps to get things up and running in the meantime:&lt;br /&gt;&lt;br /&gt; &lt;ul&gt;&lt;li&gt;Install Aspell&lt;br /&gt; &lt;/li&gt;&lt;li&gt;Recompile and install PHP with the required PSPELL support&lt;br /&gt; &lt;/li&gt;&lt;li&gt;Check php to ensure the new PSPELL libs are active&lt;br /&gt; &lt;/li&gt;&lt;li&gt;Do a simple test to see if everything is working as it should&lt;br /&gt; &lt;/li&gt;&lt;/ul&gt;&lt;br /&gt;&lt;b&gt;Install Aspell&lt;/b&gt;&lt;br /&gt;I'll assume you're using MacPorts for your your package management on your OS X host.&lt;br /&gt;&lt;br /&gt;Grab the aspell application, libs and whichever dictionaries catch your fancy:&lt;br /&gt;&lt;code&gt;&lt;br /&gt;$ sudo port install aspell aspell-dict-en&lt;br /&gt;&lt;/code&gt;&lt;br /&gt;&lt;br /&gt;Be sure to install at least one dictionary or your spell checking days will be rather deflated.&lt;br /&gt;&lt;br /&gt;&lt;b&gt;Recompile and install PHP with the required PSPELL support&lt;/b&gt;&lt;br /&gt;To manually add a compilation flag you need to edit the Portfile that comes with your installed PHP version. Mine is located at:&lt;br /&gt;&lt;code&gt;&lt;br /&gt;/opt/local/var/macports/sources/rsync.macports.org/release/ports/www/php5/Portfile&lt;br /&gt;&lt;/code&gt;&lt;br /&gt;Edit the Portfile and add “–with-pspell=${prefix}” to configure.args. You can then re-ininstall PHP and it should now use the modified Portfile to compile PHP.&lt;br /&gt;&lt;br /&gt;&lt;b&gt;Check php to ensure the new PSPELL libs are active&lt;/b&gt;&lt;br /&gt;Use the following script from the command line to determine if your PHP was re-installed with the required PSPELL bits enabled:&lt;br /&gt;&lt;code&gt;&lt;br /&gt;$ php -r 'phpinfo();' | grep PSpell&lt;br /&gt;PSpell Support =&gt; enabled&lt;br /&gt;&lt;/code&gt;&lt;br /&gt;&lt;b&gt;Do a simple test to see if everything is working as it should&lt;/b&gt;&lt;br /&gt;You should be able to just use the example from the PHP documentation &lt;a href="http://www.php.net/manual/en/function.pspell-check.php"&gt;page&lt;/a&gt; to ensure everything is fine:&lt;br /&gt;&lt;code&gt;&lt;br /&gt;$ cat /tmp/t_pspell.php&lt;br /&gt;&lt;br /&gt;$ php -f /tmp/t_pspell.php&lt;br /&gt;Sorry, wrong spelling&lt;br /&gt;&lt;/code&gt;&lt;br /&gt;&lt;b&gt;The packaging gods exist!&lt;/b&gt;&lt;br /&gt;A few days after creating &lt;a href="http://trac.macosforge.org/projects/macports/ticket/14595"&gt;this ticket&lt;/a&gt; on the MacPorts trac I got a response from the package maintainer informing me that the variant had been added to all the relevant PHP packages.&lt;br /&gt;&lt;br /&gt;Cool!&lt;br /&gt;&lt;br /&gt;If your ports distribution files are up-to-date you should now be able to do the following to see which variants are available for your PHP version of choice:&lt;br /&gt;&lt;code&gt;&lt;br /&gt; $ port info php5&lt;br /&gt; php5 5.2.5, Revision 2, www/php5 (Variants: universal, darwin_6, darwin_7, macosx, apache, apache2, fastcgi, gmp, imap, pspell, tidy, mssql, snmp, macports_snmp, mysql3, mysql4, mysql5, oracle, postgresql, sqlite, ipc, pcntl, pear, readline, sockets)&lt;br /&gt; http://www.php.net/&lt;br /&gt;&lt;br /&gt; PHP is a widely-used general-purpose scripting language that is especially suited for developing web sites, but can also be used for command-line scripting.&lt;br /&gt;&lt;br /&gt; Library Dependencies: libxml2, libxslt, openssl, zlib, bzip2, libiconv, expat, gettext, tiff, mhash, libmcrypt, curl, pcre, jpeg, libpng, freetype&lt;br /&gt; Platforms: darwin freebsd&lt;br /&gt; Maintainers: ryandesign@macports.org jwa@macports.org&lt;br /&gt;&lt;/code&gt;&lt;br /&gt;Now that pspell is listed as a variant you can install PHP with this variant by doing the following, after removing previous PHP version:&lt;br /&gt;&lt;code&gt;&lt;br /&gt;$ sudo port install php5 +pspell&lt;br /&gt;&lt;/code&gt;&lt;br /&gt;&lt;br /&gt;&lt;pre&gt;&lt;/pre&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/8719896753162286533-7276151461168893177?l=blog.ntrippy.net' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://blog.ntrippy.net/feeds/7276151461168893177/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=8719896753162286533&amp;postID=7276151461168893177' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/8719896753162286533/posts/default/7276151461168893177'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/8719896753162286533/posts/default/7276151461168893177'/><link rel='alternate' type='text/html' href='http://blog.ntrippy.net/2008/04/aspell-pspell-php-and-os-x.html' title='Aspell, PSPELL, PHP and OS X'/><author><name>Charl</name><uri>http://www.blogger.com/profile/17742115469030093068</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='26' src='http://3.bp.blogspot.com/_4MLU7uPNEIY/TP2zTauOWAI/AAAAAAAAABs/OXj8i31AC0E/S220/blue%2B69.jpg'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-8719896753162286533.post-4017557270774531523</id><published>2008-02-22T14:48:00.000-08:00</published><updated>2009-06-11T19:27:02.465-07:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='linux'/><category scheme='http://www.blogger.com/atom/ns#' term='logwatch'/><category scheme='http://www.blogger.com/atom/ns#' term='Ubuntu'/><category scheme='http://www.blogger.com/atom/ns#' term='postfix'/><title type='text'>Pre-queue content-filter connection overload</title><content type='html'>In the last while I've been seeing the following error pop up in my &lt;a href="http://www2.logwatch.org:81/"&gt;logwatch&lt;/a&gt; report for &lt;a href="http://www.postfix.org/"&gt;postfix&lt;/a&gt;:&lt;br /&gt;&lt;br /&gt;&lt;code&gt;&lt;br /&gt; *Warning: Pre-queue content-filter connection overload&lt;br /&gt;&lt;/code&gt;&lt;br /&gt;&lt;br /&gt;At first I was concerned that the pre-queue content-filtering subsystem of postfix was somehow being overwhelmed and I was possibly loosing mail. Digging around a bit more lead to these types of log entries that seemed like they were the subject of the logwatch report:&lt;br /&gt;&lt;br /&gt;&lt;code&gt;&lt;br /&gt; Feb 21 13:01:45 pyxidis postfix/smtpd[5994]: connect from unknown[unknown]&lt;br /&gt; Feb 21 13:01:45 pyxidis postfix/smtpd[5994]: lost connection after CONNECT from unknown[unknown]&lt;br /&gt; Feb 21 13:01:45 pyxidis postfix/smtpd[5994]: disconnect from unknown[unknown]&lt;br /&gt;&lt;/code&gt;&lt;br /&gt;&lt;br /&gt;&lt;b&gt;Huh?!&lt;/b&gt;&lt;br /&gt;From what I can glean &lt;a href="http://www.mikecappella.com/logwatch/faq.html#connectionoverload"&gt;here&lt;/a&gt; the log entries above indicate that a SMTP connection was established with the kernel but the connecting host &lt;a href="http://idioms.thefreedictionary.com/drop+like+a+hot+potato"&gt;hot potatoed&lt;/a&gt; it before postfix was able to process the connection. &lt;br /&gt;&lt;br /&gt;When postfix tries to process the connection there's nobody home because the kernel had already removed the connection and it dumps something like the lines above to the mail log.&lt;br /&gt;&lt;br /&gt;&lt;b&gt;Wherefor?&lt;/b&gt;&lt;br /&gt;Here's the scoop from the logwatch docs:&lt;br /&gt;&lt;br /&gt;&lt;code&gt;&lt;br /&gt;This sometimes occurs in reaction to a portscan or broken bots, or when postfix is overloaded, due to excessive header_checks / body_checks content filtering, or even too few smtpd processes to service the demand. One could reduce the number of header_checks and body_checks, and possibly set smtpd_timeout to 60 (seconds). The key is that existing clients are overloading the number of smtpd daemons. The postfix-logwatch section configuration variable is postfix_ConnectionLostOverload, and the command line option is --connectionlostoverload. If you consider this sub-section to be meaningless, set the level limiter value to 0 and the sub-section will be suppressed. &lt;br /&gt;&lt;/code&gt;&lt;br /&gt;&lt;br /&gt;I was not going to change my header or body checks (because they keep the unwashed spammers at bay) so I opted for tuning my smtpd_timeout down to 30 seconds in main.cf.&lt;br /&gt;&lt;br /&gt;Because this looks like it is a possible resource issue you could also up the amount of smtpd processes allowed to service the pre-queue content-filtering subsystem.&lt;br /&gt;&lt;br /&gt;&lt;pre&gt;&lt;/pre&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/8719896753162286533-4017557270774531523?l=blog.ntrippy.net' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://blog.ntrippy.net/feeds/4017557270774531523/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=8719896753162286533&amp;postID=4017557270774531523' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/8719896753162286533/posts/default/4017557270774531523'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/8719896753162286533/posts/default/4017557270774531523'/><link rel='alternate' type='text/html' href='http://blog.ntrippy.net/2008/02/pre-queue-content-filter-connection.html' title='Pre-queue content-filter connection overload'/><author><name>Charl</name><uri>http://www.blogger.com/profile/17742115469030093068</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='26' src='http://3.bp.blogspot.com/_4MLU7uPNEIY/TP2zTauOWAI/AAAAAAAAABs/OXj8i31AC0E/S220/blue%2B69.jpg'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-8719896753162286533.post-6470844058792958172</id><published>2008-02-20T19:08:00.000-08:00</published><updated>2009-06-11T19:27:18.443-07:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='Fireclipse'/><category scheme='http://www.blogger.com/atom/ns#' term='Firebug'/><category scheme='http://www.blogger.com/atom/ns#' term='Firefox'/><title type='text'>Firefox 3 (beta x) and Firebug</title><content type='html'>18 February 2008&lt;br /&gt;&lt;br /&gt;Firefox 3 (beta x) and Firebug&lt;br /&gt;&lt;br /&gt;My favourite extension, by far, for Firefox is &lt;a href="http://www.getfirebug.com/"&gt;Firebug&lt;/a&gt;. It is a combination of strace and tcpdump for web applications. It allows you to drill down into all aspects of the send-response loop between your browser and a web app.&lt;br /&gt;&lt;br /&gt;If you're doing any JavaScript/AJAX development this tool will be invaluable to you!&lt;br /&gt;&lt;br /&gt;Living on the edge though, as you do, I upgraded the version of Firefox I was running to v3.0b3 and too my annoyance Firebug now no longer worked.&lt;br /&gt;&lt;br /&gt;Damn.&lt;br /&gt;&lt;br /&gt;Lucky for me the crew at &lt;a href="http://fireclipse.xucia.com/#Fireclipse%20Overview"&gt;Fireclipse&lt;/a&gt; have enhanced Firebug 1.05 by Joe Hewitt with enhancements and bug fixes. I simply grabbed the XPI from &lt;a href="http://fireclipse.xucia.com/files/fireclipse/firebug-1.1.0b10.xpi"&gt;here&lt;/a&gt; and installed it from within Firefox.&lt;br /&gt;&lt;br /&gt;Et Voilà!&lt;br /&gt;&lt;br /&gt;Another Reality Dysfunction is averted and life continues unimpeded.&lt;br /&gt;&lt;br /&gt;&lt;pre&gt;&lt;/pre&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/8719896753162286533-6470844058792958172?l=blog.ntrippy.net' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://blog.ntrippy.net/feeds/6470844058792958172/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=8719896753162286533&amp;postID=6470844058792958172' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/8719896753162286533/posts/default/6470844058792958172'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/8719896753162286533/posts/default/6470844058792958172'/><link rel='alternate' type='text/html' href='http://blog.ntrippy.net/2008/02/firefox-3-beta-x-and-firebug.html' title='Firefox 3 (beta x) and Firebug'/><author><name>Charl</name><uri>http://www.blogger.com/profile/17742115469030093068</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='26' src='http://3.bp.blogspot.com/_4MLU7uPNEIY/TP2zTauOWAI/AAAAAAAAABs/OXj8i31AC0E/S220/blue%2B69.jpg'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-8719896753162286533.post-1598331606873045298</id><published>2008-02-20T18:45:00.000-08:00</published><updated>2009-06-11T19:27:33.519-07:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='ntpdate'/><category scheme='http://www.blogger.com/atom/ns#' term='Xen'/><category scheme='http://www.blogger.com/atom/ns#' term='openntpd'/><category scheme='http://www.blogger.com/atom/ns#' term='Ubuntu'/><title type='text'>Dovecot time Machine</title><content type='html'>05 January 2008&lt;br /&gt;&lt;br /&gt;Dovecot time Machine&lt;br /&gt;&lt;br /&gt;I have a &lt;a href="http://www.xen.org/"&gt;XEN&lt;/a&gt; virtual machine which was complaining about time. Dovecot seemed to be the most vocal with errors like this in the logs:&lt;br /&gt;&lt;br /&gt;&lt;code&gt;&lt;br /&gt; dovecot: IMAP(charl): Time just moved backwards by 1 seconds. I'll sleep now until we're back in present. http://wiki.dovecot.org/TimeMovedBackwards&lt;br /&gt;&lt;/code&gt;&lt;br /&gt;&lt;br /&gt;I run &lt;a href="http://www.openntpd.org/"&gt;openntpd&lt;/a&gt; (OpenBSD NTP daemon) on the box but that was not seemingly keeping the date, well, up-to-date. Manually running ntpdate was also not providing the sync I sought and because this is a XEN box I have no access to hwclock.&lt;br /&gt;&lt;br /&gt;&lt;b&gt;Travelling back in time&lt;/b&gt;&lt;br /&gt;A bit of &lt;a href="http://forum.slicehost.com/comments.php?DiscussionID=138"&gt;digging&lt;/a&gt; on my provider's forums though showed that they were controlling the time syncing and had forgotten to turn the time sync back on after some troubleshooting they were doing.&lt;br /&gt;&lt;br /&gt;That's all good and well but my box was still not syncing.&lt;br /&gt;&lt;br /&gt;The Dovecot linked &lt;a href="http://wiki.dovecot.org/TimeMovedBackwards"&gt;article&lt;/a&gt; in the log error message simply states:&lt;br /&gt;&lt;br /&gt;&lt;code&gt;&lt;br /&gt; With Xen you should run ntpd only in dom0. Other domains should synchronize time automatically (see &lt;a href="http://xen.epiuse.com/xen-faq.txt"&gt;this Xen FAQ&lt;/a&gt;).&lt;br /&gt;&lt;/code&gt;&lt;br /&gt;&lt;br /&gt;&lt;b&gt;XEN wisdom&lt;/b&gt;&lt;br /&gt;The XEN FAQ has the following relevant things to say about time on a XEN box:&lt;br /&gt;&lt;br /&gt;&lt;code&gt;&lt;br /&gt; Q:  My xen machines doesn't accept setting its time. Basically, it's stuck to RTC time. ntpdate, date, hwclock all "seem" to work, but they don't actually change the system time. The only way I have to change it right now is to change it in the BIOS.&lt;br /&gt; A: Only affects 1.0. Fixed in newer versions.&lt;br /&gt;&lt;br /&gt; Q: Where does a domain get its time from?&lt;br /&gt; A: Briefly, Xen reads the RTC at start of day and by default will track that with the precision of the periodic timer crystal. Xen's estimate of the wall-clock time can only be updated by domain 0. If domain 0 runs ntpdate, ntpd, etc. then the synchronised time will automatically be pushed down to Xen every minute (and written to the RTC every 11 minutes, just as normal x86 Linux does).  All other domains always track Xen's wall-clock time: setting the date, or running ntpd, on these domains will not affect their wall-clock time. Note that the wall-clock time exported by Xen is UTC --- all domains must have appropriate timezone handling (i.e. a correct /etc/localtime file). &lt;br /&gt;&lt;br /&gt; Q: Is there is some cross-domain time synchronization : are they always in perfect sync, or should I run some kind of ntp in each subdomain ? Or only domain 0 would be enough ?&lt;br /&gt; A:If you want each domain to keep its own time, there are two ways to cause a domain to run its wallclock independently from Xen:&lt;br /&gt;   1. Specify 'independent_wallclock' on the command line.&lt;br /&gt;   2. 'echo 1 &gt;/proc/sys/xen/independent_wallclock'&lt;br /&gt;&lt;br /&gt;  To reenable tracking of Xen wallclock:&lt;br /&gt;   1. 'echo 0 &gt;/proc/sys/xen/independent_wallclock'&lt;br /&gt;&lt;/code&gt;&lt;br /&gt;&lt;br /&gt;&lt;b&gt;"Shut it down, Shut it down forever!"&lt;/b&gt;&lt;br /&gt;Following the FAQ I modified /proc/sys/xen/independent_wallclock and added it to /etc/sysctl.conf ("xen.independent_wallclock = 1") so that the change would survive a reboot.&lt;br /&gt;&lt;br /&gt;I also changed my timezone to the same local as where I am working currently by doing the following:&lt;br /&gt;&lt;br /&gt;$ sudo cp /usr/share/zoneinfo/Australia/Sydney /etc/localtime&lt;br /&gt;&lt;br /&gt;&lt;b&gt;Time marches on&lt;/b&gt;&lt;br /&gt;openntpd now keeps time synced and Dovecot no longer complains about running in the future.&lt;br /&gt;&lt;br /&gt;&lt;pre&gt;&lt;/pre&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/8719896753162286533-1598331606873045298?l=blog.ntrippy.net' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://blog.ntrippy.net/feeds/1598331606873045298/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=8719896753162286533&amp;postID=1598331606873045298' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/8719896753162286533/posts/default/1598331606873045298'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/8719896753162286533/posts/default/1598331606873045298'/><link rel='alternate' type='text/html' href='http://blog.ntrippy.net/2008/02/dovecot-time-machine.html' title='Dovecot time Machine'/><author><name>Charl</name><uri>http://www.blogger.com/profile/17742115469030093068</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='26' src='http://3.bp.blogspot.com/_4MLU7uPNEIY/TP2zTauOWAI/AAAAAAAAABs/OXj8i31AC0E/S220/blue%2B69.jpg'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-8719896753162286533.post-1554208957715139149</id><published>2007-12-27T16:01:00.000-08:00</published><updated>2009-06-11T19:27:47.829-07:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='upstart-compat-sysv'/><category scheme='http://www.blogger.com/atom/ns#' term='sysvinit'/><category scheme='http://www.blogger.com/atom/ns#' term='inittab'/><category scheme='http://www.blogger.com/atom/ns#' term='init'/><category scheme='http://www.blogger.com/atom/ns#' term='upstart'/><category scheme='http://www.blogger.com/atom/ns#' term='event.d'/><category scheme='http://www.blogger.com/atom/ns#' term='Ubuntu'/><title type='text'>Ubuntu's little upstart</title><content type='html'>One of my customers has a mix of Debian and Ubuntu servers installed in their network with a treasure trove of distribution versions from Debian GNU/Linux 3.0 (Woody) to Ubuntu 7.10 (Gutsy Gibbon). They have a fair amount of custom debs which need to coexist on these servers.&lt;br /&gt;&lt;br /&gt;While installing a new Gutsy box I noticed that several of the in-house debs were failing while trying to modify /etc/inittab  (our preferred way to keep things running that should never die). This was quite confusing to me as the last Ubuntu server I worked with was Ubuntu 6.06.1 LTS (Dapper Drake) which did not exhibit this weirdness.&lt;br /&gt;&lt;br /&gt;&lt;b&gt;Down the rabbit hole&lt;/b&gt;&lt;br /&gt;Imagine my amazement when I tried to find /etc/inittab and it was completely missing! My reality reset, I checked again and it was still missing.&lt;br /&gt;&lt;br /&gt;At first I thought some critical package (sysvinit in the versions of Ubuntu I know) was somehow missing after the base install. I jumped on the &lt;a href="http://packages.ubuntu.com/"&gt;Ubuntu packages site&lt;/a&gt; and did a search for 'sysvinit'. Sure enough, &lt;a href="http://packages.ubuntu.com/gutsy/base/sysvinit"&gt;there&lt;/a&gt; it was but the &lt;a href="http://packages.ubuntu.com/cgi-bin/search_contents.pl?searchmode=filelist&amp;amp;word=sysvinit&amp;amp;version=gutsy&amp;amp;arch=amd64"&gt;file list&lt;/a&gt; showed no inittab.&lt;br /&gt;&lt;br /&gt;Suddenly I felt like I a four year old whose mommy had lost them at the Mall.&lt;br /&gt;&lt;br /&gt;&lt;b&gt;Further down the spiral&lt;/b&gt;&lt;br /&gt;The file list did however list a bunch of items that referred to something called 'upstart-compat-sysv'. Browsing to &lt;a href="http://packages.ubuntu.com/gutsy/base/upstart-compat-sysv"&gt;this&lt;/a&gt; package listed the follwoing description for the package:&lt;br /&gt;&lt;br /&gt;This package contains compatibility tasks and utilities that emulate the behaviour of the original sysvinit package, including runlevels, and ensures that the initscripts in /etc/rc*.d are still run.&lt;br /&gt;&lt;br /&gt;OK. I faintly hear someone announcing that my mother is looking for me at the Mall's security office.&lt;br /&gt;&lt;br /&gt;So if upstart-compat-sysv _emulates_ the original sysvinit, then what has _replaced_ it?&lt;br /&gt;&lt;br /&gt;&lt;b&gt;Upstart, show thy face&lt;/b&gt;&lt;br /&gt;Google provided me with &lt;a href="http://upstart.ubuntu.com/"&gt;this&lt;/a&gt; link that points to the Ubuntu upstart page which went a long way towards uniting me and my mommy:&lt;br /&gt;&lt;br /&gt;Upstart is an event-based replacement for the /sbin/init daemon which handles starting of tasks and services during boot, stopping them during shutdown and supervising them while the system is running.&lt;br /&gt;&lt;br /&gt;It was originally developed for the Ubuntu distribution, but is intended to be suitable for deployment in all Linux distributions as a replacement for the venerable System-V init.&lt;br /&gt;&lt;br /&gt;Sounds even more pervasive than a simple missing iniitab!&lt;br /&gt;&lt;br /&gt;Features:&lt;br /&gt; &lt;ul&gt;&lt;li&gt;Tasks and Services are started and stopped by events&lt;/li&gt; &lt;li&gt;Events are generated as tasks and services are started and stopped&lt;/li&gt; &lt;li&gt;Events may be received from any other process on the system&lt;/li&gt; &lt;li&gt;Services may be respawned if they die unexpectedly&lt;/li&gt; &lt;li&gt;Bi-directional communication with init daemon to discover which jobs are running, why jobs failed, etc.&lt;/li&gt;&lt;/ul&gt;To my amazement upstart has been turned on by default since &lt;a href="http://www.ubuntu.com/getubuntu/releasenotes/610" title="Ubuntu 6.10 Release Notes | Ubuntu"&gt;Ubuntu 6.10&lt;/a&gt; (Edgy Eft) which explains why I was totally in the dark (but upstart was seemingly nothing new to everyone that had been following the normal upgrade path).&lt;br /&gt;&lt;br /&gt;So, &lt;a href="http://packages.ubuntu.com/gutsy/base/upstart" title="upstart"&gt;upstart&lt;/a&gt; was not only superseding everything we were trying to do with inittab but also changed the way one interacts with scripts via /etc/event.d.&lt;br /&gt;&lt;br /&gt;&lt;b&gt;Why reinvent the wheel?&lt;/b&gt;&lt;br /&gt;With the Ubuntu decision to move to the 2.6 kernel, and all the hotplug facilities it provides, they were left with several problems in Dapper. The kernel could now completely cope with hardware coming and going but that had a knock-on effect in that there was now no way to guarantee that particular devices were available at particular point in the boot process.&lt;br /&gt;&lt;br /&gt;For example: Dapper cannot mount USB disks in /etc/fstab because it is not guaranteed that the block device exists at the point in the mount process where that happens.&lt;br /&gt;&lt;br /&gt;Several &lt;a href="https://wiki.ubuntu.com/ReplacementInit#head-ff5e09faf7e75c37a07d2be772e673ef42fd6494"&gt;other&lt;/a&gt; reasons are also provided on the Ubuntu site.&lt;br /&gt;&lt;br /&gt;At first, the team decided to look at the  available alternatives in third party projects such as &lt;a href="http://opensolaris.org/os/community/smf/" title="Service Management Facility (smf(5)) at OpenSolaris.org"&gt;Solaris SMF&lt;/a&gt;, &lt;a href="http://developer.apple.com/macosx/launchd.html" title="Getting Started with launchd"&gt;Apple's launchd&lt;/a&gt;, the LSB initserv/chkconfig tools and &lt;a href="http://www.initng.org/" title="Initng – Trac"&gt;initNG&lt;/a&gt;. None of these met the design criteria that the team had set out for themselves and in true OSS style they decided to roll their own.&lt;br /&gt;&lt;br /&gt;The &lt;a href="https://wiki.ubuntu.com/ReplacementInit#head-eab6c5cfb8720a22c5116687520aee99a5aadd64"&gt;design&lt;/a&gt; and &lt;a href="https://wiki.ubuntu.com/ReplacementInit#head-f6ec051eaed55af0b78a871cbf57347c04f21ec9"&gt;implementation&lt;/a&gt; documentation is pretty clear so I won't repeat it here.&lt;br /&gt;&lt;br /&gt;&lt;b&gt;Epilogue&lt;/b&gt;&lt;br /&gt;In the end I simply had to add some logic to the custom debs to check if the system was running upstart and do The Right Thing(TM) (using upstart if it was available or falling back to using inittab) based on that. That amounted to dropping a relevant file in /etc/event.d/ for every server that we previously ran from inittab for systems that were using upstart.&lt;br /&gt;&lt;br /&gt;&lt;b&gt;Further Reading&lt;/b&gt;&lt;br /&gt; &lt;ul&gt;&lt;li&gt;&lt;a href="https://wiki.ubuntu.com/ReplacementInit"&gt;ReplacementInit&lt;/a&gt;&lt;/li&gt; &lt;p&gt;&lt;a href="http://upstart.ubuntu.com/wiki/" title="Upstart Wiki"&gt;Upstart Wiki&lt;/a&gt;&lt;/p&gt;&lt;/ul&gt;&lt;br /&gt;&lt;br /&gt;&lt;pre&gt;&lt;/pre&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/8719896753162286533-1554208957715139149?l=blog.ntrippy.net' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://blog.ntrippy.net/feeds/1554208957715139149/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=8719896753162286533&amp;postID=1554208957715139149' title='1 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/8719896753162286533/posts/default/1554208957715139149'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/8719896753162286533/posts/default/1554208957715139149'/><link rel='alternate' type='text/html' href='http://blog.ntrippy.net/2007/12/ubuntus-little-upstart.html' title='Ubuntu&apos;s little upstart'/><author><name>Charl</name><uri>http://www.blogger.com/profile/17742115469030093068</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='26' src='http://3.bp.blogspot.com/_4MLU7uPNEIY/TP2zTauOWAI/AAAAAAAAABs/OXj8i31AC0E/S220/blue%2B69.jpg'/></author><thr:total>1</thr:total></entry><entry><id>tag:blogger.com,1999:blog-8719896753162286533.post-6613683209431489836</id><published>2007-10-05T07:10:00.000-07:00</published><updated>2009-06-11T19:28:03.608-07:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='javascript'/><category scheme='http://www.blogger.com/atom/ns#' term='functional programming'/><category scheme='http://www.blogger.com/atom/ns#' term='functional-javascript'/><title type='text'>Func it up with JavaScript</title><content type='html'>&lt;span style="font-weight: bold;"&gt;UPDATE:&lt;/span&gt; &lt;a href="http://invisibleblocks.wordpress.com/2007/02/23/functional-programming-in-javascript-and-ruby/" title="Functional Programming in JavaScript and Ruby « Invisible Blocks"&gt;David Pollak&lt;/a&gt; has a great introduction to FP via JavaScript and Ruby.&lt;br /&gt;&lt;br /&gt;Using functional programming paradigms in JavaScript are non-existent, clumsy, verbose and difficult to read in most cases. "Oliver Steel":http://osteele.com/ has built an excellent little &lt;a href="http://code.google.com/p/functional-javascript/"&gt;library&lt;/a&gt; that does the grunt work for you when trying to get your func on.&lt;br /&gt;&lt;br /&gt;&lt;b&gt;To Func || ! Func&lt;/b&gt;&lt;br /&gt;Most people go about their imperial programming days without a thought of functional programming techniques and how they can be applied to their daily problems. In most part this is due to the inherent difficulties with _doing_ functional programming in their tool of choice.&lt;br /&gt;&lt;br /&gt;I urge you to do some further investigation (read &lt;a href="http://www.math.chalmers.se/%7Erjmh/Papers/whyfp.html"&gt;Why Functional Programming Matters&lt;/a&gt;) into functional programming techniques even if you think you'll never use them anywhere. This is not intended to be an exercise in (academic) pointlessness but to place you outside of your comfort zone and expand your thinking across different domains. The depth of knowledge gained from this will enable you to solve problems from a larger pool of tools (sometimes allowing you to bring functional programming paradigms to bare on a problem or simply augmenting your existing tools for a more efficient or elegant solution to problems).&lt;br /&gt;&lt;br /&gt;Learn to get your Func on!&lt;br /&gt;&lt;br /&gt;From a pure functional programming language perspective this approach to problem solving offers the following advantages over the imperative and OOP approaches:&lt;br /&gt;&lt;ul&gt;&lt;li&gt;No (re-)assignment&lt;/li&gt; &lt;/ul&gt;&lt;ul&gt;&lt;li&gt;&lt;l&gt;No side effects&lt;/l&gt;&lt;/li&gt;&lt;/ul&gt;&lt;ul&gt; &lt;li&gt;No flow of control&lt;/li&gt;&lt;/ul&gt;Functional calls can therefore have no other effect that to compute its result. In a pure functional language there are no assignments statements. Once you assign a value to a variable the variable never changes. In this sense variables in a functional language have more in common with algebraic variables that the normal programming stock we're used to.&lt;br /&gt;&lt;br /&gt;At first this seems like a debilitating restriction but after giving your brain some time to expand you'll find that this simple restriction eliminates one of the largest sources of bugs in programming and makes the order of execution irrelevant as no side-effect can change the value of an expression and it can be evaluated at any time.&lt;br /&gt;&lt;br /&gt;Gone are the days of worrying about orchestrating the flow control of your program. Your programs are now referentially transparent because expressions, variables and their values can be freely evaluated and replaced at any time.&lt;br /&gt;&lt;br /&gt;&lt;b&gt;Elements of Func&lt;/b&gt;&lt;br /&gt;From a strict academic sense functional programming refers to programs that has a main body which is a function that receives it's input as its arguments and delivers the output/transformation as it's result.&lt;br /&gt;&lt;br /&gt;So far this definition should not seem too foreign to most people that have worked with c/c++. Where this departs from the general imperative meme is that the main function is generally defined in terms of other functions, which in&lt;br /&gt;turn are defined in terms of still more functions, until at the lowest level the functions are first-class citizens (language primitives).&lt;br /&gt;&lt;br /&gt;These functions are much like ordinary mathematical functions (in that the same input will always deliver the same output).&lt;br /&gt;&lt;br /&gt;&lt;a href="http://en.wikipedia.org/wiki/Higher-order_programming"&gt;Higher-order programming&lt;/a&gt; (HOP), &lt;a href="http://en.wikipedia.org/wiki/Function-level_programming"&gt;function level programming&lt;/a&gt; (FLP) and &lt;a href="http://blogs.msdn.com/wesdyer/archive/2007/01/29/currying-and-partial-function-application.aspx"&gt;partial function application&lt;/a&gt; (PFA) are all styles used in functional programming.&lt;br /&gt;&lt;br /&gt;&lt;b&gt;Programming Transcendence&lt;/b&gt;&lt;br /&gt;HOP is the ability to use functions as values, in other words you can pass functions as arguments to other functions and functions can be returned as a value of other functions. An example of HOP in JavaScript would be something like the simple sort() method that you can apply to an array.&lt;br /&gt;&lt;br /&gt;In its simplest form the sort() function takes an unordered/ordered array and sorts the array:&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;var a = [2,3,1,4]&lt;br /&gt;document.write(a.sort())&lt;br /&gt;// prints "1,2,3,4"&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;The sort() method however allows you to use a comparison function as an optional argument, allowing you to pass it a function as a parameter, ergo implementing HOP. Let's assume we've got an array of date objects that we want to sort in a chronological order:&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;array_of_dates.sort{ function (x, y) { return x.date - y.date; } }&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;Here we pass in an anonymous function as our comparison function to sort(). The anonymous function is called for each object in the array of dates and it must return a negative value when x &lt; x ="="&gt; y.&lt;br /&gt;&lt;br /&gt;This technique is best used when you have at least two functions that perform the same take with a slight variance. Here you would then combine the functions by replacing the part(s) that are different with a function call to a separate function which is passed in to the more general function as a function parameter.&lt;br /&gt;&lt;br /&gt;The Functional library implements string lambdas that allow you to express some of the functional programming tools more succinctly. The traditional JavaScript way of doing say a map or filter would be something like this:&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;map(function(x){return x+1}, [1,2,3])         // returns [2,3,4]&lt;br /&gt;filter(function(x){return x&amp;gt;2}, [1,2,3,4]]    // returns [3,4]&lt;br /&gt;some(function(w){return w.length &amp;lt; 3}, 'are there any short words?'.split(' '))  // returns false&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;Instead, string lambdas allow you to write this in the following way:&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;map('x+1', [1,2,3])&lt;br /&gt;select('x&amp;gt;2', [1,2,3,4])&lt;br /&gt;some('_.length &amp;lt; 3', 'are there any short words?'.split(' '))&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;Here are some other way to bend a program to your functional will using simply map, reduce and filter:&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;// Double the items in a list:&lt;br /&gt;map('*2', [1,2,3])              // [2, 4, 6]&lt;br /&gt;&lt;br /&gt;// Find just the odd numbers:&lt;br /&gt;filter('%2', [1,2,3,4])         // [1, 3]&lt;br /&gt;&lt;br /&gt;// Find just the evens:&lt;br /&gt;filter(not('%2'), [1,2,3,4])    // [2, 4]&lt;br /&gt;&lt;br /&gt;// Find the length of the longest word:&lt;br /&gt;reduce(Math.max, 0, map('_.length', 'how long is the longest word?'.split(' ')))  // 7&lt;br /&gt;&lt;br /&gt;// Parse a binary array:&lt;br /&gt;reduce('2*x+y', 0, [1,0,1,0])   // 10&lt;br /&gt;&lt;br /&gt;// Parse a (non-negative) decimal string:&lt;br /&gt;reduce('x*10+y', 0, map('.charCodeAt(0)-48', '123'.split(/(?=.)/)))               // 123&lt;br /&gt;&lt;/pre&gt;Much more succinct, clear to read and easier to understand.&lt;br /&gt;&lt;br /&gt;&lt;b&gt;Func Levels&lt;/b&gt;&lt;br /&gt;&lt;a href="http://en.wikipedia.org/wiki/Value-level_programming"&gt;Value-level programming&lt;/a&gt; manipulates values, transforming a sequence of inputs into an output.  Function-level programming manipulates functions, applying operations to functions to construct a new function. This new function transforms the inputs into outputs.&lt;br /&gt;&lt;br /&gt;How can we make JavaScript dance to a functional-level programming paradigm using the Functional library as meter? Here's some example's:&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;// Find the reciprocal only of values that test true:&lt;br /&gt;map(guard('1/'), [1,2,null,4])              // [1, 0.5, null, 0.25]&lt;br /&gt;&lt;br /&gt;// Apply '10+' only to even values, leaving the odd ones alone:&lt;br /&gt;map(guard('10+', not('%2')), [1,2,3,4])     // [1, 12, 3, 14]&lt;br /&gt;&lt;br /&gt;// Write a version of map that only applies to the evens:&lt;br /&gt;var even = not('%2');&lt;br /&gt;var mapEvens = map.prefilterAt(0, guard.rcurry(even));&lt;br /&gt;mapEvens('10+', [1,2,3,4])&lt;br /&gt;&lt;br /&gt;// Find the first power of two that's greater than 100:&lt;br /&gt;until('&amp;gt;100', '2*')(1)                      // 128&lt;br /&gt;&lt;br /&gt;// Or, the first three-digit power of two (these are equivalent):&lt;br /&gt;until('String(_).length&amp;gt;2', '2*')(1)&lt;br /&gt;until(compose('&amp;gt;2', pluck('length'), String), '2*')(1)&lt;br /&gt;until(sequence(String, pluck('length'), '&amp;gt;2'), '2*')(1)&lt;br /&gt;&lt;/pre&gt;&lt;b&gt;Hot/Medium/Mild Curry?&lt;/b&gt;&lt;br /&gt;Partial function application (aka &lt;a href="http://en.wikipedia.org/wiki/Currying"&gt;currying&lt;/a&gt;) transforms a function that takes n arguments into a function that takes only one argument and returns a curried function of n - 1 arguments.&lt;br /&gt;&lt;br /&gt;In English please! OK, let's try:&lt;br /&gt;&lt;br /&gt;Currying is the process of partially, or incrementally, supplying arguments to a function. Curried functions are delayed functions expecting the remainder of the arguments to be supplied. Once all the arguments are supplied, the function evaluates normally. So, curried functions lead to lazy execution of the complete function.&lt;br /&gt;&lt;br /&gt;From the definitions above it is clear that partial function application, or specialisation, creates a new function out of an old one. To illustrate how we apply this with Functional we'll implement between(x, y, z) which determines whether y is bounded by x and z. We then curry the first and last arguments to produce a function that tests whether a number is positive:&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;// Function that needs to be curried&lt;br /&gt;function increasing(a, b, c)&lt;br /&gt;{&lt;br /&gt;return a &amp;lt; b &amp;amp;&amp;amp; b &amp;lt; c;&lt;br /&gt;}&lt;br /&gt;&lt;br /&gt;// Define the set of positive numbers via lazy evaluation&lt;br /&gt;var positive = increasing.partial(0, _, Infinity);&lt;br /&gt;&lt;br /&gt;// Determine if each of the values -1, 0 and 1 fall in our range&lt;br /&gt;map(positive, [-1, 0, 1])    // [false, false, true]&lt;br /&gt;&lt;br /&gt;// Define the set of negative numbers via lazy evaluation&lt;br /&gt;var negative = increasing.partial(-Infinity, _, 0);&lt;br /&gt;&lt;br /&gt;// Determine if each of -1, 0 and 1 fall in our range&lt;br /&gt;map(negative, [-1, 0, 1])    // [true, false, false]&lt;br /&gt;&lt;/pre&gt;Currying leads to lazy evaluation which allows you to work with structures like the infinite sets we created above. Cool eh?!&lt;br /&gt;&lt;br /&gt;&lt;b&gt;Epilogue&lt;/b&gt;&lt;br /&gt;Functional does a great job at making your life easier if you want to experiment with functional programming in JavaScript without getting yourself tangled up in the verbose, standard syntax. The creator does however offer a word of warning with regards to performance if you use this lib in production. Functional is also confirmed to work in Firefox 2.0, Safari 3.0, and MSIE 6.0.&lt;br /&gt;&lt;br /&gt;&lt;pre&gt;&lt;/pre&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/8719896753162286533-6613683209431489836?l=blog.ntrippy.net' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://blog.ntrippy.net/feeds/6613683209431489836/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=8719896753162286533&amp;postID=6613683209431489836' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/8719896753162286533/posts/default/6613683209431489836'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/8719896753162286533/posts/default/6613683209431489836'/><link rel='alternate' type='text/html' href='http://blog.ntrippy.net/2007/08/func-it-up-with-javascript.html' title='Func it up with JavaScript'/><author><name>Charl</name><uri>http://www.blogger.com/profile/17742115469030093068</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='26' src='http://3.bp.blogspot.com/_4MLU7uPNEIY/TP2zTauOWAI/AAAAAAAAABs/OXj8i31AC0E/S220/blue%2B69.jpg'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-8719896753162286533.post-8567172881247441841</id><published>2007-08-14T13:13:00.000-07:00</published><updated>2007-12-26T14:56:40.155-08:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='ruby'/><category scheme='http://www.blogger.com/atom/ns#' term='controller'/><category scheme='http://www.blogger.com/atom/ns#' term='finders'/><category scheme='http://www.blogger.com/atom/ns#' term='model'/><category scheme='http://www.blogger.com/atom/ns#' term='rparsec'/><title type='text'>Links</title><content type='html'>&lt;b&gt;Dynamic Attribute-based Finder Extensions&lt;/b&gt;&lt;br /&gt;&lt;a href="http://szeryf.wordpress.com/2007/07/15/extending-rails-magic-%EF%AC%81nders/"&gt;szeryf&lt;/a&gt; has written a nice little exposé on how you can extend ActiveRecord::Base to add _or_ and _not_ operators to the dynamic finders that rails provides you with.&lt;br /&gt;&lt;br /&gt;You can write the following types of finders out of the box with rails:&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;User.find_by_login_and_status(some_login, 1)&lt;br /&gt;User.find_by_login_and_status_and_role_(some_login, 1, role)&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;The additional extensions as described in the article adds the following to the repertoire above:&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;User.find_by_login_and_status_or_role(some_login, 1, role)&lt;br /&gt;User.find_by_login_and_status_not_role_(some_login, 1)&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;The two statements above would then result in the following SQL, respectively:&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;login = ? and (status = ? or (role = ?))&lt;br /&gt;login = ? and (status = ? not (role = ?))&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;b&gt;rparsec (the union of ActiveRecord :select and :include)&lt;/b&gt;&lt;br /&gt;As you most probably already know you use :select in a find to modify the fields that are in your result set and :include to specify that related tables are loaded via joins to provide improved performance. Unfortunately you cannot use either of these two together due to the limitations imposed by the ActiveRecord implementation.&lt;br /&gt;&lt;br /&gt;&lt;a href="http://cfis.savagexi.com/articles/2007/02/13/select-meets-include-or-a-pitch-for-rparsec"&gt;:select meets :include (or a pitch for rparsec)&lt;/a&gt; is an interesting article by &lt;a href="http://cfis.savagexi.com/"&gt;Charlie Savage&lt;/a&gt; which suggests using a SQL SELECT parser to provide the required functionality.&lt;br /&gt;&lt;br /&gt;He goes on to suggest doing this with the &lt;a href="http://docs.codehaus.org/display/JPARSEC/Ruby+Parsec"&gt;rparsec&lt;/a&gt; parser combinator framework.&lt;br /&gt;&lt;br /&gt;&lt;b&gt;The Controller Formula&lt;/b&gt;&lt;br /&gt;&lt;a href="http://www.pivotalblabs.com/articles/2007/07/16/the-controller-formula"&gt;Nick Kallen&lt;/a&gt; provides a lucid look at how one can produce poetic controller code.&lt;br /&gt;&lt;br /&gt;&lt;b&gt;Creating Multiple Models in One Action&lt;/b&gt;&lt;br /&gt;&lt;a href="http://www.pivotalblabs.com/articles/2007/07/18/creating-multiple-models-in-one-action"&gt;This&lt;/a&gt; article is a followup on the previous one elaborating on the method that can be used to create multiple models from one action.&lt;br /&gt;&lt;br /&gt;It covers the simplistic case where one model is simply created based on the creation of the other (when crating a group model, the creating uses needs to be the first member of the group) and the more complex case where there is a dependancy relationship between two models that needs to be enforced (a cyclops creation cannot succeed if the creation of the eye does not succeed).&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/8719896753162286533-8567172881247441841?l=blog.ntrippy.net' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://blog.ntrippy.net/feeds/8567172881247441841/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=8719896753162286533&amp;postID=8567172881247441841' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/8719896753162286533/posts/default/8567172881247441841'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/8719896753162286533/posts/default/8567172881247441841'/><link rel='alternate' type='text/html' href='http://blog.ntrippy.net/2007/08/links.html' title='Links'/><author><name>Charl</name><uri>http://www.blogger.com/profile/17742115469030093068</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='26' src='http://3.bp.blogspot.com/_4MLU7uPNEIY/TP2zTauOWAI/AAAAAAAAABs/OXj8i31AC0E/S220/blue%2B69.jpg'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-8719896753162286533.post-4196884601799202455</id><published>2007-08-12T23:20:00.000-07:00</published><updated>2007-12-26T14:45:36.473-08:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='ruby'/><category scheme='http://www.blogger.com/atom/ns#' term='Edge Rails'/><category scheme='http://www.blogger.com/atom/ns#' term='IE'/><category scheme='http://www.blogger.com/atom/ns#' term='rails'/><category scheme='http://www.blogger.com/atom/ns#' term='Prototype'/><title type='text'>Prototype, IE and Edge Rails Failures</title><content type='html'>I recently wrote a little conceptual file upload application with scaffold_resource that used the &lt;a href="http://developer.apple.com/internet/webcontent/iframe.html"&gt;iframe remoting pattern&lt;/a&gt; with some baked-in AJAX goodness to minimise the amount of data returned from the server as well as making the UI a lot more snappy.&lt;br /&gt;&lt;br /&gt;Everything went well and tested a-OK in Firefox. Unfortunately my default development platform does not support IE so I only tested the app in IE a little later. To my horror IE rendered the page differently as well as spewing the following JS error:&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;Line: 1629&lt;br /&gt;Char: 9&lt;br /&gt;Error: Invalid target element for this operation.&lt;br /&gt;Code: 0&lt;br /&gt;URL: &amp;lt;REMOVED&amp;gt;&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;b&gt;Prototype goodness wherefor art thou?&lt;/b&gt;&lt;br /&gt;A few concise questions to the Oracle of Google and I found a thread started by &lt;a href="http://robsanheim.com/2005/10/11/prototype-insertion-ie-6-trs-and-invalid-target-element-for-this-operation/"&gt;Rob Sanheim&lt;/a&gt; which detailed the same problem I was seeing.&lt;br /&gt;&lt;br /&gt;A fix, that worked for many of the thread readers, was proposed by Andy (12 December 2005 @ 1pm) in the same thread to deal with the way in which prototype inserts content in a &lt;a href="http://www.w3schools.com/tags/tag_tbody.asp"&gt;tbody&lt;/a&gt; or &lt;a href="http://www.w3schools.com/tags/tag_tr.asp"&gt;tr&lt;/a&gt; tag. &lt;br /&gt; &lt;br /&gt;So, off I went and upgraded my app to the latest Edge Rails using:&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;rake rails:freeze:edge&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;b&gt;Edge Rails will you be the end of me?&lt;/b&gt;&lt;br /&gt;&lt;br /&gt;Starting my app up I got a similar nasty to this:&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;/Applications/Locomotive2/Bundles/standardRailsFeb2007.locobundle/i386/lib/ruby/site_ruby/1.8/rubygems/custom_require.rb:27:in `gem_original_require&amp;apos;: no such file to load -- active_resource (MissingSourceFile) from /Applications/Locomotive2/Bundles/standardRailsFeb2007.locobundle/i386/lib/ruby/site_ruby/1.8/rubygems/custom_require.rb:27:in `require&amp;apos; from /Users/joelmeyer/src/FotoDir/vendor/rails/activerecord/lib/../../activesupport/lib/active_support/dependencies.rb:495:in `require&amp;apos; from /Users/joelmeyer/src/FotoDir/vendor/rails/activerecord/lib/../../activesupport/lib/active_support/dependencies.rb:342:in `new_constants_in&amp;apos; from /Users/joelmeyer/src/FotoDir/vendor/rails/activerecord/lib/../../activesupport/lib/active_support/dependencies.rb:495:in `require&amp;apos; from ./config/../vendor/rails/railties/lib/initializer.rb:160:in `require_frameworks&amp;apos; from ./config/../vendor/rails/railties/lib/initializer.rb:160:in `each&amp;apos; from ./config/../vendor/rails/railties/lib/initializer.rb:160:in `require_frameworks&amp;apos; from ./config/../vendor/rails/railties/lib/initializer.rb:88:in `process&amp;apos; ... 8 levels... from /Applications/Locomotive2/Bundles/standardRailsFeb2007.locobundle/i386/lib/ruby/gems/1.8/gems/capistrano-1.4.1/lib/capistrano/cli.rb:12:in `execute!&amp;apos; from /Applications/Locomotive2/Bundles/standardRailsFeb2007.locobundle/i386/lib/ruby/gems/1.8/gems/capistrano-1.4.1/bin/cap:11 from /Applications/Locomotive2/Bundles/standardRailsFeb2007.locobundle/i386/bin/cap:16:in `load&amp;apos; from /Applications/Locomotive2/Bundles/standardRailsFeb2007.locobundle/i386/bin/cap:16&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;Seems that when you're using Edge Rails you need to use the active resource gem in tandem or you world will turn pear-shaped. Installing the gem, with dependancies did the trick:&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;gem install -y activeresource --source http://gems.rubyonrails.org&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;b&gt;IE: Dark side of the moon&lt;/b&gt;&lt;br /&gt;So, I have now upgraded to the latest prototype lib (via the Edge Rails upgrade) and got Edge Rails to run with the active_scaffold and active resources I was using in my app.&lt;br /&gt;&lt;br /&gt;Was everything fine then and did I get to live a more fulfilling existence? NAY!&lt;br /&gt;&lt;br /&gt;Back to the entry by Rob Sanheim and I noticed that &lt;a href="http://chrisnolan.ca/"&gt;Chris Nolan&lt;/a&gt; reported that the prototype lib had been &lt;a href="http://dev.rubyonrails.org/ticket/3925"&gt;fixed&lt;/a&gt; but that he was still experiencing the same issue.&lt;br /&gt;&lt;br /&gt;There is a boat and I feel that Chris and I are both in it together, brothers in misery.&lt;br /&gt;&lt;br /&gt;While debugging the issue with trusty old JS alert() he found that he was trying to insert content at an id that belonged to a &lt;a href="http://www.w3schools.com/tags/tag_table.asp"&gt;table&lt;/a&gt; tag which was not supported. The tags supported for this were of course &lt;a href="http://www.w3schools.com/tags/tag_tbody.asp"&gt;tbody&lt;/a&gt; and/or &lt;a href="http://www.w3schools.com/tags/tag_tr.asp"&gt;tr&lt;/a&gt;.&lt;br /&gt;&lt;br /&gt;Even though the tbody tag seems to be optional according to the &lt;a href="http://www.w3.org/TR/html4/struct/tables.html"&gt;W3C&lt;/a&gt; IE still does not allow you to insert the content based on a &lt;a href="http://www.w3schools.com/tags/tag_table.asp"&gt;table&lt;/a&gt; id.&lt;br /&gt;&lt;br /&gt;The simple addition of the required tbody tags cured all.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/8719896753162286533-4196884601799202455?l=blog.ntrippy.net' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://blog.ntrippy.net/feeds/4196884601799202455/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=8719896753162286533&amp;postID=4196884601799202455' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/8719896753162286533/posts/default/4196884601799202455'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/8719896753162286533/posts/default/4196884601799202455'/><link rel='alternate' type='text/html' href='http://blog.ntrippy.net/2007/08/prototype-ie-and-edge-rails-failures.html' title='Prototype, IE and Edge Rails Failures'/><author><name>Charl</name><uri>http://www.blogger.com/profile/17742115469030093068</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='26' src='http://3.bp.blogspot.com/_4MLU7uPNEIY/TP2zTauOWAI/AAAAAAAAABs/OXj8i31AC0E/S220/blue%2B69.jpg'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-8719896753162286533.post-8537074034407271481</id><published>2007-08-05T14:56:00.000-07:00</published><updated>2007-12-27T14:59:19.940-08:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='ruby'/><category scheme='http://www.blogger.com/atom/ns#' term='delegateclass'/><category scheme='http://www.blogger.com/atom/ns#' term='simpledelegator'/><category scheme='http://www.blogger.com/atom/ns#' term='pattern'/><category scheme='http://www.blogger.com/atom/ns#' term='delegation'/><title type='text'>Turning responsibility inside-out via delegation</title><content type='html'>5 August 2007&lt;br /&gt;&lt;br /&gt;Turning responsibility inside-out via delegation&lt;br /&gt;&lt;br /&gt;What is the &lt;a href="http://en.wikipedia.org/wiki/Delegation_pattern"&gt;delegation pattern&lt;/a&gt;? You find this where you have an object that expresses a certain behaviour externally but internally defers, or, delegates the responsibility for implementation to another object in an &lt;a href="http://en.wikipedia.org/wiki/Inversion_of_Responsibility"&gt;inversion of responsibility&lt;/a&gt;.&lt;br /&gt;&lt;br /&gt;&lt;b&gt;Turning responsibility inside-out&lt;/b&gt;&lt;br /&gt;Ruby provides five ways to accomplish this: three (&lt;a href="http://stdlib.rubyonrails.org/libdoc/delegate/rdoc/classes/SimpleDelegator.html"&gt;SimpleDelegator&lt;/a&gt;, &lt;a href="http://stdlib.rubyonrails.org/libdoc/delegate/rdoc/files/delegate_rb.html#M000456"&gt;DelegateClass&lt;/a&gt; and &lt;a href="http://stdlib.rubyonrails.org/libdoc/delegate/rdoc/classes/Delegator.html"&gt;Delegator&lt;/a&gt;) encapsulated in the &lt;a href="http://stdlib.rubyonrails.org/libdoc/delegate/rdoc/index.html"&gt;delegate&lt;/a&gt; library and the remaining two (&lt;a href="http://stdlib.rubyonrails.org/libdoc/forwardable/rdoc/classes/Forwardable.html"&gt;Forwardable&lt;/a&gt; and &lt;a href="http://stdlib.rubyonrails.org/libdoc/forwardable/rdoc/classes/SingleForwardable.html"&gt;SingleForwardable&lt;/a&gt;) via the &lt;a href="http://stdlib.rubyonrails.org/libdoc/forwardable/rdoc/index.html"&gt;forwardable&lt;/a&gt; library.&lt;br /&gt;&lt;br /&gt;Let's use a queue data structure that delegates to an array to illustrate the various ways of accomplishing delegation.&lt;br /&gt;&lt;br /&gt;&lt;b&gt;SimpleDelegator&lt;/b&gt;&lt;br /&gt;This is the simplest way to accomplish delegation. You simply pass an object to the constructor and all methods supported by the object will be delegated. &lt;br /&gt;&lt;br /&gt;_This object can be changed later._&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;require &amp;apos;delegate&amp;apos;&lt;br /&gt;&lt;br /&gt;class Queue&lt;br /&gt; def initialize&lt;br /&gt;  @sd = SimpleDelegator.new([]) # we delegate to an array object&lt;br /&gt; end&lt;br /&gt;&lt;br /&gt; def enqueue(element) &lt;br /&gt;  @sd.push(element)&lt;br /&gt; end&lt;br /&gt;&lt;br /&gt; def dequeue &lt;br /&gt;  @sd.shift&lt;br /&gt; end&lt;br /&gt;end&lt;br /&gt;&lt;br /&gt;q = Queue.new&lt;br /&gt;q.enqueue(10)    # [10]&lt;br /&gt;q.enqueue(20)    # [10, 20]&lt;br /&gt;q.dequeue        # [20]&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;If you want to change the object you're delegating to you just use __setobj__(obj). You should just keep in mind that this does *not* cause SimpleDelegator’s methods to change which means that you should only be delegating to objects of the same type as the original delegate to avoid nastiness.&lt;br /&gt;&lt;br /&gt;&lt;b&gt;DelegateClass&lt;/b&gt;&lt;br /&gt;If SimpleDelegator does not spin your propeller then the next step would be to look at DelegateClass. Using the top level DelegateClass method to setup delegation through class inheritance is considered more flexible and is seemingly the most common use for this library.&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;require &amp;apos;delegate&amp;apos;&lt;br /&gt;&lt;br /&gt;class Queue &amp;lt; DelegateClass(Array) # we delegate to an array object&lt;br /&gt;  def initialize(arg=[])&lt;br /&gt;    super(arg)&lt;br /&gt;  end&lt;br /&gt;&lt;br /&gt;  alias_method :enqueue, :push     # alias_method sets up the method aliasing for us&lt;br /&gt;  alias_method :dequeue, :shift&lt;br /&gt;end&lt;br /&gt;&lt;br /&gt;q = Queue.new&lt;br /&gt;q.enqueue(10)    # [10]&lt;br /&gt;q.enqueue(20)    # [10, 20]&lt;br /&gt;q.dequeue        # [20]&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;b&gt;Delegator&lt;/b&gt;&lt;br /&gt;The final tool from the delegator library is Delegator which provides you with full control over the delegation scheme. The contrived example below is derived from the SimpleDelegator’s implementation.&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;require 'delegate'&lt;br /&gt;&lt;br /&gt;class QueueDelegator &lt; Delegator # inherit from the Delegator class&lt;br /&gt; def initialize(obj)&lt;br /&gt;  super             # pass obj to Delegator constructor&lt;br /&gt;  @_sd_obj = obj    # store obj for future use&lt;br /&gt; end&lt;br /&gt;&lt;br /&gt; def __getobj__&lt;br /&gt;  @_sd_obj          # return the object we are delegating to&lt;br /&gt; end&lt;br /&gt;&lt;br /&gt; def __setobj__(obj)&lt;br /&gt;  @_sd_obj = obj    # change delegation object, a feature we're providing&lt;br /&gt; end&lt;br /&gt;end&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;The conventional wisdom here however is that you should most likely be using the forwardable library instead of Delegator.&lt;br /&gt;&lt;br /&gt;&lt;b&gt;Fowardable&lt;/b&gt;&lt;br /&gt;If you need class-level delegation this is your beast of burden.&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;require &amp;apos;forwardable&amp;apos;&lt;br /&gt;&lt;br /&gt;class Queue&lt;br /&gt; extend Forwardable&lt;br /&gt;&lt;br /&gt; def initialize(obj=[])&lt;br /&gt;  @queue = obj    # delegate to this object&lt;br /&gt; end&lt;br /&gt;&lt;br /&gt; def_delegator :@queue, :push,  :enqueue&lt;br /&gt; def_delegator :@queue, :shift, :dequeue&lt;br /&gt; def_delegators :@queue, :clear, :empty?, :length, :size, :&amp;lt;&amp;lt;&lt;br /&gt;end&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;There are a few things to take note of here. First, def_delegator is used to set up the delegation relationship between the method call, the delegated object and the method to call on the delegated object.&lt;br /&gt;&lt;br /&gt;Second, notice the syntax (:@queue, instead of @queue or :queue) to specify the delegated object we're defining methods for. This is simply an artefact of the way that Forwardable is implemented.&lt;br /&gt;&lt;br /&gt;&lt;b&gt;SingleForwardable&lt;/b&gt;&lt;br /&gt;Where Forwardable provides class-level delegation, SingleForwardable provides object level delegation. For this example I'll simply copy the example provided in the library documentation.&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;require &amp;apos;forwardable&amp;apos;&lt;br /&gt;&lt;br /&gt;printer = String.new&lt;br /&gt;printer.extend SingleForwardable        # prepare object for delegation&lt;br /&gt;printer.def_delegator &amp;quot;STDOUT&amp;quot;, &amp;quot;puts&amp;quot;  # add delegation for STDOUT.puts()&lt;br /&gt;printer.puts &amp;quot;Howdy!&amp;quot;&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;b&gt;Epilogue&lt;/b&gt;&lt;br /&gt;Using DelegateClass and Forwardable for your delegation needs will most likely cover most of the cases you may end up needing to implement the delegator pattern.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/8719896753162286533-8537074034407271481?l=blog.ntrippy.net' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://blog.ntrippy.net/feeds/8537074034407271481/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=8719896753162286533&amp;postID=8537074034407271481' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/8719896753162286533/posts/default/8537074034407271481'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/8719896753162286533/posts/default/8537074034407271481'/><link rel='alternate' type='text/html' href='http://blog.ntrippy.net/2007/08/turning-responsibility-inside-out-via.html' title='Turning responsibility inside-out via delegation'/><author><name>Charl</name><uri>http://www.blogger.com/profile/17742115469030093068</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='26' src='http://3.bp.blogspot.com/_4MLU7uPNEIY/TP2zTauOWAI/AAAAAAAAABs/OXj8i31AC0E/S220/blue%2B69.jpg'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-8719896753162286533.post-5005135849574627758</id><published>2007-08-05T08:10:00.000-07:00</published><updated>2007-12-26T13:57:34.390-08:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='ruby'/><title type='text'>Did we default or not?</title><content type='html'>You may sometimes find yourself having to distinguish whether a method attribute was supplied externally or taken from the default specified in the method definition.&lt;br /&gt;&lt;br /&gt;Let's say, for example, that you want to warn a user when they have neglected to set an attribute but still continue with execution. Here is a snippet that would accomplish this:&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;irb(main):060:0&amp;gt; def some_method(first, second=(flag=true; '2nd'))&lt;br /&gt;irb(main):061:1&amp;gt; p "Default value #{second} used for unspecified parameter 'second'" if flag.inspect == "true"&lt;br /&gt;irb(main):062:1&amp;gt; end&lt;br /&gt;=&amp;gt; nil&lt;br /&gt;irb(main):063:0&amp;gt; some_method(1,2)&lt;br /&gt;=&amp;gt; nil&lt;br /&gt;irb(main):064:0&amp;gt; some_method(1,'2nd')&lt;br /&gt;=&amp;gt; nil&lt;br /&gt;irb(main):065:0&amp;gt; some_method(1)&lt;br /&gt;"Default value 2nd used for unspecified parameter 'second'"&lt;br /&gt;=&amp;gt; nil&lt;br /&gt;irb(main):066:0&amp;gt;&lt;br /&gt;&lt;/pre&gt;Can you work out what is going on in the method parameter declaration?&lt;br /&gt;&lt;br /&gt;All that's happening is that the code in the round brackets after the equals sign defines a local variable _flag_, sets its value and returns the default value we want to set it to.&lt;br /&gt;&lt;br /&gt;Ruby rocks!&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/8719896753162286533-5005135849574627758?l=blog.ntrippy.net' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://blog.ntrippy.net/feeds/5005135849574627758/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=8719896753162286533&amp;postID=5005135849574627758' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/8719896753162286533/posts/default/5005135849574627758'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/8719896753162286533/posts/default/5005135849574627758'/><link rel='alternate' type='text/html' href='http://blog.ntrippy.net/2007/08/did-we-default-or-not.html' title='Did we default or not?'/><author><name>Charl</name><uri>http://www.blogger.com/profile/17742115469030093068</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='26' src='http://3.bp.blogspot.com/_4MLU7uPNEIY/TP2zTauOWAI/AAAAAAAAABs/OXj8i31AC0E/S220/blue%2B69.jpg'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-8719896753162286533.post-8487203195502461639</id><published>2007-07-29T07:18:00.000-07:00</published><updated>2007-12-26T13:43:57.490-08:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='sarge'/><category scheme='http://www.blogger.com/atom/ns#' term='etch'/><category scheme='http://www.blogger.com/atom/ns#' term='debian-volatile'/><category scheme='http://www.blogger.com/atom/ns#' term='debian'/><category scheme='http://www.blogger.com/atom/ns#' term='apt'/><category scheme='http://www.blogger.com/atom/ns#' term='volatile-sloppy'/><title type='text'>Tracking fast-paced packages on debian based systems (aka  debian-volatile project)</title><content type='html'>&lt;b&gt;debian-volatile&lt;/b&gt;&lt;br /&gt;If you run some ISP services (your own mail server with virus and/or spam scanning tools) you will have run into the age old problem that the scanning tools in the stable distribution do not evolve as fast as they should to keep up with their fast-paced projects.&lt;br /&gt;&lt;br /&gt;Even continual updates of the software in your distribution are not enough to stay up to date as the release cycle of the stable distribution is out of sync with the speed at which things change in the wild.&lt;br /&gt;&lt;br /&gt;According to the &lt;a href="http://www.debian.org/volatile/"&gt;debian-volatile project&lt;/a&gt; page:&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;The main goal of volatile is allowing system administrators to update their systems&lt;br /&gt;in a nice, consistent way, without getting the drawbacks of using unstable, even&lt;br /&gt;without getting the drawbacks for the selected packages. So debian-volatile will&lt;br /&gt;only contain changes to stable programs that are necessary to keep them functional.&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;b&gt;volatile-sloppy&lt;/b&gt;&lt;br /&gt;Great effort goes into ensuring that no functional changes are made to packages in debian-volatile (so that configuration file changes, etc. are not required) for painless upgrades. Unfortunately painful upgrades are not always avoidable so a volatile-sloppy section was created to contain packages that are fast-paced but also require some functional change to how it runs, is installed or configured.&lt;br /&gt;&lt;br /&gt;&lt;b&gt;Security&lt;/b&gt;&lt;br /&gt;You should note that the debian-volatile project is not supported by the _official_ security team. This responsibility falls to the &lt;a href="http://www.debian.org/volatile/team"&gt;debian-volatile team&lt;/a&gt; who currently has at least one member that is shared with the official &lt;a href="http://secure-testing-master.debian.net/"&gt;debian testing security team&lt;/a&gt;.&lt;br /&gt;&lt;br /&gt;&lt;b&gt;How do I use it?&lt;/b&gt;&lt;br /&gt;Add the relevant repository (volatile and/or volatile-sloppy) to your /etc/apt/sources.list file:&lt;br /&gt;&lt;br /&gt;Sarge&lt;br /&gt;&lt;pre&gt;deb http://volatile.debian.org/debian-volatile sarge/volatile main contrib non-free&lt;br /&gt;deb http://volatile.debian.org/debian-volatile sarge/volatile-sloppy main contrib non-free&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;Etch&lt;br /&gt;&lt;pre&gt;deb http://volatile.debian.org/debian-volatile etch/volatile main contrib non-free&lt;br /&gt;deb http://volatile.debian.org/debian-volatile etch/volatile-sloppy main contrib non-free&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;Save sources.list and run _atp-get update_ which should generate something like this (your listing will vary depending on the repositories you have listed in your sources file):&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;# apt-get update&lt;br /&gt;Get:1 http://archive.ubuntu.com dapper Release.gpg [189B]&lt;br /&gt;Get:2 http://us.archive.ubuntu.com dapper Release.gpg [189B]                                                 &lt;br /&gt;Get:3 http://us.archive.ubuntu.com dapper-backports Release.gpg [191B]                                       &lt;br /&gt;Get:4 http://archive.ubuntu.com dapper-updates Release.gpg [191B]                                            &lt;br /&gt;Get:5 http://volatile.debian.org etch/volatile Release.gpg [189B]                                            &lt;br /&gt;Hit http://us.archive.ubuntu.com dapper Release                                 &lt;br /&gt;Hit http://archive.ubuntu.com dapper Release                                    &lt;br /&gt;Get:6 http://volatile.debian.org etch/volatile Release [40.7kB]                                                     &lt;br /&gt;Hit http://archive.ubuntu.com dapper-updates Release                                                                &lt;br /&gt;Hit http://us.archive.ubuntu.com dapper-backports Release                                                           &lt;br /&gt;Hit http://archive.ubuntu.com dapper/main Packages                                                                  &lt;br /&gt;Hit http://archive.ubuntu.com dapper/restricted Packages                                                    &lt;br /&gt;Hit http://us.archive.ubuntu.com dapper/universe Packages                                                   &lt;br /&gt;Hit http://us.archive.ubuntu.com dapper/universe Sources                                                    &lt;br /&gt;Hit http://archive.ubuntu.com dapper/main Sources                                                           &lt;br /&gt;Hit http://archive.ubuntu.com dapper/restricted Sources                               &lt;br /&gt;Hit http://archive.ubuntu.com dapper-updates/main Packages                            &lt;br /&gt;Hit http://archive.ubuntu.com dapper-updates/restricted Packages                                             &lt;br /&gt;Hit http://us.archive.ubuntu.com dapper-backports/main Packages                                              &lt;br /&gt;Hit http://us.archive.ubuntu.com dapper-backports/restricted Packages                                        &lt;br /&gt;Hit http://us.archive.ubuntu.com dapper-backports/universe Packages                                          &lt;br /&gt;Hit http://archive.ubuntu.com dapper-updates/main Sources                                                    &lt;br /&gt;Hit http://archive.ubuntu.com dapper-updates/restricted Sources                                              &lt;br /&gt;Hit http://us.archive.ubuntu.com dapper-backports/multiverse Packages                                        &lt;br /&gt;Hit http://us.archive.ubuntu.com dapper-backports/main Sources                         &lt;br /&gt;Hit http://us.archive.ubuntu.com dapper-backports/restricted Sources                   &lt;br /&gt;Hit http://us.archive.ubuntu.com dapper-backports/universe Sources                     &lt;br /&gt;Hit http://us.archive.ubuntu.com dapper-backports/multiverse Sources                   &lt;br /&gt;Ign http://volatile.debian.org etch/volatile Release                                   &lt;br /&gt;Get:7 http://volatile.debian.org etch/volatile/main Packages [3953B]&lt;br /&gt;Hit http://volatile.debian.org etch/volatile/contrib Packages                  &lt;br /&gt;Hit http://volatile.debian.org etch/volatile/non-free Packages&lt;br /&gt;Get:8 http://security.ubuntu.com dapper-security Release.gpg [191B]&lt;br /&gt;Hit http://security.ubuntu.com dapper-security Release&lt;br /&gt;Hit http://security.ubuntu.com dapper-security/main Packages&lt;br /&gt;Hit http://security.ubuntu.com dapper-security/restricted Packages&lt;br /&gt;Hit http://security.ubuntu.com dapper-security/main Sources&lt;br /&gt;Hit http://security.ubuntu.com dapper-security/restricted Sources&lt;br /&gt;Hit http://security.ubuntu.com dapper-security/universe Packages&lt;br /&gt;Hit http://security.ubuntu.com dapper-security/universe Sources&lt;br /&gt;Fetched 44.8kB in 5s (7554B/s)&lt;br /&gt;Reading package lists... Done&lt;br /&gt;W: GPG error: http://volatile.debian.org etch/volatile Release: The following signatures couldn't be verified because the public key is not available: NO_PUBKEY EC61E0B0BBE55AB3&lt;br /&gt;W: You may want to run apt-get update to correct these problems&lt;br /&gt;#&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;The inclusion of the debian-volatile release fails because we do not have a key to authenticate the repository. Adding the following will import their key (mentioned as EC61E0B0BBE55AB3 above) into your key ring:&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;# gpg --keyserver subkeys.pgp.net --recv-keys EC61E0B0BBE55AB3&lt;br /&gt;gpg: directory `/root/.gnupg' created&lt;br /&gt;gpg: new configuration file `/root/.gnupg/gpg.conf' created&lt;br /&gt;gpg: WARNING: options in `/root/.gnupg/gpg.conf' are not yet active during this run&lt;br /&gt;gpg: keyring `/root/.gnupg/secring.gpg' created&lt;br /&gt;gpg: keyring `/root/.gnupg/pubring.gpg' created&lt;br /&gt;gpg: requesting key BBE55AB3 from hkp server subkeys.pgp.net&lt;br /&gt;gpg: /root/.gnupg/trustdb.gpg: trustdb created&lt;br /&gt;gpg: key BBE55AB3: public key "Debian-Volatile Archive Automatic Signing Key (4.0/etch)" imported&lt;br /&gt;gpg: no ultimately trusted keys found&lt;br /&gt;gpg: Total number processed: 1&lt;br /&gt;gpg:               imported: 1&lt;br /&gt;# gpg --armor --export EC61E0B0BBE55AB3 | apt-key add -&lt;br /&gt;gpg: no ultimately trusted keys found&lt;br /&gt;OK&lt;br /&gt;#&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;Another spin of _apt-get update_ (and possibly _apt-get upgrade_ if their are any outdated packages) should then do the trick.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/8719896753162286533-8487203195502461639?l=blog.ntrippy.net' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://blog.ntrippy.net/feeds/8487203195502461639/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=8719896753162286533&amp;postID=8487203195502461639' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/8719896753162286533/posts/default/8487203195502461639'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/8719896753162286533/posts/default/8487203195502461639'/><link rel='alternate' type='text/html' href='http://blog.ntrippy.net/2007/05/tracking-fast-paced-packages-on-debian.html' title='Tracking fast-paced packages on debian based systems (aka  debian-volatile project)'/><author><name>Charl</name><uri>http://www.blogger.com/profile/17742115469030093068</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='26' src='http://3.bp.blogspot.com/_4MLU7uPNEIY/TP2zTauOWAI/AAAAAAAAABs/OXj8i31AC0E/S220/blue%2B69.jpg'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-8719896753162286533.post-839002384103620588</id><published>2007-07-24T11:00:00.000-07:00</published><updated>2007-12-26T13:25:58.043-08:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='Wirble'/><category scheme='http://www.blogger.com/atom/ns#' term='ruby'/><category scheme='http://www.blogger.com/atom/ns#' term='.irbrc'/><category scheme='http://www.blogger.com/atom/ns#' term='GuessMethod'/><category scheme='http://www.blogger.com/atom/ns#' term='gem'/><category scheme='http://www.blogger.com/atom/ns#' term='irb'/><title type='text'>Links</title><content type='html'>&lt;b&gt;IRB&lt;/b&gt;&lt;br /&gt;&lt;a href="http://drnicwilliams.com/about/"&gt;Dr Nic&lt;/a&gt; has a great little &lt;a href="http://drnicwilliams.com/2006/10/12/my-irbrc-for-consoleirb/"&gt;article&lt;/a&gt; describing his favourite additions to his .irbrc. He covers some common productivity wins such as:&lt;br /&gt; &lt;ul&gt;&lt;li&gt;TABed auto-completion&lt;/li&gt; &lt;li&gt;Map by method which allows you to get rid of constructs like articles.columns.map {|p| p.name} or articles.columns.map &amp;amp;:name and simply replace it with a plural: articles.columns.names or articles.columns.name&lt;/li&gt; &lt;li&gt;MethodFinder/Object.what?&lt;/li&gt; &lt;li&gt;pp&lt;/li&gt; &lt;li&gt;Auto-tabbing &lt;/li&gt;&lt;/ul&gt;&lt;b&gt;GuessMethod&lt;/b&gt;&lt;br /&gt;This little &lt;a href="http://ruby.tie-rack.org/9/guessmethod-002/"&gt;gem&lt;/a&gt; is the &lt;a href="http://ruby.tie-rack.org/8/an-aggressive-spellchecker-for-method-calls/"&gt;best bad idea&lt;/a&gt; ever! Gone are those frustrating typos that waste extra cycles finding and fixing them.&lt;br /&gt;&lt;br /&gt;&lt;b&gt;Wirble&lt;/b&gt;&lt;br /&gt;Continuing on the irb enhancements theme do yourself a favour and have a look at &lt;a href="http://pablotron.org/software/wirble/"&gt;Wirble&lt;/a&gt;. It offers you tab-completion, history, and a built-in ri command as well as colorised results and a couple &lt;a href="http://pablotron.org/software/wirble/README"&gt;other&lt;/a&gt; goodies.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/8719896753162286533-839002384103620588?l=blog.ntrippy.net' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://blog.ntrippy.net/feeds/839002384103620588/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=8719896753162286533&amp;postID=839002384103620588' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/8719896753162286533/posts/default/839002384103620588'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/8719896753162286533/posts/default/839002384103620588'/><link rel='alternate' type='text/html' href='http://blog.ntrippy.net/2007/07/links_24.html' title='Links'/><author><name>Charl</name><uri>http://www.blogger.com/profile/17742115469030093068</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='26' src='http://3.bp.blogspot.com/_4MLU7uPNEIY/TP2zTauOWAI/AAAAAAAAABs/OXj8i31AC0E/S220/blue%2B69.jpg'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-8719896753162286533.post-2374722515479089472</id><published>2007-07-17T13:49:00.000-07:00</published><updated>2007-12-26T13:12:13.490-08:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='ruby'/><category scheme='http://www.blogger.com/atom/ns#' term='AR-Delegation'/><category scheme='http://www.blogger.com/atom/ns#' term='Exception Notifier'/><category scheme='http://www.blogger.com/atom/ns#' term='rails'/><category scheme='http://www.blogger.com/atom/ns#' term='Sequel'/><title type='text'>Links</title><content type='html'>&lt;b&gt;Sequel&lt;/b&gt;&lt;br /&gt;&lt;a href="http://sequel.rubyforge.org/"&gt;Sequel&lt;/a&gt; is a light-weight ORM that fills in the gaps where using &lt;a href="http://ar.rubyonrails.org/"&gt;ActiveRecord&lt;/a&gt; without rails doesn't fit your needs.&lt;br /&gt;&lt;br /&gt;&lt;a href="http://www.oreillynet.com/pub/au/2593"&gt;Gregory Brown&lt;/a&gt; has a nice little expose on it as the winner of the &lt;a href="http://www.oreillynet.com/ruby/blog/2007/07/ruby_project_spotlight_june_07.html"&gt;June 2007 Ruby Project Spotlight&lt;/a&gt;.&lt;br /&gt;&lt;br /&gt;&lt;b&gt;Introduction to .NET 3.0 for Architects&lt;/b&gt;&lt;br /&gt;Keep your friends close and competition^H^H^H^H^H^H^H^H^H^H alternative platforms closer. &lt;a href="http://www.infoq.com/"&gt;InfoQ&lt;/a&gt; has a &lt;a href="http://www.infoq.com/articles/akif-dotnet-architect"&gt;great&lt;/a&gt; 50000 foot look at .NET 3.0.&lt;br /&gt;&lt;br /&gt;&lt;b&gt;Profiling your ails app with ruby-prof&lt;/b&gt;&lt;br /&gt;&lt;a href="http://cfis.savagexi.com/"&gt;Charlie Savage&lt;/a&gt; wrote a great article on doing some profiling on your rails app using &lt;a href="http://ruby-prof.rubyforge.org/"&gt;ruby-prof&lt;/a&gt;. He discusses using both flat and graph (with associated call tree information) profiles to nuke performance hogs.&lt;br /&gt;&lt;br /&gt;ruby-prof is a fast code profiler for Ruby. Its features include:&lt;br /&gt;   &lt;ul&gt;&lt;li&gt;Speed - it is a C extension and therefore many times faster than the standard Ruby profiler.&lt;/li&gt;    &lt;li&gt;Flat Profiles - similar to the reports generated by the standard Ruby profiler&lt;/li&gt;    &lt;li&gt;Graph profiles - similar to GProf, these show how long a method runs, which methods call it and which methods it calls&lt;/li&gt;    &lt;li&gt;Threads - supports profiling multiple threads simultaneously&lt;/li&gt;    &lt;li&gt;Recursive calls - supports profiling recursive method calls&lt;/li&gt;    &lt;li&gt;Reports - can generate both text and cross-referenced html reports&lt;/li&gt;    &lt;li&gt;Output - can output to standard out or to a file&lt;/li&gt;&lt;/ul&gt;&lt;br /&gt;&lt;b&gt;AR-Delegation&lt;/b&gt;&lt;br /&gt;&lt;a href="http://agilewebdevelopment.com/plugins/ar_delegation"&gt;This&lt;/a&gt; plugin extends ActiveRecord::Base to add useful delegation features. For example: has_columns :from =&gt; :source, :only =&gt; ["title", "name"] has_column "title", :from =&gt; :source, :as =&gt; "source_title".&lt;br /&gt;&lt;br /&gt;It really improves the conciseness of your code but in so doing hides your implementation adding a layer of indirection that may make your code a little more difficult to understand if the person reading your code does not know that AR-Delegation was used.&lt;br /&gt;&lt;br /&gt;&lt;b&gt;Exception Notifier&lt;/b&gt;&lt;br /&gt;I use &lt;a href="http://agilewebdevelopment.com/plugins/exception_notifier"&gt;this&lt;/a&gt; with most of my projects that reside on remote customer networks where the only way for the application to give me a heads up is if it sends me an email with an attached problem report.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/8719896753162286533-2374722515479089472?l=blog.ntrippy.net' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://blog.ntrippy.net/feeds/2374722515479089472/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=8719896753162286533&amp;postID=2374722515479089472' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/8719896753162286533/posts/default/2374722515479089472'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/8719896753162286533/posts/default/2374722515479089472'/><link rel='alternate' type='text/html' href='http://blog.ntrippy.net/2007/07/links.html' title='Links'/><author><name>Charl</name><uri>http://www.blogger.com/profile/17742115469030093068</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='26' src='http://3.bp.blogspot.com/_4MLU7uPNEIY/TP2zTauOWAI/AAAAAAAAABs/OXj8i31AC0E/S220/blue%2B69.jpg'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-8719896753162286533.post-833730926942236498</id><published>2007-05-19T08:24:00.000-07:00</published><updated>2007-12-25T00:50:56.667-08:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='lambda'/><category scheme='http://www.blogger.com/atom/ns#' term='lambda calculus'/><category scheme='http://www.blogger.com/atom/ns#' term='unlambda'/><title type='text'>The Journey from Lambda to Unlambda</title><content type='html'>While scrounging around the Net looking at functional programming languages I came across &lt;a href="http://www.madore.org/%7Edavid/programs/unlambda/"&gt;Unlambda&lt;/a&gt;. The name by itself piqued my interest so I though I'd go have a look-see.&lt;br /&gt;&lt;br /&gt;Here follows the troubled tale of the journey from &lt;a href="http://en.wikipedia.org/wiki/Lambda_calculus#Lambda_calculus_and_programming_languages"&gt;Lambda&lt;/a&gt; to Unlambda ...&lt;br /&gt;&lt;a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="http://4.bp.blogspot.com/_4MLU7uPNEIY/R3DDx33q6rI/AAAAAAAAAAk/5jpg6r-k9Js/s1600-h/lambda.png"&gt;&lt;img style="margin: 0px auto 10px; display: block; text-align: center; cursor: pointer;" src="http://4.bp.blogspot.com/_4MLU7uPNEIY/R3DDx33q6rI/AAAAAAAAAAk/5jpg6r-k9Js/s320/lambda.png" alt="" id="BLOGGER_PHOTO_ID_5147829635662932658" border="0" /&gt;&lt;/a&gt;&lt;b&gt;Nature of the Beast&lt;/b&gt;&lt;br /&gt;It was created by &lt;a href="http://en.wikipedia.org/wiki/David_Madore"&gt;David Madore&lt;/a&gt; and is a minimal functional programming language that was specifically built to make programming in it obtuse (or as the author's site refers to it as: "... fun and challenging"). It is based on combinatory logic but omits the lambda abstraction forcing you to rely on K and S combinators.&lt;br /&gt;&lt;br /&gt;Beside hailing itself as a functional language it also succeeds in being an &lt;a href="http://www.madore.org/%7Edavid/programs/unlambda/#links-obf"&gt;obfuscated programming language&lt;/a&gt; through severely restricting the set of allowed operations in the language and making it generally alien to programmers from more conventional languages.&lt;br /&gt;&lt;br /&gt;It &lt;underline&gt;strictly&lt;/underline&gt; only manipulates functions. A function is the only function parameter that you can pass in to a function and a function is the only construct that can be returned from a function. It relies heavily on its built-in k and s functions (K and S combinators) to get anything done.&lt;br /&gt;&lt;br /&gt;The source is built to be intentionally incomprehensible for a human making it next to impossible to deduce what the intention of the program is by simply reading the source.&lt;br /&gt;&lt;br /&gt;You are welcome to create your own functions, but be warned, you cannot name or save the custom function(s) because Unlambda does not have support for variables. Besides dispelling variables from its cloth you will also notice the lack of built-in support for data structures or code constructs (e.g. loops, conditionals, etc.). You are however welcome to build your own code constructs.&lt;br /&gt;&lt;br /&gt;As an illustration, here is a loop that prints “Hello, world!” repeatedly, followed by an incrementing number of asterisks (an explanation of this severely limited subset of alphabet soup can be found &lt;a href="http://www.madore.org/%7Edavid/programs/unlambda/#howto_loop"&gt;here&lt;/a&gt;):&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;```s``sii`ki&lt;br /&gt;``s``s`ks&lt;br /&gt;   ``s``s`ks``s`k`s`kr&lt;br /&gt;             ``s`k`si``s`k`s`k&lt;br /&gt;                             `d````````````.H.e.l.l.o.,. .w.o.r.l.d.!&lt;br /&gt;                      k&lt;br /&gt;    k&lt;br /&gt;`k``s``s`ksk`k.*&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;Unlambda is the little functional language that could however and in the face of all its consciously implemented obscurity it still manages to be &lt;a href="http://en.wikipedia.org/wiki/Turing-complete"&gt;Turing-complete&lt;/a&gt;.&lt;br /&gt;&lt;br /&gt;&lt;b&gt;Sure, but can it speak?&lt;/b&gt;&lt;br /&gt;Here's the Unlambda equivalent for the trusty old "Hello world" we're used to:&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;`r```````````.H.e.l.l.o. .w.o.r.l.di&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;More examples, a &lt;a href="http://www.madore.org/%7Edavid/programs/unlambda/#tut"&gt;tutorial&lt;/a&gt; and some &lt;a href="http://www.madore.org/%7Edavid/programs/unlambda/#howto"&gt;HOWTOs&lt;/a&gt; can be found at David's site for &lt;a href="http://www.madore.org/%7Edavid/programs/unlambda/"&gt;Unlambda&lt;/a&gt;.&lt;br /&gt;&lt;br /&gt;&lt;b&gt;Secret Murmurings on Unlambda&lt;/b&gt;&lt;br /&gt;&lt;ul&gt;&lt;br /&gt;  &lt;li&gt;“It's disgusting — it's revolting — we love it.” CyberTabloid&lt;/li&gt;&lt;br /&gt;  &lt;li&gt;“Unlambda, the language in which every program is an IOUCC.” Encyclopædia Internetica&lt;/li&gt;&lt;br /&gt;  &lt;li&gt;“The worst thing to befall us since Intercal.” Computer Languages Today&lt;/li&gt;&lt;br /&gt;  &lt;li&gt;“The effect of reading an Unlambda program is like having your brains smashed out by a Lisp sexp wrapped around an ENIAC. You won't find anything like it west of Alpha Centauri.” The Hitch-Hacker's Guide to Programming&lt;/li&gt;&lt;br /&gt;&lt;/ul&gt;&lt;br /&gt;&lt;br /&gt;&lt;b&gt;Epitaph&lt;/b&gt;&lt;br /&gt;Unlambda is inevitably compared to &lt;a href="http://en.wikipedia.org/wiki/Intercal"&gt;Intercal&lt;/a&gt;, but unlike Intercal it has a kind of weird elegance; this is because Intercal gets in your way, but Unlambda simply fails to help you."&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/8719896753162286533-833730926942236498?l=blog.ntrippy.net' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://blog.ntrippy.net/feeds/833730926942236498/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=8719896753162286533&amp;postID=833730926942236498' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/8719896753162286533/posts/default/833730926942236498'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/8719896753162286533/posts/default/833730926942236498'/><link rel='alternate' type='text/html' href='http://blog.ntrippy.net/2007/05/journey-from-lambda-to-unlambda.html' title='The Journey from Lambda to Unlambda'/><author><name>Charl</name><uri>http://www.blogger.com/profile/17742115469030093068</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='26' src='http://3.bp.blogspot.com/_4MLU7uPNEIY/TP2zTauOWAI/AAAAAAAAABs/OXj8i31AC0E/S220/blue%2B69.jpg'/></author><media:thumbnail xmlns:media='http://search.yahoo.com/mrss/' url='http://4.bp.blogspot.com/_4MLU7uPNEIY/R3DDx33q6rI/AAAAAAAAAAk/5jpg6r-k9Js/s72-c/lambda.png' height='72' width='72'/><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-8719896753162286533.post-5917302373587393466</id><published>2007-05-15T18:02:00.000-07:00</published><updated>2007-12-25T00:34:50.478-08:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='os x'/><title type='text'>OS X File Name Subterfuge</title><content type='html'>While saving a file via the well known &lt;a href="http://www.apple.com/macosx/"&gt;OS X&lt;/a&gt; file Save As dialog I noticed something really queer. The filename I had pasted in was magically altered to adhere to OS X's ideas on which characters are kosher for file/directory names!&lt;br /&gt;&lt;br /&gt;&lt;b&gt;Silent Substitution&lt;/b&gt;&lt;br /&gt;To see the magic simply open TextEdit, create a new document (if you are not already presented with one) and add the following text to the document:&lt;br /&gt;&lt;pre&gt;&lt;br /&gt; watch: this: space:&lt;/pre&gt;&lt;br /&gt;&lt;a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="http://4.bp.blogspot.com/_4MLU7uPNEIY/R3C_r33q6pI/AAAAAAAAAAU/DKhq8k8KbgE/s1600-h/textedit.png"&gt;&lt;img style="margin: 0px auto 10px; display: block; text-align: center; cursor: pointer;" src="http://4.bp.blogspot.com/_4MLU7uPNEIY/R3C_r33q6pI/AAAAAAAAAAU/DKhq8k8KbgE/s320/textedit.png" alt="" id="BLOGGER_PHOTO_ID_5147825134537206418" border="0" /&gt;&lt;/a&gt;&lt;br /&gt;Now, select the text, copy it and save the file with the selected text as the file name.&lt;br /&gt;&lt;br /&gt;&lt;pre&gt;&lt;a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="http://1.bp.blogspot.com/_4MLU7uPNEIY/R3DAFH3q6qI/AAAAAAAAAAc/cLbzpcn2x2w/s1600-h/save-dialog.png"&gt;&lt;img style="margin: 0px auto 10px; display: block; text-align: center; cursor: pointer;" src="http://1.bp.blogspot.com/_4MLU7uPNEIY/R3DAFH3q6qI/AAAAAAAAAAc/cLbzpcn2x2w/s320/save-dialog.png" alt="" id="BLOGGER_PHOTO_ID_5147825568328903330" border="0" /&gt;&lt;/a&gt;&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;Et voilà!&lt;br /&gt;&lt;br /&gt;&lt;b&gt;Subterfuge Uncorked&lt;/b&gt;&lt;br /&gt;The dialog magically converts the colons to hyphens (-) to ensure that you don't have to go through he whole 'This filename is not allowed' error cycle needlessly. Brill.&lt;br /&gt;&lt;br /&gt;The reason for the substitution is that OS X prohibits the use of colon characters in file/directory names because this character is used to represent a directory in the  &lt;a href="http://en.wikipedia.org/wiki/HFS_Plus"&gt;HFS+&lt;/a&gt; file system.&lt;br /&gt;&lt;br /&gt;According to the HFS+ spec you can use any &lt;a href="http://en.wikipedia.org/wiki/Unicode"&gt;Unicode&lt;/a&gt; or &lt;a href="http://en.wikipedia.org/wiki/ASCII"&gt;ASCII&lt;/a&gt; (including &lt;a href="http://en.wikipedia.org/wiki/ASCII_code#ASCII_control_characters"&gt;NUL&lt;/a&gt;) characters. OS APIs may limit some of these characters for legacy reasons.&lt;br /&gt;&lt;br /&gt;I love subtlety.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/8719896753162286533-5917302373587393466?l=blog.ntrippy.net' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://blog.ntrippy.net/feeds/5917302373587393466/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=8719896753162286533&amp;postID=5917302373587393466' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/8719896753162286533/posts/default/5917302373587393466'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/8719896753162286533/posts/default/5917302373587393466'/><link rel='alternate' type='text/html' href='http://blog.ntrippy.net/2007/05/os-x-file-name-subterfuge.html' title='OS X File Name Subterfuge'/><author><name>Charl</name><uri>http://www.blogger.com/profile/17742115469030093068</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='26' src='http://3.bp.blogspot.com/_4MLU7uPNEIY/TP2zTauOWAI/AAAAAAAAABs/OXj8i31AC0E/S220/blue%2B69.jpg'/></author><media:thumbnail xmlns:media='http://search.yahoo.com/mrss/' url='http://4.bp.blogspot.com/_4MLU7uPNEIY/R3C_r33q6pI/AAAAAAAAAAU/DKhq8k8KbgE/s72-c/textedit.png' height='72' width='72'/><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-8719896753162286533.post-1044224747329079621</id><published>2007-05-15T17:23:00.000-07:00</published><updated>2007-12-27T20:52:18.289-08:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='ruby'/><category scheme='http://www.blogger.com/atom/ns#' term='Auto Vivification'/><category scheme='http://www.blogger.com/atom/ns#' term='hash'/><title type='text'>Dynamic Arbitrary Depth Hashes In Ruby</title><content type='html'>&lt;span style="font-weight: bold;"&gt;UPDATE: &lt;/span&gt; &lt;a href="http://www.eecs.harvard.edu/%7Ecduan/technical/ruby/ycombinator.shtml" title="A Use of the Y Combinator in Ruby"&gt;Charles Duan&lt;/a&gt; has an interesting article in a similar vein.&lt;br /&gt;&lt;br /&gt;Arbitrary array and hash depth constructs cannot be created in Ruby in the way you would in Perl or PHP. The following will simply fail with an error:&lt;br /&gt;&lt;pre&gt;&lt;span style="font-size:85%;"&gt;&lt;span style="font-family:courier new;"&gt;&lt;br /&gt;irb(main):001:0&amp;gt; a = []&lt;br /&gt;=&amp;gt; []&lt;br /&gt;irb(main):002:0&amp;gt; a[1][2][3][4] = 1&lt;br /&gt;NoMethodError: undefined method `[]' for nil:NilClass&lt;br /&gt;      from (irb):2&lt;br /&gt;irb(main):003:0&amp;gt; h = {}&lt;br /&gt;=&amp;gt; {}&lt;br /&gt;irb(main):004:0&amp;gt; h[1][2][3][4] = 5&lt;br /&gt;NoMethodError: undefined method `[]' for nil:NilClass&lt;br /&gt;      from (irb):4&lt;br /&gt;&lt;/span&gt;&lt;/span&gt;&lt;/pre&gt;&lt;br /&gt;When dynamically constructing your array or hash (aka &lt;a href="http://en.wikipedia.org/wiki/Autovivification"&gt;Autovivification&lt;/a&gt;) this really gets in the way.&lt;br /&gt;&lt;br /&gt;&lt;b&gt;Autovivification&lt;/b&gt;&lt;br /&gt;This is a dynamic data structure creation feature that can be found in Perl and PHP (those are the ones I know of). It allows you to create dynamic, complex, nested data structures  based on the types implied in the syntax of the statement of code accessed through the data structure.&lt;br /&gt;&lt;br /&gt;IOW, the act of fetching or storing a value at a leaf through a branch dynamically creates the branch(es) to the leaf.&lt;br /&gt;&lt;br /&gt;&lt;b&gt;VivifiedHash&lt;/b&gt;&lt;br /&gt;One approach is to do the following:&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;  irb(main):011:0* VivifiedHash = Hash.new(&amp;amp;(p=lambda{|h,k| h[k] = Hash.new(&amp;amp;p)}))&lt;br /&gt;  =&gt; {}&lt;br /&gt;  irb(main):012:0&gt; VivifiedHash[1][2][3][4] = 5&lt;br /&gt;  =&gt; 5&lt;br /&gt;  irb(main):013:0&gt; VivifiedHash[1][2][3][4]&lt;br /&gt;  =&gt; 5&lt;br /&gt;  irb(main):014:0&gt; VivifiedHash[1][2][3]&lt;br /&gt;  =&gt; {4=&gt;5}&lt;br /&gt;  irb(main):015:0&gt; VivifiedHash[1][2]&lt;br /&gt;  =&gt; {3=&gt;{4=&gt;5}}&lt;br /&gt;  irb(main):016:0&gt; VivifiedHash[1]&lt;br /&gt;  =&gt; {2=&gt;{3=&gt;{4=&gt;5}}}&lt;br /&gt;  irb(main):017:0&gt; VivifiedHash&lt;br /&gt;  =&gt; {1=&gt;{2=&gt;{3=&gt;{4=&gt;5}}}}&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;All this does is recursively assign the default key of the hash a new hash object as value. Each branch you specify in your assignment will recursively trigger the creation of a new hash.&lt;br /&gt;&lt;br /&gt;&lt;b&gt;Limitations&lt;/b&gt;&lt;br /&gt;The limitation on this are of course that your data structure cannot contain anything but hashes as branches. Leaf nodes can be any data type though.&lt;br /&gt;&lt;br /&gt;&lt;b&gt;Sources&lt;/b&gt;&lt;br /&gt;&lt;ol&gt;&lt;br /&gt;  &lt;li&gt;&lt;a href="http://blog.inquirylabs.com/2006/09/20/ruby-hashes-of-arbitrary-depth/"&gt;Ruby Hashes of Arbitrary Depth&lt;/a&gt;&lt;/li&gt;&lt;br /&gt;  &lt;li&gt;&lt;a href="http://blade.nagaokaut.ac.jp/cgi-bin/scat.rb/ruby/ruby-talk/13397"&gt;Multidimensional arrays and hashes discussion on the RubyTalk mailing&lt;/a&gt;&lt;/li&gt;&lt;br /&gt;  &lt;li&gt;&lt;a href="http://www.c2.com/cgi/wiki?AutoVivification"&gt;Auto Vivification&lt;/a&gt;&lt;/li&gt;&lt;br /&gt;&lt;/ol&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/8719896753162286533-1044224747329079621?l=blog.ntrippy.net' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://blog.ntrippy.net/feeds/1044224747329079621/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=8719896753162286533&amp;postID=1044224747329079621' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/8719896753162286533/posts/default/1044224747329079621'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/8719896753162286533/posts/default/1044224747329079621'/><link rel='alternate' type='text/html' href='http://blog.ntrippy.net/2007/05/dynamic-arbitrary-depth-hashes-in-ruby.html' title='Dynamic Arbitrary Depth Hashes In Ruby'/><author><name>Charl</name><uri>http://www.blogger.com/profile/17742115469030093068</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='26' src='http://3.bp.blogspot.com/_4MLU7uPNEIY/TP2zTauOWAI/AAAAAAAAABs/OXj8i31AC0E/S220/blue%2B69.jpg'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-8719896753162286533.post-8731756851717240745</id><published>2007-05-07T18:56:00.000-07:00</published><updated>2007-12-24T23:45:22.118-08:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='ruby'/><category scheme='http://www.blogger.com/atom/ns#' term='WWW:Mechanize'/><category scheme='http://www.blogger.com/atom/ns#' term='screen scrape'/><title type='text'>Mechanized Scraping</title><content type='html'>Ever needed to interface with a web application without any real APIs? Take one step back from looking for a traditional API and use &lt;a href="http://mechanize.rubyforge.org/mechanize/"&gt;WWW::Mechanize&lt;/a&gt; to bend the application to your will.&lt;br /&gt;&lt;br /&gt;WWW:Mechanize (inspired by "Andy Lester's":mailto://andy@petdance.com perl &lt;a href="http://search.cpan.org/~petdance/WWW-Mechanize-1.20/"&gt;Mechanize&lt;/a&gt; module and written by &lt;a href="http://tenderlovemaking.com/category/mechanize/"&gt;Aaron Patterson&lt;/a&gt;) allows you to moonlight as a web User Agent (browser) from the comfort of your ruby scripting environment. It is great for building automated tests of your web applications, creating your favourite mashups and also to treat another web application's UI as the API to the application.&lt;br /&gt;&lt;br /&gt;I've been working on some code that needs to gather reporting information from our billing system but I have no real access to the Oracle db in the back to get to the require stored procedures. So, I decided to simply use the UI as my API to the data and dusted my trusty old  WWW:Mechanize (which uses Hpricot internally to parse and tokenise pages) off for the challenge.&lt;br /&gt;&lt;br /&gt;It provides you with all the required tools to log in to a site (as well as automatic cookie handling), click on URI, submit forms and oh so much more. The only real feature currently lacking is support for JavaScript (they do however provide you with ideas on how to manoeuvre around some of the more mundane corers) which is becoming more and more painful in this Web2.0 world of ours.&lt;br /&gt;&lt;br /&gt;WWW:Mechanize is quite easy to use so I am not going to write an exposé on the in's and out's of the lib or share with you its secrets that helped me to sate world hunger and bring peace to all. Instead, I will mention some of the bits that tripped me up while trying to make the web application dance to my flute.&lt;br /&gt;&lt;br /&gt;&lt;b&gt;Button Value Attributes&lt;/b&gt;&lt;br /&gt;I was getting nowhere while trying to submit a form in the web application with some crafted values. Tinker here, tinker there and still no go. Try a browser and the application itself and things work like swiss &lt;strike&gt;cheese&lt;/strike&gt;watches.&lt;br /&gt;&lt;br /&gt;Right you mangy ASP application, its time for the big guns! Out comes &lt;a href="http://www.wireshark.org"&gt;Wireshark&lt;/a&gt; and the debugging starts in earnest. First I dump a session from my script and then one from a browser.&lt;br /&gt;&lt;br /&gt;From the diff of of the POST request I notice that the browser has the value attribute for the 'Save' button in the form set whereas I didn't. Because the form was posting back to itself I assume they had some code like (pseudocode):&lt;br /&gt;&lt;pre&gt;&lt;span style="font-size:85%;"&gt;&lt;span style="font-family:courier new;"&gt;&lt;br /&gt;if $submit == &amp;apos;Submit&amp;apos;&lt;br /&gt;then&lt;br /&gt;    do your stuff when the form has been submitted&lt;br /&gt;else&lt;br /&gt;    display the normal form&lt;br /&gt;end&lt;br /&gt;&lt;/span&gt;&lt;/span&gt;&lt;/pre&gt;&lt;br /&gt;Adding something that resembles the following did the trick:&lt;br /&gt;&lt;pre&gt;&lt;span style="font-size:85%;"&gt;&lt;span style="font-family:courier new;"&gt;&lt;br /&gt;form.buttons.name(&amp;apos;some_convoluted_button_name&amp;apos;).value = &amp;apos;Submit&amp;apos;&lt;br /&gt;&lt;/span&gt;&lt;/span&gt;&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;&lt;b&gt;Out of Buffer Error&lt;/b&gt;&lt;br /&gt;A few more form hoops later and I started getting an error like:&lt;br /&gt;&lt;pre&gt;&lt;span style="font-size:85%;"&gt;&lt;span style="font-family:courier new;"&gt;&lt;br /&gt;hpricot/parse.rb:44:in `scan&amp;apos;: ran out of buffer space on element &amp;lt;group&amp;gt;, starting on line 361. (Hpricot::ParseError?)&lt;br /&gt;&lt;/span&gt;&lt;/span&gt;&lt;/pre&gt;&lt;br /&gt;Hey?!&lt;br /&gt;&lt;br /&gt;A quick look on the bug db for WWW:Mechanize on &lt;a href="http://www.rubyforge.org"&gt;RubyForge&lt;/a&gt; listed &lt;a href="http://rubyforge.org/tracker/?group_id=1453&amp;amp;atid=5709&amp;amp;func=detail&amp;amp;aid=6131"&gt;this&lt;/a&gt; closed bug that has some application to our situation. The error messages are not the same (I assume this is the case due to an earlier version of Hpricot that was used when this was reported).&lt;br /&gt;&lt;br /&gt;According to this &lt;a href="http://rubyforge.org/tracker/?group_id=1453&amp;amp;atid=5709&amp;amp;func=detail&amp;amp;aid=6131"&gt;TT&lt;/a&gt; it is a Hrpicot issue and refers to this &lt;a href="http://code.whytheluckystiff.net/hpricot/ticket/13"&gt;TT&lt;/a&gt;.&lt;br /&gt;&lt;br /&gt;According to the problem description:&lt;br /&gt;&lt;pre&gt;&lt;span style="font-size:85%;"&gt;&lt;span style="font-family:courier new;"&gt;&lt;br /&gt;An &amp;apos;OUT OF BUFFER SPACE&amp;apos; error shuts down my whole app when I try to parse through an aspx page with an abnormally (or normally?) large viewstate stuffed into an input. Here&amp;apos;s what it looks like:&lt;br /&gt;&lt;br /&gt;    &amp;lt;input type=&amp;quot;hidden&amp;quot; name=&amp;quot;__VIEWSTATE&amp;quot;&lt;br /&gt;        value=&amp;quot;dDw3NzQ0ODQ2ODQ ... 11954 characters in total ... DsXdJfP+k&amp;quot; /&amp;gt;&lt;br /&gt;&lt;br /&gt;If I remove the large value it works fine. Is there a way hpricot could not exit when trying to parse a page like this?&lt;br /&gt;&lt;/span&gt;&lt;/span&gt;&lt;/pre&gt;&lt;br /&gt;DING! DING DING!&lt;br /&gt;&lt;br /&gt;I am also scraping an ASP application and lo and behold I too have a ginormous __VIEWSTATE input tag in the page in question. I knew ASP was evil, but this?!&lt;br /&gt;&lt;br /&gt;The limit on the buffer was of course a protection mechanism to ensure that a parsed page does not cause your computer to become the black hole of memory. The workaround for this is quite simple though, just increase the buffer&lt;br /&gt;&lt;pre&gt;&lt;span style="font-size:85%;"&gt;&lt;span style="font-family:courier new;"&gt;&lt;br /&gt;Okay, kids. [98] now has a buffer_size method.&lt;br /&gt;    Hpricot.buffer_size = 262144&lt;br /&gt;    doc = Hpricot(open(&amp;quot;http://asp.net/big-viewstate-vomit.html&amp;quot;))&lt;br /&gt;&lt;br /&gt;Perhaps I will find the wherewithal to fix the parser to read these massive attributes, but on-the-other-hand I don&amp;apos;t want to encourage this disastrous behavior by ASP.NET!! You know?&lt;br /&gt;&lt;/span&gt;&lt;/span&gt;&lt;/pre&gt;&lt;br /&gt;"That's all good and well but we're not really using Hpricot directly, we're using WWW:Mechanize!", you all shout in unison.&lt;br /&gt;&lt;br /&gt;True, true. All you do is simply add the buffer_size declaration after instantiating your shiny new WWW:Mechanize object like so:&lt;br /&gt;&lt;pre&gt;&lt;span style="font-size:85%;"&gt;&lt;span style="font-family:courier new;"&gt;&lt;br /&gt;agent = WWW::Mechanize.new&lt;br /&gt;Hpricot.buffer_size = 204800&lt;br /&gt;&lt;/span&gt;&lt;/span&gt;&lt;/pre&gt;&lt;br /&gt;The default buffer size is defined in hpricot_scan.rl as:&lt;br /&gt;&lt;pre&gt;&lt;span style="font-size:85%;"&gt;&lt;span style="font-family:courier new;"&gt;&lt;br /&gt;[...]&lt;br /&gt;&lt;br /&gt;#define BUFSIZE 16384&lt;br /&gt;&lt;br /&gt;[...]&lt;br /&gt;&lt;br /&gt;buffer_size = BUFSIZE;&lt;br /&gt;if (rb_ivar_defined(self, rb_intern(&amp;quot;@buffer_size&amp;quot;)) == Qtrue) {&lt;br /&gt;  bufsize = rb_ivar_get(self, rb_intern(&amp;quot;@buffer_size&amp;quot;));&lt;br /&gt;  if (!NIL_P(bufsize)) {&lt;br /&gt;    buffer_size = NUM2INT(bufsize);&lt;br /&gt;  }&lt;br /&gt;}&lt;br /&gt;buf = ALLOC_N(char, buffer_size);&lt;br /&gt;&lt;br /&gt;[...]&lt;br /&gt;&lt;/span&gt;&lt;/span&gt;&lt;/pre&gt;&lt;br /&gt;That's a buffer of about 16KB for an attribute which under &lt;i&gt;normal&lt;/i&gt; circumstances would be more than ample space for an attribute but working with ASP seems to be anything &lt;i&gt;but&lt;/i&gt; normal.&lt;br /&gt;&lt;br /&gt;&lt;b&gt;In Closing&lt;/b&gt;&lt;br /&gt;I have not had as much fun in quite some time. WWW:Mechanize had me clapping my little hands in glee while shouting "Wheeeeeeeeeee!" like a little kid that was given his first bunny rabbit just after having his second double espresso for the hour.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/8719896753162286533-8731756851717240745?l=blog.ntrippy.net' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://blog.ntrippy.net/feeds/8731756851717240745/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=8719896753162286533&amp;postID=8731756851717240745' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/8719896753162286533/posts/default/8731756851717240745'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/8719896753162286533/posts/default/8731756851717240745'/><link rel='alternate' type='text/html' href='http://blog.ntrippy.net/2007/05/mechanized-scraping.html' title='Mechanized Scraping'/><author><name>Charl</name><uri>http://www.blogger.com/profile/17742115469030093068</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='26' src='http://3.bp.blogspot.com/_4MLU7uPNEIY/TP2zTauOWAI/AAAAAAAAABs/OXj8i31AC0E/S220/blue%2B69.jpg'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-8719896753162286533.post-4914148210423964498</id><published>2007-05-02T22:25:00.000-07:00</published><updated>2007-12-24T23:41:48.064-08:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='ruby'/><category scheme='http://www.blogger.com/atom/ns#' term='HPricot'/><title type='text'>Ruby (Hpricot) Program Guide - III</title><content type='html'>As discussed in the &lt;a href="http://blog.ntrippy.net/2007/4/30/ruby-hpricot-program-guide-ii"&gt;previous&lt;/a&gt; article our next steps will be to refactor the constructor and provide an example of how we can use objects from the DSTVSchedule class to collect and display channels of our choice.&lt;br /&gt;&lt;br /&gt;Let's change the constructor to take the channel ID, time offset (to account for different time zones) and the period ahead in time for which we want to gather schedule information as parameters. This will mean that we get rid of the custom hash class and tidy things up a little bit:&lt;br /&gt;&lt;pre&gt;&lt;span style="font-size:85%;"&gt;&lt;span style="font-family:courier new;"&gt;&lt;br /&gt;def initialize(channel=219, offset=2, period=30)&lt;br /&gt;  start_date, end_date = get_search_dates(period)&lt;br /&gt;  url = build_url(build_query_string(channel, start_date ,end_date))&lt;br /&gt;&lt;br /&gt;  p &amp;quot;Start: #{start_date}  End: #{end_date}  URL: #{url}&amp;quot;&lt;br /&gt;&lt;br /&gt;  @hp = Hpricot(open(url))&lt;br /&gt;  @ic = Iconv.new(&amp;apos;US-ASCII//TRANSLIT&amp;apos;, &amp;apos;UTF-8&amp;apos;)&lt;br /&gt;  @coder = HTMLEntities.new&lt;br /&gt;  @schedule = process_html(@hp, offset)&lt;br /&gt;end&lt;br /&gt;&lt;br /&gt;def get_search_dates(period=30)&lt;br /&gt;  [DateTime.now().strftime(&amp;quot;%d %b %Y&amp;quot;), (DateTime.now()+period).strftime(&amp;quot;%d %b %Y&amp;quot;)]&lt;br /&gt;end&lt;br /&gt;&lt;br /&gt;def build_query_string(channel, start_date, end_date)&lt;br /&gt;  urlencode({&lt;br /&gt;    &amp;apos;channelid&amp;apos; =&amp;gt; channel,&lt;br /&gt;    &amp;apos;startDate&amp;apos; =&amp;gt; start_date,&lt;br /&gt;    &amp;apos;EndDate&amp;apos;   =&amp;gt; end_date}) +&lt;br /&gt;      &amp;apos;&amp;amp;sType=5&amp;amp;searchstring=&amp;amp;submit=Submit&amp;apos;&lt;br /&gt;end&lt;br /&gt;&lt;br /&gt;def build_url(query_string)&lt;br /&gt;  host = &amp;apos;www.mnet.co.za&amp;apos;&lt;br /&gt;  cgi = &amp;apos;/schedules/default.asp?&amp;apos;&lt;br /&gt;  &amp;quot;http://#{host}#{cgi}#{query_string}&amp;quot;&lt;br /&gt;end&lt;br /&gt;&lt;br /&gt;def urlencode(hash)&lt;br /&gt;  hash.map {|k, v| &amp;quot;#{URI::encode(k.to_s)}=#{URI::encode(v.to_s)}&amp;quot;}.join(&amp;apos;&amp;amp;&amp;apos;)&lt;br /&gt;end&lt;br /&gt;&lt;/span&gt;&lt;/span&gt;&lt;/pre&gt;&lt;br /&gt;We no longer statically define the query parameters in the constructor and therefore have no real need for the custom hash. We can still use the urlencode() method though and add it as a helper in the class.&lt;br /&gt;&lt;br /&gt;The start and end dates for the query are calculated based on today's date and the period provided to the constructor as an argument.&lt;br /&gt;&lt;br /&gt;We also dumped all that horrible looking query string and url variable construction code into separate methods.&lt;br /&gt;&lt;br /&gt;The next step is to provide some automation to the channel schedule collection code for our example program. Look at the the HTML data in any of the search pages and you'll see the following (excerpt):&lt;br /&gt;&lt;pre&gt;&lt;span style="font-size:85%;"&gt;&lt;span style="font-family:courier new;"&gt;&lt;br /&gt;&amp;lt;select  name=&amp;quot;channelid&amp;quot; class=&amp;quot;ScheduleInputSelect&amp;quot;&amp;gt;&lt;br /&gt;    &amp;lt;option value=&amp;quot;&amp;quot; &amp;gt;CHANNEL&amp;lt;/option&amp;gt;&lt;br /&gt;    &amp;lt;option value=246&amp;gt;actionX                                           &amp;lt;/option&amp;gt;&lt;br /&gt;    &amp;lt;option value=322&amp;gt;Activate                                          &amp;lt;/option&amp;gt;&lt;br /&gt;    &amp;lt;option value=496&amp;gt;Africa Magic&amp;lt;/option&amp;gt;&lt;br /&gt;    &amp;lt;option value=487&amp;gt;Africa Magic Channel (C-Band)                     &amp;lt;/option&amp;gt;&lt;br /&gt;    &amp;lt;option value=639&amp;gt;Africa Magic W4&amp;lt;/option&amp;gt;&lt;br /&gt;    &amp;lt;option value=417&amp;gt;Animal Planet                                     &amp;lt;/option&amp;gt;&lt;br /&gt;[...]&lt;br /&gt;    &amp;lt;option value=254&amp;gt;TV Globo                                          &amp;lt;/option&amp;gt;&lt;br /&gt;    &amp;lt;option value=493&amp;gt;TV5 Afrique                                       &amp;lt;/option&amp;gt;&lt;br /&gt;    &amp;lt;option value=110&amp;gt;TV5 Afrique (Africa)                              &amp;lt;/option&amp;gt;&lt;br /&gt;    &amp;lt;option value=65&amp;gt;VH1                                               &amp;lt;/option&amp;gt;&lt;br /&gt;    &amp;lt;option value=67&amp;gt;ZEE TV                                            &amp;lt;/option&amp;gt;&lt;br /&gt;&amp;lt;/select&amp;gt;&lt;br /&gt;&lt;/span&gt;&lt;/span&gt;&lt;/pre&gt;&lt;br /&gt;These are the channels that we can search for. What we need is to represent this information as an internal data structure that we can use to search for the channels we want. I suggest a hash that has the channel name as a key and the channel ID and offset as a tuple.&lt;br /&gt;&lt;br /&gt;I am lazy so I'd prefer to avoid typing all that information up or manually trying to transform it in the editor. Perhaps we can use some good old command line ruby to chew up and spit out the code we need which we can then just cut 'n paste or import (depending on the editor you use).&lt;br /&gt;&lt;br /&gt;Copy the HTML and drop it in a file somewhere. Let's call the file in.html and run it through this command line script (output is truncated):&lt;br /&gt;&lt;pre&gt;&lt;span style="font-size:85%;"&gt;&lt;span style="font-family:courier new;"&gt;&lt;br /&gt;$ ruby -n -e &amp;apos;$_=~/value=(\d+)\&amp;gt;(.+)\s+\&amp;lt;/;if $1&amp;amp;&amp;amp;$2 then a=$1;b=$2;print &amp;quot;\# \&amp;quot;#{b.sub(/\s+$/,&amp;quot;&amp;quot;)}\&amp;quot; =&amp;gt; [#{a}, 120],\n&amp;quot; end&amp;apos; &amp;lt; in.html | head   &lt;br /&gt;# &amp;quot;actionX&amp;quot; =&amp;gt; [246, 120],&lt;br /&gt;# &amp;quot;Activate&amp;quot; =&amp;gt; [322, 120],&lt;br /&gt;# &amp;quot;Africa Magic Channel (C-Band)&amp;quot; =&amp;gt; [487, 120],&lt;br /&gt;# &amp;quot;Animal Planet&amp;quot; =&amp;gt; [417, 120],&lt;br /&gt;# &amp;quot;B4U Movies&amp;quot; =&amp;gt; [227, 120],&lt;br /&gt;# &amp;quot;BBC Food&amp;quot; =&amp;gt; [284, 120],&lt;br /&gt;# &amp;quot;BBC Prime&amp;quot; =&amp;gt; [121, 120],&lt;br /&gt;# &amp;quot;BBC World&amp;quot; =&amp;gt; [5, 120],&lt;br /&gt;# &amp;quot;Bloomberg Information TV&amp;quot; =&amp;gt; [8, 120],&lt;br /&gt;# &amp;quot;Boomerang&amp;quot; =&amp;gt; [314, 120],&lt;br /&gt;[...]&lt;br /&gt;&lt;/span&gt;&lt;/span&gt;&lt;/pre&gt;&lt;br /&gt;Now take the output and place it in your script as a hash (as described above):&lt;br /&gt;&lt;pre&gt;&lt;span style="font-size:85%;"&gt;&lt;span style="font-family:courier new;"&gt;&lt;br /&gt;channels = {&lt;br /&gt;  # &amp;quot;actionX&amp;quot; =&amp;gt; [246, 120],&lt;br /&gt;  # &amp;quot;Activate&amp;quot; =&amp;gt; [322, 120],&lt;br /&gt;  # &amp;quot;Africa Magic Channel (C-Band)&amp;quot; =&amp;gt; [487, 120],&lt;br /&gt;  # &amp;quot;Animal Planet&amp;quot; =&amp;gt; [417, 120],&lt;br /&gt;  # &amp;quot;B4U Movies&amp;quot; =&amp;gt; [227, 120],&lt;br /&gt;  &amp;quot;BBC Food&amp;quot; =&amp;gt; [284, 120],&lt;br /&gt;  &amp;quot;BBC Prime&amp;quot; =&amp;gt; [121, 120],&lt;br /&gt;  # &amp;quot;BBC World&amp;quot; =&amp;gt; [5, 120],&lt;br /&gt;  # &amp;quot;Bloomberg Information TV&amp;quot; =&amp;gt; [8, 120],&lt;br /&gt;  # &amp;quot;Boomerang&amp;quot; =&amp;gt; [314, 120],&lt;br /&gt;  # &amp;quot;BVN&amp;quot; =&amp;gt; [270, 120],&lt;br /&gt;  # &amp;quot;Canal+ Horizons&amp;quot; =&amp;gt; [237, 120],&lt;br /&gt;  # &amp;quot;Cartoon Network&amp;quot; =&amp;gt; [13, 120],&lt;br /&gt;  # &amp;quot;Cartoon Network (Africa)&amp;quot; =&amp;gt; [219, 120],&lt;br /&gt;  # &amp;quot;Cartoon Network (W4)&amp;quot; =&amp;gt; [182, 120],&lt;br /&gt;  # &amp;quot;Channel O - Sound Television&amp;quot; =&amp;gt; [27, 120],&lt;br /&gt;  # &amp;quot;China Central Television 4&amp;quot; =&amp;gt; [15, 120],&lt;br /&gt;  # &amp;quot;China Central Television 9 (Africa)&amp;quot; =&amp;gt; [226, 120],&lt;br /&gt;  # &amp;quot;CNBC&amp;quot; =&amp;gt; [90, 120],&lt;br /&gt;  # &amp;quot;CNBC (Africa)&amp;quot; =&amp;gt; [194, 120],&lt;br /&gt;  # &amp;quot;CNBC (W4)&amp;quot; =&amp;gt; [187, 120],&lt;br /&gt;  # &amp;quot;CNN International&amp;quot; =&amp;gt; [18, 120],&lt;br /&gt;  # &amp;quot;Deukom - 3SAT&amp;quot; =&amp;gt; [165, 120],&lt;br /&gt;  # &amp;quot;Deukom - ARD&amp;quot; =&amp;gt; [93, 120],&lt;br /&gt;  # &amp;quot;Deukom - DW&amp;quot; =&amp;gt; [94, 120],&lt;br /&gt;  # &amp;quot;Deukom - PRO 7&amp;quot; =&amp;gt; [164, 120],&lt;br /&gt;  # &amp;quot;Deukom - RTL&amp;quot; =&amp;gt; [91, 120],&lt;br /&gt;  # &amp;quot;Deukom - SAT 1&amp;quot; =&amp;gt; [92, 120],&lt;br /&gt;  # &amp;quot;Deukom - ZDF&amp;quot; =&amp;gt; [95, 120],&lt;br /&gt;  &amp;quot;Discovery Channel&amp;quot; =&amp;gt; [21, 120],&lt;br /&gt;  # &amp;quot;E-Entertainment&amp;quot; =&amp;gt; [646, 120],&lt;br /&gt;  &amp;quot;ESPN&amp;quot; =&amp;gt; [24, 120],&lt;br /&gt;  # &amp;quot;eTV&amp;quot; =&amp;gt; [111, 120],&lt;br /&gt;  # &amp;quot;Fashion TV&amp;quot; =&amp;gt; [145, 120],&lt;br /&gt;  # &amp;quot;Fashion TV (Africa)&amp;quot; =&amp;gt; [196, 120],&lt;br /&gt;  # &amp;quot;Fashion TV (W4)&amp;quot; =&amp;gt; [216, 120],&lt;br /&gt;  &amp;quot;GO&amp;quot; =&amp;gt; [542, 120],&lt;br /&gt;  # &amp;quot;Go  (K-World Teen)&amp;quot; =&amp;gt; [341, 120],&lt;br /&gt;  &amp;quot;Hallmark Entertainment Network&amp;quot; =&amp;gt; [32, 120],&lt;br /&gt;  &amp;quot;History Channel&amp;quot; =&amp;gt; [484, 120],&lt;br /&gt;  # &amp;quot;History Channel (Africa)&amp;quot; =&amp;gt; [485, 120],&lt;br /&gt;  # &amp;quot;K-TV World&amp;quot; =&amp;gt; [36, 120],&lt;br /&gt;  # &amp;quot;KTV (Indian Bouquet)&amp;quot; =&amp;gt; [501, 120],&lt;br /&gt;  # &amp;quot;kykNET&amp;quot; =&amp;gt; [112, 120],&lt;br /&gt;  # &amp;quot;M-Net Domestic&amp;quot; =&amp;gt; [39, 120],&lt;br /&gt;  &amp;quot;M-Net East (Africa)&amp;quot; =&amp;gt; [40, 120],&lt;br /&gt;  &amp;quot;M-Net Series&amp;quot; =&amp;gt; [75, 120],&lt;br /&gt;  # &amp;quot;MK89&amp;quot; =&amp;gt; [592, 120],&lt;br /&gt;  # &amp;quot;Movie Magic (Africa)&amp;quot; =&amp;gt; [57, 120],&lt;br /&gt;  &amp;quot;Movie Magic 2 (Africa)&amp;quot; =&amp;gt; [234, 120],&lt;br /&gt;  # &amp;quot;Movie Magic 2 (W4)&amp;quot; =&amp;gt; [233, 120],&lt;br /&gt;  # &amp;quot;MTV&amp;quot; =&amp;gt; [42, 120],&lt;br /&gt;  # &amp;quot;MTV Base&amp;quot; =&amp;gt; [69, 120],&lt;br /&gt;  &amp;quot;National Geographic&amp;quot; =&amp;gt; [102, 120],&lt;br /&gt;  # &amp;quot;NDTV&amp;quot; =&amp;gt; [499, 120],&lt;br /&gt;  # &amp;quot;Parliamentary Service&amp;quot; =&amp;gt; [45, 120],&lt;br /&gt;  # &amp;quot;Pay Per View&amp;quot; =&amp;gt; [109, 120],&lt;br /&gt;  &amp;quot;Reality TV&amp;quot; =&amp;gt; [248, 120],&lt;br /&gt;  # &amp;quot;Rhema Network&amp;quot; =&amp;gt; [46, 120],&lt;br /&gt;  # &amp;quot;RTPi&amp;quot; =&amp;gt; [48, 120],&lt;br /&gt;  # &amp;quot;SABC 1&amp;quot; =&amp;gt; [84, 120],&lt;br /&gt;  # &amp;quot;SABC 2&amp;quot; =&amp;gt; [85, 120],&lt;br /&gt;  # &amp;quot;SABC 3&amp;quot; =&amp;gt; [86, 120],&lt;br /&gt;  # &amp;quot;SABC Africa&amp;quot; =&amp;gt; [87, 120],&lt;br /&gt;  # &amp;quot;SIC&amp;quot; =&amp;gt; [255, 120],&lt;br /&gt;  # &amp;quot;Sky News&amp;quot; =&amp;gt; [120, 120],&lt;br /&gt;  &amp;quot;Sony Entertainment&amp;quot; =&amp;gt; [228, 90],&lt;br /&gt;  # &amp;quot;Summit&amp;quot; =&amp;gt; [104, 120],&lt;br /&gt;  # &amp;quot;Sun TV&amp;quot; =&amp;gt; [500, 120],&lt;br /&gt;  # &amp;quot;SuperSport&amp;quot; =&amp;gt; [52, 120],&lt;br /&gt;  # &amp;quot;SuperSport 2&amp;quot; =&amp;gt; [54, 120],&lt;br /&gt;  # &amp;quot;SuperSport 3&amp;quot; =&amp;gt; [80, 120],&lt;br /&gt;  # &amp;quot;SuperSport 3 (W4)&amp;quot; =&amp;gt; [172, 120],&lt;br /&gt;  # &amp;quot;SuperSport 5&amp;quot; =&amp;gt; [208, 120],&lt;br /&gt;  # &amp;quot;SuperSport 5 (Africa)&amp;quot; =&amp;gt; [252, 120],&lt;br /&gt;  # &amp;quot;SuperSport 5 (W4)&amp;quot; =&amp;gt; [251, 120],&lt;br /&gt;  # &amp;quot;SuperSport 6&amp;quot; =&amp;gt; [209, 120],&lt;br /&gt;  # &amp;quot;SuperSport 7 (C-Band)&amp;quot; =&amp;gt; [580, 120],&lt;br /&gt;  # &amp;quot;SuperSport Zone Mosaic&amp;quot; =&amp;gt; [235, 120],&lt;br /&gt;  # &amp;quot;TellyTrack&amp;quot; =&amp;gt; [34, 120],&lt;br /&gt;  # &amp;quot;Travel Channel&amp;quot; =&amp;gt; [61, 120],&lt;br /&gt;  # &amp;quot;Trinity Broadcasting Network&amp;quot; =&amp;gt; [276, 120],&lt;br /&gt;  # &amp;quot;Turner Classic Movies&amp;quot; =&amp;gt; [59, 120],&lt;br /&gt;  # &amp;quot;Turner Classic Movies (Africa)&amp;quot; =&amp;gt; [60, 120],&lt;br /&gt;  # &amp;quot;Turner Classic Movies (W4)&amp;quot; =&amp;gt; [181, 120],&lt;br /&gt;  # &amp;quot;TV Globo&amp;quot; =&amp;gt; [254, 120],&lt;br /&gt;  # &amp;quot;TV5 Afrique&amp;quot; =&amp;gt; [493, 120],&lt;br /&gt;  # &amp;quot;TV5 Afrique (Africa)&amp;quot; =&amp;gt; [110, 120],&lt;br /&gt;  # &amp;quot;VH1&amp;quot; =&amp;gt; [65, 120],&lt;br /&gt;  # &amp;quot;ZEE TV&amp;quot; =&amp;gt; [67, 120]&lt;br /&gt;}&lt;br /&gt;&lt;/span&gt;&lt;/span&gt;&lt;/pre&gt;&lt;br /&gt;You'll notice I have removed the comments from any of the channels I want (I recommend you do the same for the channels you may be interested in). I also added a default time offset of 2 hours (120 minutes) for most of the channels to adjust the time for my time zone. You can change this in the command line ruby filter above to suit your needs.&lt;br /&gt;&lt;br /&gt;All we need to do now is wrap our object creation and the output from it in a loop and we're off:&lt;br /&gt;&lt;pre&gt;&lt;span style="font-size:85%;"&gt;&lt;span style="font-family:courier new;"&gt;&lt;br /&gt;channels.keys.each do |channel|&lt;br /&gt;  p &amp;quot;Channel: #{channel}&amp;quot;&lt;br /&gt;  schedule = DSTVSchedule.new(channels[channel][0], channels[channel][1], 30)&lt;br /&gt;  schedule.print_schedule&lt;br /&gt;  print &amp;quot;\n\n&amp;quot;&lt;br /&gt;end&lt;br /&gt;&lt;/span&gt;&lt;/span&gt;&lt;/pre&gt;&lt;br /&gt;All done. Here is the complete script source listing:&lt;br /&gt;&lt;pre&gt;&lt;span style="font-size:85%;"&gt;&lt;span style="font-family:courier new;"&gt;&lt;br /&gt;#!/usr/bin/ruby&lt;br /&gt;&lt;br /&gt;class DSTVSchedule&lt;br /&gt;  require &amp;apos;rubygems&amp;apos;&lt;br /&gt;  require &amp;apos;hpricot&amp;apos;&lt;br /&gt;  require &amp;apos;open-uri&amp;apos;&lt;br /&gt;  require &amp;apos;htmlentities&amp;apos;&lt;br /&gt;  require &amp;apos;iconv&amp;apos;&lt;br /&gt;  require &amp;apos;collections/sequenced_hash&amp;apos;&lt;br /&gt;&lt;br /&gt;  def initialize(channel=219, offset=2, period=30)&lt;br /&gt;    start_date, end_date = get_search_dates(period)&lt;br /&gt;    url = build_url(build_query_string(channel, start_date ,end_date))&lt;br /&gt;&lt;br /&gt;    p &amp;quot;Start: #{start_date}  End: #{end_date}  URL: #{url}&amp;quot;&lt;br /&gt;&lt;br /&gt;    @hp = Hpricot(open(url))&lt;br /&gt;    @ic = Iconv.new(&amp;apos;US-ASCII//TRANSLIT&amp;apos;, &amp;apos;UTF-8&amp;apos;)&lt;br /&gt;    @coder = HTMLEntities.new&lt;br /&gt;    @schedule = process_html(@hp, offset)&lt;br /&gt;  end&lt;br /&gt;&lt;br /&gt;  def process_html(hp, offset)&lt;br /&gt;    schedule = SequencedHash.new&lt;br /&gt;    date = &amp;quot;&amp;quot;&lt;br /&gt;    time = &amp;quot;&amp;quot;&lt;br /&gt;    (hp/&amp;quot;td&amp;quot;).each do |line|&lt;br /&gt;      case line.inner_html&lt;br /&gt;      when /ScheduleChannel/&lt;br /&gt;        @channel = sanitize((line/&amp;quot;[@class=&amp;apos;ScheduleChannel&amp;apos;]&amp;quot;).inner_html)&lt;br /&gt;      when /(ScheduleDate|date)/&lt;br /&gt;        date = utf7((line/&amp;quot;[@class=&amp;apos;ScheduleDate&amp;apos;]|[@class=date]&amp;quot;).inner_html)&lt;br /&gt;        schedule[date] = SequencedHash.new&lt;br /&gt;      when /ScheduleTime/&lt;br /&gt;        time = sanitize((line/&amp;quot;[@class=&amp;apos;ScheduleTime&amp;apos;]&amp;quot;).inner_html)&lt;br /&gt;        time = (Time.parse(&amp;quot;#{date} #{time}&amp;quot;) + (60 * offset)).strftime(&amp;quot;%H:%M&amp;quot;)&lt;br /&gt;        schedule[date][time] = []&lt;br /&gt;      when /ScheduleTitle/&lt;br /&gt;        schedule[date][time] &amp;lt;&amp;lt; sanitize((line/&amp;quot;[@class=&amp;apos;ScheduleTitle&amp;apos;]&amp;quot;).inner_html)&lt;br /&gt;      when /\&amp;lt;p\&amp;gt;/&lt;br /&gt;        schedule[date][time] &amp;lt;&amp;lt; sanitize((line/&amp;quot;p&amp;quot;).inner_html)&lt;br /&gt;      end&lt;br /&gt;    end&lt;br /&gt;&lt;br /&gt;    schedule&lt;br /&gt;  end&lt;br /&gt;&lt;br /&gt;  def to_s&lt;br /&gt;    self.print_schedule(&amp;quot;\t&amp;quot;)&lt;br /&gt;  end&lt;br /&gt;&lt;br /&gt;  alias :to_tdt :to_s&lt;br /&gt;&lt;br /&gt;  def to_csv&lt;br /&gt;    ##TODO - Add channel to the output&lt;br /&gt;    self.print_schedule(&amp;quot;,&amp;quot;)&lt;br /&gt;  end&lt;br /&gt;&lt;br /&gt;  def print_schedule(separator=&amp;quot;||&amp;quot;)&lt;br /&gt;    sep = separator&lt;br /&gt;    @schedule.keys.each do |date|&lt;br /&gt;      @schedule[date].keys.each do |time|&lt;br /&gt;        print [date, time, @schedule[date][time][0], @schedule[date][time][1]].join(sep) + &amp;quot;\n&amp;quot;&lt;br /&gt;      end&lt;br /&gt;    end&lt;br /&gt;  end&lt;br /&gt;&lt;br /&gt;  protected&lt;br /&gt;&lt;br /&gt;  def sanitize(string)&lt;br /&gt;    string.gsub!(/\&amp;lt;\!\-\-.+$/, &amp;apos;&amp;apos;)  # remove HTML comments to the end of the line&lt;br /&gt;    string.gsub!(/^\s+/, &amp;apos;&amp;apos;)  # remove leading whitespace&lt;br /&gt;    string.gsub!(/\s+$/, &amp;apos;&amp;apos;)  # remove trailing whitespace&lt;br /&gt;    string&lt;br /&gt;  end&lt;br /&gt;&lt;br /&gt;  def utf7(string=&amp;quot;&amp;quot;)&lt;br /&gt;    @ic.iconv(@coder.decode(string))&lt;br /&gt;  end&lt;br /&gt;&lt;br /&gt;  def get_search_dates(period=30)&lt;br /&gt;    [DateTime.now().strftime(&amp;quot;%d %b %Y&amp;quot;), (DateTime.now()+period).strftime(&amp;quot;%d %b %Y&amp;quot;)]&lt;br /&gt;  end&lt;br /&gt;&lt;br /&gt;  def build_query_string(channel, start_date, end_date)&lt;br /&gt;    urlencode({&lt;br /&gt;      &amp;apos;channelid&amp;apos; =&amp;gt; channel,&lt;br /&gt;      &amp;apos;startDate&amp;apos; =&amp;gt; start_date,&lt;br /&gt;      &amp;apos;EndDate&amp;apos;   =&amp;gt; end_date}) +&lt;br /&gt;        &amp;apos;&amp;amp;sType=5&amp;amp;searchstring=&amp;amp;submit=Submit&amp;apos;&lt;br /&gt;  end&lt;br /&gt;&lt;br /&gt;  def build_url(query_string)&lt;br /&gt;    host = &amp;apos;www.mnet.co.za&amp;apos;&lt;br /&gt;    cgi = &amp;apos;/schedules/default.asp?&amp;apos;&lt;br /&gt;    &amp;quot;http://#{host}#{cgi}#{query_string}&amp;quot;&lt;br /&gt;  end&lt;br /&gt;&lt;br /&gt;  def urlencode(hash)&lt;br /&gt;    hash.map {|k, v| &amp;quot;#{URI::encode(k.to_s)}=#{URI::encode(v.to_s)}&amp;quot;}.join(&amp;apos;&amp;amp;&amp;apos;)&lt;br /&gt;  end&lt;br /&gt;end&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;#&lt;br /&gt;# Main&lt;br /&gt;#&lt;br /&gt;channels = {&lt;br /&gt;  # &amp;quot;actionX&amp;quot; =&amp;gt; [246, 120],&lt;br /&gt;  # &amp;quot;Activate&amp;quot; =&amp;gt; [322, 120],&lt;br /&gt;  # &amp;quot;Africa Magic Channel (C-Band)&amp;quot; =&amp;gt; [487, 120],&lt;br /&gt;  # &amp;quot;Animal Planet&amp;quot; =&amp;gt; [417, 120],&lt;br /&gt;  # &amp;quot;B4U Movies&amp;quot; =&amp;gt; [227, 120],&lt;br /&gt;  &amp;quot;BBC Food&amp;quot; =&amp;gt; [284, 120],&lt;br /&gt;  &amp;quot;BBC Prime&amp;quot; =&amp;gt; [121, 120],&lt;br /&gt;  # &amp;quot;BBC World&amp;quot; =&amp;gt; [5, 120],&lt;br /&gt;  # &amp;quot;Bloomberg Information TV&amp;quot; =&amp;gt; [8, 120],&lt;br /&gt;  # &amp;quot;Boomerang&amp;quot; =&amp;gt; [314, 120],&lt;br /&gt;  # &amp;quot;BVN&amp;quot; =&amp;gt; [270, 120],&lt;br /&gt;  # &amp;quot;Canal+ Horizons&amp;quot; =&amp;gt; [237, 120],&lt;br /&gt;  # &amp;quot;Cartoon Network&amp;quot; =&amp;gt; [13, 120],&lt;br /&gt;  # &amp;quot;Cartoon Network (Africa)&amp;quot; =&amp;gt; [219, 120],&lt;br /&gt;  # &amp;quot;Cartoon Network (W4)&amp;quot; =&amp;gt; [182, 120],&lt;br /&gt;  # &amp;quot;Channel O - Sound Television&amp;quot; =&amp;gt; [27, 120],&lt;br /&gt;  # &amp;quot;China Central Television 4&amp;quot; =&amp;gt; [15, 120],&lt;br /&gt;  # &amp;quot;China Central Television 9 (Africa)&amp;quot; =&amp;gt; [226, 120],&lt;br /&gt;  # &amp;quot;CNBC&amp;quot; =&amp;gt; [90, 120],&lt;br /&gt;  # &amp;quot;CNBC (Africa)&amp;quot; =&amp;gt; [194, 120],&lt;br /&gt;  # &amp;quot;CNBC (W4)&amp;quot; =&amp;gt; [187, 120],&lt;br /&gt;  # &amp;quot;CNN International&amp;quot; =&amp;gt; [18, 120],&lt;br /&gt;  # &amp;quot;Deukom - 3SAT&amp;quot; =&amp;gt; [165, 120],&lt;br /&gt;  # &amp;quot;Deukom - ARD&amp;quot; =&amp;gt; [93, 120],&lt;br /&gt;  # &amp;quot;Deukom - DW&amp;quot; =&amp;gt; [94, 120],&lt;br /&gt;  # &amp;quot;Deukom - PRO 7&amp;quot; =&amp;gt; [164, 120],&lt;br /&gt;  # &amp;quot;Deukom - RTL&amp;quot; =&amp;gt; [91, 120],&lt;br /&gt;  # &amp;quot;Deukom - SAT 1&amp;quot; =&amp;gt; [92, 120],&lt;br /&gt;  # &amp;quot;Deukom - ZDF&amp;quot; =&amp;gt; [95, 120],&lt;br /&gt;  &amp;quot;Discovery Channel&amp;quot; =&amp;gt; [21, 120],&lt;br /&gt;  # &amp;quot;E-Entertainment&amp;quot; =&amp;gt; [646, 120],&lt;br /&gt;  &amp;quot;ESPN&amp;quot; =&amp;gt; [24, 120],&lt;br /&gt;  # &amp;quot;eTV&amp;quot; =&amp;gt; [111, 120],&lt;br /&gt;  # &amp;quot;Fashion TV&amp;quot; =&amp;gt; [145, 120],&lt;br /&gt;  # &amp;quot;Fashion TV (Africa)&amp;quot; =&amp;gt; [196, 120],&lt;br /&gt;  # &amp;quot;Fashion TV (W4)&amp;quot; =&amp;gt; [216, 120],&lt;br /&gt;  &amp;quot;GO&amp;quot; =&amp;gt; [542, 120],&lt;br /&gt;  # &amp;quot;Go  (K-World Teen)&amp;quot; =&amp;gt; [341, 120],&lt;br /&gt;  &amp;quot;Hallmark Entertainment Network&amp;quot; =&amp;gt; [32, 120],&lt;br /&gt;  &amp;quot;History Channel&amp;quot; =&amp;gt; [484, 120],&lt;br /&gt;  # &amp;quot;History Channel (Africa)&amp;quot; =&amp;gt; [485, 120],&lt;br /&gt;  # &amp;quot;K-TV World&amp;quot; =&amp;gt; [36, 120],&lt;br /&gt;  # &amp;quot;KTV (Indian Bouquet)&amp;quot; =&amp;gt; [501, 120],&lt;br /&gt;  # &amp;quot;kykNET&amp;quot; =&amp;gt; [112, 120],&lt;br /&gt;  # &amp;quot;M-Net Domestic&amp;quot; =&amp;gt; [39, 120],&lt;br /&gt;  &amp;quot;M-Net East (Africa)&amp;quot; =&amp;gt; [40, 120],&lt;br /&gt;  &amp;quot;M-Net Series&amp;quot; =&amp;gt; [75, 120],&lt;br /&gt;  # &amp;quot;MK89&amp;quot; =&amp;gt; [592, 120],&lt;br /&gt;  # &amp;quot;Movie Magic (Africa)&amp;quot; =&amp;gt; [57, 120],&lt;br /&gt;  &amp;quot;Movie Magic 2 (Africa)&amp;quot; =&amp;gt; [234, 120],&lt;br /&gt;  # &amp;quot;Movie Magic 2 (W4)&amp;quot; =&amp;gt; [233, 120],&lt;br /&gt;  # &amp;quot;MTV&amp;quot; =&amp;gt; [42, 120],&lt;br /&gt;  # &amp;quot;MTV Base&amp;quot; =&amp;gt; [69, 120],&lt;br /&gt;  &amp;quot;National Geographic&amp;quot; =&amp;gt; [102, 120],&lt;br /&gt;  # &amp;quot;NDTV&amp;quot; =&amp;gt; [499, 120],&lt;br /&gt;  # &amp;quot;Parliamentary Service&amp;quot; =&amp;gt; [45, 120],&lt;br /&gt;  # &amp;quot;Pay Per View&amp;quot; =&amp;gt; [109, 120],&lt;br /&gt;  &amp;quot;Reality TV&amp;quot; =&amp;gt; [248, 120],&lt;br /&gt;  # &amp;quot;Rhema Network&amp;quot; =&amp;gt; [46, 120],&lt;br /&gt;  # &amp;quot;RTPi&amp;quot; =&amp;gt; [48, 120],&lt;br /&gt;  # &amp;quot;SABC 1&amp;quot; =&amp;gt; [84, 120],&lt;br /&gt;  # &amp;quot;SABC 2&amp;quot; =&amp;gt; [85, 120],&lt;br /&gt;  # &amp;quot;SABC 3&amp;quot; =&amp;gt; [86, 120],&lt;br /&gt;  # &amp;quot;SABC Africa&amp;quot; =&amp;gt; [87, 120],&lt;br /&gt;  # &amp;quot;SIC&amp;quot; =&amp;gt; [255, 120],&lt;br /&gt;  # &amp;quot;Sky News&amp;quot; =&amp;gt; [120, 120],&lt;br /&gt;  &amp;quot;Sony Entertainment&amp;quot; =&amp;gt; [228, 90],&lt;br /&gt;  # &amp;quot;Summit&amp;quot; =&amp;gt; [104, 120],&lt;br /&gt;  # &amp;quot;Sun TV&amp;quot; =&amp;gt; [500, 120],&lt;br /&gt;  # &amp;quot;SuperSport&amp;quot; =&amp;gt; [52, 120],&lt;br /&gt;  # &amp;quot;SuperSport 2&amp;quot; =&amp;gt; [54, 120],&lt;br /&gt;  # &amp;quot;SuperSport 3&amp;quot; =&amp;gt; [80, 120],&lt;br /&gt;  # &amp;quot;SuperSport 3 (W4)&amp;quot; =&amp;gt; [172, 120],&lt;br /&gt;  # &amp;quot;SuperSport 5&amp;quot; =&amp;gt; [208, 120],&lt;br /&gt;  # &amp;quot;SuperSport 5 (Africa)&amp;quot; =&amp;gt; [252, 120],&lt;br /&gt;  # &amp;quot;SuperSport 5 (W4)&amp;quot; =&amp;gt; [251, 120],&lt;br /&gt;  # &amp;quot;SuperSport 6&amp;quot; =&amp;gt; [209, 120],&lt;br /&gt;  # &amp;quot;SuperSport 7 (C-Band)&amp;quot; =&amp;gt; [580, 120],&lt;br /&gt;  # &amp;quot;SuperSport Zone Mosaic&amp;quot; =&amp;gt; [235, 120],&lt;br /&gt;  # &amp;quot;TellyTrack&amp;quot; =&amp;gt; [34, 120],&lt;br /&gt;  # &amp;quot;Travel Channel&amp;quot; =&amp;gt; [61, 120],&lt;br /&gt;  # &amp;quot;Trinity Broadcasting Network&amp;quot; =&amp;gt; [276, 120],&lt;br /&gt;  # &amp;quot;Turner Classic Movies&amp;quot; =&amp;gt; [59, 120],&lt;br /&gt;  # &amp;quot;Turner Classic Movies (Africa)&amp;quot; =&amp;gt; [60, 120],&lt;br /&gt;  # &amp;quot;Turner Classic Movies (W4)&amp;quot; =&amp;gt; [181, 120],&lt;br /&gt;  # &amp;quot;TV Globo&amp;quot; =&amp;gt; [254, 120],&lt;br /&gt;  # &amp;quot;TV5 Afrique&amp;quot; =&amp;gt; [493, 120],&lt;br /&gt;  # &amp;quot;TV5 Afrique (Africa)&amp;quot; =&amp;gt; [110, 120],&lt;br /&gt;  # &amp;quot;VH1&amp;quot; =&amp;gt; [65, 120],&lt;br /&gt;  # &amp;quot;ZEE TV&amp;quot; =&amp;gt; [67, 120]&lt;br /&gt;}&lt;br /&gt;&lt;br /&gt;channels.keys.each do |channel|&lt;br /&gt;  p &amp;quot;Channel: #{channel}&amp;quot;&lt;br /&gt;  schedule = DSTVSchedule.new(channels[channel][0], channels[channel][1], 30)&lt;br /&gt;  schedule.print_schedule&lt;br /&gt;  print &amp;quot;\n\n&amp;quot;&lt;br /&gt;end&lt;br /&gt;&lt;/span&gt;&lt;/span&gt;&lt;/pre&gt;&lt;br /&gt;I hope these articles have tickled your lobes and gets you to go explore &lt;a href="http://code.whytheluckystiff.net/hpricot/"&gt;Hpricot&lt;/a&gt; and the Wonderful World of &lt;a href="http://en.wikipedia.org/wiki/Web_scraping"&gt;Web Scraping&lt;/a&gt;.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/8719896753162286533-4914148210423964498?l=blog.ntrippy.net' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://blog.ntrippy.net/feeds/4914148210423964498/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=8719896753162286533&amp;postID=4914148210423964498' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/8719896753162286533/posts/default/4914148210423964498'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/8719896753162286533/posts/default/4914148210423964498'/><link rel='alternate' type='text/html' href='http://blog.ntrippy.net/2007/05/ruby-hpricot-program-guide-iii.html' title='Ruby (Hpricot) Program Guide - III'/><author><name>Charl</name><uri>http://www.blogger.com/profile/17742115469030093068</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='26' src='http://3.bp.blogspot.com/_4MLU7uPNEIY/TP2zTauOWAI/AAAAAAAAABs/OXj8i31AC0E/S220/blue%2B69.jpg'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-8719896753162286533.post-2059764250378972695</id><published>2007-04-30T23:38:00.000-07:00</published><updated>2007-12-24T23:37:33.010-08:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='ruby'/><category scheme='http://www.blogger.com/atom/ns#' term='HPricor'/><category scheme='http://www.blogger.com/atom/ns#' term='screen scrape'/><title type='text'>Ruby (Hpricot) Program Guide - II</title><content type='html'>For this installment we'll see if we can build on what we learnt last time to provide a less naive solution to get a complete schedule for a channel that spans several days, each having variable amounts of programs per day.&lt;br /&gt;&lt;br /&gt;First thing first though. Let's add the code that will retrieve the page for the channel we choose. Let's assume we want the schedule for Cartoon Network (Africa). The channel id for this channels happens to be 219 (as per the select list on the &lt;a href="http://www.mnet.co.za/schedules/default.asp"&gt;search page&lt;/a&gt;).&lt;br /&gt;&lt;pre&gt;&lt;span style="font-size:85%;"&gt;&lt;span style="font-family:courier new;"&gt;&lt;br /&gt;class Hash&lt;br /&gt;  require &amp;apos;uri&amp;apos;&lt;br /&gt;&lt;br /&gt;  def urlencode&lt;br /&gt;    map {|k, v| &amp;quot;#{URI::encode(k.to_s)}=#{URI::encode(v.to_s)}&amp;quot;}.join(&amp;apos;&amp;amp;&amp;apos;)&lt;br /&gt;  end&lt;br /&gt;end&lt;br /&gt;&lt;br /&gt;class DSTVSchedule&lt;br /&gt;  require &amp;apos;rubygems&amp;apos;&lt;br /&gt;  require &amp;apos;hpricot&amp;apos;&lt;br /&gt;  require &amp;apos;open-uri&amp;apos;&lt;br /&gt;  require &amp;apos;htmlentities&amp;apos;&lt;br /&gt;  require &amp;apos;iconv&amp;apos;&lt;br /&gt;&lt;br /&gt;  def initialize()&lt;br /&gt;    query_params = {&lt;br /&gt;      &amp;apos;startDate&amp;apos; =&amp;gt; &amp;apos;30 Apr 2007&amp;apos;,&lt;br /&gt;      &amp;apos;EndDate&amp;apos;   =&amp;gt; &amp;apos;01 May 2007&amp;apos;,&lt;br /&gt;      &amp;apos;channelid&amp;apos; =&amp;gt; 219&lt;br /&gt;    }&lt;br /&gt;    query_string = query_params.urlencode + &amp;apos;&amp;amp;sType=5&amp;amp;searchstring=&amp;amp;submit=Submit&amp;apos;&lt;br /&gt;    host = &amp;apos;www.mnet.co.za&amp;apos;&lt;br /&gt;    cgi = &amp;apos;/schedules/default.asp?&amp;apos;&lt;br /&gt;    url = &amp;quot;http://#{host}#{cgi}#{query_string}&amp;quot;&lt;br /&gt;    @hp = Hpricot(open(url))&lt;br /&gt;    @ic = Iconv.new(&amp;apos;US-ASCII//TRANSLIT&amp;apos;, &amp;apos;UTF-8&amp;apos;)&lt;br /&gt;    @coder = HTMLEntities.new&lt;br /&gt;    @channel = channel&lt;br /&gt;    @date = date&lt;br /&gt;    @time = time&lt;br /&gt;    @title = title&lt;br /&gt;    @synopsis = synopsis&lt;br /&gt;&lt;br /&gt;    printf &amp;quot;Channel: %s\nDate: %s\nTime: %s\nTitle: %s\nSynopsis: %s\n&amp;quot;,&lt;br /&gt;        @channel, @date, @time, @title, @synopsis&lt;br /&gt;  end&lt;br /&gt;&lt;br /&gt;  def channel&lt;br /&gt;    sanitize(@hp.at(&amp;quot;font[@class=&amp;apos;ScheduleChannel&amp;apos;]&amp;quot;).inner_html)&lt;br /&gt;  end&lt;br /&gt;&lt;br /&gt;  def date&lt;br /&gt;    sanitize(@hp.at(&amp;quot;font[@class=&amp;apos;ScheduleDate&amp;apos;]&amp;quot;).inner_html)&lt;br /&gt;  end&lt;br /&gt;&lt;br /&gt;  def time&lt;br /&gt;    sanitize(@hp.at(&amp;quot;font[@class=&amp;apos;ScheduleTime&amp;apos;]&amp;quot;).inner_html)&lt;br /&gt;  end&lt;br /&gt;&lt;br /&gt;  def title&lt;br /&gt;    sanitize(@hp.at(&amp;quot;font[@class=&amp;apos;ScheduleTitle&amp;apos;]&amp;quot;).inner_html)&lt;br /&gt;  end&lt;br /&gt;&lt;br /&gt;  def synopsis&lt;br /&gt;    sanitize((@hp/&amp;quot;td[@colspan=5]/p&amp;quot;).first.inner_html)&lt;br /&gt;  end&lt;br /&gt;&lt;br /&gt;  def sanitize(string)&lt;br /&gt;    @ic.iconv(@coder.decode(string))&lt;br /&gt;  end&lt;br /&gt;end&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;#&lt;br /&gt;# Main&lt;br /&gt;#&lt;br /&gt;schedule = DSTVSchedule.new()&lt;br /&gt;&lt;/span&gt;&lt;/span&gt;&lt;/pre&gt;&lt;br /&gt;So what interesting changes are there from our last try? The first thing you'll notice is that I &lt;a href="http://en.wikipedia.org/wiki/Monkey_patching"&gt;monkey patched&lt;/a&gt; the Hash class and added a nifty urlencode method to encode my URL parameters that are used to construct the query string which we will be sending off to the search application.&lt;br /&gt;&lt;br /&gt;Inside the DSTVSchedule class we've added query_params to temporarily hold our variable URL parameters. We then construct the URL we'll use for the query and simply pass that to the &lt;a href="http://www.ruby-doc.org/stdlib/libdoc/open-uri/rdoc/classes/OpenURI/OpenRead.html#M001333"&gt;open()&lt;/a&gt; method from &lt;a href="http://www.ruby-doc.org/stdlib/libdoc/open-uri/rdoc/index.html"&gt;open-uri&lt;/a&gt;.&lt;br /&gt;&lt;br /&gt;The rest should all seem familiar to you (if you followed the &lt;a href="http://blog.ntrippy.net/2007/4/18/ruby-hpricot-program-guide-i"&gt;previous&lt;/a&gt; article).&lt;br /&gt;&lt;br /&gt;Now that we have that behind us do you notice we sit with a little dilemma? If we want multiple days' programs we cannot use the class as it stands because we will religiously only output the first program in the schedule. Let's replace all those methods (channel, time, date, title, synopsis) with one method that initialises an internal data structure which will represent the channel information.&lt;br /&gt;&lt;pre&gt;&lt;span style="font-size:85%;"&gt;&lt;span style="font-family:courier new;"&gt;&lt;br /&gt;def initialize()&lt;br /&gt;  query_params = {&lt;br /&gt;    &amp;apos;startDate&amp;apos; =&amp;gt; &amp;apos;30 Apr 2007&amp;apos;,&lt;br /&gt;    &amp;apos;EndDate&amp;apos;   =&amp;gt; &amp;apos;01 May 2007&amp;apos;,&lt;br /&gt;    &amp;apos;channelid&amp;apos; =&amp;gt; 219&lt;br /&gt;  }&lt;br /&gt;  query_string = query_params.urlencode + &amp;apos;&amp;amp;sType=5&amp;amp;searchstring=&amp;amp;submit=Submit&amp;apos;&lt;br /&gt;  host = &amp;apos;www.mnet.co.za&amp;apos;&lt;br /&gt;  cgi = &amp;apos;/schedules/default.asp?&amp;apos;&lt;br /&gt;  url = &amp;quot;http://#{host}#{cgi}#{query_string}&amp;quot;&lt;br /&gt;  @hp = Hpricot(open(url))&lt;br /&gt;  @ic = Iconv.new(&amp;apos;US-ASCII//TRANSLIT&amp;apos;, &amp;apos;UTF-8&amp;apos;)&lt;br /&gt;  @coder = HTMLEntities.new&lt;br /&gt;  @schedule = process_html(@hp)&lt;br /&gt;&lt;br /&gt;  self.print_schedule&lt;br /&gt;end&lt;br /&gt;&lt;br /&gt;def process_html(hp)&lt;br /&gt;  schedule = SequencedHash.new&lt;br /&gt;  date = &amp;quot;&amp;quot;&lt;br /&gt;  time = &amp;quot;&amp;quot;&lt;br /&gt;  (hp/&amp;quot;td&amp;quot;).each do |line|&lt;br /&gt;    case line.inner_html&lt;br /&gt;    when /ScheduleChannel/&lt;br /&gt;      @channel = sanitize((line/&amp;quot;[@class=&amp;apos;ScheduleChannel&amp;apos;]&amp;quot;).inner_html)&lt;br /&gt;    when /(date|ScheduleDate)/&lt;br /&gt;      date = utf7((line/&amp;quot;[@class=date]|[@class=&amp;apos;ScheduleDate&amp;apos;]&amp;quot;).inner_html)&lt;br /&gt;      schedule[date] = SequencedHash.new&lt;br /&gt;    when /ScheduleTime/&lt;br /&gt;      time = sanitize((line/&amp;quot;[@class=&amp;apos;ScheduleTime&amp;apos;]&amp;quot;).inner_html)&lt;br /&gt;      schedule[date][time] = []&lt;br /&gt;    when /ScheduleTitle/&lt;br /&gt;      schedule[date][time] &amp;lt;&amp;lt; sanitize((line/&amp;quot;[@class=&amp;apos;ScheduleTitle&amp;apos;]&amp;quot;).inner_html)&lt;br /&gt;    when /\&amp;lt;p\&amp;gt;/&lt;br /&gt;      schedule[date][time] &amp;lt;&amp;lt; sanitize((line/&amp;quot;p&amp;quot;).inner_html)&lt;br /&gt;    end&lt;br /&gt;  end&lt;br /&gt;&lt;br /&gt;  schedule&lt;br /&gt;end&lt;br /&gt;&lt;/span&gt;&lt;/span&gt;&lt;/pre&gt;&lt;br /&gt;The process_html method replaces all the methods we removed. All we've done is use Hpricot to search for all table column tags, and their content, and done some further search refinement in the case statement.&lt;br /&gt;&lt;br /&gt;In the case structure I use simple regexps to find the classes I want and then use Hpricot to pull out the information contained in the matched tag. The structure I create is a hash of hashes that has the date and time as keys and the title and synopsis as 2 elements in an array (tuple).&lt;br /&gt;&lt;br /&gt;There is one strange case above; when searching for dates. The reason for this is to cope with the inconsistent semantics used in the HTML (as mentioned in the previous article). The first date is listed with a class attribute of 'ScheduleDate' while all the rest have a class attribute of 'date'.&lt;br /&gt;&lt;br /&gt;Take note of the use of the specialised hash &lt;a href="http://collections.rubyforge.org/classes/SequencedHash.html"&gt;SequencedHash&lt;/a&gt; that is used instead of the vanilla hash that is included in the core of ruby. The SequencedHash is part of the &lt;a href="http://collections.rubyforge.org/files/README_txt.html"&gt;Ruby Collections gem&lt;/a&gt; which keeps track in which order we add elements so that we're able to pull them out in the same order.&lt;br /&gt;&lt;br /&gt;I suspect storing the order of the keys may be a lot faster than trying to sort through a (potentially) large data set at the end to ensure the data is printed out in ascending date/time order.&lt;br /&gt;&lt;br /&gt;The sanitize() method has changed in the following ways from the last article:&lt;br /&gt;&lt;ol&gt;&lt;br /&gt;    &lt;li&gt;Forcing of encoding to UTF7 has been moved to the utf7() method.&lt;/li&gt;&lt;br /&gt;    &lt;li&gt;Drop any text that is a HTML comment to the end of the string.&lt;/li&gt;&lt;br /&gt;    &lt;li&gt;Reap any leading and trailing white space.&lt;/li&gt;&lt;br /&gt;&lt;/ol&gt;&lt;br /&gt;They are protected so we can only use them in our class.&lt;br /&gt;&lt;pre&gt;&lt;span style="font-size:85%;"&gt;&lt;span style="font-family:courier new;"&gt;&lt;br /&gt;protected&lt;br /&gt;&lt;br /&gt;def sanitize(string)&lt;br /&gt;  string.gsub!(/\&amp;lt;\!\-\-.+$/, &amp;apos;&amp;apos;)  # remove HTML comments to the end of the line&lt;br /&gt;  string.gsub!(/^\s+/, &amp;apos;&amp;apos;)  # remove leading whitespace&lt;br /&gt;  string.gsub!(/\s+$/, &amp;apos;&amp;apos;)  # remove trailing whitespace&lt;br /&gt;  string&lt;br /&gt;end&lt;br /&gt;&lt;br /&gt;def utf7(string=&amp;quot;&amp;quot;)&lt;br /&gt;  @ic.iconv(@coder.decode(string))&lt;br /&gt;end&lt;br /&gt;&lt;/span&gt;&lt;/span&gt;&lt;/pre&gt;&lt;br /&gt;We can now construct a valid query, execute the search and build an internal data structure that represents our schedule. We now need to find some way to output what we have internally.&lt;br /&gt;&lt;pre&gt;&lt;span style="font-size:85%;"&gt;&lt;span style="font-family:courier new;"&gt;&lt;br /&gt;def to_s&lt;br /&gt;  self.print_schedule(&amp;quot;\t&amp;quot;)&lt;br /&gt;end&lt;br /&gt;&lt;br /&gt;alias :to_tdt :to_s&lt;br /&gt;&lt;br /&gt;def to_csv&lt;br /&gt;  ##TODO - Add channel to the output&lt;br /&gt;  self.print_schedule(&amp;quot;,&amp;quot;)&lt;br /&gt;end&lt;br /&gt;&lt;br /&gt;def print_schedule(separator=&amp;quot;||&amp;quot;)&lt;br /&gt;  sep = separator&lt;br /&gt;  @schedule.keys.each do |date|&lt;br /&gt;    @schedule[date].keys.each do |time|&lt;br /&gt;      print [date, time, @schedule[date][time][0], @schedule[date][time][1]].join(sep) + &amp;quot;\n&amp;quot;&lt;br /&gt;    end&lt;br /&gt;  end&lt;br /&gt;end&lt;br /&gt;&lt;/span&gt;&lt;/span&gt;&lt;/pre&gt;&lt;br /&gt;print_schedule() forms the basis of my output strategy. It takes an optional separator character(s) and walks the internal data structure to construct a schedule entry with data concatenated by the separator.&lt;br /&gt;&lt;br /&gt;I reuse this method in the to_s() and to_csv() methods to print out TAB delimited and comma separated values, respectively. I also added a to_tdt (TAD Delimited Text) alias which is essentially just another name for to_s().&lt;br /&gt;&lt;br /&gt;Running the class as it stands should give you something like this (extract):&lt;br /&gt;&lt;pre&gt;&lt;span style="font-size:85%;"&gt;&lt;span style="font-family:courier new;"&gt;&lt;br /&gt;30 April 2007||00:20||King Arthur&amp;apos;s Disasters||Following the crazy adventures of King Arthur as he tries to find a present for his true love, Princess Guinevere.&lt;br /&gt;30 April 2007||00:45||Spaced Out||&amp;apos;Death Of An Alien!&amp;apos;. George feels guilty when a Russian astronaut who saved his life is evicted from the space station.&lt;br /&gt;30 April 2007||01:10||The Cramp Twins||Follow the fun and adventures of the troublesome twins, Lucien and Wayne Cramp, who are always fighting, arguing and embarrassing each other!&lt;br /&gt;[...]&lt;br /&gt;1 May 2007||00:20||King Arthur&amp;apos;s Disasters||&amp;apos;The Ice Palace&amp;apos;. King Arthur and Merlin are sent to Switzerland to find Guinevere an ice palace that she can live inside.&lt;br /&gt;1 May 2007||00:45||Spaced Out||&amp;apos;Invasion&amp;apos;. When cockroaches invade the space station, the Martins are asked by a cockroach prince to solve a conflict between his people and another clan.&lt;br /&gt;1 May 2007||01:10||The Cramp Twins||Follow the fun and adventures of the troublesome twins, Lucien and Wayne Cramp, who are always fighting, arguing and embarrassing each other!&lt;br /&gt;[...]&lt;br /&gt;&lt;/span&gt;&lt;/span&gt;&lt;/pre&gt;&lt;br /&gt;Feel free to play with the other output options for more fun.&lt;br /&gt;&lt;br /&gt;Here is the complete class as it stands now:&lt;br /&gt;&lt;pre&gt;&lt;span style="font-size:85%;"&gt;&lt;span style="font-family:courier new;"&gt;&lt;br /&gt;class Hash&lt;br /&gt;  require &amp;apos;uri&amp;apos;&lt;br /&gt;&lt;br /&gt;  def urlencode&lt;br /&gt;    map {|k, v| &amp;quot;#{URI::encode(k.to_s)}=#{URI::encode(v.to_s)}&amp;quot;}.join(&amp;apos;&amp;amp;&amp;apos;)&lt;br /&gt;  end&lt;br /&gt;end&lt;br /&gt;&lt;br /&gt;class DSTVSchedule&lt;br /&gt;  require &amp;apos;rubygems&amp;apos;&lt;br /&gt;  require &amp;apos;hpricot&amp;apos;&lt;br /&gt;  require &amp;apos;open-uri&amp;apos;&lt;br /&gt;  require &amp;apos;htmlentities&amp;apos;&lt;br /&gt;  require &amp;apos;iconv&amp;apos;&lt;br /&gt;  require &amp;apos;collections/sequenced_hash&amp;apos;&lt;br /&gt;&lt;br /&gt;  def initialize(channel=&amp;apos;&amp;apos;, period=30, time_offset=2)&lt;br /&gt;    query_params = {&lt;br /&gt;      &amp;apos;startDate&amp;apos; =&amp;gt; &amp;apos;30 Apr 2007&amp;apos;,&lt;br /&gt;      &amp;apos;EndDate&amp;apos;   =&amp;gt; &amp;apos;1 May 2007&amp;apos;,&lt;br /&gt;      &amp;apos;channelid&amp;apos; =&amp;gt; &amp;quot;219&amp;quot;&lt;br /&gt;    }&lt;br /&gt;    query_string = query_params.urlencode + &amp;apos;&amp;amp;sType=5&amp;amp;searchstring=&amp;amp;submit=Submit&amp;apos;&lt;br /&gt;    host = &amp;apos;www.mnet.co.za&amp;apos;&lt;br /&gt;    cgi = &amp;apos;/schedules/default.asp?&amp;apos;&lt;br /&gt;    url = &amp;quot;http://#{host}#{cgi}#{query_string}&amp;quot;&lt;br /&gt;    @hp = Hpricot(open(url))&lt;br /&gt;    @ic = Iconv.new(&amp;apos;US-ASCII//TRANSLIT&amp;apos;, &amp;apos;UTF-8&amp;apos;)&lt;br /&gt;    @coder = HTMLEntities.new&lt;br /&gt;    @schedule = process_html(@hp)&lt;br /&gt;  end&lt;br /&gt;&lt;br /&gt;  def process_html(hp)&lt;br /&gt;    schedule = SequencedHash.new&lt;br /&gt;    date = &amp;quot;&amp;quot;&lt;br /&gt;    time = &amp;quot;&amp;quot;&lt;br /&gt;    (hp/&amp;quot;td&amp;quot;).each do |line|&lt;br /&gt;      case line.inner_html&lt;br /&gt;      when /ScheduleChannel/&lt;br /&gt;        @channel = sanitize((line/&amp;quot;[@class=&amp;apos;ScheduleChannel&amp;apos;]&amp;quot;).inner_html)&lt;br /&gt;      when /(ScheduleDate|date)/&lt;br /&gt;        date = utf7((line/&amp;quot;[@class=&amp;apos;ScheduleDate&amp;apos;]|[@class=date]&amp;quot;).inner_html)&lt;br /&gt;        schedule[date] = SequencedHash.new&lt;br /&gt;      when /ScheduleTime/&lt;br /&gt;        time = sanitize((line/&amp;quot;[@class=&amp;apos;ScheduleTime&amp;apos;]&amp;quot;).inner_html)&lt;br /&gt;        schedule[date][time] = []&lt;br /&gt;      when /ScheduleTitle/&lt;br /&gt;        schedule[date][time] &amp;lt;&amp;lt; sanitize((line/&amp;quot;[@class=&amp;apos;ScheduleTitle&amp;apos;]&amp;quot;).inner_html)&lt;br /&gt;      when /\&amp;lt;p\&amp;gt;/&lt;br /&gt;        schedule[date][time] &amp;lt;&amp;lt; sanitize((line/&amp;quot;p&amp;quot;).inner_html)&lt;br /&gt;      end&lt;br /&gt;    end&lt;br /&gt;&lt;br /&gt;    schedule&lt;br /&gt;  end&lt;br /&gt;&lt;br /&gt;  def to_s&lt;br /&gt;    self.print_schedule(&amp;quot;\t&amp;quot;)&lt;br /&gt;  end&lt;br /&gt;&lt;br /&gt;  alias :to_tdt :to_s&lt;br /&gt;&lt;br /&gt;  def to_csv&lt;br /&gt;    ##TODO - Add channel to the output&lt;br /&gt;    self.print_schedule(&amp;quot;,&amp;quot;)&lt;br /&gt;  end&lt;br /&gt;&lt;br /&gt;  def print_schedule(separator=&amp;quot;||&amp;quot;)&lt;br /&gt;    sep = separator&lt;br /&gt;    @schedule.keys.each do |date|&lt;br /&gt;      @schedule[date].keys.each do |time|&lt;br /&gt;        print [date, time, @schedule[date][time][0], @schedule[date][time][1]].join(sep) + &amp;quot;\n&amp;quot;&lt;br /&gt;      end&lt;br /&gt;    end&lt;br /&gt;  end&lt;br /&gt;&lt;br /&gt;  protected&lt;br /&gt;&lt;br /&gt;  def sanitize(string)&lt;br /&gt;    string.gsub!(/\&amp;lt;\!\-\-.+$/, &amp;apos;&amp;apos;)  # remove HTML comments to the end of the line&lt;br /&gt;    string.gsub!(/^\s+/, &amp;apos;&amp;apos;)  # remove leading whitespace&lt;br /&gt;    string.gsub!(/\s+$/, &amp;apos;&amp;apos;)  # remove trailing whitespace&lt;br /&gt;    string&lt;br /&gt;  end&lt;br /&gt;&lt;br /&gt;  def utf7(string=&amp;quot;&amp;quot;)&lt;br /&gt;    @ic.iconv(@coder.decode(string))&lt;br /&gt;  end&lt;br /&gt;end&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;#&lt;br /&gt;# Main&lt;br /&gt;#&lt;br /&gt;schedule = DSTVSchedule.new()&lt;br /&gt;schedule.print_schedule&lt;br /&gt;&lt;/span&gt;&lt;/span&gt;&lt;/pre&gt;&lt;br /&gt;Further refactoring may see us adding some attributes to the constructor (channel name, time offset) and providing an example on how we can use objects from this class to collect and display multiple channels of our choice.&lt;br /&gt;&lt;br /&gt;Sounds like there's another article in there somewhere.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/8719896753162286533-2059764250378972695?l=blog.ntrippy.net' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://blog.ntrippy.net/feeds/2059764250378972695/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=8719896753162286533&amp;postID=2059764250378972695' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/8719896753162286533/posts/default/2059764250378972695'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/8719896753162286533/posts/default/2059764250378972695'/><link rel='alternate' type='text/html' href='http://blog.ntrippy.net/2007/04/ruby-hpricot-program-guide-ii.html' title='Ruby (Hpricot) Program Guide - II'/><author><name>Charl</name><uri>http://www.blogger.com/profile/17742115469030093068</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='26' src='http://3.bp.blogspot.com/_4MLU7uPNEIY/TP2zTauOWAI/AAAAAAAAABs/OXj8i31AC0E/S220/blue%2B69.jpg'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-8719896753162286533.post-3696722985566096814</id><published>2007-04-27T16:22:00.000-07:00</published><updated>2007-12-23T19:14:05.829-08:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='rubygems'/><category scheme='http://www.blogger.com/atom/ns#' term='ruby'/><category scheme='http://www.blogger.com/atom/ns#' term='macports'/><category scheme='http://www.blogger.com/atom/ns#' term='fink'/><category scheme='http://www.blogger.com/atom/ns#' term='macbook pro'/><category scheme='http://www.blogger.com/atom/ns#' term='textmate'/><category scheme='http://www.blogger.com/atom/ns#' term='Ubuntu'/><title type='text'>Unholy Triumvirate: TextMate, MacPorts and Ruby</title><content type='html'>After switching back from a &lt;a href="http://www.ubuntiu.com/"&gt;Ubuntu&lt;/a&gt; laptop to my MacBook Pro I was once again getting back to using &lt;a href="http://macromates.com/"&gt;TextMate&lt;/a&gt; to do some development and systems scripting. The combination of &lt;a href="http://www.ruby-lang.org/en/"&gt;ruby&lt;/a&gt; and &lt;a href="http://www.rubygems.org/"&gt;RubyGems&lt;/a&gt; have been a little bit rocky on OS X.&lt;br /&gt;&lt;br /&gt;In part it was due to the default install of ruby on OS X, me using Fink for package management and then later switching from that to &lt;a href="http://www.macports.org/"&gt;MacPorts&lt;/a&gt;.&lt;br /&gt;&lt;br /&gt;Apple (and I presumably) suck &lt;a href="http://www.rot13.com/index.php?text=cvyrf&amp;amp;submit=Cypher"&gt;cvyrf&lt;/a&gt;.&lt;br /&gt;&lt;br /&gt;The problem I ran into was that after installing ruby and rb-rubygem via the ports system, TextMate no longer seems too interested in compiling ruby scripts when I hit CMD-R and provides me with a lovely:&lt;br /&gt;&lt;pre&gt;&lt;span style="font-size:85%;"&gt;&lt;span style="font-family:courier new;"&gt;"No such file to load ” rubygems&lt;/span&gt;&lt;/span&gt;&lt;br /&gt;&lt;/pre&gt;Checking &lt;a href="http://www.google.com/search?hl=en&amp;amp;client=opera&amp;amp;rls=en&amp;amp;hs=BrG&amp;amp;q=" textmate="" com="" btng="Search&amp;quot;"&gt;Google&lt;/a&gt; the first listing I get is &lt;a href="http://jimmyzimmerman.com/blog/2007/02/radrails-error-no-such-file-to-load-rubygems-loaderror.html"&gt;this&lt;/a&gt;.&lt;br /&gt;&lt;br /&gt;It did not provide me with an applicable solution but got me thinking ... Either I have some environment variables that are not being set (or set incorrectly) or my library paths are screwy somehow.&lt;br /&gt;&lt;br /&gt;An easy way to confirm the former is to check if your shell environment also suffers from the same malady:&lt;br /&gt;&lt;pre&gt;&lt;span style="font-size:85%;"&gt;&lt;span style="font-family:courier new;"&gt;$ ruby -r rubygems -e "p 1"&lt;br /&gt;1&lt;/span&gt;&lt;/span&gt;&lt;br /&gt;&lt;/pre&gt;Not the problem then. Next step, let's pull out find and off a hunting we go:&lt;br /&gt;&lt;pre&gt;&lt;span style="font-size:85%;"&gt;&lt;span style="font-family:courier new;"&gt;$ sudo find / -name ruby -type f&lt;br /&gt;Password:&lt;br /&gt;/opt/local/bin/ruby /opt/local/var/db/dports/software/ruby/1.8.6_0/opt/local/bin/ruby /usr/bin/ruby&lt;br /&gt;&lt;/span&gt;&lt;/span&gt;&lt;/pre&gt;Let's see if there is some disparity between the ruby binary in /opt/local/bin and /usr/bin:&lt;br /&gt;&lt;pre&gt;&lt;span style="font-size:85%;"&gt;&lt;span style="font-family:courier new;"&gt;$ /usr/bin/ruby -v&lt;br /&gt;ruby 1.8.2 (2004-12-25) [universal-darwin8.0]&lt;br /&gt;$ /opt/local/bin/ruby -v&lt;br /&gt;ruby 1.8.6 (2007-03-13 patchlevel 0) [i686-darwin8.9.1]&lt;/span&gt;&lt;/span&gt;&lt;br /&gt;&lt;/pre&gt;Well, what do you know. The version in /usr/bin is older and also looks for its libs in a non /opt location which means that it won't pick up the good work port has done for me. I moved /usr/bin/ruby to /tmp and added a soft link for /opt/local/bin/ruby to /usr/bin.&lt;br /&gt;&lt;br /&gt;Running my script in TextMate now works like a charm!&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/8719896753162286533-3696722985566096814?l=blog.ntrippy.net' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://blog.ntrippy.net/feeds/3696722985566096814/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=8719896753162286533&amp;postID=3696722985566096814' title='1 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/8719896753162286533/posts/default/3696722985566096814'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/8719896753162286533/posts/default/3696722985566096814'/><link rel='alternate' type='text/html' href='http://blog.ntrippy.net/2007/04/unholy-triumvirate-textmate-macports.html' title='Unholy Triumvirate: TextMate, MacPorts and Ruby'/><author><name>Charl</name><uri>http://www.blogger.com/profile/17742115469030093068</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='26' src='http://3.bp.blogspot.com/_4MLU7uPNEIY/TP2zTauOWAI/AAAAAAAAABs/OXj8i31AC0E/S220/blue%2B69.jpg'/></author><thr:total>1</thr:total></entry><entry><id>tag:blogger.com,1999:blog-8719896753162286533.post-9013100663015939231</id><published>2007-04-26T13:20:00.000-07:00</published><updated>2007-12-23T18:47:33.663-08:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='SSHKeychain'/><category scheme='http://www.blogger.com/atom/ns#' term='ssh tunnel'/><category scheme='http://www.blogger.com/atom/ns#' term='os x'/><category scheme='http://www.blogger.com/atom/ns#' term='ssh-agent'/><category scheme='http://www.blogger.com/atom/ns#' term='OpenSSH'/><title type='text'>Puffing with SSHKeychain</title><content type='html'>In one of my &lt;a href="http://blog.ntrippy.net/2007/4/5/ssh-agent-for-developers"&gt;previous&lt;/a&gt; articles I showed how you could use ssh-agent to your advantage to maximize lackadaisicalness. I have since then moved from the Ubuntu laptop that I was using at the time to my Mac that became available again.&lt;br /&gt;&lt;br /&gt;I was looking for a nice and neat way to integrate ssh-agent into the Mac environment but could not get my shell scripting approach to gel elegantly. While doing the obligatory search on the web I found and fell in love with &lt;a href="http://www.sshkeychain.org/"&gt;SSHKeychain&lt;/a&gt;.&lt;br /&gt;&lt;br /&gt;This little app does all the had work (running ssh-agent from the correct place and exporting your keys into memory with ssh-add) for you, and more ... It not only handles the ssh-agent side of things but also provide support for integrating with the Apple Keychain and forward local ports over a ssh connection to set up &lt;a href="http://www.openssh.com/faq.html#2.11"&gt;ssh tunnels&lt;/a&gt;.&lt;br /&gt;&lt;br /&gt;Go see the full &lt;a href="http://www.sshkeychain.org/features.php"&gt;feature list&lt;/a&gt; for more info.&lt;br /&gt;&lt;br /&gt;&lt;span style="font-weight: bold;"&gt;Installation&lt;/span&gt;&lt;br /&gt;Here are the step from their site:&lt;br /&gt;&lt;ul&gt;&lt;li style="font-style: italic;"&gt;Download SSHKeychain.dmg and mount it. &lt;/li&gt;&lt;li style="font-style: italic;"&gt;Copy SSHKeychain (SSHKeychain.app) to your Applications folder. &lt;/li&gt;&lt;li style="font-style: italic;"&gt;Run SSHKeychain. This should open a dock item and a statusbar item. &lt;/li&gt;&lt;li style="font-style: italic;"&gt;Click either the Statusbar Item, the Dock Item, or Main Menu and open the Preferences. &lt;/li&gt;&lt;li style="font-style: italic;"&gt;Open the Environment tab. &lt;/li&gt;&lt;li style="font-style: italic;"&gt;Enable "Manage global environment variables". This will make SSHKeychain available for other applications. &lt;/li&gt;&lt;li style="font-style: italic;"&gt;Open the keys tab and see if any of your keys are missing (~/.ssh/id_dsa and ~/.ssh/identity are default). &lt;/li&gt;&lt;li style="font-style: italic;"&gt;Re-login to make the global variables work. &lt;/li&gt;&lt;li&gt;&lt;span style="font-style: italic;"&gt;Start up SSHKeychain, and you're set.&lt;/span&gt; &lt;/li&gt;&lt;/ul&gt;I added SSHKeychain to my Login Items in the System Preferences panel to ensure the app was running after a restart or log out/in sequence.&lt;br /&gt;&lt;br /&gt;&lt;span style="font-weight: bold;"&gt;Setup&lt;/span&gt;&lt;span style="font-weight: bold;"&gt;&lt;br /&gt;&lt;/span&gt;&lt;span&gt;If you followed the installation instructions above there should be nothing further to do (assuming you had some pre-created keys in the default place like I had).&lt;br /&gt;&lt;br /&gt;Excellent!&lt;br /&gt;&lt;br /&gt;When I now fire Terminal.app up and log into a box that has my public key on it no password is required and I am logged in without further ado.&lt;br /&gt;&lt;br /&gt;&lt;/span&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/8719896753162286533-9013100663015939231?l=blog.ntrippy.net' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://blog.ntrippy.net/feeds/9013100663015939231/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=8719896753162286533&amp;postID=9013100663015939231' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/8719896753162286533/posts/default/9013100663015939231'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/8719896753162286533/posts/default/9013100663015939231'/><link rel='alternate' type='text/html' href='http://blog.ntrippy.net/2007/12/puffing-with-sshkeychain.html' title='Puffing with SSHKeychain'/><author><name>Charl</name><uri>http://www.blogger.com/profile/17742115469030093068</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='26' src='http://3.bp.blogspot.com/_4MLU7uPNEIY/TP2zTauOWAI/AAAAAAAAABs/OXj8i31AC0E/S220/blue%2B69.jpg'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-8719896753162286533.post-614696135734068415</id><published>2007-04-20T17:29:00.000-07:00</published><updated>2007-12-23T18:49:46.342-08:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='ssh-add'/><category scheme='http://www.blogger.com/atom/ns#' term='ssh'/><category scheme='http://www.blogger.com/atom/ns#' term='ssh-agent'/><category scheme='http://www.blogger.com/atom/ns#' term='Ubuntu'/><category scheme='http://www.blogger.com/atom/ns#' term='OpenSSH'/><title type='text'>ssh-agent for Developers</title><content type='html'>Have you ever wanted to automate the ssh pass phrase login procedure when connecting to remote systems that have your public key in their .ssh/authorized_keys?&lt;br /&gt;&lt;br /&gt;This is done using &lt;span style="font-style: italic;"&gt;ssh-agent&lt;/span&gt; (and &lt;span style="font-style: italic;"&gt;ssh-add&lt;/span&gt;) which will be on your Debian or Ubuntu system if you have the &lt;span style="font-style: italic;"&gt;openssh-client&lt;/span&gt; dep installed. For other flavours of Linux, OS X or UNIX please refer to &lt;i&gt;your&lt;/i&gt; package management documentation (or install from source) to see how you can install the required software.&lt;br /&gt;&lt;br /&gt;On an Ubuntu system ssh-agent is started for you by default. Please refer to your system documentation for ssh-agent to find the correct way to run it on &lt;i&gt;your&lt;/i&gt; system.&lt;br /&gt;&lt;br /&gt;The following approach should work for situations where the client (the computer with the private key) is either a server (access is generally restricted to it via remote shell) or a desktop (includes laptops) with a graphical terminal program.&lt;br /&gt;&lt;br /&gt;Add the following to the end of your ~/.bashrc (or other suitable shell setup configuration file):&lt;br /&gt;&lt;pre&gt;&lt;code&gt;# Run ssh-add if it has not been run already.&lt;br /&gt;if  ssh-add -l | grep -q 'The agent has no identities.'&lt;br /&gt;then&lt;br /&gt;eval "ssh-add"&lt;br /&gt;fi&lt;/code&gt;&lt;/pre&gt;Save the addition to your .bashrc (or suitable alternative) and log out and back in.&lt;br /&gt;&lt;br /&gt;You will be presented with a request for your pass phrase you chose when creating your public/private keys. Enter it and sigh with relief as your default key(s) are cached in memory.&lt;br /&gt;&lt;br /&gt;When you now try and log into the remote system again there will be no passwords or pass phrases required for this session.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/8719896753162286533-614696135734068415?l=blog.ntrippy.net' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://blog.ntrippy.net/feeds/614696135734068415/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=8719896753162286533&amp;postID=614696135734068415' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/8719896753162286533/posts/default/614696135734068415'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/8719896753162286533/posts/default/614696135734068415'/><link rel='alternate' type='text/html' href='http://blog.ntrippy.net/2007/04/ssh-agent-for-developers.html' title='ssh-agent for Developers'/><author><name>Charl</name><uri>http://www.blogger.com/profile/17742115469030093068</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='26' src='http://3.bp.blogspot.com/_4MLU7uPNEIY/TP2zTauOWAI/AAAAAAAAABs/OXj8i31AC0E/S220/blue%2B69.jpg'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-8719896753162286533.post-8027912349824642157</id><published>2007-04-18T17:50:00.000-07:00</published><updated>2007-12-23T18:31:44.782-08:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='ruby'/><category scheme='http://www.blogger.com/atom/ns#' term='M-Net'/><category scheme='http://www.blogger.com/atom/ns#' term='screen scrape'/><category scheme='http://www.blogger.com/atom/ns#' term='HPricot'/><title type='text'>Ruby (Hpricot) Program Guide - I</title><content type='html'>Do you live outside of South Africa and subscribe to the M-Net Africa service? Ever wanted to avoid the M-Net Africa site and just get the program guide for your region?&lt;br /&gt;&lt;br /&gt;Well, look no further. Ruby and Hpricot to the rescue!&lt;br /&gt;&lt;br /&gt;The &lt;a href="http://www.mnet.co.za/schedules/default.asp"&gt;M-Net Schedule site&lt;/a&gt; has changed quite often over the last few months so chances are good that by the time you get to this article their site may have &lt;strike&gt;d&lt;/strike&gt;evolved again. Doing screen scraping on web sites is generally fraught with pain, suffering and disappointment.&lt;br /&gt;&lt;br /&gt;This is generally due to the fact that you're providing a static way to read dynamic (over time) content. Don't get discouraged though, just build notification of changes into your screen scraper and ensure that it can notify you when things have changed so that you can up date it.&lt;br /&gt;&lt;br /&gt;To compound the problem, many sites (including the M-Net Schedule site) do not conform to the &lt;a href="http://en.wikipedia.org/wiki/Xhtml"&gt;XHTML&lt;/a&gt; standard. This simply means that they have not used semantic tools to layout their site to abstract the structure, content and behaviour from their site. A quick validation via the &lt;a href="http://validator.w3.org/detailed.html"&gt;W3C Markup Validation Service&lt;/a&gt; confirms that the parser can't even determine the content encoding.&lt;br /&gt;&lt;br /&gt;Embrace change - it is a lot less painful (not to mention more productive ;).&lt;br /&gt;&lt;br /&gt;&lt;span style="font-weight: bold;"&gt;Analysis of Structure&lt;/span&gt;&lt;br /&gt;The first step of parsing content from an outside source is to analyse the structure of the content to determine what strategies you are going to employ to read and parse the content. Below is an extract of the type of content we're interested in:&lt;br /&gt;&lt;pre&gt;&lt;span style="font-size:85%;"&gt;&amp;lt;tr&amp;gt;&lt;br /&gt;&amp;lt;td colspan="5"&amp;gt;&lt;br /&gt;  &amp;lt;font class="ScheduleSchedule"&amp;gt;Today's Schedule for :&amp;lt;/font&amp;gt;&lt;br /&gt;  &amp;lt;font class="ScheduleChannel"&amp;gt;Cartoon Network (Africa)&amp;lt;/font&amp;gt;&lt;br /&gt;&amp;lt;/td&amp;gt;&lt;br /&gt;&amp;lt;/tr&amp;gt;&lt;br /&gt;&amp;lt;tr&amp;gt;&lt;br /&gt;&amp;lt;td colspan="5"&amp;gt;&amp;amp;nbsp;&amp;lt;/td&amp;gt;&lt;br /&gt;&amp;lt;/tr&amp;gt;&lt;br /&gt;&amp;lt;tr&amp;gt;&lt;br /&gt;&amp;lt;td colspan="5"&amp;gt;&lt;br /&gt;  &amp;lt;font class="ScheduleDate"&amp;gt;17&amp;amp;nbsp;April&amp;amp;nbsp;2007&amp;lt;/font&amp;gt;&lt;br /&gt;&amp;lt;/td&amp;gt;&lt;br /&gt;&amp;lt;/tr&amp;gt;&lt;br /&gt;&amp;lt;tr bgcolor="F5F5F5"&amp;gt;&lt;br /&gt;&amp;lt;td colspan="5"&amp;gt;&amp;amp;nbsp;&amp;lt;/td&amp;gt;&lt;br /&gt;&amp;lt;/tr&amp;gt;&lt;br /&gt;&amp;lt;tr bgcolor="F5F5F5"&amp;gt;&lt;br /&gt;&amp;lt;td width="40"&amp;gt;&lt;br /&gt;  &amp;lt;b&amp;gt;&amp;lt;font class="ScheduleTime"&amp;gt; 06:25&amp;lt;/font&amp;gt;&amp;lt;/b&amp;gt;&lt;br /&gt;&amp;lt;/td&amp;gt;&lt;br /&gt;&amp;lt;!--Time--&amp;gt;&lt;br /&gt;&amp;lt;td width="420"&amp;gt;&lt;br /&gt;  &amp;lt;font class="ScheduleTitle"&amp;gt;Codename: Kids Next Door&lt;br /&gt;  &amp;lt;!--Title--&amp;gt;&lt;br /&gt;  &amp;lt;/font&amp;gt;&lt;br /&gt;&amp;lt;/td&amp;gt;&lt;br /&gt;&amp;lt;td width="17"&amp;gt;&amp;lt;/td&amp;gt;&lt;br /&gt;&amp;lt;td width="188"&amp;gt;&amp;lt;/td&amp;gt;&lt;br /&gt;&amp;lt;!--SMS Reminder--&amp;gt;&lt;br /&gt;&amp;lt;td width="50" align="right"&amp;gt;&lt;br /&gt;  &amp;lt;a href="#" onclick="OpenAgeRestriction(1);return false;"&amp;gt;Family&amp;lt;/a&amp;gt;&lt;br /&gt;&amp;lt;/td&amp;gt;&lt;br /&gt;&amp;lt;!--Age Restriction--&amp;gt;&lt;br /&gt;&amp;lt;/tr&amp;gt;&lt;br /&gt;&amp;lt;tr bgcolor="F5F5F5"&amp;gt;&lt;br /&gt;&amp;lt;td colspan="5"&amp;gt;&lt;br /&gt;  &amp;lt;p&amp;gt;A gang of 10 year olds takes on top secret missions, using fantastic home-made technology to safeguard their treehouse against attack and grown-ups.&amp;lt;/p&amp;gt;&lt;br /&gt;&amp;lt;/td&amp;gt;&lt;br /&gt;&amp;lt;/tr&amp;gt;&lt;br /&gt;&amp;lt;tr&amp;gt;&lt;br /&gt;&amp;lt;td colspan="5"&amp;gt;&amp;amp;nbsp;&amp;lt;/td&amp;gt;&lt;br /&gt;&amp;lt;/tr&amp;gt;&lt;br /&gt;&amp;lt;tr bgcolor="F5F5F5"&amp;gt;&lt;br /&gt;&amp;lt;td width="40"&amp;gt;&lt;br /&gt;  &amp;lt;b&amp;gt;&amp;lt;font class="ScheduleTime"&amp;gt; 06:50&amp;lt;/font&amp;gt;&amp;lt;/b&amp;gt;&lt;br /&gt;&amp;lt;/td&amp;gt;&lt;br /&gt;&amp;lt;!--Time--&amp;gt;&lt;br /&gt;&amp;lt;td width="420"&amp;gt;&lt;br /&gt;  &amp;lt;font class="ScheduleTitle"&amp;gt;The Powerpuff Girls&lt;br /&gt;  &amp;lt;!--Title--&amp;gt;&lt;br /&gt;  &amp;lt;/font&amp;gt;&lt;br /&gt;&amp;lt;/td&amp;gt;&lt;br /&gt;&amp;lt;td width="17"&amp;gt;&amp;lt;/td&amp;gt;&lt;br /&gt;&amp;lt;td width="188"&amp;gt;&amp;lt;/td&amp;gt;&lt;br /&gt;&amp;lt;!--SMS Reminder--&amp;gt;&lt;br /&gt;&amp;lt;td width="50" align="right"&amp;gt;&lt;br /&gt;  &amp;lt;a href="#" onclick="OpenAgeRestriction(1);return false;"&amp;gt;Family&amp;lt;/a&amp;gt;&lt;br /&gt;&amp;lt;/td&amp;gt;&lt;br /&gt;&amp;lt;!--Age Restriction--&amp;gt;&lt;br /&gt;&amp;lt;/tr&amp;gt;&lt;br /&gt;&amp;lt;tr bgcolor="F5F5F5"&amp;gt;&lt;br /&gt;&amp;lt;td colspan="5"&amp;gt;&lt;br /&gt;  &amp;lt;p&amp;gt;The wild and wacky escapades of three girls with extraordinary powers. Blossom, Buttercup and Bubbles use their superpowers to fight crime and villainy in Townsville.&amp;lt;/p&amp;gt;&lt;br /&gt;&amp;lt;/td&amp;gt;&lt;br /&gt;&amp;lt;/tr&amp;gt;&lt;br /&gt;&amp;lt;tr&amp;gt;&lt;br /&gt;&amp;lt;td colspan="5"&amp;gt;&amp;amp;nbsp;&amp;lt;/td&amp;gt;&lt;br /&gt;&amp;lt;/tr&amp;gt;&lt;/span&gt;&lt;br /&gt;&lt;/pre&gt;The first table row we're interested in is the one that tells us which channels we are looking at and what this day's date is (lightly formatted for readability):&lt;br /&gt;&lt;pre&gt;&lt;span style="font-size:85%;"&gt;&amp;lt;tr&amp;gt;&lt;br /&gt;&amp;lt;td colspan="5"&amp;gt;&lt;br /&gt;  &amp;lt;font class="ScheduleSchedule"&amp;gt;Today\'s Schedule for :&amp;lt;/font&amp;gt;&lt;br /&gt;  &amp;lt;font class="ScheduleChannel"&amp;gt;Cartoon Network (Africa)&amp;lt;/font&amp;gt;&lt;br /&gt;&amp;lt;/td&amp;gt;&lt;br /&gt;&amp;lt;/tr&amp;gt;&lt;br /&gt;&amp;lt;tr&amp;gt;&lt;br /&gt;&amp;lt;td colspan="5"&amp;gt;&amp;amp;nbsp;&amp;lt;/td&amp;gt;&lt;br /&gt;&amp;lt;/tr&amp;gt;&lt;br /&gt;&amp;lt;tr&amp;gt;&lt;br /&gt;&amp;lt;td colspan="5"&amp;gt;&lt;br /&gt;  &amp;lt;font class="ScheduleDate"&amp;gt;17&amp;amp;nbsp;April&amp;amp;nbsp;2007&amp;lt;/font&amp;gt;&lt;br /&gt;&amp;lt;/td&amp;gt;&lt;br /&gt;&amp;lt;/tr&amp;gt;&lt;br /&gt;&lt;/span&gt;&lt;/pre&gt;The name of the channel resides in a font tag whit a class attribute of "ScheduleChannel" and the date we're working with also resides in a font tag with a class attribute of "ScheduleDate". How does the search for this information translate into code?I will be using a XPath query (Hpricot supports both XPath and CSS selector based queries) to find the first font tag that has a class attribute that I am searching for:&lt;br /&gt;&lt;pre&gt;&lt;span style="font-size:85%;"&gt;&lt;span style="font-family:courier new;"&gt;def channel&lt;/span&gt;&lt;br /&gt;&lt;span style="font-family:courier new;"&gt;  @channel = @hp.at("font[@class='ScheduleChannel']").inner_html&lt;/span&gt;&lt;br /&gt;&lt;span style="font-family:courier new;"&gt;end&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;span style="font-family:courier new;"&gt;def date&lt;/span&gt;&lt;br /&gt;&lt;span style="font-family:courier new;"&gt;  @date = @hp.at("font[@class='ScheduleDate']").inner_html&lt;/span&gt;&lt;br /&gt;&lt;span style="font-family:courier new;"&gt;end&lt;/span&gt;&lt;/span&gt;&lt;br /&gt;&lt;/pre&gt;That's all pretty plain Jane so far. Here is what a typical table row looks like that contains the time of the program (reformatted for readability):&lt;br /&gt;&lt;pre&gt;&lt;span style="font-size:85%;"&gt;&amp;lt;tr bgcolor="F5F5F5"&amp;gt;&lt;br /&gt;&amp;lt;td width="40"&amp;gt;&lt;br /&gt;  &amp;lt;b&amp;gt;&amp;lt;font class="ScheduleTime"&amp;gt; 06:25&amp;lt;/font&amp;gt;&amp;lt;/b&amp;gt;&lt;br /&gt;&amp;lt;/td&amp;gt;&lt;br /&gt;&amp;lt;!--Time--&amp;gt;&lt;br /&gt;&amp;lt;td width="420"&amp;gt;&lt;br /&gt;  &amp;lt;font class="ScheduleTitle"&amp;gt;Codename: Kids Next Door&lt;br /&gt;  &amp;lt;!--Title--&amp;gt;&lt;br /&gt;  &amp;lt;/font&amp;gt;&lt;br /&gt;&amp;lt;/td&amp;gt;&lt;br /&gt;&amp;lt;td width="17"&amp;gt;&amp;lt;/td&amp;gt;&lt;br /&gt;&amp;lt;td width="188"&amp;gt;&amp;lt;/td&amp;gt;&lt;br /&gt;&amp;lt;!--SMS Reminder--&amp;gt;&lt;br /&gt;&amp;lt;td width="50" align="right"&amp;gt;&lt;br /&gt;  &amp;lt;a href="#" onclick="OpenAgeRestriction(1);return false;"&amp;gt;Family&amp;lt;/a&amp;gt;&lt;br /&gt;&amp;lt;/td&amp;gt;&lt;br /&gt;&amp;lt;!--Age Restriction--&amp;gt;&lt;br /&gt;&amp;lt;tr&amp;gt;&lt;br /&gt;&amp;lt;tr bgcolor="F5F5F5"&amp;gt;&lt;br /&gt;&amp;lt;td colspan="5"&amp;gt;&lt;br /&gt;  &amp;lt;p&amp;gt;A gang of 10 year olds takes on top secret missions, using fantastic home-made technology to safeguard their treehouse against attack and grown-ups.&amp;lt;/p&amp;gt;&lt;br /&gt;&amp;lt;/td&amp;gt;&lt;br /&gt;&amp;lt;/tr&amp;gt;&lt;br /&gt;&lt;/span&gt;&lt;/pre&gt;The time is similarly found in a font tag with a class tag of "ScheduleTime" and the program is found in a font tag with a class attribute of "ScheduleTitle". The program synopsis is however wrapped in a table column with a span of 5 and a paragraph tag.&lt;br /&gt;&lt;pre  style="font-family:courier new;"&gt;&lt;span style="font-size:85%;"&gt;def time&lt;br /&gt;@time = @hp.at("font[@class='ScheduleTime']").inner_html&lt;br /&gt;end&lt;br /&gt;&lt;br /&gt;def title&lt;br /&gt;@title = @hp.at("font[@class='ScheduleTitle']").inner_html&lt;br /&gt;end&lt;br /&gt;&lt;br /&gt;def synopsis&lt;br /&gt;@synopsis = (@hp/"td[@colspan=5]/p").first.inner_html&lt;br /&gt;end&lt;br /&gt;&lt;/span&gt;&lt;/pre&gt;You will notice that the extraction of the time and title holds no surprises. The synopsis extraction however is something new. I chose to use a CSS selector search for the synopsis by looking for the first td tag that has a colspan=5 attribute, followed by a p tag's contents (inner_html).&lt;br /&gt;&lt;br /&gt;If you were to print the values of the relevant variables you would see that there is still some cleaning up that needs to be done on them before they can be considered for programmatic consumption:&lt;br /&gt;&lt;pre  style="font-family:courier new;"&gt;&lt;span style="font-size:85%;"&gt;Channel: Cartoon Network (Africa)&lt;br /&gt;Date: 17&amp;amp;nbsp;April&amp;amp;nbsp;2007&lt;br /&gt;Time:  06:25&lt;br /&gt;Title: Codename: Kids Next Door              &amp;lt;!--Title--&amp;gt;&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;Synopsis: A gang of 10 year olds takes on top secret missions, using fantastic home-made technology to safeguard their treehouse against attack and grown-ups.&lt;br /&gt;&lt;/span&gt;&lt;/pre&gt;The channel and time looks fine so we'll just skip them for now. The date has some HTML entities in it so let's remove them using the handy &lt;a href="http://htmlentities.rubyforge.org/"&gt;HTMLEntities&lt;/a&gt; (I recommend installing from the gem) lib. The problem is that if they sneaked in some HTML entities in the date they may choose to do this elsewhere as well so let's not trust the input and ensure we sanitise all input in a generic way:&lt;br /&gt;&lt;pre&gt;&lt;span style="font-size:85%;"&gt;def initialize(url)&lt;br /&gt;@coder = HTMLEntities.new&lt;br /&gt;p sanitize(url)&lt;br /&gt;end&lt;br /&gt;&lt;br /&gt;def sanitize(string)&lt;br /&gt;@coder.decode(string)&lt;br /&gt;end&lt;br /&gt;&lt;/span&gt;&lt;/pre&gt;The only problem with this is that that HTMLEntities uses UTF-8 encoding which outputs (on my system) something like this for the date value:&lt;br /&gt;&lt;pre&gt;&lt;span style="font-size:85%;"&gt;"17\\302\\240April\\302\\2402007"&lt;/span&gt;&lt;br /&gt;&lt;/pre&gt;Not really ideal ... let's use the &lt;a href="http://www.ruby-doc.org/stdlib/libdoc/iconv/rdoc/index.html"&gt;iconv&lt;/a&gt; lib to get the &lt;a href="http://en.wikipedia.org/wiki/Utf-8"&gt;UTF-8&lt;/a&gt; string forced into a US-ASCII encoding:&lt;br /&gt;&lt;pre&gt;d&lt;span style="font-size:85%;"&gt;ef initialize(url)&lt;br /&gt;@coder = HTMLEntities.new&lt;br /&gt;@ic = Iconv.new('US-ASCII//TRANSLIT', 'UTF-8')&lt;br /&gt;p sanitize(url)&lt;br /&gt;end&lt;br /&gt;&lt;br /&gt;def sanitize(string)&lt;br /&gt;@ic.iconv(@coder.decode(string))&lt;br /&gt;end&lt;br /&gt;&lt;/span&gt;&lt;/pre&gt;Right, now to get back on track after that slight detour. To recap, we now have a strategy to get all the items we're interested in but the solution above is a little naive because it assumes we only have one day with one program. The complete schedule for a channel could span several days, each having variable amounts of programs per day.&lt;br /&gt;&lt;br /&gt;One can also extend the ideas above to make it a lot more usable by downloading multiple channels for you and possibly pretty print it, send it to yourself via email or drop it in a db for later processing or display.&lt;br /&gt;&lt;br /&gt;I'll cover these in followup articles to come ...&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/8719896753162286533-8027912349824642157?l=blog.ntrippy.net' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://blog.ntrippy.net/feeds/8027912349824642157/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=8719896753162286533&amp;postID=8027912349824642157' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/8719896753162286533/posts/default/8027912349824642157'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/8719896753162286533/posts/default/8027912349824642157'/><link rel='alternate' type='text/html' href='http://blog.ntrippy.net/2007/04/ruby-hpricot-program-guide-i.html' title='Ruby (Hpricot) Program Guide - I'/><author><name>Charl</name><uri>http://www.blogger.com/profile/17742115469030093068</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='26' src='http://3.bp.blogspot.com/_4MLU7uPNEIY/TP2zTauOWAI/AAAAAAAAABs/OXj8i31AC0E/S220/blue%2B69.jpg'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-8719896753162286533.post-8002439460765623639</id><published>2007-03-30T12:36:00.000-07:00</published><updated>2007-12-23T15:41:58.584-08:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='wrapper'/><category scheme='http://www.blogger.com/atom/ns#' term='ruby'/><category scheme='http://www.blogger.com/atom/ns#' term='composition'/><category scheme='http://www.blogger.com/atom/ns#' term='rails'/><category scheme='http://www.blogger.com/atom/ns#' term='select_datetime'/><category scheme='http://www.blogger.com/atom/ns#' term='rtfm'/><title type='text'>(Almost) Overriding helpers in rails</title><content type='html'>In a project I am currently working on I use the &lt;a href="http://api.rubyonrails.org/classes/ActionView/Helpers/DateHelper.html#M000580"&gt;select_datetime()&lt;/a&gt; helper to provide me with year + month + day + hour + minute drop down boxes. After using the UI for a while the customer mentioned that they do not require the full minute listing (from 00 to 59 minutes) for the minute drop down box and would prefer to only have 15 minute increments to shorten  the drop down and waste less scrolling time when choosing the minutes.&lt;br /&gt;&lt;br /&gt;Umm ... OK!&lt;br /&gt;&lt;br /&gt;This type of functionality can be provided in a few ways (that I&lt;br /&gt;know of ):&lt;br /&gt;&lt;ul&gt;&lt;li&gt;&lt;b&gt;Wrapper:&lt;/b&gt; Create your own helper wrapper&lt;br /&gt;&lt;/li&gt; &lt;li&gt;&lt;b&gt;Replace:&lt;/b&gt; Replace the original helper method in the module where it comes from&lt;br /&gt;&lt;/li&gt; &lt;li&gt;&lt;b&gt;RTFM:&lt;/b&gt; Read the docs first and then code&lt;br /&gt;&lt;/li&gt;&lt;/ul&gt;&lt;a href="http://api.rubyonrails.org/classes/ActionView/Helpers/DateHelper.html#M000580"&gt;select_datetime()&lt;/a&gt; is actually a composition of two other helpers:&lt;br /&gt;&lt;ul&gt;&lt;li&gt;&lt;a href="http://api.rubyonrails.org/classes/ActionView/Helpers/DateHelper.html#M000581"&gt;select_date()&lt;/a&gt;&lt;br /&gt;&lt;/li&gt;&lt;li&gt;  &lt;a href="http://api.rubyonrails.org/classes/ActionView/Helpers/DateHelper.html#M000582"&gt;select_time()&lt;/a&gt;&lt;br /&gt;&lt;/li&gt;&lt;/ul&gt;&lt;a href="http://api.rubyonrails.org/classes/ActionView/Helpers/DateHelper.html#M000582"&gt;select_time()&lt;/a&gt; is another composition of:&lt;br /&gt;&lt;ul&gt;&lt;li&gt;  &lt;a href="http://api.rubyonrails.org/classes/ActionView/Helpers/DateHelper.html#M000585"&gt;select_hour()&lt;/a&gt;&lt;br /&gt;&lt;/li&gt;&lt;li&gt;  &lt;a href="http://api.rubyonrails.org/classes/ActionView/Helpers/DateHelper.html#M000584"&gt;select_minute()&lt;/a&gt;&lt;br /&gt;&lt;/li&gt;&lt;li&gt;  &lt;a href="http://api.rubyonrails.org/classes/ActionView/Helpers/DateHelper.html#M000583"&gt;select_second()&lt;/a&gt;&lt;br /&gt;&lt;/li&gt;&lt;/ul&gt;Because we're simply interested in changing the behaviour of the minutes we will only be changing &lt;a href="http://api.rubyonrails.org/classes/ActionView/Helpers/DateHelper.html#M000584"&gt;select_minute()&lt;/a&gt;.&lt;br /&gt;&lt;br /&gt;&lt;span style="font-weight: bold;"&gt;Wrapper&lt;/span&gt;&lt;br /&gt;This approach is pretty straight forward. All you need to do is add your own helper as a wrapper for the helper you wish to masquerade. Unfortunately we cannot use this technique here because the helper we're using  (&lt;a href="http://api.rubyonrails.org/classes/ActionView/Helpers/DateHelper.html#M000580"&gt;select_datetime()&lt;/a&gt;) cannot provide us with the relevant functionality we want by simply massaging the helper's input or output.&lt;br /&gt;&lt;br /&gt;Bummer.&lt;br /&gt;&lt;br /&gt;&lt;span style="font-weight: bold;"&gt;Replace&lt;/span&gt;&lt;br /&gt;Your intuition may be itching at this point and that annoying inner voice you never ignore could be saying:&lt;br /&gt;&lt;br /&gt;'Why don't you just simply override the &lt;a href="http://api.rubyonrails.org/classes/ActionView/Helpers/DateHelper.html#M000580"&gt;select_datetime()&lt;/a&gt; in application_helper.rb or ActionView::Base class, lilly-livered limp-wristed sissy!'.&lt;br /&gt;&lt;br /&gt;In this case your inner voice needs to be muzzled. Helpers are included as mixins. This means the methods in the mixed in module become available to the classes using them as if they were part of the actual class.&lt;br /&gt;&lt;br /&gt;The &lt;a href="http://www.rubycentral.com/book/tut_modules.html"&gt;PickAxe&lt;/a&gt; book says it succinctly:&lt;br /&gt;&lt;br /&gt;&lt;span style="font-style: italic;"&gt;'In fact, mixed-in modules effectively behave as superclasses.'&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;Unfortunately this also means that if you change the methods in a module they will change for all classes that use the mixed-in module. If you want to change the helper behaviour you need to change the behaviour in the module that is providing the helper as a mixin, in this case  &lt;a href="http://api.rubyonrails.org/classes/ActionView/Helpers/DateHelper.html"&gt;ActionView::Helpers::DateHelper&lt;/a&gt;.&lt;br /&gt;&lt;br /&gt;Off we go and open &lt;a href="http://api.rubyonrails.org/classes/ActionView/Helpers/DateHelper.html"&gt;ActionView::Helpers::DateHelper&lt;/a&gt; to start mucking with &lt;a href="http://api.rubyonrails.org/classes/ActionView/Helpers/DateHelper.html#M000584"&gt;select_minute()&lt;/a&gt; so that we can support periods as the customer requested. Here is the relevant code:&lt;br /&gt;&lt;blockquote&gt;# File vendor/rails/actionpack/lib/action_view/helpers/date_helper.rb, line 190&lt;br /&gt;&lt;br /&gt;190:       def select_minute(datetime, options = {})&lt;br /&gt;191:         val = datetime ? (datetime.kind_of?(Fixnum) ? datetime : datetime.min) : ''&lt;br /&gt;192:         if options[:use_hidden]&lt;br /&gt;193:           hidden_html(options[:field_name] || 'minute', val, options)&lt;br /&gt;194:         else&lt;br /&gt;195:           minute_options = []&lt;br /&gt;196:           0.step(59, options[:minute_step] || 1) do |minute|&lt;br /&gt;197:             minute_options &lt;&lt; ((val == minute) ? 198:               %(#{leading_zero_on_single_digits(minute)}\n) : 199:               %(#{leading_zero_on_single_digits(minute)}\n) 200:             ) 201:           end 202:           select_html(options[:field_name] || 'minute', minute_options, options) 203:          end 204:       end&lt;/blockquote&gt;&lt;br /&gt;Alrighty! Let's add that stepping code to provide the periodic minutes ...&lt;br /&gt;&lt;br /&gt;&lt;span style="font-weight: bold;"&gt;RTFM&lt;/span&gt;&lt;br /&gt;Damn! Had I spent a little time drilling down through the documentation I would have noticed that the &lt;a href="http://api.rubyonrails.org/classes/ActionView/Helpers/DateHelper.html#M000584"&gt;select_minute()&lt;/a&gt; helper already provides the required functionality via its :minute_step option.&lt;br /&gt;&lt;br /&gt;Here is what the documentation says about this helper:&lt;br /&gt;&lt;pre&gt;&lt;code&gt;select_minute(datetime, options = {})&lt;br /&gt;&lt;br /&gt;Returns a select tag with options for each of the minutes 0 through 59 with the&lt;br /&gt;current minute selected. Also can return a select tag with options by minute_step&lt;br /&gt;from 0 through 59 with the 00 minute selected The minute can also be substituted&lt;br /&gt;for a minute number. Override the field name using the :field_name option, 'minute'&lt;br /&gt;by default.&lt;/code&gt;&lt;br /&gt;&lt;/pre&gt;So, all of this may seem a little pointless but the lesson here is check the docs before you take-off at light speed. I look forward to writing an actual article on overriding helpers in rails soon.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/8719896753162286533-8002439460765623639?l=blog.ntrippy.net' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://blog.ntrippy.net/feeds/8002439460765623639/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=8719896753162286533&amp;postID=8002439460765623639' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/8719896753162286533/posts/default/8002439460765623639'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/8719896753162286533/posts/default/8002439460765623639'/><link rel='alternate' type='text/html' href='http://blog.ntrippy.net/2007/03/almost-overriding-helpers-in-rails.html' title='(Almost) Overriding helpers in rails'/><author><name>Charl</name><uri>http://www.blogger.com/profile/17742115469030093068</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='26' src='http://3.bp.blogspot.com/_4MLU7uPNEIY/TP2zTauOWAI/AAAAAAAAABs/OXj8i31AC0E/S220/blue%2B69.jpg'/></author><thr:total>0</thr:total></entry></feed>
