Keeping it Small and Simple

2008.04.05

An mpd clone in Ruby

Filed under: Ruby Programming, Rubygame — Tags: , , , , , , , — Lorenzo E. Danielsson @ 18:02

I was bored, so I decided to write a clone of my music player of choice, the excellent mpd. I also put together a little client, similar to my favorite mpd client, the highly usable mpc.

These are of course watered-down clones that cannot match the original mpd. I wasn’t so much interested in writing an alternative to mdp as just writing something for the fun of it. Feel free to improve upon it.

You will need to have Rubygame installed on the machine that is running the server. There are alternatives that you could look into, but I happen to know Rubygame so I am using it. It is likely that I will at some point experiment with other libraries.

Server/client communication is done via XML-RPC. This makes everything very simple. Again, if you want to practice, you change to some other method for client/server communication (SOAP, your own protocol, or whatever).

Other things you could do include writing a GUI client (I recommend Tk, but Qt4 is also nice, Gtk less so, but at least its Ruby bindings are decent to work with). Or, if you think everything should run in the browser, why not a web client? Of course, you can also add more features. In that case I recommend looking into what mpd supports and try to implement as much of it as possible.

First the server, rmpd:

  1 #! /usr/bin/ruby
  2
  3 # A music playing daemon written in Ruby.
  4
  5 begin
  6   require rubygame
  7   require xmlrpc/server
  8   require optparse
  9 rescue LoadError => e
 10   msg, lib = e.to_s.split /\s+–\s+/
 11   abort "Unable to find #{lib}, cannot continue."
 12 end
 13
 14 class PlayerService
 15   def initialize(player)
 16     @player = player
 17   end
 18
 19   def add(song)
 20     @player.add song.to_i
 21   end
 22
 23   def lsc
 24     @player.lsc
 25   end
 26
 27   def lsq
 28     @player.lsq
 29   end
 30
 31   def play
 32     @player.play
 33   end
 34
 35   def next
 36     @player.next
 37   end
 38
 39   def stop
 40     @player.stop
 41   end
 42 end
 43
 44 class ControllerService
 45   def initialize(player)
 46     @player = player
 47   end
 48
 49   def shutdown
 50     @player.shutdown
 51   end
 52 end
 53
 54 class Player
 55   def initialize(path, port=8080, addr=*)
 56     @col = []
 57     @queue = []
 58     @current = nil
 59     @control = nil
 60
 61     parsedir(path)
 62
 63     @server = XMLRPC::Server.new port, addr
 64     @server.add_handler "player", PlayerService.new(self)
 65     @server.add_handler "controller", ControllerService.new(self)
 66     @server.set_default_handler do |name, *args|
 67       obj, meth = name.split /\./
 68       "No such command: #{meth}"
 69     end
 70
 71     Rubygame.init
 72     Rubygame::Mixer.open_audio
 73   end
 74
 75   def run
 76     @server.serve
 77   end
 78
 79   def add(song)
 80     @queue << @col[song]
 81     "Added #{song}: #{File.basename(@col[song])}"
 82   end
 83
 84   def lsc
 85     return "Collection is empty." if @col.empty?
 86
 87     str = ""
 88     @col.each_index do |idx|
 89       str += "#{idx.to_s.rjust(3)}: #{File.basename(@col[idx])}\n"
 90     end
 91
 92     return str
 93   end
 94
 95   def lsq
 96     return "Queue is empty." if @queue.empty?
 97
 98     str = ""
 99     @queue.each_index do |idx|
100       str += "#{idx.to_s.rjust(3)}: #{File.basename(@queue[idx])}\n"
101     end
102
103     return str
104   end
105
106   def play
107     return "Already playing" if !@current.nil? && @current.playing?
108     return "Queue is empty" if @queue.empty?
109
110     begin
111       song = @queue.shift
112       @current = Rubygame::Mixer::Music.load_audio song
113       @current.play
114       control
115       "Playing #{File.basename(song)}"
116     rescue 
117       "Something went wrong"
118     end
119   end
120
121   def next
122     return "Not playing." if @current.nil? || !@current.playing?
123     @control.exit unless @control.nil?
124     @current.stop
125     sleep 1 while @current.playing?
126     play
127   end
128
129   def stop
130     return "Not playing." if @current.nil? || !@current.playing?
131
132     @control.exit unless @control.nil?
133     @current.stop
134     "Stopped."
135   end
136
137   def control
138     @control = Thread.new {
139       sleep 1 while @current.playing?
140       play unless @queue.empty?
141     }
142
143     @control.run
144   end
145
146   def shutdown
147     @server.shutdown
148     Rubygame::Mixer.close_audio
149     Rubygame.quit
150     "Shutting down server."
151   end
152
153   def parsedir(path)
154     Dir.new(path).each do |file|
155       next if file =~ /^\./
156
157       if File.directory? "#{path}/#{file}"
158         parsedir "#{path}/#{file}"
159       else
160         @col << "#{path}/#{file}"
161       end
162     end
163   end
164 end
165
166 options = {
167   :addr => 127.0.0.1,
168   :port => 8080,
169   :dir => "#{ENV[HOME]}/music"    
170 }
171
172 begin
173   OptionParser.new do |opts|
174     opts.banner = "usage: #{$0} [options]"
175
176     opts.on("-a", "–address=ADDR", String, "Address to listen on.") do |addr|
177       options[:addr] = addr
178     end
179
180     opts.on("-p", "–port=PORT", Integer, "Port to listen on.") do |port|
181       options[:port] = port
182     end
183
184     opts.on("-d", "–dir=DIR", String, "Collection directory.") do |dir|
185       options[:dir] = dir
186     end
187   end.parse!
188 rescue
189   abort "Command line foo!"
190 end
191
192 mpd = Player.new options[:dir], options[:port], options[:addr]
193 mpd.run

