Archive for the ‘ruby’ Category

Ruby script to recursively svn up/git pull your sources directory

Monday, March 24th, 2008

Do you have a directory full of checkouts of other people’s projects? For reference, for contribution work, etc?

Wouldn’t you like to update them all at once?

Here’s a basic ruby script that you should run at the root level of your “sources” directory. It will go through the directory, find all the folders, check if they’re under source control (supports svn, git and bzr) and if so, attempts to update them.

It’s not very nice to look at, but it’s saved me a lot of time, and I can’t find anything similar around so I thought I’d release it here. Please tell me if you have any suggestions or improvements.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
#!/usr/bin/env ruby
 
# Basic script to walk through all directories in a folder and perform an "scm up" on them.
# Supports svn, git and bzr
# Copyright (c) 2008 Sho Fukamachi
# Released under MIT License: http://www.opensource.org/licenses/mit-license.php
 
require 'pathname'
 
def execute(*cmd)
  begin
    cmd.flatten!
    $stdout.puts "executing: " + cmd.join(' ')
    system(*cmd)
    unless $?.success?
      fail "Command failed: " + cmd.join(' ')
       << 
    else
       << 
    end
    rescue RuntimeError
      puts "!!! command '#{cmd.join(' ')}' failed in #{}, retry manually?"
       << 
  end
end
 
 = ENV['PWD']
 = Dir.entries() # very dumb, basically an ls -la
 
 = []
 = []
 = []
 
def purge_invisible dirlist
  dirlist.delete_if do |entry|
    entry[0] == 46  # 46 is the hex number for fullstop
  end
end
 
def check_directories dirlist
  dirlist.delete_if do |entry|
    !FileTest.directory?(entry)
  end
end
 
purge_invisible 
check_directories 
puts "found directories: " + .join(' ')
 
def update_recursively dirlist
  dirlist.each do |path|
     = path
    Dir.chdir path
    if File::exists?('.svn')
      puts 'svn detected in ' + path
      execute 'svn', 'up'
    elsif File::exists?('.git')
      puts 'git detected in ' + path  
      execute 'git', 'pull'
    elsif File::exists?('.bzr')
      puts 'bazaar dected in ' + path
      execute 'bzr', 'pull'
    else
       << path
    end
    Dir.chdir 
  end
end
 
update_recursively 
puts
puts "updated in: " + .join(' ') if 
puts
puts "failed in: " + .join(' ') if 
puts
puts "unsupported: " + .join(' ') if 
puts
puts 'done!'

I know you might think it’s strange to put a copyright license on such a small script but it’s for your protection, not mine. I’ve seen copyright claims over lesser things. By explicitly releasing software code, no matter how simple, you can rest easy in its use anywhere. I wish everyone included licenses with everything; I’d feel a lot more comfortable using things I find on the net if it’s been explicitly released in this manner.

Simple irb extension script

Wednesday, March 19th, 2008

I tend to create projects as a directory, with a few files in it. I might have a classes.rb file, a Rakefile, utilities.rb, that kind of thing. And chances are, I want to access them all – and any gems they require – in irb.

I often wish I had a simple way to auto-require these gems or files on a per-project level for use in irb. MacOSX 10.5 comes with a very nice .irbrc file in /etc/irbrc, and I didn’t want to get into the game of rewriting it every time I make a new project. If you make your own .irbrc, the default one won’t load – and my editor of choice, TextMate, doesn’t show .files by default in its folder view.

So what I really want is a plain file I can put in the directory with a few requires in it. I’ll have a standard name, say, “irbx” for “irb extension”. If it’s there, anything in it will be loaded – if not, no problem. I want the script that loads it in /etc/irbrc so everything to do with irb config is in one place. And I want it super simple! Just some requires! Anything more complex, and I’ll put it in another file which is then required.

So I added a few lines to /etc/irbrc so it does what I want.

First, make a backup:

$ cp /etc/irbrc /etc/irbrc.backup

Then, add this:

## in /etc/irbrc
# Require local additions
begin
  require 'pathname'
  ext_file = ENV['PWD'] + "/irbx"
  if File::exists?(ext_file)
    eval File::read(ext_file)
    puts "extension file loaded from " + ext_file.to_s
  else
    puts "no extension file at " + ext_file.to_s
  end
end

I put it underneath “Activate auto-completion” but you can put it anywhere. I kept it inside the “unless defined” statement so it can still be overridden by an .irbrc file if chosen.

Now, if you create an irbx file like this:

## in ~/my_project_directory/irbx
require 'sequel'
require 'active_couch'
require 'classes' # local classes.rb file

and then start irb in the same directory, those libraries will be loaded. If it doesn’t exist, irb starts same as always. Excellent!

There are a zillion different ways to do this kind of thing, of course. But for me, the main priority is simplicity. Convention over configuration. If you want more, writing your own .ircbc file will override the default, including this. But for the vast majority of the time, this is exactly what I need.

Laika’s ThingFish hits 0.2.0

Thursday, March 13th, 2008

