Subscribe:

Pages

Interacting with the Command Line from Ruby

One of the strengths of Ruby is as a scripting language, that is, a language for managing control of other programs. A typical setting is to provide our script with some parameters which are then used to set up and run other programs. For example, we could control the number of times the external program is called, or collect the results and store them in specific files. This kind of automation can help speed up our work considerably, and also lets us think of our research at a higher level of abstraction: i.e. just create a pool of training data as big as is needed, rather than having to laboriously construct every item in the pool by hand. Ruby is also highly suited to this task, as many of the tasks, such as parsing input arguments or formatting strings for output display, are easier in Ruby than in, for example, C.

To achieve this level of control, we first need some way for our Ruby script to handle command-line arguments provided to it. Second we need to be able to construct and run commands from within Ruby just as if we had typed them at the command line.


For example, I am working on the analysis of what are known as k-SAT landscapes: the idea is to construct a boolean function, such as x_1 & x_2 | x_1 & x_3, which contains a given number, m, of clauses, each clause with k terms. The clauses are constructed from n different variables, and there should be N of the landscapes for any given combination of n, m and k. I have a C program which will take n, m, and k as input arguments and print the landscape to standard output. I want to automate the construction of the N landscapes, and capture the output into files named in the format: 'results-n-m-k-i.txt', where i will range from 0 to N-1. This is ideal for automation, as the construction of any landscape can take up to a day.

Basic Techniques

When you start a Ruby program, using: 'ruby program.rb X ...' you can provide command-line arguments as 'X ...'. These arguments are placed into the array ARGV, as strings. (Warning for C programmers: ARGV[0] is the first argument, not the name of the program!) It is a simple matter to read each item out of ARGV, convert to a suitable number format, and place the result into an internal variable: to convert the first parameter to an integer and store in 'x', write: x = ARGV[0].to_i

Ruby strings make it easy to interpolate values from variables using the #{} syntax, which interprets anything inside the brackets and puts the result into the string. For example:

5.times do |i|
puts "The value is #{i}"
end

Will print:

The value is 0
The value is 1
The value is 2
The value is 3
The value is 4

Finally, to call an external program, and pass arguments to it, Ruby provides convenient backticks, ` `. Anything between the backticks will first be interpreted as a string (so variable values can be added in, as above) and then passed to the shell for execution, just as if you had typed the command within the backticks on the command line. Thus, `wc #{filename}` will call the word-count program on the file named within the 'filename' variable.

My final code is below. Notice the check for the number of input arguments, and output of a help message if there are not enough; this is always useful, as I very quickly forget what those input arguments should be. The script can be run easily using:

ruby create-samples.rb 20 50 3 20

# Automate the construction of landscapes
#
# ruby create-samples.rb n m k N
#
# where n = number of variables
# m = number of clauses
# k = number of variables per clause
# N = number of formulae
#
# Results for formulae constructed with names: results-n-m-k-i.txt
# where i runs from 0 to N-1

if ARGV.length != 4
puts <<-HELPMESSAGE
Automate the construction of landscapes
Copyright (c) Peter Lane, 2008.

ruby create-samples.rb n m k N

where n = number of variables
m = number of clauses
k = number of variables per clause
N = number of formulae

Results for formulae constructed with names: results-n-m-k-i.txt
where i runs from 0 to N-1
HELPMESSAGE
else
n = ARGV[0].to_i
m = ARGV[1].to_i
k = ARGV[2].to_i
N = ARGV[3].to_i
N.times do |i|
puts "Creating file #{i} for n=#{n}, m=#{m}, k=#{k}"
`../makels #{n} #{m} #{k} > results-#{n}-#{m}-#{k}-#{i}.txt`
sleep 1.0 # wait at least a second, to allow for C's randomisation!
end
end

Control Widgets in wxRuby

Ruby's high-level syntax makes it simple to design and write graphical interfaces in code. In this post, I explore the basic techniques for handling control widgets: how to construct them, retrieve values, and respond to events. There are two broad classes of control: static controls act as simple display devices, such as a label on the screen; non-static controls provide some form of user interaction, such as a button. There are some common principles for how all these control widgets are constructed and operate.

General Principles

Nearly all of the controls are created in a similar way.

Wx::StaticText.new(self, :label => "Password", :style => Wx::ALIGN_RIGHT
) Wx::TextCtrl.new(self, :style => Wx::TE_PASSWORD)

Every control must have a
parent window within which it is placed. This parent window is usually the frame or dialog within which you are placing your controls, self in the above examples. Each control also has some optional styles. Finally, there is some specific information for setting up the control, such as the label to display in a StaticText label, or the style for displaying information within the control, such as the 'password' reference in the TextCtrl.

(Things in the documentation which you can usually forget about are the widget's ID identifier, its position, and size. The ID number is used to identify your widget for the purpose of handling events within C++ code using wxWidgets. But Ruby provides a better way to register events directly with your widgets. The widget's position and size are best left to sizers which layout your controls within a window, for which see below.)

A small note about the 'BoxSizer' which appears in the examples: this is a way to arrange controls in a vertical or horizontal order. Without it, all of the controls will appear on top of one another. I shall cover sizers in more detail in another post.


Static controls


StaticText - a Label