Here is the client, rmpc:

 1 #! /usr/bin/ruby
 2
 3 # A client for rmpd.
 4
 5 require xmlrpc/client
 6 require optparse
 7
 8 options = {
 9   :port => 8080,
10   :addr => "127.0.0.1"
11 }
12
13 begin
14   OptionParser.new do |opts|
15     opts.banner = "usage: #{$0} [options] command"
16
17     opts.on("-a", "–address=ADDR", String, "Address of server") do |addr|
18       options[:addr] = addr
19     end
20
21     opts.on("-p", "–port=PORT", Integer, "Port of server") do |port|
22       options[:port] = port
23     end
24   end.parse!
25 rescue
26   abort "Command line foo!"
27 end
28
29 abort "No command." if ARGV.empty?
30
31 begin
32   server = XMLRPC::Client.new options[:addr], "/RPC2", options[:port]
33 rescue
34   abort "No contact with server."
35 end
36
37 player = server.proxy "player"
38 control = server.proxy "controller"
39
40 command = ARGV.shift
41 if command == shutdown
42   puts control.shutdown
43 else
44   puts player.send(command, *ARGV)
45 end

2008.03.01

Testing JRuby 1.1 RC2

Filed under: JRuby, Ruby Programming — Tags: , , , — Lorenzo E. Danielsson @ 18:20

Of late my schedule has been a bit more hectic than I had hoped. So I haven’t had as much time I would have liked to play around with JRuby and Rubinius. But the other day I was finally able to find a little time. So I downloaded the latest JRuby 1.1 release candidate, which is RC2.

So far it has worked well. I hear that 1.1 is significantly faster than previous versions of JRuby. I cannot comment on that yet, because I haven’t begun playing around with the types of applications where speed would matter.

Installing it

I downloaded the .tar.gz package and installed it to /opt. Do this (as root):

# cd /opt
# tar zxf /home/lorenzod/dl/jruby-bin-1.1RC2.tar.gz

After that you will want to either (1) add /opt/jruby-1.1RC2/bin to PATH or (2) create a few symlinks. I chose the symlink option.

# JD=/opt/jruby-1.1RC2/bin
# ln -s $JD/jruby /usr/local/bin
# ln -s $JD/jrubyc /usr/local/bin
# ln -s $JD/jirb /usr/local/bin
# ln -s $JD/gem /usr/local/bin/jgem

You can of course add all the links that you need. These were enough for me to get started. Note that I named the link to jruby’s gem jgem.

Testing the compiler

I wanted to see how well the JRuby compiler works. So, I quickly put together the following little test program.


 1 #! /usr/bin/jruby
 2
 3 # Count the number of times the user clicks some buttons.
 4
 5 require java
 6
 7 include_class java.awt.GridLayout
 8 include_class java.awt.event.ActionListener
 9 include_class java.awt.event.WindowListener
10 include_class java.lang.System
11 include_class javax.swing.JButton
12 include_class javax.swing.JFrame
13
14 class ClickButton < JButton
15   include ActionListener
16
17   def initialize(text)
18     @count = 0
19     @text = text
20     super "#{@text} (0)"
21     add_action_listener self
22   end
23
24   def actionPerformed(event)
25     @count += 1
26     self.text = "#{@text} (#{@count})"
27   end
28 end
29
30 class MainWindow < JFrame
31   include WindowListener
32   include ActionListener
33
34   def initialize
35     super "Click Counter"
36     set_layout GridLayout.new(5, 1)
37     @total = 0
38
39     1.upto 5 do |n|
40       button = ClickButton.new "Button #{n}"
41       button.add_action_listener self
42       add button
43     end
44
45     add_window_listener self
46     pack
47   end
48
49   def actionPerformed(event)
50     @total += 1
51   end
52
53   # Bah, humbug!
54   def windowActivated(event); end
55   def windowClosed(event); end
56   def windowDeactivated(event); end
57   def windowDeiconified(event); end
58   def windowIconified(event); end
59   def windowOpened(event); end
60
61   def windowClosing(event)
62     puts "Total clicks: #{@total}"
63     System::exit 0
64   end
65 end
66
67 MainWindow.new.set_visible(true)

First I tried to run it normally.

% jruby cc.rb

That worked fine. After a few seconds a Swing application popped up on my screen. Next I tried compiling it.

% jrubyc cc.rb
Compiling cc.rb to class ruby/cc
% ls ruby/
cc.class

To run this program I did the following:

% java -cp /opt/jruby-1.1RC2/lib/jruby.jar:. ruby.cc

It worked. JVM takes a moment or two to fire up, then the application window pops up. Of course, I haven’t looked into the options to jrubyc yet, but at least this was enough to get me started.

Next, I’ll want to try JRuby out in Netbeans. I haven’t tried JRuby on Rails yet, so that is something I want to dedicate some time to. So expect more on JRuby from me in the near future.

2008.02.22

Rubygame tutorial #3: In control

Filed under: Ruby Programming, Rubygame Tutorial — Tags: , , , — Lorenzo E. Danielsson @ 07:37

Welcome back. Due to (somewhat) popular demand, I’m going to continue the rubygame tutorials. In this tutorial we will look at a keyboard event: KeyDownEvent.

Looking back for a second or two

Let us remind ourselves of the steps we need to take to create a very simple Rubygame program:

  1. Create a screen
  2. Create a event queue
  3. Create a clock
  4. Enter the main loop. The main loop does drawing and contains an event loop.
  5. The event loop should have some exit condition