Laika’s interesting REST network filestore, ThingFish, has just reached milestone 0.2.0. The project can be thought of as a ruby-language, more flexible and lightweight implementation of LiveJournal’s MogileFS, and is actually by the same developers.

To get it running, you’ll need to do the following:

## install dependencies
$ sudo port install ImageMagick 
$ sudo gem install mp3info exifr mongrel pluginfactory uuidtools rdf-redland rmagick sequel_core sequel_model
$ wget http://prdownloads.sourceforge.net/clxmlserial/clxmlserial.1.0.pre4.zip
$ unzip clxmlserial.1.0.pre4.zip
$ cd unzip clxmlserial
$ sudo ruby install.rb
$ cd ..
## download and run thingfish
$ svn co svn://opensource.laika.com/thingfish/releases/0.2.0 thingfish
$ cd thingfish
$ chmod +x start
$ ./start

Thingfish should now be available at http://0.0.0.0:3474/.

UPDATE: To actually be able to upload a file, you’ll need to manually create the directory /tmp/thingfish:

mkdir /tmp/thingfish

UPDATE 2: Not easily usable yet. After a two-hour adventure to try and build the gem, no dice – this ticket pretty much sums it up. Sorry if I’ve spoilt the “fun” for you! UPDATE: might have a fix for this, see comments.

I’m still excited about this project, and they don’t owe me a thing, but unsatisfiable dependencies to some untagged trunk version of some guy’s library in his personal svn is not exactly good practise. I’m looking forward to getting started with a howto article or two – but if I can’t even install it as a gem with reasonable efficacy that’s not going to happen. Here’s hoping an 0.2.1 release follows soon fixing some of these showstopper issues.

UPDATE 3: Many of these issues have been fixed in trunk (thanks devs!), stay tuned for an initial howto.

Issues remain in running from trunk, at least in OSX 10.5.2. It’s currently impossible, apparently, to install the redland bindings necessary to properly load the rdf-redland gem used by ThingFish using macports. I’m unwilling to start installing stuff from source when I have a perfectly good ports system, so I’ve emailed the maintainer asking for an update and will keep on top of this.

Eval, friend and enemy

Sunday, March 9th, 2008

The prevailing wisdom in the Ruby world is that using eval is bad. It’s brittle, hackish and inflexible – if you find yourself using it, it’s usually a sign you’re doing something wrong.

Well, I agree, but the keyword is “usually”. Sometimes using eval is a lot better than any alternatives, and it can help work around problems in, say, other people’s code, where the design decisions they made stop you doing what you want. I avoided using it for a long time, maybe based on my acceptance of the consensus opinion – but recently I’ve had cases where it’s the “lesser of two evils”, and I wanted to share one with you.

The example is Rails’ horrible mailing system. It’s one of the worst parts of Rails – but let me temper that by saying it’s a hard problem and difficult to see how else they could have done it. A Mailer in Rails is kind of a pseudo-model black box that waits to be sent a list of parameters, then inserts some of them into a specific text file, then sends that text using the remainder of the parameters.

All very well and good so far. But the problem with this is when you want to have mail in multiple languages. So you don’t just have

Notifier.deliver_welcome_mail(params)

you have

Notfier.deliver_welcome_mail_eng(params)
Notfier.deliver_welcome_mail_jpn(params)
Notfier.deliver_welcome_mail_spa(params)

etc. All with corresponding entries in the model, and matching text files.

Now, I don’t know any way to get around the need for those text files and entries in the model that doesn’t involve writing some huge text generator (bad) or hacking Rails itself (even worse). But we can at least improve on the statements used to call these deliveries.

Here’s an example of what I used to have:

if .language_iso == 'zht'
  Notifier.deliver_invite_zht(, , subject)
elsif .language_iso == 'zhs'
  Notifier.deliver_invite_zhs(, , subject)
elsif .language_iso == 'jpn'
  Notifier.deliver_invite_jpn(, , subject)
else
  Notifier.deliver_invite_eng(, , subject)
end

That’s a shortened version of what can be much longer if or case statements (this is pretty old code..). But you can see this gets very nasty, very quickly.

Using eval we can change that to:

eval("Notifier.deliver_invite_#{.language_iso}(, , subject)")

Yes, it’s still nasty. But it’s one line of nastiness as opposed to 10 (or many more). I think this is one example of a case where using eval is definitely better than the alternative.

Next, I’m experimenting with trying to reduce the horrendous duplication in the Mailer pseudomodel itself.

Ruby web server in 7 lines

Monday, February 4th, 2008

OK OK it’s not exactly full featured. It listens on port 8080 and spits out exactly one line of invalid HTML. But it’s still pretty cool IMO : )

1
2
3
4
5
6
require 'socket'
server = TCPServer.new("127.0.0.1", 8080)
while socket = server.accept
  socket.puts "The current time is: " + Time.now.to_s
  socket.close
end

Add HTML to taste. Suggestions on how to make it shorter welcome!

UPDATE: Lol, I made it 6 lines.