The simplest control to display is a piece of text, to act as a label. For this, wxRuby provides the StaticText class. You must provide two pieces of information, the parent of the control and the text for the label.


StaticBitmap
- an Image

Displaying an image is a two stage process. Assuming your image is stored in a file, you must first create an instance of the Bitmap class from your file: Bitmap.new(filename) Then display it in a StaticBitmap control. The syntax for StaticBitmap requires you to fill out the first three arguments: the third is the bitmap to display, the first is the familiar parent panel, and the second is the identifier number. I said before we are not interested in this number, and wxRuby lets us use Wx::ID_ANY as the parameter so that a new number is assigned internally.


Here is a complete example using both static controls:

require 'wx'

class MyFrame < Wx::Frame
def initialize
super(nil, :title => "Controls 1")

main_sizer = Wx::BoxSizer.new Wx::VERTICAL
main_sizer.add(Wx::StaticText.new(self, :label => "Picture of Mars"))
main_sizer.add(Wx::StaticBitmap.new(self, Wx::ID_ANY, Wx::Bitmap.new("planet5.gif")))

set_sizer main_sizer
end
end

Wx::App.run do
MyFrame.new.show
end


Non-static controls


The setup and display of non-static controls follows the same pattern as the static controls. However, users can interact with non-static controls, perhaps clicking or selecting displayed items, or entering new information. So these controls provide a range of events and accessor methods to track what the user is up to.


Buttons

Buttons are the simplest form of interactive control: they display some text, and react if clicked. The creation and display part for a button is the same as for a StaticText control:


@button = Wx::Button.new(self, :label => "Click me")

How do we make the code react to a click? We associate an action with the button's
event. Every control has one or more events associated with it. These events can be attached to actions, so that something in the code is called whenever the event is triggered. In this case, the button has one event, triggered when the button is clicked. We associate an action, say calling the 'button_clicked' method, with the button as follows:

evt_button(@button) {button_clicked}


Radio box


A radio button is a simple button with a usually round bullet which can be selected, on or off. Unlike checkboxes, radio buttons are organised into groups, and the idea is that one button in the group is always selected, and never more than one. The RadioBox is constructed using an array of strings which it uses to label up a series of radio buttons and display neatly wrapped in a static line. It is also possible to add a label to the complete box. The method get_string_selection is used to retrieve the currently selected string. Notice how the radio_box is stored in an instance variable, so as to be visible to the 'show_value' method.

require 'wx'

class MyFrame < Wx::Frame
def initialize
super(nil, :title => "Controls 4")

@radio_box = Wx::RadioBox.new(
self,
:label => "Language:",
:choices => ["C", "C++", "Lisp", "Ruby", "Scheme"])
button = Wx::Button.new(self, :label => "Show")
evt_button(button) {show_value}

main_sizer = Wx::BoxSizer.new Wx::VERTICAL
main_sizer.add(@radio_box, 0, Wx::ALL, 10)
main_sizer.add(button)

set_sizer main_sizer
end

def show_value
puts @radio_box.get_string_selection
end
end

Wx::App.run do
MyFrame.new.show
end


Listboxes

A listbox is a scrollable display of a list of strings. The listbox can be used to select one or more items, and the control can be set up to permit single selection only (the default) or multiple selection (using style LB_MULTIPLE). The method get_selection returns the index of the current selection, if any, for a single selection listbox, and get_selections returns an array of indices for any selected items in a multiple selection listbox. The following code constructs a multiple-selection listbox and then prints out selected indices when the 'show' button is clicked. Notice how the list_box is stored in an instance variable, so as to be visible to the 'show_choices' method.
require 'wx'

class MyFrame < Wx::Frame
def initialize
super(nil, :title => "Controls 3")

@list_box = Wx::ListBox.new(
self,
:choices => ["C", "C++", "Java", "Lisp", "Ruby", "Scheme"],
:style => Wx::LB_MULTIPLE)

button = Wx::Button.new(self, :label => "Show")
evt_button(button) {show_choices}

main_sizer = Wx::BoxSizer.new Wx::VERTICAL
main_sizer.add(@list_box)
main_sizer.add(button)

set_sizer main_sizer
end

def show_choices
puts @list_box.get_selections
end
end

Wx::App.run do
MyFrame.new.show
end


Calendar control


This control enables the user to select a date. You can catch different events, such as whether the user clicked on a heading, or changed the day, month or year. In this example, I just catch the 'double-click on a single day' event, and show how to extract the day, month and year information from the calendar control.

require 'wx'

class MyFrame < Wx::Frame
def initialize
super(nil, :title => "Controls:Calendar")

cal = Wx::CalendarCtrl.new(self)
evt_calendar(cal) do |event|
puts "Day #{cal.get_date.day}, Month: #{cal.get_date.month}, Year: #{cal.get_date.year}"
end

main_sizer = Wx::BoxSizer.new Wx::VERTICAL
main_sizer.add(cal, 1, Wx::GROW | Wx::ALL, 10)

set_sizer main_sizer
end
end

Wx::App.run do
MyFrame.new.show
end



Finally ...

There are many more controls described in the wxRuby documentation: TextCtrl and RichTextCtrl allow for the display and input of complex formatted text; TreeCtrl enables the display of hierarchical data; and MediaCtrl supports the display of video or sound. They all follow similar principles to the above, so explore the documentation for details!