That is really it. Armed with only this knowledge and a few drawing methods we can do a lot already.

A single pixel

Let’s start off with a program that plots a single pixel. The following program will just plot a single pixel at the center of the screen.


 1 #! /usr/bin/ruby
 2
 3 # A pixel.
 4
 5 require rubygame
 6 include Rubygame
 7
 8 screen = Screen.new([640, 400])
 9 events = EventQueue.new
10 clock = Clock.new
11 clock.target_framerate = 120
12 running = true
13
14 while running
15   events.each do |event|
16     case event
17     when QuitEvent
18       running = false
19     end
20   end
21
22   screen.fill([0, 0, 0])
23   screen.set_at([screen.w / 2, screen.h / 2], [255, 255, 255])
24   screen.update
25   clock.tick
26 end
27

Not too interesting I guess. I single pixel and it doesn’t even move! Let’s change that.

Exercises

1. Modify the program to display a blue pixel.

2. Modify the progam to plot the pixel at the position 100, 100.

3. Change the screen size to 500, 500.

4. Write a program that takes the pixel coordinates as command-line arguments. Provide the center as a default position if no arguments are passed. Give an error if the coordinates are beyond the size of the screen.

Preparing for motion

Let’s give our pixel a life of its own. We will create a pixel class. Here is a new version of the program.


 1 #! /usr/bin/ruby
 2
 3 # Another pixel.
 4
 5 require rubygame
 6 include Rubygame
 7
 8 class Pixel
 9   attr_reader 😡, :y
10
11   def initialize(x, y, surface)
12     @x, @y = x, y
13     @surface = surface
14   end
15
16   def plot
17     @surface.set_at [@x, @y], [255, 255, 255]
18   end
19 end
20
21 screen = Screen.new [640, 400]
22 events = EventQueue.new
23 clock = Clock.new
24 clock.target_framerate = 120
25 pix = Pixel.new(screen.w/2, screen.h/2, screen)
26 running = true
27
28 while running
29   events.each { |event|
30     case event
31     when QuitEvent
32       running = false
33     end
34   }
35
36   pix.plot
37   screen.update
38   clock.tick
39 end

We still only see a single pixel at the middle of the screen. Still no fun! 😦

On the other hand, we now have a pixel that knows its own position and is able to “draw itself” onto its target surface.

Exercises

1. Add a color attribute to the Pixel class. Set the color attribute to red before plotting the pixel.

2. Create five Pixel instances, each with different coordinates and plot each of them.

Now, move it!

So far we have used QuitEvent to determine if the user has closed the Rubygame screen. Now let’s look at another event: KeyDownEvent. This event is triggered any time a key is pressed down. We can find out which key via KeyDownEvent#key. Let’s use this to add the ability to move the pixel around.

We give our Pixel class some new methods: up, down, left, right and stop. Internally, it will also have two new fields: one to determine the horizontal direction and one to determine the vertical direction.


 1 #! /usr/bin/ruby
 2
 3 # Moving pixel.
 4
 5 require rubygame
 6 include Rubygame
 7
 8 class Pixel
 9   attr_reader 😡, :y
10
11   def initialize(x, y, surface)
12     @x, @y = x, y
13     @dx, @dy = 0, 0
14     @surface = surface
15   end
16
17   def up
18     @dx, @dy = 0, –1
19   end
20
21   def down 
22     @dx, @dy = 0, 1
23   end
24
25   def left
26     @dx, @dy = –1, 0
27   end
28
29   def right
30     @dx, @dy = 1, 0
31   end
32
33   def stop
34     @dx = @dy = 0
35   end
36
37   def move
38     tx, ty = @x + @dx, @y + @dy
39     return unless (0..@surface.w-1).include? tx
40     return unless (0..@surface.h-1).include? ty
41
42     @x, @y = tx, ty
43   end
44
45   def plot
46     @surface.set_at [@x, @y], [255, 255, 255]
47   end
48 end
49
50 screen = Screen.new [640, 400]
51 events = EventQueue.new
52 clock = Clock.new
53 clock.target_framerate = 120
54 pix = Pixel.new(screen.w/2, screen.h/2, screen)
55 running = true
56
57 while running
58   events.each { |event|
59     case event
60     when QuitEvent
61       running = false
62     when KeyDownEvent
63       case event.key
64       when K_UP: pix.up
65       when K_DOWN: pix.down
66       when K_LEFT: pix.left
67       when K_RIGHT: pix.right
68       when K_SPACE: pix.stop
69       end
70     end
71   }
72
73   screen.fill [0, 0, 0]
74   pix.move
75   pix.plot
76   screen.update
77   clock.tick
78 end

Exercises

1. Try to comment out the screen.fill line inside the drawing loop. What happens?

2. Currently, the pixel can move in four directions: up, down, left and right. How could you modify the program to allow the pixel to move diagonally. (There are several ways, experiment as much as you can.)

3. Modify the program to create three Pixels. Give each a different starting position. When one of the motion keys is pressed, all three should move.

4. What if you wanted to create three Pixels that each used a different set of keys to control its motion, how would you go about that? Would you make any changes to the Pixel class itself?

5. Rewrite the program to check the KeyUpEvent instead of KeyDown. How does this affect the program?

Conclusion

In this tutorial you have learned how to use the KeyDown event. We will continue with our moving pixel in the next tutorial (hey, I told you I was going to go real slow).

In the meantime, experiment with what you have learned so far. There are loads of things you can do to get extra practice. For instance you could modify the program so that the pixel only moves while a key is pressed down. Once the key is released it should stop (use both KeyDownEvent and KeyUpEvent). The more you practice the better you will become.

2008.02.12

Use Ruby’s Array#delete_if

Filed under: Ruby Programming — Tags: , — Lorenzo E. Danielsson @ 15:20

In case you didn’t know, Ruby has a convenient way of allowing you to remove items from an array called delete_if. You simply pass in a block with an expression. All items in the array for which the block expression evaluates to true are removed. Here are a few simple examples:

Remove a specific item:

 1 #! /usr/bin/ruby
 2
 3 wd = %w[Monday Tuesday Wednesday Thursday Friday Saturday Sunday]
 4
 5 # Tell me why
 6 # I don’t like Mondays
 7 # I want to shoot
 8 # The whole day down
 9 wd.delete_if { |day| day == "Monday" }
10 p wd

Remove all words that end with the letter s:

1 #! /usr/bin/ruby
2
3 words = %w[eggs ham vinegar salt pancakes wine oranges]
4 words.delete_if { |w| w =~ /s$/ }
5 p words

Remove odd numbers.

1 #! /usr/bin/ruby
2
3 nums = [ 1, 2, 3, 4, 5, 6, 7, 8, 9 ]
4 nums.delete_if { |n| n % 2 > 0 }
5 p nums

Remove people under 18.

 1 #! /usr/bin/ruby
 2
 3 class Person
 4   attr_reader :name, :age
 5
 6   def initialize(name, age)
 7     @name = name
 8     @age = age
 9   end
10
11   def to_s
12     @name
13   end
14 end
15
16 class Bar
17   def enter(group)
18     group.delete_if { |person| person.age < 18 }
19     puts "#{group.join(, )} are welcome!"
20   end
21 end
22
23 drunken_sailor = Bar.new
24 drunks = [
25   Person.new("Tom", 28),
26   Person.new("Dick", 17),
27   Person.new("Harry", 18)
28 ]
29
30 drunken_sailor.enter drunks

Delete all items:

1 #! /usr/bin/ruby
2
3 nums = [ 1, 2, 3, 4, 5, 6, 7, 8, 9 ]
4 nums.delete_if { true }
5 p nums

Delete no items (a way of doing a NOP in Ruby).

1 #! /usr/bin/ruby
2
3 nums = [ 1, 2, 3, 4, 5, 6, 7, 8, 9 ]
4 nums.delete_if { false }
5 p nums

There you have it. Using delete_if makes your life simpler, so just use it.

2008.01.31

Why I keep stressing students to write more code

Filed under: Ruby Programming — Tags: , , , — Lorenzo E. Danielsson @ 21:08

One thing I always tell my students is that if they really want to become good at programming they have to write a lot of code. They shouldn’t just work on projects but also create a separate directory dedicated to little experimental code snippets.

When somebody asks me if “such and such” works, I normally tell them to go and write some code to test it. I usually refuse to answer such questions until I can see some code. Needless to say, many of my students over the years have felt that I’m a royal pain in the ass.

Today I gave one of my Ruby students a little challenge: to extend Ruby’s Array class with methods to rotate the array left and right. The ROL and ROR methods should be familiar to every programmer unless, of course, you fell asleep during assembly language class. In this case we’re not even rotating bits in a byte but elements of an array.

At the end of the day, my student told me that he’ll present his solution tomorrow. I think he’s got it mostly figured out but he’s over-complicating it. This student is at the stage where he has a firm grasp of the basic elements of Ruby. But he needs to learn how to combine the facilities that the language gives him in different ways to produce the results he wants.

I’ve come to realize that this is the most difficult part of learning how to program, and the time when most students risk giving up. I guess it’s a bit similar to learning how to speak a new language. It’s fairly simple to learn the formal grammar of a language and to pick up a basic vocabulary of common, every-day expressions.

But, to combine these building blocks into more interesting sentences is challenging. I often fumble on sentences in Ga where I know all the individual words I need to use, but somehow fail to combine them in a way where I can make myself understood. The solution is simple: I have to force myself to go out and listen to and speak Ga much more. Most importantly, I have to practice the basic constructs of the language until I can use them “without” thinking.

Exactly the same thing goes with writing code. The syntax of the language and other basic elements (the most commonly used classes in Ruby’s core classes) need to be practiced until the student “becomes fluent” in using them.

So I keep on forcing my students to write loads and loads of small, trivial programs. It gets very boring for them from time to time, and I’m sure some of them would love to run me over with a freight train or something like that. But, at end my responsibility is to turn them into programmers, not win popularity contests. And I do feel that that the extra time spent on programming basics pays of for them at a later stage.

If you are a struggling with learning how to program, don’t give up. Give yourself little challenges every day. Don’t tell me you can’t think of any, because their are loads of them around you. They don’t have to be “useful” (whatever that means), and it’s okay to reinvent the wheel over and over again while you are learning.

Back to our rotating array. Anybody who has not only used, but has understood what the following methods do won’t have any problems implementing it.

  • Array#shift
  • Array#<<
  • Array#unshift
  • Array#pop

Of course there are other ways of solving it, but I’m allergic to solutions that are more complex than they need to be. It gives me a bad rash.


 1 #! /usr/bin/ruby
 2
 3 # Rotate an array.
 4
 5 class Array
 6   def rol!(n=1)
 7     n.times { self << self.shift }
 8   end
 9
10   def ror!(n=1)
11     n.times { self.unshift self.pop }
12   end
13 end
14
15 wd = %w[Monday Tuesday Wednesday Thursday Friday Saturday Sunday]
16 puts "Original array:"
17 p wd
18
19 puts "Rotating right 3 times."
20 wd.ror! 3
21 p wd
22
23 puts "Rotating left once."
24 wd.rol!
25 p wd
26     
27 puts "Rotating left nine times (should be like rotating left twice)."
28 wd.rol! 9
29 p wd

2008.01.30

My #1 Ruby wishlist item

Filed under: Ruby Programming — Tags: — Lorenzo E. Danielsson @ 00:35


String#=~ s/PATTERN/REPLACEMENT/egimosx

2008.01.27

Another Ruby implementation: XRuby

Filed under: Ruby Programming — Tags: , , , , — Lorenzo E. Danielsson @ 21:21

In #rubinius on Freenode today, I heard of another Ruby implementation called xruby. It compiles ruby files to Java class files that can be run in the JVM. I just downloaded xruby-0.3.2, which appears to be the latest release at least as this time of writing.

It’s cool that there is a lot of interest in Ruby and so many different implementations coming up. At this point in time I think it’s a good idea that there are several different implementations. I also think it’s cool if at least some of them take the opportunity to experiment a little bit, without straying away too far from the core Ruby language of course. Several implementations gives us the ability to compare and contrast ideas, to see what works well and what doesn’t.

What I wouldn’t want is that each implementation over time adding its own extensions to the point where we end up with 100,000 incompatible Ruby implementations. I don’t want Ruby to become fragmented to point where we have Ruby, Ruby++, Ruby#, RubyLite and RubyDuby. Wouldn’t you also hate to have to sit and add a section in your READMEs about which Rubies are supported or to go to download some Ruby program just to be confronted with 1000 different packages for different rubies?

I understand the good folks over at the Rubinius have added a few extensions. A few methods here and there that have been added to core Ruby classes. If these extensions prove useful they should obviously go into all Ruby implementations. I don’t have anything against Rubinius at all (on the contrary, I’m very excited about it). But I’d be reluctant to use a feature that is only available in Rubinius, at least if it is going to remain a Rubinius-specific extension.

JRuby obviously has its own set of extensions which are required in order to interact with the Java class libraries and to be a good citizen of the JVM community. JRuby manages to be a good citizen of the Ruby community as well.

And now there’s XRuby. That was a lie. It’s been around for quite some time, just that I hadn’t heard of it before today. I’ve managed to build it while I’m writing this post. I compiled one of the samples. It seems to compile and JARify the ruby sources. I haven’t been able to figure out how to run the compiled program yet (complaining about not finding RubyProgram.class), but I haven’t tried all that hard yet.

Of course, I hear there is a Ruby implementation for Mono as well. But an aptitude search doesn’t turn up anything interesting. It could be that it’s not in Debian yet. It could also be that it’s got a weird name that has nothing to do with Ruby. I don’t really have much experience with Mono.

What I think is important is that the various communities work closely together so that all the good stuff finds its way back and becomes an official (whatever *that* means) part of Ruby eventually. I guess in the end MRI is the One Ruby to rule them all, so if a feature makes it there it is likely to get adapted by the others. I hope MRI doesn’t turn the others into mindless zombie (read: nazgul) processes that only know how to obey their master.. 😉

Enough already! I’m a programmer not a damn philosopher.

2008.01.25

Simple wallpaper tool in Ruby

Filed under: Ruby Programming — Tags: , , , , — Lorenzo E. Danielsson @ 15:39

Ben is constantly on the look-out for small applications that he can write in order to practice his Ruby skills. This morning he told me he wants to write a “wallpaper switcher” (or whatever you call those things). It seemed like an interesting little program, so I hastily put this together.


 1 #! /usr/bin/ruby
 2
 3 # A wallpaper tool.
 4
 5 require optparse
 6
 7 def parsedir(path)
 8   a = []
 9
10   Dir.new(path).each do |file|
11     next if file =~ /^\./
12   
13     fp = "#{path}/#{file}"
14     File.directory?(fp) ? a.concat(parsedir(fp)) : a << fp
15   end
16   a
17 end
18
19 options = {
20   :dir => nil,
21   :file => nil,
22   :interval => 60,
23   :random => false,
24   :use => wmsetbg
25 }
26
27 begin
28   OptionParser.new do |opts|
29     opts.banner = "usage: #{$0} [options]"
30
31     opts.on("-d", "–directory=DIR", String, "scan directory") do |dir|
32       options[:dir] = dir
33     end
34
35     opts.on("-f", "–file=FILE", String, "read wallpapers from file") do |file|
36       options[:file] = file
37     end
38
39     opts.on("-i", "–interval=I", Integer, "set the interval") do |interval|
40       options[:interval] = interval
41     end
42
43     opts.on("-r", "–[no-]random", "randomize the order") do |r|
44       options[:random] = r
45     end
46
47     opts.on("-u", "–use=APP", String, "set the utility to use") do |use|
48       options[:use] = use
49     end
50   end.parse!
51 rescue OptionParser::InvalidOption => err
52   $stderr.print err.to_s + "\n"
53   exit
54 rescue OptionParser::MissingArgument => err
55   $stderr.print err.to_s + "\n"
56   exit
57 end
58
59 wp = []
60 wp.concat(IO.readlines(options[:file])) unless options[:file].nil?
61 wp.concat(parsedir(options[:dir])) unless options[:dir].nil?
62 wp.collect! { |image| image.chomp }.delete_if { |image| !File.exists? image }
63 abort "No wallpapers in list." if wp.empty?
64
65 %w[TERM INT].each do |sig|
66   trap(sig) {
67     puts "Are you crazy?? What are you doing to me?"
68     exit
69   }
70 end
71
72 loop do
73   (options[:random] ? wp.sort_by { rand } : wp).each { |image|
74     `#{options[:use]} #{image} > /dev/null 2>&1`
75     sleep options[:interval]
76   }
77 end

Improving it

Several ideas come to mind:

  • Add standard options like –help
  • Handle errors.
  • Make sure all the files added to the wp array actually are images.
  • Add an option to make the program run through the wallpapers once only (or a certain number of times.
  • Currently all the images get scaled by default (using wmsetbg). Add options for centering, tiling etc. images.
  • I’m calling a utility called wmsetbg, which comes with Window Maker. Instead of doing this, you could set the “paint” an image on the root window yourself. Look into Ruby/Xlib, Imlib2-ruby or any other library that you know of that will allow you access to the root window.
  • I’m sure you can think of lots more.

If you really wanted to take this little exercise a bit further you could create a custom format for the wallpapers file. For example, it could look like this

path/to/wallpaper:options

Where options would be something like scale, center, tile, etc. You could take it way too far and create an XML file with all kinds of options, such as display such-and-such image only during a specific time of the day, or on a specific day or interval of days. This way you could have special images for special occasions, such as Yule, Easter and so on. If you do decide to implement this, you may also want to consider a visit to your shrink. You are going waaay overboard and need help. Urgently.

Does it work with JRuby?

I’m glad you asked. Short answer: yes. Long answer: Hell, yeah! 😉

Does it work with Ruby 1.9?

Yes it does. Notice that there are some things you could do to ruby1.9-ify it. For instance, you could initialize the options array as follows:


options = {
  dir: nil,
  file: nil,
  interval: 60,
  random: false,
  use: wmsetbg
}

Does it work with Rubinius?

Yes, of course it does! 🙂

I did discover one small annoyance. Hitting CTRL+C will display the “Are you..” message alright, but the program will keep running. Not sure why, but I’ll look into that.

2008.01.24

Scrambling words in Ruby

Filed under: Ruby Programming — Tags: , , , — Lorenzo E. Danielsson @ 22:50

Somebody needed a program that takes an input string and scrambles the letters in each of the words. Capitalization and punctuation should be intact. I don’t normally solve people’s exercises for them, but I did decide to write something of my own. I get severe allergic attacks from typing too much, so instead of Java, I chose Ruby as an implementation language.

Scramble a string

The first part of the problem is to scramble a string. The following seems to work fairly well, and should be easy to understand:


1 def scramble(str)
2   s = str.split(//).sort_by { rand }.join()
3   s =~ /[A-Z]/ && s =~ /[a-z]/ ? s.capitalize : s
4 end
5
6 puts scramble("Hello world")

The first line in the method does exactly what you would think it does: splits a string into an array of characters, “sorts” them randomly (whatever that means), and joins the characters back into a string.

The second line maybe deserves a comment. I use a very naive (not to mention stupid) way of deciding whether or not to capitalize the string. If I can find both upper-case and lower-case letters then I capitalize, otherwise I don’t. It works well enough for normal English, but if you pass some camelCased strings then it probably won’t do what you expect. Then again, that wasn’t in my friends specification..

One thing before we continue. Scrambling looks like an interesting candidate for the String class, don’t you think? Okay, let’s fix that right away!


1 class String
2   def scramble
3     s = self.split(//).sort_by { rand }.join()
4     s =~ /[A-Z]/ && s =~ /[a-z]/ ? s.capitalize : s
5   end
6 end
7
8 puts "Hello world".scramble

Now, that looks so much better, wouldn’t you agree? Now we can go on and figure out how to scramble individual words instead of strings.

Scrambling words

Just as a reminder: punctuation characters are supposed to be left in place. So, if I have a string like Hello, world! then the comma and the exclamation mark (or the bang, if you have been using UNIX for too long) need to be left in place.

So the procedure will be to split the string on white-space. That way we get an array of words, possibly with punctuation characters attached. We then break out the actual word and scramble it. The following would do just that:


 1 def scramble_words
 2   ret = []
 3   self.split(/\s+/).each { |nws|
 4     nws.scan(/^(\W*)(\w*)(\W*)$/) { |pre, word, post|
 5       ret << pre + word.scramble + post
 6     }
 7   }
 8
 9   ret.join " "
10 end
11

Of course, this method should be added to the String class as well. You can find the full code below. The first thing we do is split on one or more white-spaces. Since I join the array of words at the end of the method separated by a single string this means that you lose multiple consecutive white-spaces in the string. This may be a problem. The good news is that it is trivial to fix. Can you figure it out? Good, I knew you would.

Once I have split out the “words”, I use String#scan to separate any punctuation characters from the word itself. You may wonder why I use ^(\W*)(\w*)(\W*)$/ as the regex. It would appear to be better to use ^(\W*)(\w+)(\W*)$/. Well, the fact is that you could end up having one or more lone punctuation characters, which means the match would fail with a \w+.

Now we are ready to put it all together.

The scramble-words program

The String class, extended with the scramble and scramble_words methods follows. There is also a little loop there that lets the user enter strings, which get passed to scramble_words.


 1 #! /usr/bin/ruby
 2
 3 # Scramble words.
 4
 5 class String
 6   def scramble
 7     s = self.split(//).sort_by { rand }.join()
 8     s =~ /[A-Z]/ && s =~ /[a-z]/ ? s.capitalize : s
 9   end
10
11   def scramble_words
12     ret = []
13     self.split(/\s+/).each { |nws|
14       nws.scan(/^(\W*)(\w*)(\W*)$/) { |pre, word, post|
15         ret << pre + word.scramble + post
16       }
17     }
18
19     ret.join " "
20   end
21 end
22
23 loop do
24   print "Enter something: "
25   str = gets.chomp
26   exit if str.empty?
27   puts str.scramble_words
28 end

Okay. So how does this fare?


% ruby scramble.rb
Enter something: "Hello, world!", said the C programmer.
"Hello, dlorw!", adis the C mreorrgapm.

First test worked okay. Let’s try something else.


Enter something: Hey! Hey! My, my, Rock'n'roll will never die!
Hey! Eyh! My, my, llwi nvree die!

Uh-oh! Seems we lost something. If you think about it, it’s obvious why. I don’t really expect my friend would be parsing words like Rock’n’roll, but words with an apostrophe are likely: I’m, I’ll, let’s, he’s, don’t and so on. Considering the frequency of this character you could fix this by changing the regex in line 14 to look like this:


/^(\W*)([\w|’]*)(\W*)$/

That fixes words with an apostrophe, but for some reason capitalization on such words gets screwed. I haven’t looked into it yet. Feel free to fix it as an exercise. If you’ve solved that and need more, figure out how scrambling of words containing apostrophe should behave. Should the position of the apostrophe be maintained? If so, the program currently does not behave well. So go on and fix it! 😉

If we disregard a few warts here and there the program does mostly what it’s supposed to. Or at least it appears so. I cannot say I have given it any extensive amount of testing.

But wait! We’re being a bit rude by only testing it on ruby 1.8. Let’s try it out on a few other implementations of Ruby (the language).

Does it work with ruby 1.9?

Ruby 1.9 works fine, but the program goes to gets without displaying the prompt. I’m not sure why, but I’ve seen it before. The fix is simple. You just need to flush stdout as shown below.


loop do
  print "Enter something: "
  $stdout.flush
  str = gets.chomp
  exit if str.empty?
  puts str.scramble_words
end

Does it work with JRuby?

What a silly question. Of course it does. What did you expect? 😉

Does it work with Rubinius?

Well, we won’t know unless we try it, will we?


% rbx scramble.rb
Enter something: An exception has occurred:
undefined local variable or method `gets' for main (NameError)

Backtrace:
Object#gets (method_missing_cv) at kernel/core/object.rb:117
main.__script__ at ./scramble.rb:26
CompiledMethod#as_script at kernel/bootstrap/primitives.rb:35
Compile.single_load at kernel/core/compile.rb:204
Compile.unified_load {} at kernel/core/compile.rb:124
Array#each at kernel/core/array.rb:557
Compile.unified_load at kernel/core/compile.rb:107
Kernel(Object)#load at kernel/core/compile.rb:329
main.__script__ at kernel/loader.rb:171

Obviously not. Nothing we can do here. Go home, folks. No, but wait! Those error messages are actually there for our benefit, so why not read them? This looks interesting: undefined local variable or method `gets' for main (NameError). If Rubinius doesn’t know of Kernel.gets, what would happen if we were a little more precise?


loop do
  print "Enter something: "
  str = $stdin.gets.chomp
  exit if str.empty?
  puts str.scramble_words
end


% rbx scramble.rb
Enter something: "Hello, world!", said the C programmer.
"Llhoe, lwrod!", said teh C arpmergmor.

Sweet!


% rbx -v
rubinius 0.8.0 (ruby 1.8.6 compatible) (r) (01/23/2008) [i686-pc-linux-gnu]

Conclusion

I need coffee.


Update 2008.01.27: with Rubinius checked out and compiled this morning, $stdin.gets is no longer necessary. I guess Kernel.gets is working now.

2008.01.23

Getting started with Rubinius

Filed under: Ruby Programming — Tags: , , — Lorenzo E. Danielsson @ 03:06

According to the web site, Rubinius is:

Rubinius is a project to develop the next generation virtual machine for the Ruby programming language.

Of course there is some more information about it, but I’m not going to yank and put all of it. Go to the site and read for yourself. This is still a young project so don’t expect your Ruby scripts to just work. But it’s still worth installing it and toying around with it. If you find issues, you can always help out by filing bug reports.

Installing

Pretty much a no-brainer. If you don’t want to use git you can get a tarball here. First.


% cd ~/tmp
% tar zxf ~/dl/rubinius-daily-current.tar.gz

Of course, you’ll have to replace ~/dl with the directory you downloaded to. Also, I extracted it into ~/tmp, but you are of course free to choose where you want it extracted. Once you have extracted the archive, cd rubinius-daily and read the INSTALL file and make sure you have all the dependencies installed. Then issue:


% rake build
% su
# rake install

There, that didn’t hurt, did it? Now you should have Rubinius installed as /usr/local/bin/rbx. You will also have the Rubinius modules in /usr/local/lib/rubinius/. It's probably a good idea to start off by looking at what modules are there. That will give you an idea of what you can and cannot do. You will notice that there are some modules missing, if you compare to your Ruby module directory. You will also notice that Rubinius has added some modules of its own.

Trying it out

Now you are ready to put Rubinius to the test. I started out with something very simple.


 1 #! /usr/bin/ruby
 2
 3 # Print command-line arguments.
 4 #
 5 # Author: Lorenzo E. Danielsson
 6 # Date created: 2008.01.23
 7
 8 abort "You called #{$0} without any command line arguments." if ARGV.empty?
 9
10 puts "You called #{$0} with #{ARGV.length} arguments:"
11 ARGV.each { |arg| puts "  * #{arg}" }

Then I tried it out:

% ls
args.rb
% rbx args.rb foo bar
You called args.rb with 2 arguments:
* foo
* bar
% ls
args.rb args.rbc
% rbx args.rbc foo bar
You called args.rbc with 2 arguments:
* foo
* bar

Notice that after I ran the script the first time, I now have an args.rbc file. Once the Ruby script has been compiled, I can pass the byte-code compiled program to rbx and it works just as well.

Next test: readline, since:

% ls /usr/local/lib/rubinius/0.8/readline*
/usr/local/lib/rubinius/0.8/readline-native.rb
/usr/local/lib/rubinius/0.8/readline-native.rbc
/usr/local/lib/rubinius/0.8/readline.rb
/usr/local/lib/rubinius/0.8/readline.rbc

Here is my trivial program, which work fine in Ruby (as in Matz' Ruby, cRuby, or whatever the name-of-the-day is):


 1 #! /usr/bin/ruby
 2
 3 # Complete month names.
 4 #
 5 # Author: Lorenzo E. Danielsson
 6 # Date created: 2008.01.23
 7
 8 require 'readline'
 9
10 months = %w[ January February March April May June July August September
11 October November December ]
12
13 Readline.completion_proc = Proc.new { |str|
14   months.find_all { |m| m =~ /^#{str}/ }
15 }
16
17 puts <<EOH
18 Enter the names of months to get their ordinal numbers.
19 Enter 'quit' or 'exit' to quit this program.
20 EOH
21
22 loop do
23   inp = Readline.readline('-- ', true).strip
24   next if inp.empty?
25   break if %w[ quit exit ].include? inp
26
27   case months.include? inp
28   when true
29     puts "#{inp} is month number #{months.index(inp) + 1}."
30   when false
31     puts "Not a month: #{inp}"
32   end
33 end

Things started well:

% rbx months.rbc
Enter the names of months to get their ordinal numbers.
Enter 'quit' or 'exit' to quit this program.
-- J [TAB,TAB]
January July June
-- January
January is month number 1.

To get January to complete, I only added an 'a' and pressed TAB, just like you would expect from readline. Now what happens if I hit TAB without having typed any letters? Well, in Ruby it shows a list of all available completions, as shown below:

% ruby months.rb
-- [TAB*2]
April December January June May October
August February July March November September
--

So I tried this in Rubinius as well. The result:

% rbx months.rbc
Enter the names of months to get their ordinal numbers.
Enter 'quit' or 'exit' to quit this program.
-- *** glibc detected *** rbx: free(): invalid next size (fast): 0x081107d0 ***
======= Backtrace: =========
/lib/i686/cmov/libc.so.6[0xb7d59735]
/lib/i686/cmov/libc.so.6(cfree+0x90)[0xb7d5d1a0]
/lib/libreadline.so.5[0xb6e9a6fe]
/lib/libreadline.so.5(rl_complete_internal+0x118)[0xb6e9c848]
...
...
[More output]

Ouch! Moreover my terminal was completely dead after this. Oh, well, just close it and launch a new one. Rxvt windows are like soooooooo cheap.. 😉

As I said earlier, don't expect things to work perfectly. By trying all kinds of things out, and reporting bugs when they occur, you can help to make Rubinius better. And to be fair, I'm on a mixed etch/lenny/sid system, so this doesn't *have* to be a Rubinius problem..

I wasn't quite done with my testing yet. I wanted to try to see if I could pull a module from /usr/lib/ruby/ and get it to compile with Rubinius and then write a simple test script. I chose ping.rb, for no other reason than that it does not exist as a Rubinius module.

First, I compiled it:

% cp /usr/lib/ruby/1.8/ping.rb .
% rbx ping.rb
% ls
ping.rb ping.rbc

So far so good. Next, for the test script:


 1 #! /usr/bin/ruby
 2
 3 # Check if hosts are up.
 4 #
 5 # Author: Lorenzo E. Danielsson
 6 # Date created: 2008.01.23
 7
 8 require 'ping'
 9
10 abort "No host to check." if ARGV.empty?
11
12 ARGV.each do |host|
13   puts "Host #{host} is #{Ping.pingecho(host) ? 'up' : 'down'}"
14 end

I compiled that as well:

% rbx pinghosts.rb
No host to check.

The message comes from the fact that rbx compiles and runs the program. I didn't pass in any hosts. Let's give a real try, shall we?

% rbx pinghosts.rb 127.0.0.1
*** glibc detected *** rbx: double free or corruption (!prev): 0x081829d0 ***
======= Backtrace: =========
/lib/i686/cmov/libc.so.6[0xb7d8c735]
/lib/i686/cmov/libc.so.6(cfree+0x90)[0xb7d901a0]
/usr/local/lib/librubinius-0.8.0.so(XFREE+0x1d)[0xb7f2abcd]
/usr/local/lib/librubinius-0.8.0.so(cpu_event_clear_channel+0xa7)[0xb7eca117]
/usr/local/lib/librubinius-0.8.0.so(cpu_thread_exited+0x7f)[0xb7efd50f]
/usr/local/lib/librubinius-0.8.0.so(cpu_perform_system_primitive+0xe3)[0xb7ee93a3]
...
...
[ Lots more where this came from]

Oops.. Oh well, at least it compiled. And, you get good stack traces, so if you are so inclined, you can start following the Yellow Brick Road.

Conclusion

It's a bit hard to say much about Rubinius after having played around with it for a single evening. But it was a fun way to spend an evening and I'll be keeping 1.73 eyes on it. I think Rubinius can be a really neat addition to the Ruby family. Tomorrow I'll spend some time and actually read up on Rubinius internals a little.

These are interesting times for Ruby: 1.9 with YARV, Rubinius and of course JRuby. What next? Ruby for VIC-20? 😀

Older Posts »

Blog at WordPress.com.