Programming Ruby 1.9 & 2.0: The Pragmatic Programmers’ Guide (2013)
Part 4. Ruby Crystallized
Chapter 28. Standard Library
The Ruby interpreter comes with a large number of classes, modules, and methods built in—they are available as part of the running program. When you need a facility that isn’t part of the built-in repertoire, you’ll often find it in a library that you can require into your program. Sometimes you’ll need to download one of these libraries (perhaps as a Ruby gem).
However, Ruby also ships as standard with a large number of libraries. Some of these are written in pure Ruby and will be available on all Ruby platforms. Others are Ruby extensions, and some of these will be present only if your system supports the resources that they need. All can be included into your Ruby program using require . And, unlike libraries you may find on the Internet, you can pretty much guarantee that all Ruby users will have these libraries already installed on their machines.
Ruby 1.9 has more than 100 standard libraries included in the distribution. For each of these libraries, this section shows a one- or a two-page summary. For each library, we give some introductory notes and typically an example or two of use. You won’t find detailed method descriptions here; for that, consult the library’s own documentation.
It’s all very well suggesting that you “consult the library’s own documentation,” but where can you find it? The answer is that it depends. Some libraries have already been documented using RDoc (see Chapter 19, Documenting Ruby). That means you can use the ri command to get the documentation.
If there’s no RDoc documentation available, the next place to look is the library. If you have a source distribution of Ruby, these library files are in the ext/ and lib/ subdirectories. If instead you have a binary-only installation, you can still find the source of pure-Ruby library modules (normally in the lib/ruby/1.9/ directory under your Ruby installation). Often, library source directories contain documentation that the author has not yet converted to RDoc format.
If you still can’t find documentation, turn to your search engine of choice. Many of the Ruby standard libraries are also hosted as external projects. The authors develop them stand-alone and then periodically integrate the code into the standard Ruby distribution. For example, if you want detailed information on the API for the YAML library, Try searching for yaml ruby—you’ll probably end up at http://www.yaml.org/YAML_for_ruby.html .
The next port of call is the ruby-talk mailing list. Ask a (polite) question there, and chances are that you’ll get a knowledgeable response within hours. See the tips in Section 28.4, Mailing Lists for pointers on how to subscribe.
And if you still can’t find documentation, you can always follow Obi Wan’s advice and do what we did when documenting Ruby—use the source. You’d be surprised at how easy it is to read the actual source of Ruby libraries and work out the details of usage.
There are some libraries that we don’t document, either because they are pretty low level or because we cover them extensively elsewhere in the book. These libraries include:
- debug—the Ruby debugger, covered in Section 14.1,Ruby Debugger.
- iconv—has been removed from Ruby 2.0. UseString#encode.«2.0»
- mkmf—covered in the online guide to extending Ruby.
- objspace—extensions to the ObjectSpace class designed to be used by the Ruby core team.
- psych—an interface to libyaml. You’ll probably just use the YAML library.
- racc—this is the runtime used by the Racc parser generator. If you need this library, you’ll also need the external Racc system.
- rake—see Section 15.6,The Rake Build Tool.
- rdoc—see Chapter 19,Documenting Ruby.
- rubygems—covered in Section 15.5,RubyGems Integration and in Using RubyGems.
28.1 Library Changes in Ruby 1.9
These are the library changes in Ruby 1.9:
- Much of the Complex and Rational libraries are now built in to the interpreter. However, requiring the external libraries adds some functionally. In the case of Rational, this functionality is minimal.
- The CMath library has been added.
- The Enumerator library is now built in.
- The Fiber library has been added (it adds coroutine support to fibers).
- The Fiddle library (an interface to libffi, which supports calling functions in shared librries) is documented as a replacement for DL.
- ftools has been removed (and replaced by fileutils).
- The Generator library has been removed (use fibers).
- Notes on using irb from inside applications have been added.
- jcode has been removed in favor of built-in encoding support.
- The json library has been added.
- The matrix library no longer requires that you include mathn.
- The mutex library is now built in.
- parsedate has been removed. The Date class handles most of its functionality.
- readbytes has been removed. Class IO now supports the method directly.
- A description of Ripper has been added.
- A description of SecureRandom has been added.
- The shell library has been omitted, because it seems more like a curiosity than something folks would use (and it’s broken under 1.9).
- The soap library has been removed.
- I’ve omitted the sync library. It is broken under 1.9, and the monitor library seems to be cleaner.
- Win32API is now deprecated in favor of using the DL library.
Library Abbrev: Generate Sets of Unique Abbreviations
Given a set of strings, calculates the set of unambiguous abbreviations for those strings and returns a hash where the keys are all the possible abbreviations and the values are the full strings. Thus, given input of “car” and “cone,” the keys pointing to “car” would be “ca” and “car,” and those pointing to “cone” would be “co,” “con,” and “cone.”
An optional pattern or a string may be specified—only those input strings matching the pattern, or beginning with the string, are considered for inclusion in the output hash.
Including the Abbrev library also adds an abbrev method to class Array.
- Shows the abbreviation set of some words:
|
require 'abbrev' |
|
|
|
Abbrev::abbrev(%w{ruby rune}) # => {"ruby"=>"ruby", "rub"=>"ruby", |
|
# .. "rune"=>"rune", "run"=>"rune"} |
- A trivial command loop using abbreviations:
|
require 'abbrev' |
|
|
|
COMMANDS = %w{ sample send start status stop }.abbrev |
|
|
|
while line = gets |
|
line = line.chomp |
|
|
|
case COMMANDS[line] |
|
when "sample" then # ... |
|
when "send" then # ... |
|
# ... |
|
else |
|
STDERR.puts "Unknown command: #{line}" |
|
end |
|
end |
Library Base64: Base64 Conversion Functions
Performs encoding and decoding of binary data using a Base64 representation. This allows you to represent any binary data in purely printable characters. The encoding is specified in RFC 2045 and RFC 4648.[125]
- Encodes and decodes strings. Note the newlines inserted into the Base64 string.
|
require 'base64' |
|
str = "Now is the time for all good coders\nto learn Ruby" |
|
converted = Base64.encode64(str) |
|
puts converted |
|
puts Base64.decode64(converted) |
- Produces:
|
Tm93IGlzIHRoZSB0aW1lIGZvciBhbGwgZ29vZCBjb2RlcnMKdG8gbGVhcm4g |
|
UnVieQ== |
|
Now is the time for all good coders |
|
to learn Ruby |
- Now uses RFC 4648 variants:
|
require 'base64' |
|
str = "Now is the time for all good coders\nto learn Ruby" |
|
converted = Base64.strict_encode64(str) |
|
puts converted |
|
puts Base64.strict_decode64(converted) |
- Produces:
|
Tm93IGlzIHRoZSB0aW1lIGZvciBhbGwgZ29vZCBjb2RlcnMKdG8gbGVhcm4gUnVieQ== |
|
Now is the time for all good coders |
|
to learn Ruby |
Library Benchmark: Time Code Execution
Allows code execution to be timed and the results tabulated. The Benchmark module is easier to use if you include it in your top-level environment.
- Profile
- Compares the costs of four kinds of method dispatch:
|
require 'benchmark' |
|
include Benchmark |
|
string = "Stormy Weather" |
|
m = string.method(:length) |
|
bm(6) do |x| |
|
x.report("direct") { 100_000.times { string.length } } |
|
x.report("call") { 100_000.times { m.call } } |
|
x.report("send") { 100_000.times { string.send(:length) } } |
|
x.report("eval") { 100_000.times { eval "string.length" } } |
|
end |
- Produces:
|
user system total real |
|
direct 0.010000 0.000000 0.010000 ( 0.012705) |
|
call 0.020000 0.000000 0.020000 ( 0.022576) |
|
send 0.020000 0.000000 0.020000 ( 0.020664) |
|
eval 1.220000 0.000000 1.220000 ( 1.224656) |
- Which is better: reading all of a dictionary and splitting it or splitting it line by line? Use bmbm to run a rehearsal before doing the timing:
|
require 'benchmark' |
|
include Benchmark |
|
bmbm(6) do |x| |
|
x.report("all") do |
|
str = File.read("/usr/share/dict/words") |
|
words = str.scan(/[-\w']+/) |
|
end |
|
x.report("lines") do |
|
words = [] |
|
File.foreach("/usr/share/dict/words") do |line| |
|
words << line.chomp |
|
end |
|
end |
|
end |
- Produces:
|
Rehearsal ------------------------------------------ |
|
all 0.200000 0.010000 0.210000 ( 0.218034) |
|
lines 0.150000 0.020000 0.170000 ( 0.165469) |
|
--------------------------------- total: 0.380000sec |
|
|
|
user system total real |
|
all 0.180000 0.010000 0.190000 ( 0.185983) |
|
lines 0.290000 0.010000 0.300000 ( 0.302548) |
Library BigDecimal: Large-Precision Decimal Numbers
Ruby’s standard Bignum class supports integers with large numbers of digits. The BigDecimal class supports decimal numbers with large numbers of decimal places. The standard library supports all the normal arithmetic operations. BigDecimal also comes with some extension libraries.
bigdecimal/ludcmp
Performs an LU decomposition of a matrix.
bigdecimal/math
Provides the transcendental functions sqrt, sin, cos, atan, exp, and log, along with functions for computing PI and E. All functions take an arbitrary precision argument.
bigdecimal/jacobian
Constructs the Jacobian (a matrix enumerating the partial derivatives) of a given function. Not dependent on BigDecimal.
bigdecimal/newton
Solves the roots of nonlinear function using Newton’s method. Not dependent on BigDecimal.
bigdecimal/nlsolve
Wraps the bigdecimal/newton library for equations of big decimals.
You can find English-language documentation in the file ext/bigdecimal/bigdecimal_en.html in the Ruby source distribution.
|
# Calculate the area of a circle using BigDecimal numbers |
|
|
|
require 'bigdecimal' |
|
require 'bigdecimal/math' |
|
include BigMath |
|
|
|
pi = BigMath::PI(20) # 20 is the number of decimal digits |
|
|
|
radius = BigDecimal("2.14156987652974674392") |
|
|
|
area = pi * radius**2 |
|
|
|
area.to_s # => "0.144083540446856044176720033806679561688599846410 |
|
# .. 445032583215824758780405545861780909930190528E2" |
|
# The same with regular floats |
|
|
|
radius = 2.14156987652974674392 |
|
|
|
Math::PI * radius**2 # => 14.408354044685602 |
Library CGI: CGI Programming Support
The CGI class provides support for programs used as Common Gateway Interface (CGI) scripts in a web server. CGI objects are initialized with data from the environment and from the HTTP request, and they provide convenient accessors to form data and cookies. They can also manage sessions using a variety of storage mechanisms. Class CGI also provides basic facilities for HTML generation and class methods to escape and unescape requests and HTML.
- CGI::Session
- Escapes and unescapes special characters in URLs and HTML. Numeric entities less than 256 will be encoded based on the encoding of the input string.Other numeric entities will be left unchanged.
|
require 'cgi' |
|
CGI.escape('c:\My Files') # => c%3A%5CMy+Files |
|
CGI.unescape('c%3a%5cMy+Files') # => c:\My Files |
|
CGI::escapeHTML('"a"<b & c') # => "a"<b & c |
|
CGI.unescapeHTML('"a"<=>b') # => "a"<=>b |
|
CGI.unescapeHTML('AA') # => AA |
|
str = '2πr' |
|
str.force_encoding("utf-8") |
|
CGI.unescapeHTML(str) # => 2πr |
- Access information from the incoming request:
|
require 'cgi' |
|
c = CGI.new |
|
c.auth_type # => "basic" |
|
c.user_agent # => "Mozscape Explorari V5.6" |
- Access form fields from an incoming request. Assume that the following script, installed as test.cgi, was linked to using http://mydomain.com/test.cgi?fred=10&barney=cat:
|
require 'cgi' |
|
c = CGI.new |
|
c['fred'] # => "10" |
|
c.keys # => ["fred", "barney"] |
|
c.params # => {"fred"=>["10"], "barney"=>["cat"]} |
- If a form contains multiple fields with the same name, the corresponding values will be returned to the script as an array. The [] accessor returns just the first of these—index the result of the params method to get them all.
In this example, assume the form has three fields called “name”:
|
require 'cgi' |
|
c = CGI.new |
|
c['name'] # => "fred" |
|
c.params['name'] # => ["fred", "wilma", "barney"] |
|
c.keys # => ["name"] |
|
c.params # => {"name"=>["fred", "wilma", "barney"]} |
- Sends a response to the browser. (Not many folks use this form of HTML generation–use one of the templating libraries described in Section 20.3,Templating Systems.
|
require 'cgi' |
|
cgi = CGI.new("html5") |
|
cgi.http_header("type" => "text/html", "expires" => Time.now + 30) |
|
cgi.out do |
|
cgi.html do |
|
cgi.head{ cgi.title{"Hello World!"} } + |
|
cgi.body do |
|
cgi.pre do |
|
CGI::escapeHTML( |
|
"params: " + cgi.params.inspect + "\n" + |
|
"cookies: " + cgi.cookies.inspect + "\n") |
|
end |
|
end |
|
end |
|
end |
- Stores a cookie in the client browser:
|
require 'cgi' |
|
cgi = CGI.new("html5") |
|
cookie = CGI::Cookie.new('name' => 'mycookie', |
|
'value' => 'chocolate chip', |
|
'expires' => Time.now + 3600) |
|
|
|
cgi.out('cookie' => cookie) do |
|
cgi.head + cgi.body { "Cookie stored" } |
|
end |
- Retrieves a previously stored cookie:
|
require 'cgi' |
|
cgi = CGI.new("html5") |
|
cookie = cgi.cookies['mycookie'] |
|
|
|
cgi.out('cookie' => cookie) do |
|
cgi.head + cgi.body { "Flavor: " + cookie[0] } |
|
end |
Library CGI::Session: CGI Sessions
A CGI::Session maintains a persistent state for web users in a CGI environment. Sessions may be memory resident or may be stored on disk. See the discussion in Sessions for details.
- CGI
sl_cgi_session/session.rb |
|
|
# Store the timestamp of last access, along with the access count |
|
# using a session object |
|
|
|
require 'cgi' |
|
require 'cgi/session' |
|
|
|
cgi = CGI.new("html3") |
|
sess = CGI::Session.new(cgi, |
|
"session_key" => "rubyweb", |
|
"prefix" => "web-session.") |
|
|
|
if sess['lastaccess'] |
|
msg = "<p>You were last here #{sess['lastaccess']}.</p>" |
|
else |
|
msg = "<p>Looks like you haven't been here for a while</p>" |
|
end |
|
|
|
count = (sess["accesscount"] || 0).to_i |
|
count += 1 |
|
|
|
msg << "<p>Number of visits: #{count}</p>" |
|
|
|
sess["accesscount"] = count |
|
sess["lastaccess"] = Time.now.to_s |
|
sess.close |
|
|
|
cgi.out { |
|
cgi.html { |
|
cgi.body { |
|
msg |
|
} |
|
} |
|
} |
Library CMath: Complex Transcendental Functions
As of Ruby 1.9, the Complex class is built in to the interpreter. There is no need to require the complex library to create and manipulate complex numbers. However, if you want the transcendental functions defined by Math to work with complex numbers, you must also require the cmath library. The functions affected are as follows: acosh , acos , asinh , asin , atan2 , atanh , atan , cosh , cos , exp , log10 , log , sinh , sin , sqrt , tanh , and tan .
The complex library makes these complex functions the default (so, if you require ’complex’, you can use Math::sin and not CMath::sin).
|
require 'cmath' |
|
point = Complex(2, 3) |
|
CMath::sin(point) # => (9.15449914691143-4.168906959966565i) |
|
CMath::cos(point) # => (-4.189625690968807-9.109227893755337i) |
Library Complex: Complex Numbers
Loads the cmath library, which defines the transcendental functions for complex numbers. It then arranges things so that these complex-aware functions are the ones invoked when you use Math::. The net effect is that, after requiring complex, you can use functions such as Math::sin on any numeric value, including complex numbers.
- Using transcendental numbers with complex arguments will, by default, cause an error:
|
point = Complex(2, 3) |
|
Math::sin(point) |
- Produces:
|
from prog.rb:2:in `sin' |
|
from prog.rb:2:in `<main>' |
|
prog.rb:2:in `to_f': can't convert 2+3i into Float (RangeError) |
- However...
|
require 'complex' |
|
point = Complex(2, 3) |
|
Math::sin(point) # => (9.15449914691143-4.168906959966565i) |
Library Continuation: Continuations
Continuation objects are generated by the Object#callcc method, which becomes available only when the continuation library is loaded. They hold a return address and execution context, allowing a nonlocal return to the end of the callcc block from anywhere within a program. Continuations are somewhat analogous to a structured version of C’s setjmp/longjmp (although they contain more state, so you may consider them closer to threads). This (somewhat contrived) example allows the inner loop to abandon processing early.
- Does a nonlocal exit when a condition is met:
|
require 'continuation' |
|
callcc do |cont| |
|
for i in 0..4 |
|
print "\n#{i}: " |
|
for j in i*5...(i+1)*5 |
|
cont.call() if j == 7 |
|
printf "%3d", j |
|
end |
|
end |
|
end |
|
print "\n" |
- Produces:
|
0: 0 1 2 3 4 |
|
1: 5 6 |
- The call stack for methods is preserved in continuations:
|
require 'continuation' |
|
def strange |
|
callcc {|continuation| return continuation} |
|
print "Back in method, " |
|
end |
|
print "Before method. " |
|
continuation = strange() |
|
print "After method. " |
|
continuation.call if continuation |
- Produces:
|
Before method. After method. Back in method, After method. |
Library coverage: Experimental Code Coverage Analysis
The coverage module counts the number of times each line of Ruby code is executed in one or more source files and provides a summary as a hash. The keys of the hash are the names of files that were analyzed, and the values are each an array containing counts (on a per-line basis).
Here’s a simple implementation of the Fizz Buzz program:
sl_coverage/fizzbuzz.rb |
|
|
1.upto(100).with_object('') do |i, x| |
|
if i % 3 == 0 |
|
x += 'Fizz' |
|
end |
|
if i % 5 == 0 |
|
x += 'Buzz' |
|
end |
|
if x.empty? |
|
puts i |
|
else |
|
puts x |
|
end |
|
end |
And here’s a program that loads and runs that program, using the coverage library to report on execution counts. (Note that it discards the output of the FizzBuzz program, simply to save space on this page.)
|
require 'coverage' |
|
Coverage.start |
|
STDOUT.reopen("/dev/null") |
|
require_relative 'fizzbuzz.rb' |
|
Coverage.result.each do |file_name, counts| |
|
File.readlines(file_name).each.with_index do |code_line, line_number| |
|
count = counts[line_number] || "--" |
|
STDERR.printf "%3s: %s", count, code_line |
|
end |
|
end |
Produces:
|
1: 1.upto(100).with_object('') do |i, x| |
|
100: if i % 3 == 0 |
|
33: x += 'Fizz' |
|
--: end |
|
100: if i % 5 == 0 |
|
20: x += 'Buzz' |
|
--: end |
|
100: if x.empty? |
|
53: puts i |
|
--: else |
|
47: puts x |
|
--: end |
|
--: end |
Library CSV: Comma-Separated Values
Comma-separated data files are often used to transfer tabular information (and are a lingua franca for importing and exporting spreadsheet and database information). As of Ruby 1.9, the old library has been replaced by James Edward Gray II’s FasterCSV version. It has a few incompatibilities with the original. In particular, CSV.open now works like File.open , not File.foreach , and options are passed as a hash and not positional parameters.
Ruby’s CSV library deals with arrays (corresponding to the rows in the CSV file) and strings (corresponding to the elements in a row). If an element in a row is missing, it will be represented as a nil in Ruby.
The files used in these examples are as follows:
sl_csv/csvfile |
|
|
12,eggs,2.89, |
|
2,"shirt, blue",21.45,special |
|
1,"""Hello Kitty"" bag",13.99 |
sl_csv/csvfile_hdr |
|
|
Count,Description,Price |
|
12,eggs,2.89, |
|
2,"shirt, blue",21.45,special |
|
1,"""Hello Kitty"" bag",13.99 |
- Reads a file containing CSV data and processes line by line:
|
require 'csv' |
|
CSV.foreach("csvfile") do |row| |
|
qty = row[0].to_i |
|
price = row[2].to_f |
|
printf "%20s: $%5.2f %s\n", row[1], qty*price, row[3] || " ---" |
|
end |
- Produces:
|
eggs: $34.68 --- |
|
shirt, blue: $42.90 special |
|
"Hello Kitty" bag: $13.99 --- |
- Processes a CSV file that contains a header line. Automatically converts fields that look like numbers.
|
require 'csv' |
|
total_cost = 0 |
|
CSV.foreach("csvfile_hdr", headers: true, converters: :numeric) do |data| |
|
total_cost += data["Count"] * data["Price"] |
|
end |
|
puts "Total cost is #{total_cost}" |
- Produces:
|
Total cost is 91.57 |
- Writes CSV data to an existing open stream (STDOUT in this case). Uses | as the column separator.
|
require 'csv' |
|
CSV(STDOUT, col_sep: "|") do |csv| |
|
csv << [ 1, "line 1", 27 ] |
|
csv << [ 2, nil, 123 ] |
|
csv << [ 3, "|bar|", 32.5] |
|
end |
- Produces:
|
1|line 1|27 |
|
2||123 |
|
3|"|bar|"|32.5 |
- Accesses a CSV file as a two-dimensional table:
|
require 'csv' |
|
|
|
table = CSV.read("csvfile_hdr", |
|
headers: true, |
|
header_converters: :symbol) |
|
puts "Row count = #{table.count}" |
|
puts "First row = #{table[0].fields}" |
|
puts "Count of eggs = #{table[0][:count]}" |
|
table << [99, "red balloons", 1.23] |
|
table[:in_stock] = [10, 5, 10, 10] |
|
puts "\nAfter adding a row and a column, the new table is:" |
|
puts table |
- Produces:
|
Row count = 3 |
|
First row = ["12", "eggs", "2.89", nil] |
|
Count of eggs = 12 |
|
|
|
After adding a row and a column, the new table is: |
|
count,description,price,,in_stock |
|
12,eggs,2.89,,10 |
|
2,"shirt, blue",21.45,special,5 |
|
1,"""Hello Kitty"" bag",13.99,10 |
|
99,red balloons,1.23,,10 |
Library Curses: CRT Screen Handling
curses or ncurses installed in target environment
The Curses library is a thin wrapper around the C curses or ncurses libraries, giving applications a device-independent way to draw on consoles and other terminal-like devices. As a nod toward object-orientation, curses windows and mouse events are represented as Ruby objects. Otherwise, the standard curses calls and constants are simply defined in the Curses module.
sl_curses/pong_paddle.rb |
|
|
# Draw the paddle for game of 'pong' that moves in response to up and down keys |
|
require 'curses' |
|
include Curses |
|
|
|
class Paddle |
|
HEIGHT = 4 |
|
PADDLE = " \n" + "|\n"*HEIGHT + " " |
|
def initialize |
|
@top = (Curses::lines - HEIGHT)/2 |
|
draw |
|
end |
|
def up |
|
@top -= 1 if @top > 1 |
|
end |
|
def down |
|
@top += 1 if (@top + HEIGHT + 1) < lines |
|
end |
|
def draw |
|
setpos(@top-1, 0) |
|
addstr(PADDLE) |
|
refresh |
|
end |
|
end |
|
|
|
init_screen |
|
begin |
|
cbreak |
|
noecho |
|
stdscr.keypad(true) |
|
paddle = Paddle.new |
|
|
|
loop do |
|
case ch = getch |
|
when "Q", "q" thenbreak |
|
when Key::UP, 'U', 'u' then paddle.up |
|
when Key::DOWN, 'D', 'd' then paddle.down |
|
else |
|
beep |
|
end |
|
paddle.draw |
|
end |
|
ensure |
|
close_screen |
|
end |
Library Date/DateTime: Date and Time Manipulation
The date library implements classes Date and DateTime, which provide a comprehensive set of facilities for storing, manipulating, and converting dates with or without time components. The classes can represent and manipulate civil, ordinal, commercial, Julian, and standard dates, starting January 1, 4713 BCE. The DateTime class extends Date with hours, minutes, seconds, and fractional seconds, and it provides some support for time zones. The classes also provide support for parsing and formatting date and datetime strings. The classes have a rich interface—consult the ri documentation for details. The introductory notes in the file lib/date.rb in the Ruby source tree are also well worth reading.
- Experiment with various representations:
|
require 'date' |
|
|
|
d = Date.new(2000, 3, 31) |
|
[d.year, d.yday, d.wday] # => [2000, 91, 5] |
|
[d.month, d.mday] # => [3, 31] |
|
[d.cwyear, d.cweek, d.cwday] # => [2000, 13, 5] |
|
[d.jd, d.mjd] # => [2451635, 51634] |
|
d1 = Date.commercial(2000, 13, 7) |
|
d1.to_s # => "2000-04-02" |
|
[d1.cwday, d1.wday] # => [7, 0] |
- Essential information about Christmas:
|
require 'date' |
|
|
|
now = DateTime.now |
|
year = now.year |
|
year += 1 if now.month == 12 && now.day > 25 |
|
xmas = DateTime.new(year, 12, 25) |
|
|
|
diff = xmas - now |
|
|
|
puts "It's #{diff.to_i} days to Christmas" |
|
puts "Christmas #{year} falls on a #{xmas.strftime('%A')}" |
- Produces:
|
It's 211 days to Christmas |
|
Christmas 2013 falls on a Wednesday |
Library DBM: Interface to DBM Databases
a DBM library is installed in target environment
DBM files implement simple, hashlike persistent stores. Many DBM implementations exist: the Ruby library can be configured to use one of the DBM libraries db, dbm (ndbm), gdbm, and qdbm. The interface to DBM files is similar to class Hash, except that DBM keys and values will be strings. This can cause confusion, as the conversion to a string is performed silently when the data is written. The DBM library is a wrapper around the lower-level access method. For true low-level access, see also the GDBM and SDBM libraries.
- gdbm
- sdbm
The following creates a simple DBM file and then reopens it read-only and reads some data. Note the conversion of a date object to its string form.
sl_dbm/dbm1.rb |
|
|
require 'dbm' |
|
require 'date' |
|
|
|
DBM.open("data.dbm") do |dbm| |
|
dbm['name'] = "Walter Wombat" |
|
dbm['dob'] = Date.new(1997, 12,25) |
|
end |
|
|
|
DBM.open("data.dbm", nil, DBM::READER) do |dbm| |
|
p dbm.keys |
|
p dbm['dob'] |
|
p dbm['dob'].class |
|
end |
Produces:
|
["name", "dob"] |
|
"1997-12-25" |
|
String |
Library Delegator: Delegate Calls to Other Object
Object delegation is a way of composing objects—extending an object with the capabilities of another—at runtime. The Ruby Delegator class implements a simple but powerful delegation scheme, where requests are automatically forwarded from a master class to delegates or their ancestors and where the delegate can be changed at runtime with a single method call.
- Forwardable
- For simple cases where the class of the delegate is fixed, make the master class a subclass of DelegateClass, passing the name of the class to be delegated as a parameter. In the master class’s initialize method, pass the object to be delegated to the superclass.
|
require 'delegate' |
|
|
|
class Words < DelegateClass(Array) |
|
def initialize(list = "/usr/share/dict/words") |
|
words = File.read(list).split |
|
super(words) |
|
end |
|
end |
|
|
|
words = Words.new |
|
words[9999] # => "anticontagionist" |
|
words.size # => 235886 |
|
words.grep(/matz/) # => ["matzo", "matzoon", "matzos", "matzoth"] |
- Use SimpleDelegator to delegate to a particular object (which can be changed):
|
require 'delegate' |
|
|
|
words = File.read("/usr/share/dict/words").split |
|
names = File.read("/usr/share/dict/propernames").split |
|
|
|
stats = SimpleDelegator.new(words) |
|
stats.size # => 235886 |
|
stats[226] # => "abidingly" |
|
stats.__setobj__(names) |
|
stats.size # => 1308 |
|
stats[226] # => "Deirdre" |
Library Digest: MD5, RIPEMD-160 SHA1, and SHA2 Digests
The Digest module is the home for a number of classes that implement message digest algorithms: MD5, RIPEMD-160, SHA1, and SHA2 (256, 384, and 512 bit). The interface to all these classes is identical.
- You can create a binary or hex digest for a given string by calling the class method digest or hexdigest .
- You can also create an object (optionally passing in an initial string) and determine the object’s hash by calling the digest or hexdigest instance methods. You can then append to the string using the update method and then recover an updated hash value.
- Calculates some MD5 and SHA1 hashes:
|
require 'digest/md5' |
|
require 'digest/sha1' |
|
|
|
for hash_class in [ Digest::MD5, Digest::SHA1 ] |
|
|
|
puts "Using #{hash_class.name}" |
|
|
|
# Calculate directly |
|
puts hash_class.hexdigest("hello world") |
|
|
|
# Or by accumulating |
|
digest = hash_class.new |
|
digest << "hello" |
|
digest << " " |
|
digest << "world" |
|
puts digest.hexdigest |
|
puts digest.base64digest # new in 1.9.2 |
|
puts |
|
end |
- Produces:
|
Using Digest::MD5 |
|
5eb63bbbe01eeed093cb22bb8f5acdc3 |
|
5eb63bbbe01eeed093cb22bb8f5acdc3 |
|
XrY7u+Ae7tCTyyK7j1rNww== |
|
|
|
Using Digest::SHA1 |
|
2aae6c35c94fcfb415dbe95f408b9ce91ee846ed |
|
2aae6c35c94fcfb415dbe95f408b9ce91ee846ed |
|
Kq5sNclPz7QV2+lfQIuc6R7oRu0= |
Library dRuby: Distributed Ruby Objects (drb)
dRuby allows Ruby objects to be distributed across a network connection. Although expressed in terms of clients and servers, once the initial connection is established, the protocol is effectively symmetrical: either side can invoke methods in objects on the other side. Normally, objects passed and returned by remote calls are passed by value; including the DRbUndumped module in an object forces it to be passed by reference (useful when implementing callbacks).
- Rinda
- XMLRPC
- This server program is observable —it notifies all registered listeners of changes to a count value:
sl_drb/drb_server1.rb |
|
|
require 'drb' |
|
require 'drb/observer' |
|
|
|
class Counter |
|
include DRb::DRbObservable |
|
|
|
def run |
|
5.times do |count| |
|
changed |
|
notify_observers(count) |
|
end |
|
end |
|
end |
|
|
|
counter = Counter.new |
|
DRb.start_service('druby://localhost:9001', counter) |
|
DRb.thread.join |
- This client program interacts with the server, registering a listener object to receive callbacks before invoking the server’s run method:
sl_drb/drb_client1.rb |
|
|
require 'drb' |
|
|
|
class Listener |
|
include DRbUndumped |
|
|
|
def update(value) |
|
puts value |
|
end |
|
end |
|
|
|
DRb.start_service |
|
counter = DRbObject.new(nil, "druby://localhost:9001") |
|
|
|
listener = Listener.new |
|
counter.add_observer(listener) |
|
counter.run |
Library English: English Names for Global Symbols
Includes the English library file in a Ruby script, and you can reference the global variables such as $_ using less-cryptic names, listed in the following table. Prior to Ruby 1.9, the name $PROGRAM_NAME was declared using English. It is now predefined in the Ruby interpreter.
\toprule $* |
$ARGV |
$_ |
$LAST_READ_LINE |
$? |
$CHILD_STATUS |
$" |
$LOADED_FEATURES |
$< |
$DEFAULT_INPUT |
$& |
$MATCH |
$> |
$DEFAULT_OUTPUT |
$. |
$NR |
$! |
$ERROR_INFO |
$, |
$OFS |
$@ |
$ERROR_POSITION |
$\ |
$ORS |
$; |
$FIELD_SEPARATOR |
$, |
$OUTPUT_FIELD_SEPARATOR |
$; |
$FS |
$\ |
$OUTPUT_RECORD_SEPARATOR |
$= |
$IGNORECASE |
$$ |
$PID |
$. |
$INPUT_LINE_NUMBER |
$’ |
$POSTMATCH |
$/ |
$INPUT_RECORD_SEPARATOR |
$‘ |
$PREMATCH |
$~ |
$LAST_MATCH_INFO |
$$ |
$PROCESS_ID |
$+ |
$LAST_PAREN_MATCH |
$/ |
$RS |
The following code shows some regular variable names along with their English counterparts.
|
require 'English' |
|
|
|
$OUTPUT_FIELD_SEPARATOR = ' -- ' |
|
"waterbuffalo" =~ /buff/ |
|
print $., $INPUT_LINE_NUMBER, "\n" |
|
print $', $POSTMATCH, "\n" |
|
print $$, $PID |
Produces:
|
0 -- 0 -- |
|
alo -- alo -- |
|
24658 -- 24658 |
Library erb: Lightweight Templating for HTML
ERb is a lightweight templating system, allowing you to intermix Ruby code and plain text. This is sometimes a convenient way to create HTML documents but also is usable in other plain-text situations. See Section 20.3, Templating Systems for other templating solutions.
ERB breaks its input text into chunks of regular text and program fragments. It then builds a Ruby program that, when run, outputs the result text and executes the program fragments. Program fragments are enclosed between <% and %> markers. The exact interpretation of these fragments depends on the character following the opening <%:
Table 26. Directives for ERB
Sequence |
Action |
<% ruby code %> |
Inserts the given Ruby code at this point in the generated program. If it outputs anything, include this output in the result. |
<%= ruby expression %> |
Evaluate expression and insert its value in the output of the generated program. |
<%# ... %> |
Comment (ignored). |
<%% and %%> |
Replaced in the output by <% and%> respectively. |
The following code uses <%…%> blocks to execute a Ruby loop, and <%=…%> to substitute a value into the output.
|
require 'erb' |
|
input = %{<% high.downto(low) do |n| # set high, low externally %> |
|
<%= n %> green bottles, hanging on the wall |
|
<%= n %> green bottles, hanging on the wall |
|
And if one green bottle should accidentally fall |
|
There'd be <%= n-1 %> green bottles, hanging on the wall |
|
<% end %>} |
|
high,low = 10, 8 |
|
erb = ERB.new(input) |
|
erb.run(binding) |
Produces:
|
10 green bottles, hanging on the wall |
|
10 green bottles, hanging on the wall |
|
And if one green bottle should accidentally fall |
|
There'd be 9 green bottles, hanging on the wall |
|
. . . |
An optional second parameter to ERB.new sets the safe level for evaluating expressions. If nil, expressions are evaluated in the current thread; otherwise, a new thread is created, and its $SAFE level is set to the parameter value.
The optional third parameter to ERB.new allows some control of the interpretation of the input and of the way whitespace is added to the output. If the third parameter is a string and that string contains a percent sign, then ERb treats lines starting with a percent sign specially. Lines starting with a single percent sign are treated as if they were enclosed in <%...%>. Lines starting with a double percent sign are copied to the output with a single leading percent sign.
|
require 'erb' |
|
str = %{\ |
|
% 2.times do |i| |
|
This is line <%= i %> |
|
%end |
|
%%%done} |
|
ERB.new(str, 0, '%').run |
Produces:
|
This is line 0 |
|
This is line 1 |
|
%%done |
If the third parameter contains the string <>, then a newline will not be written if an input line starts with an ERB directive and ends with %>. If the trim parameter contains >>, then a newline will not be written if an input line ends %>.
|
require 'erb' |
|
str1 = %{\ |
|
* <%= "cat" %> |
|
<%= "dog" %> |
|
} |
|
ERB.new(str1, 0, ">").run |
|
ERB.new(str1, 0, "<>").run |
Produces:
|
* catdog* cat |
|
dog |
The erb library also defines the helper module ERB::Util that contains two methods: html_escape (aliased as h ) and url_encode (aliased as u ). These are equivalent to the CGI methods escapeHTML and escape , respectively (except escape encodes spaces as plus signs, and url_encode uses %20).
|
require 'erb' |
|
include ERB::Util |
|
str1 = %{\ |
|
h(a) = <%= h(a) %> |
|
u(a) = <%= u(a) %> |
|
} |
|
a = "< a & b >" |
|
ERB.new(str1).run(binding) |
Produces:
|
h(a) = < a & b > |
|
u(a) = %3C%20a%20%26%20b%20%3E |
You may find the command-line utility erb is supplied with your Ruby distribution. This allows you to run erb substitutions on an input file; see erb --help for details.
Library Etc: Access User and Group Information in /etc/passwd
Unix or Cygwin
The Etc module provides a number of methods for querying the passwd and group facilities on Unix systems.
- Finds out information about the currently logged-in user:
|
require 'etc' |
|
|
|
name = Etc.getlogin |
|
info = Etc.getpwnam(name) |
|
info.name # => "dave" |
|
info.uid # => 501 |
|
info.dir # => "/Users/dave" |
|
info.shell # => "/bin/zsh" |
|
|
|
group = Etc.getgrgid(info.gid) |
|
group.name # => "staff" |
- Returns the names of users on the system used to create this book:
|
require 'etc' |
|
|
|
users = [] |
|
Etc.passwd {|passwd| users << passwd.name } |
|
users[1,5].join(", ") # => "_appleevents, _appowner, _appserver, _ard, |
|
# .. _assetcache" |
- Returns the IDs of groups on the system used to create this book:
|
require 'etc' |
|
|
|
ids = [] |
|
Etc.group {|entry| ids << entry.gid } |
|
ids[1,5].join(", ") # => "55, 87, 81, 79, 33" |
Library expect: Expect Method for IO Objects
The expect library adds the method expect to all IO objects. This allows you to write code that waits for a particular string or pattern to be available from the I/O stream. The expect method is particularly useful with pty objects (see the Pty library) and with network connections to remote servers, where it can be used to coordinate the use of external interactive processes.
If the global variable $expect_verbose is true, the expect method writes all characters read from the I/O stream to STDOUT.
- pty
- Connects to the local FTP server, logs in, and prints out the name of the user’s directory. (Note that it would be a lot easier to do this using the net/ftp library.)
|
# This code might be specific to the particular ftp daemon. |
|
|
|
require 'expect' |
|
require 'socket' |
|
|
|
$expect_verbose = true |
|
|
|
socket = TCPSocket.new('localhost', 'ftp') |
|
|
|
socket.expect("ready") |
|
socket.puts("user testuser") |
|
socket.expect("331 User testuser accepted, provide password.") |
|
socket.puts("pass wibble") |
|
socket.expect("logged in.\r\n") |
|
socket.puts("pwd") |
|
puts(socket.gets) |
|
socket.puts "quit" |
- Produces:
|
220 ::1 FTP server (tnftpd 20100324+GSSAPI) ready. |
|
331 User testuser accepted, provide password. |
|
230 User testuser logged in. |
|
257 "/Users/testuser" is the current directory. |
Library Fcntl: Symbolic Names for IO#fcntl Commands
The Fcntl module provides symbolic names for each of the host system’s available fcntl(2) constants (defined in fcntl.h). That is, if the host system has a constant named F_GETLK defined in fcntl.h, then the Fcntl module will have a corresponding constant Fcntl::F_GETLK with the same value as the header file’s #define.
- Different operating system will have different Fcntl constants available. The value associated with a constant of a given name may also differ across platforms. Here are the values on our Mac OS X system:
|
require 'fcntl' |
|
|
|
Fcntl.constants.sort.each do |name| |
|
printf "%10s: 0x%06x\n", name, Fcntl.const_get(name) |
|
end |
- Produces:
|
FD_CLOEXEC: 0x000001 |
|
F_DUPFD: 0x000000 |
|
F_GETFD: 0x000001 |
|
F_GETFL: 0x000003 |
|
F_GETLK: 0x000007 |
|
F_RDLCK: 0x000001 |
|
F_SETFD: 0x000002 |
|
F_SETFL: 0x000004 |
|
F_SETLK: 0x000008 |
|
F_SETLKW: 0x000009 |
|
F_UNLCK: 0x000002 |
|
F_WRLCK: 0x000003 |
|
O_ACCMODE: 0x000003 |
|
O_CREAT: 0x000200 |
|
O_EXCL: 0x000800 |
|
O_NDELAY: 0x000004 |
|
O_NOCTTY: 0x020000 |
|
O_NONBLOCK: 0x000004 |
|
O_RDONLY: 0x000000 |
|
O_RDWR: 0x000002 |
|
O_TRUNC: 0x000400 |
|
O_WRONLY: 0x000001 |
Library Fiber: Coroutines Using Fibers
The Fiber class that is built into Ruby provides a generator-like capability—fibers may be created and resumed from some controlling program. If you want to extend the Fiber class to provide full, symmetrical coroutines, you need first to require the fiber library. This adds two instance methods, transfer and alive? , to Fiber objects and adds the singleton method current to the Fiber class.
- It is difficult to come up with a meaningful, concise example of symmetric coroutines that can’t more easily be coded with asymetric (plain old) fibers. So, here’s an artificial example:
|
require 'fiber' |
|
|
|
# take items two at a time off a queue, calling the producer |
|
# if not enough are available |
|
consumer = Fiber.new do |producer, queue| |
|
5.times do |
|
while queue.size < 2 |
|
queue = producer.transfer(consumer, queue) |
|
end |
|
puts "Consume #{queue.shift} and #{queue.shift}" |
|
end |
|
end |
|
|
|
# add items three at a time to the queue |
|
producer = Fiber.new do |consumer, queue| |
|
value = 1 |
|
loop do |
|
puts "Producing more stuff" |
|
3.times { queue << value; value += 1} |
|
puts "Queue size is #{queue.size}" |
|
consumer.transfer queue |
|
end |
|
end |
|
|
|
consumer.transfer(producer, []) |
- Produces:
|
Producing more stuff |
|
Queue size is 3 |
|
Consume 1 and 2 |
|
Producing more stuff |
|
Queue size is 4 |
|
Consume 3 and 4 |
|
Consume 5 and 6 |
|
Producing more stuff |
|
Queue size is 3 |
|
Consume 7 and 8 |
|
Producing more stuff |
|
Queue size is 4 |
|
Consume 9 and 10 |
Library Fiddle: Access Dynamically Loaded Libraries (.dll and .so)
The Fiddle module is a wrapper around libffi, a library that provides access to shared libraries. On Windows boxes, it can be used to interface with functions in DLLs. Under Unix it can load shared libraries. Because Ruby does not have typed method parameters or return values, you must define the types expected by the methods you call by specifying their signatures.
- Here’s a trivial C program that we’ll build as a shared library:
sl_fiddle/lib.c |
|
|
#include <stdio.h> |
|
int print_msg(char *text, int number) { |
|
int count = printf("Text: %s (%d)\n", text, number); |
|
fflush(stdout); |
|
return count; |
|
} |
- Generates a proxy to access the print_msg method in the shared library. The way this book is built, the shared library is in the same directory as the Ruby code; this directory must be added to the directories searched when looking for dynamic objects. You can do this by setting the DYLD_LIBRARY_PATH environment variable.
|
require 'fiddle' |
|
include Fiddle |
|
lib = Fiddle.dlopen("lib.so") |
|
print_msg = Fiddle::Function.new(lib['print_msg'], # entry point |
|
[TYPE_VOIDP, TYPE_INT], # parameter types |
|
TYPE_INT) # return type |
|
msg_size = print_msg.call("Answer", 42) |
|
puts "Just wrote #{msg_size} bytes" |
- Produces:
|
Text: Answer (42) |
|
Just wrote 18 bytes |
Library FileUtils: File and Directory Manipulation
FileUtils is a collection of methods for manipulating files and directories. Although generally applicable, the model is particularly useful when writing installation scripts and Rake tasks.
Many methods take a src parameter and a dest parameter. If dest is a directory, src may be a single filename or an array of filenames. For example, the following copies the files a, b, and c to /tmp:
|
cp( %w{ a b c }, "/tmp") |
Most functions take a set of options. These may be zero or more of the following:
Option |
Meaning |
:verbose |
Traces execution of each function (by default to STDERR, although this can be overridden by setting the class variable @fileutils_output. |
:noop |
Does not perform the action of the function (useful for testing scripts). |
:force |
Overrides some default conservative behavior of the method (for example, overwriting an existing file). |
:preserve |
Attempts to preserve atime, mtime, and mode information from src in dest. (Setuid and setgid flags are always cleared.) |
For maximum portability, use forward slashes to separate the directory components of filenames, even on Windows.
FileUtils contains three submodules that duplicate the top-level methods but that have different default options: module FileUtils::Verbose sets the verbose option, module FileUtils::NoWrite sets noop, and FileUtils::DryRun sets verbose and noop.
- un
|
require 'fileutils' |
|
include FileUtils::Verbose |
|
cd("/tmp") do |
|
cp("/etc/passwd", "tmp_passwd") |
|
chmod(0666, "tmp_passwd") |
|
cp_r("/usr/include/net/", "headers") |
|
rm("tmp_passwd") # Tidy up |
|
rm_rf("headers") |
|
end |
Produces:
|
cd /tmp |
|
cp /etc/passwd tmp_passwd |
|
chmod 666 tmp_passwd |
|
cp -r /usr/include/net/ headers |
|
rm tmp_passwd |
|
rm -rf headers |
|
cd - |
Library Find: Traverse Directory Trees
The Find module supports the top-down traversal of a set of file paths, given as arguments to the find method. If an argument is a file, its name is passed to the block associated with the call. If it’s a directory, then its name and the name of all its files and subdirectories will be passed in. If no block is associated with the call, an Enumerator is returned.
Within the block, the method prune may be called, which skips the current file or directory, restarting the loop with the next directory. If the current file is a directory, that directory will not be recursively entered. In the following example, we don’t list the contents of the local Subversion cache directories:
|
require 'find' |
|
Find.find("/etc/passwd", "code/ducktyping") do |f| |
|
type = case |
|
when File.file?(f) then "File: " |
|
when File.directory?(f) then "Dir: " |
|
else "?" |
|
end |
|
puts "#{type} #{f}" |
|
Find.prune if f =~ /.svn/ |
|
end |
Produces:
|
File: /etc/passwd |
|
Dir: code/ducktyping |
|
Dir: code/ducktyping/.svn |
|
File: code/ducktyping/addcust.rb |
|
File: code/ducktyping/roman3.rb |
|
File: code/ducktyping/testaddcust1.rb |
|
File: code/ducktyping/testaddcust2.rb |
|
File: code/ducktyping/testaddcust3.rb |
Library Forwardable: Object Delegation
Forwardable provides a mechanism to allow classes to delegate named method calls to other objects.
- Delegator
- This simple symbol table uses a hash, exposing a subset of the hash’s methods:
|
require 'forwardable' |
|
|
|
class SymbolTable |
|
extend Forwardable |
|
def_delegator(:@hash, :[], :lookup) |
|
def_delegator(:@hash, :[]=, :add) |
|
def_delegators(:@hash, :size, :has_key?) |
|
def initialize |
|
@hash = Hash.new |
|
end |
|
end |
|
|
|
st = SymbolTable.new |
|
st.add('cat', 'feline animal') # => "feline animal" |
|
st.add('dog', 'canine animal') # => "canine animal" |
|
st.add('cow', 'bovine animal') # => "bovine animal" |
|
|
|
st.has_key?('cow') # => true |
|
st.lookup('dog') # => "canine animal" |
- Forwards can also be defined for individual objects by extending them with the SingleForwardable module. It’s hard to think of a good reason to use this feature, so here’s a silly one:
|
require 'forwardable' |
|
|
|
TRICKS = [ "roll over", "play dead" ] |
|
|
|
dog = "rover" |
|
dog.extend SingleForwardable |
|
dog.def_delegator(:TRICKS, :each, :can) |
|
|
|
dog.can do |trick| |
|
puts trick |
|
end |
- Produces:
|
roll over |
|
play dead |
Library GDBM: Interface to GDBM Database
gdbm library available
Interfaces to the gdbm database library.[126] Although the DBM library provides generic access to gdbm databases, it doesn’t expose some features of the full gdbm interface, such as the cache size, synchronization mode, reorganization, and locking. Only one process may have a GDBM database open for writing (unless locking is disabled).
- DBM
- SDBM
- Stores some values into a database and then reads them back. The second parameter to the open method specifies the file mode, and the next parameter uses two flags that (1) create the database if it doesn’t exist and (2) force all writes to be synced to disk. Create on open is the default Ruby gdbm behavior.
|
require 'gdbm' |
|
|
|
GDBM.open("data.dbm", 0644, GDBM::WRCREAT | GDBM::SYNC) do |dbm| |
|
dbm['name'] = "Walter Wombat" |
|
dbm['dob'] = "1969-12-25" |
|
dbm['uses'] = "Ruby" |
|
end |
|
|
|
GDBM.open("data.dbm") do |dbm| |
|
p dbm.keys |
|
p dbm['dob'] |
|
dbm.delete('dob') |
|
p dbm.keys |
|
end |
- Opens a database read-only. The attempt to delete a key would fail.
|
require 'gdbm' |
|
|
|
GDBM.open("data.dbm", 0, GDBM::READER) do |dbm| |
|
p dbm.keys |
|
dbm.delete('name') # !! fails !! |
|
end |
Library GetoptLong: Parse Command-Line Options
Class GetoptLong supports GNU-style command-line option parsing. Options may be a minus sign (-) followed by a single character or may be two minus signs (--) followed by a name (a long option). Long options may be abbreviated to their shortest unambiguous lengths.
A single internal option may have multiple external representations. For example, the option to control verbose output could be any of -v, --verbose, or --details. Some options may also take an associated value.
Each internal option is passed to GetoptLong as an array, containing strings representing the option’s external forms and a flag. The flag specifies how GetoptLong is to associate an argument with the option (NO_ARGUMENT, REQUIRED_ARGUMENT, or OPTIONAL_ARGUMENT).
If the environment variable POSIXLY_CORRECT is set, all options must precede nonoptions on the command line. Otherwise, the default behavior of GetoptLong is to reorganize the command line to put the options at the front. This behavior may be changed by setting the attributeGetoptLong#ordering= to one of PERMUTE, REQUIRE_ORDER, or RETURN_IN_ORDER. The environment variable POSIXLY_CORRECT may not be overridden.
- OptionParser
|
# Call using "ruby example.rb --size 10k -v -q a.txt b.doc" |
|
|
|
require 'getoptlong' |
|
|
|
# Fake out an initial command line |
|
ARGV.clear.push *%w(--size 10k -v -q a.txt b.doc) |
|
|
|
# specify the options we accept and initialize |
|
# the option parser |
|
|
|
opts = GetoptLong.new( |
|
[ "--size", "-s", GetoptLong::REQUIRED_ARGUMENT ], |
|
[ "--verbose", "-v", GetoptLong::NO_ARGUMENT ], |
|
[ "--query", "-q", GetoptLong::NO_ARGUMENT ], |
|
[ "--check", "--valid", "-c", GetoptLong::NO_ARGUMENT ] |
|
) |
|
|
|
# process the parsed options |
|
|
|
opts.each do |opt, arg| |
|
puts "Option: #{opt}, arg #{arg.inspect}" |
|
end |
|
|
|
puts "Remaining args: #{ARGV.join(', ')}" |
Produces:
|
Option: --size, arg "10k" |
|
Option: --verbose, arg "" |
|
Option: --query, arg "" |
|
Remaining args: a.txt, b.doc |
Library GServer: Generic TCP Server
This is a simple framework for writing TCP servers. To use it, subclass the GServer class, set the port (and potentially other parameters) in the constructor, and then implement a serve method to handle incoming requests.
GServer manages a thread pool for incoming connections, so your serve method may be running in multiple threads in parallel.
You can run multiple GServer copies on different ports in the same application.
- When a connection is made on port 2000, responds with the current time as a string. Terminates after handling three requests.
|
require 'gserver' |
|
class TimeServer < GServer |
|
def initialize |
|
super(2000) |
|
@count = 3 |
|
end |
|
def serve(client) |
|
client.puts Time.now |
|
@count -= 1 |
|
stop if @count.zero? |
|
end |
|
end |
|
server = TimeServer.new |
|
server.start.join |
- You can test this server by reading from localhost on port 2000. We use curl to do this—you could also use telnet:
|
$ curl -s localhost:2000 |
|
2013-05-27 12:33:22 -0500 |
Library IO/console: Add console support to IO objects
Require io/console, and I/O objects associated with terminals gain the methods IO#raw, IO#raw!, IO#getch, IO#echo=, IO#echo?, IO#noecho, IO#winsize, IO#winsize=, IO#iflush, IO#oflush, and IO#ioflush. The IO class also gains a singleton method, IO.console, which returns an I/O object connected to the controlling terminal of the process.
- Prompt for a password with no echo.
|
require 'io/console' |
|
password = STDIN.noecho do |
|
print "Your password: " |
|
gets |
|
end |
- What’s the size of the controlling terminal?
|
require "io/console" |
|
IO.console.winsize # => [22, 137] |
Library IO/nonblock: Turn blocking I/O on and off
If a program requires io/nonblock, I/O objects gain the methods IO#nonblock, IO#nonblock?, and IO#nonblock=. The first takes a block, and runs that block with the given file description in nonblocking mode. The second lets you query the blocking status of a file descriptor, and the last lets you turn blocking on and off. You’ll probably want to investigate IO.select, as you’ll need it to tell when the file cn be read or written.
Library IO/Wait: Check for Pending Data to Be Read
FIONREAD feature in ioctl(2)
Including the library io/wait adds the methods IO#nread, IO#ready?, and IO#wait to the standard IO class. These allow an IO object opened on a stream (not a file) to be queried to see whether data is available to be read without reading it and to wait for a given number of bytes to become available.
- Sets up a pipe between two processes and writes 10 bytes at a time into it. Periodically sees how much data is available.
|
require 'io/wait' |
|
|
|
reader, writer = IO.pipe |
|
|
|
if (pid = fork) |
|
writer.close |
|
8.times do |
|
sleep 0.03 |
|
if reader.ready? |
|
len = reader.nread |
|
puts "#{len} bytes available: #{reader.sysread(len)}" |
|
else |
|
puts "No data available" |
|
end |
|
end |
|
Process.waitpid(pid) |
|
else |
|
reader.close |
|
5.times do |n| |
|
sleep 0.04 |
|
writer.write n.to_s * 10 |
|
end |
|
writer.close |
|
end |
- Produces:
|
No data available |
|
10 bytes available: 0000000000 |
|
10 bytes available: 1111111111 |
|
10 bytes available: 2222222222 |
|
No data available |
|
10 bytes available: 3333333333 |
|
10 bytes available: 4444444444 |
|
No data available |
Library IPAddr: Represent and Manipulate IP Addresses
Class IPAddr holds and manipulates Internet Protocol (IP) addresses. Each address contains three parts: an address, a mask, and an address family. The family will typically be AF_INET for IPv4 and IPv6 addresses. The class contains methods for extracting parts of an address, checking for IPv4-compatible addresses (and IPv4-mapped IPv6 addresses), testing whether an address falls within a subnet, and performing many other functions. It is also interesting in that it contains as data its own unit tests.
|
require 'ipaddr' |
|
|
|
v4 = IPAddr.new('192.168.23.0/24') |
|
v4 # => #<IPAddr: IPv4:192.168.23.0/ 255.255.255.0> |
|
v4.mask(16) # => #<IPAddr: IPv4:192.168.0.0/ 255.255.0.0> |
|
v4.reverse # => "0.23.168.192.in-addr.arpa" |
|
v6 = IPAddr.new('3ffe:505:2::1') |
|
v6 # => #<IPAddr: IPv6:3ffe:0505:0002:0000:0000:0000:0000:0001/ |
|
# .. ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff> |
|
v6.mask(48) # => #<IPAddr: IPv6:3ffe:0505:0002:0000:0000:0000:0000:0000/ |
|
# .. ffff:ffff:ffff:0000:0000:0000:0000:0000> |
|
|
|
# the value for 'family' is OS dependent. This |
|
# value is for OS X |
|
v6.family # => 30 |
|
|
|
other = IPAddr.new("192.168.23.56") |
|
v4.include?(other) # => true |
Library irb: Interactive Ruby
The irb library is most commonly associated with the console command irb. However, you can also start an irb session from within your running application. A common technique is to trap a signal and start irb in the handler.
The following program sets up a signal handler that runs irb when the user hits ^C. The user can change the value of the instance variable @value. When they exit from irb, the original program continues to run with that new value.
sl_irb/run_irb.rb |
|
|
require 'irb' |
|
|
|
trap "INT" do |
|
IRB.start |
|
end |
|
|
|
count = 0 |
|
loop do |
|
count += 1 |
|
puts count |
|
puts "Value = #{@value}" ifdefined? @value |
|
sleep 1 |
|
end |
Here’s a simple session using it:
|
$ ruby code/sl_irb/run_irb.rb |
|
1 |
|
2 |
|
3 |
|
^Cruby-1.9.2-p0 > @value = "wibble" |
|
=> "wibble" |
|
ruby-1.9.2-p0 > exit |
|
4 |
|
Value = wibble |
|
5 |
|
Value = wibble |
|
. . . |
Library json: Generate and Parse JSON Format
JSON is a language-independent data interchange format based on key/value pairs (hashes in Ruby) and sequences of values (arrays in Ruby).[127] JSON is frequently used to exchange data between JavaScript running in browsers and server-based applications. JSON is not a general-purpose object marshaling format. Although you can add to_json methods to your own classes, you will lose interoperability.
- yaml
- Serializes a data structure into a string and writes that to a file:
|
require 'json' |
|
data = { name: 'dave', address: [ 'tx', 'usa' ], age: 17 } |
|
serialized = data.to_json |
|
serialized # => {"name":"dave","address":["tx","usa"],"age":17} |
|
File.open("data", "w") {|f| f.puts serialized} |
- Reads the serialized data from the file and reconstitutes it:
|
require 'json' |
|
serialized = File.read("data") |
|
data = JSON.parse(serialized) |
|
data # => {"name"=>"dave", "address"=>["tx", "usa"], "age"=>17} |
- The methods j and jj convert their argument to JSON and write the result to STDOUT (jj prettyprints). This can be useful in irb.
|
require 'json' |
|
data = { name: 'dave', address: [ 'tx', 'usa' ], age: 17 } |
|
puts "Regular" |
|
j data |
|
puts "Pretty" |
|
jj data |
- Produces:
|
Regular |
|
{"name":"dave","address":["tx","usa"],"age":17} |
|
Pretty |
|
{ |
|
"name": "dave", |
|
"address": [ |
|
"tx", |
|
"usa" |
|
], |
|
"age": 17 |
|
} |
Library Logger: Application Logging
Writes log messages to a file or stream. Supports automatic time- or size-based rolling of log files. Messages can be assigned severities, and only those messages at or above the logger’s current reporting level will be logged.
- During development, you may want to see all messages:
|
require 'logger' |
|
log = Logger.new(STDOUT) |
|
log.level = Logger::DEBUG |
|
log.datetime_format = "%H:%M:%S" |
|
log.info("Application starting") |
|
3.times do |i| |
|
log.debug("Executing loop, i = #{i}") |
|
temperature = some_calculation(i) # defined externally |
|
if temperature > 50 |
|
log.warn("Possible overheat. i = #{i}") |
|
end |
|
end |
|
|
|
log.info("Application terminating") |
- Produces:
|
I, [12:33:23#24712] INFO -- : Application starting |
|
D, [12:33:23#24712] DEBUG -- : Executing loop, i = 0 |
|
D, [12:33:23#24712] DEBUG -- : Executing loop, i = 1 |
|
D, [12:33:23#24712] DEBUG -- : Executing loop, i = 2 |
|
W, [12:33:23#24712] WARN -- : Possible overheat. i = 2 |
|
I, [12:33:23#24712] INFO -- : Application terminating |
- In deployment, you can turn off anything below INFO:
|
require 'logger' |
|
log = Logger.new(STDOUT) |
|
log.level = Logger::INFO |
|
log.datetime_format = "%H:%M:%S" |
|
|
|
# as above... |
- Produces:
|
I, [12:33:23#24714] INFO -- : Application starting |
|
W, [12:33:23#24714] WARN -- : Possible overheat. i = 2 |
|
I, [12:33:23#24714] INFO -- : Application terminating |
- Logs to a file, which is rotated when it gets to about 10KB. Keeps up to five old files.
|
require 'logger' |
|
log = Logger.new("application.log", 5, 10*1024) |
|
|
|
log.info("Application starting") |
|
# ... |
Library mathn: Unified Numbers
The mathn library attempts to bring some unity to numbers under Ruby, making classes Bignum, Complex, Fixnum, Integer, and Rational work and play better together. It automatically includes the libraries complex, rational, matrix, and prime.
- Types will tend to convert between themselves in a more natural way (so, for example, Complex::I squared will evaluate to -1, rather than Complex[-1,0]).
- Division will tend to produce more accurate results. The conventional division operator (/) is redefined to use quo , which doesn’t round.
- Related to the previous point, rational numbers will be used in preference to floats when possible. Dividing one by two results in the rational number 1/2, rather than 0.5 (or 0, the result of normal integer division).
- Matrix
- Rational
- Complex
- Prime
- Without mathn:
|
require 'matrix' |
|
36/16 # => 2 |
|
Math.sqrt(36/16) # => 1.4142135623730951 |
|
|
|
Complex::I * Complex::I # => (-1+0i) |
|
|
|
(36/16)**-2 # => 1/4 |
|
(-36/16)**-2 # => 1/9 |
|
|
|
(36/16)**(1/2) # => 1 |
|
(-36/16)**(1/2) # => 1 |
|
|
|
(36/16)**(-1/2) # => 1/2 |
|
(-36/16)**(-1/2) # => -1/3 |
|
|
|
Matrix.diagonal(6,7,8)/3 # => Matrix[[2, 0, 0], [0, 2, 0], [0, 0, 2]] |
- With mathn:
|
36/16 # => 9/4 |
|
Math.sqrt(36/16) # => 3/2 |
|
|
|
Complex::I * Complex::I # => -1 |
|
|
|
(36/16)**-2 # => 16/81 |
|
(-36/16)**-2 # => 16/81 |
|
|
|
(36/16)**(1/2) # => 3/2 |
|
(-36/16)**(1/2) # => (9.184850993605148e-17+1.5i) |
|
|
|
(36/16)**(-1/2) # => 2/3 |
|
(-36/16)**(-1/2) # => (4.082155997157844e-17-0.6666666666666666i) |
|
|
|
Matrix.diagonal(6,7,8)/3 # => Matrix[[2, 0, 0], [0, 7/3, 0], [0, 0, 8/3]] |
Library Matrix: Matrix and Vector Manipulation
The matrix library defines classes Matrix and Vector, representing rectangular matrices and vectors. As well as the normal arithmetic operations, they provide methods for matrix-specific functions (such as rank, inverse, and determinants) and a number of constructor methods (for creating special-case matrices—zero, identity, diagonal, singular, and vector).
As of Ruby 1.9, matrices use quo internally for division, so rational numbers may be returned as a result of integer division. In prior versions of Ruby, you’d need to include the mathn library to achieve this.
|
require 'matrix' |
|
|
|
m1 = Matrix[ [2, 1], [-1, 1] ] |
|
|
|
m1[0,1] # => 1 |
|
|
|
m1.inv # => Matrix[[1/3, -1/3], [1/3, 2/3]] |
|
|
|
m1 * m1.inv # => Matrix[[1/1, 0/1], [0/1, 1/1]] |
|
|
|
m1.determinant # => 3 |
|
|
|
m1.singular? # => false |
|
|
|
v1 = Vector[3, 4] # => Vector[3, 4] |
|
|
|
v1.covector # => Matrix[[3, 4]] |
|
|
|
m1 * v1 # => Vector[10, 1] |
|
|
|
m2 = Matrix[ [1,2,3], [4,5,6], [7,8,9] ] |
|
|
|
m2.minor(1, 2, 1, 2) # => Matrix[[5, 6], [8, 9]] |
Library MiniTest: Unit Testing Framework
New in Ruby 1.9, MiniTest is now the standard unit testing framework supplied with Ruby. The minitest library contains classes for unit tests, mock objects, and a (trivial) subset of RSpec-style testing syntax.
The unit testing framework is similar to the original Test::Unit framework. However, if you want functionality that is the same as Test::Unit, use the Test::Unit wrappers for MiniTest—simply require "test/unit" as normal.
Chapter 13, Unit Testing contains a tutorial on unit testing with Ruby.
Library Monitor: Monitor-Based Synchronization
Monitors are a mutual-exclusion mechanism. They allow separate threads to define shared resources that will be accessed exclusively, and they provide a mechanism for a thread to wait for resources to become available in a controlled way.
The monitor library actually defines three separate ways of using monitors: by subclassing, as a mixin, and as a extension to a particular object. In this section, we show the mixin form of Monitor. The subclassing form is effectively identical. In both it and when including MonitorMixin in an existing class, it is essential to invoke super in the class’s initialize method.
- Thread
|
# This example would be better written using fibers. |
|
require 'monitor' |
|
require 'mathn' |
|
|
|
numbers = [] |
|
numbers.extend(MonitorMixin) |
|
number_added = numbers.new_cond |
|
|
|
consumer = Thread.new do # Reporter thread |
|
5.times do |
|
numbers.synchronize do |
|
number_added.wait_while { numbers.empty? } |
|
puts numbers.shift |
|
end |
|
end |
|
end |
|
|
|
generator = Thread.new do # Prime number generator thread |
|
primes = Prime.each |
|
5.times do |
|
numbers.synchronize do |
|
numbers << primes.next |
|
number_added.signal |
|
end |
|
end |
|
end |
|
|
|
generator.join |
|
consumer.join |
Produces:
|
2 |
|
3 |
|
5 |
|
7 |
|
11 |
Library Mutex_m: Mutex Mix-In
mutex_m is a variant of class Mutex that allows mutex facilities to be mixed into any object.
The Mutex_m module defines methods that correspond to those in Mutex but with the prefix mu_ (so that lock is defined as mu_lock and so on). These are then aliased to the original Mutex names.
- Mutex
- Thread
|
require 'mutex_m' |
|
|
|
class Counter |
|
include Mutex_m |
|
attr_reader :count |
|
def initialize |
|
@count = 0 |
|
super |
|
end |
|
def tick |
|
lock |
|
@count += 1 |
|
unlock |
|
end |
|
end |
|
|
|
c = Counter.new |
|
|
|
t1 = Thread.new { 100_000.times { c.tick } } |
|
t2 = Thread.new { 100_000.times { c.tick } } |
|
|
|
t1.join |
|
t2.join |
|
|
|
c.count # => 200000 |
Library Net::FTP: FTP Client
The net/ftp library implements a File Transfer Protocol (FTP) client. As well as data transfer commands (getbinaryfile, gettextfile, list, putbinaryfile, and puttextfile), the library supports the full complement of server commands (acct, chdir, delete, mdtm, mkdir, nlst, rename, rmdir, pwd, size, status, and system). Anonymous and password-authenticated sessions are supported. Connections may be active or passive.
- open-uri
|
require 'net/ftp' |
|
|
|
ftp = Net::FTP.new('ftp.ruby-lang.org') |
|
ftp.login |
|
ftp.chdir('pub/ruby/doc') |
|
puts ftp.list('*txt') |
|
ftp.getbinaryfile('MD5SUM.txt', 'md5sum.txt', 1024) |
|
ftp.close |
|
puts File.read('md5sum.txt') |
produces:
|
-rw-rw-r-- 1 1027 100 12149 Sep 10 06:02 MD5SUM.txt |
|
-rw-rw-r-- 1 1027 100 13565 Sep 10 06:03 SHA1SUM.txt |
|
d529768c828c930c49b3766d13dc1f2c ruby-man-1.4.6-jp.tar.gz |
|
8eed63fec14a719df26247fb8384db5e ruby-man-1.4.6.tar.gz |
|
623b5d889c1f15b8a50fe0b3b8ba4b0f ruby-man-ja-1.6.6-20011225-rd.tar.gz |
|
5f37ef2d67ab1932881cd713989af6bf ruby-man-ja-html-20050214.tar.bz2 |
|
e9949b2023a63b6259b02bed4fb13064 ruby-man-ja-html-20050214.tar.gz |
|
. . . |
Library Net::HTTP: HTTP Client
The net/http library provides a simple client to fetch headers and web page contents using the HTTP and HTTPS protocols.
The get post and head methods return a response object, with the content of the response accessible through the response’s body method.
- OpenSSL
- open-uri
- URI
- Opens a connection and fetches a page, displaying the response code and message, header information, and some of the body:
|
require 'net/http' |
|
|
|
Net::HTTP.start('www.pragprog.com') do |http| |
|
response = http.get('/categories/new') |
|
puts "Code = #{response.code}" |
|
puts "Message = #{response.message}" |
|
response.each {|key, val| printf "%-14s = %-40.40s\n", key, val } |
|
p response.body[0, 55] |
|
end |
- Produces:
|
Code = 302 |
|
Message = Found |
|
content-type = text/html; charset=utf-8 |
|
date = Mon, 27 May 2013 17:36:21 GMT |
|
location = http://pragprog.com/categories/new |
|
server = nginx/1.2.6 |
|
status = 302 Found |
|
x-request-id = 1c76c5446f0a1dd001ceb768f2611364 |
|
x-runtime = 0.004833 |
|
x-ua-compatible = IE=Edge,chrome=1 |
|
content-length = 100 |
|
connection = keep-alive |
|
"<html><body>You are being <a href=\"http://pragprog.com/" |
- Fetches a single page, displaying the response code and message, header information, and some of the body:
|
require 'net/http' |
|
|
|
response = Net::HTTP.get_response('www.pragprog.com', |
|
'/categories/new') |
|
puts "Code = #{response.code}" |
|
puts "Message = #{response.message}" |
|
response.each {|key, val| printf "%-14s = %-40.40s\n", key, val } |
|
p response.body[0, 55] |
- Produces:
|
Code = 302 |
|
Message = Found |
|
content-type = text/html; charset=utf-8 |
|
date = Mon, 27 May 2013 17:36:21 GMT |
|
location = http://pragprog.com/categories/new |
|
server = nginx/1.2.6 |
|
status = 302 Found |
|
x-request-id = ab9de753032bb022cbd33fefbe030f56 |
|
x-runtime = 0.005468 |
|
x-ua-compatible = IE=Edge,chrome=1 |
|
content-length = 100 |
|
connection = keep-alive |
|
"<html><body>You are being <a href=\"http://pragprog.com/" |
- Follows redirections (the open-uri library does this automatically). This code comes from the RDoc documentation.
|
require 'net/http' |
|
require 'uri' |
|
|
|
def fetch(uri_str, limit=10) |
|
fail 'http redirect too deep' if limit.zero? |
|
puts "Trying: #{uri_str}" |
|
response = Net::HTTP.get_response(URI.parse(uri_str)) |
|
case response |
|
when Net::HTTPSuccess then response |
|
when Net::HTTPRedirection then fetch(response['location'], limit-1) |
|
else response.error! |
|
end |
|
end |
|
|
|
response = fetch('http://www.ruby-lang.org') |
|
p response.body[0, 50] |
- Produces:
|
Trying: http://www.ruby-lang.org |
|
"<html>\n <head>\n <script type=\"text/javascript\"" |
- Searches our site for things about Ruby and lists the authors. (This would be tidier using Hpricot.)
|
require 'net/http' |
|
|
|
uri = URI.parse('http://pragprog.com/search') |
|
response = Net::HTTP.post_form(uri, "q" => "ruby") |
|
puts response.body.scan(%r{<p class="by-line">by (.*?)</p>})[0,3] |
- produces:
|
Caleb Tennis |
|
Maik Schmidt |
|
Bruce Tate |
Library Net::IMAP: Access an IMAP Mail Server
The Internet Mail Access Protocol (IMAP) is used to allow mail clients to access mail servers. It supports plain-text login and the IMAP login and CRAM-MD5 authentication mechanisms. Once connected, the library supports threading, so multiple interactions with the server may take place at the same time.
The examples that follow are taken with minor modifications from the RDoc documentation in the library source file.
The TMail gem provides an interface for creating and parsing email messages.
- Net::POP
- Lists senders and subjects of messages to “dave” in the inbox:
|
require 'net/imap' |
|
|
|
imap = Net::IMAP.new('my.mailserver.com') |
|
imap.authenticate('LOGIN', 'dave', 'secret') |
|
imap.examine('INBOX') |
|
puts "Message count: #{ imap.responses["EXISTS"]}" |
|
imap.search(["TO", "dave"]).each do |message_id| |
|
envelope = imap.fetch(message_id, "ENVELOPE")[0].attr["ENVELOPE"] |
|
puts "#{envelope.from[0].name}: \t#{envelope.subject}" |
|
end |
- Moves all email messages with a date in April 2008 from the folder Mail/sent-mail to Mail/sent-apr08:
|
require 'net/imap' |
|
imap = Net::IMAP.new('my.mailserver.com') |
|
imap.authenticate('LOGIN', 'dave', 'secret') |
|
imap.select('Mail/sent-mail') |
|
ifnot imap.list('Mail/', 'sent-apr08') |
|
imap.create('Mail/sent-apr08') |
|
end |
|
imap.search(["BEFORE", "01-May-2008", |
|
"SINCE", "1-Apr-2008"]).each do |message_id| |
|
imap.copy(message_id, "Mail/sent-apr08") |
|
imap.store(message_id, "+FLAGS", [:Deleted]) |
|
end |
|
imap.expunge |
Library Net::POP: Access a POP Mail Server
The net/pop library provides a simple client to fetch and delete mail on a Post Office Protocol (POP) server.
The class Net::POP3 is used to access a POP server, returning a list of Net::POPMail objects, one per message stored on the server. These POPMail objects are then used to fetch and/or delete individual messages.
The library also provides class APOP, an alternative to the POP3 class that performs encrypted authentication.
|
require 'net/pop' |
|
pop = Net::POP3.new('server.ruby-stuff.com') |
|
pop.start('joe', 'secret') do |server| |
|
msg = server.mails[0] |
|
|
|
# Print the 'From:' header line |
|
from = msg.header.split("\r\n").grep(/^From: /)[0] |
|
puts from |
|
puts |
|
puts "Full message:" |
|
text = msg.pop |
|
puts text |
|
end |
Produces:
|
From: dave@facet.ruby-stuff.com (Dave Thomas) |
|
|
|
Full message: |
|
Return-Path: <dave@facet.ruby-stuff.com> |
|
Received: from facet.ruby-stuff.com (facet.ruby-stuff.com [10.96.0.122]) |
|
by pragprog.com (8.11.6/8.11.6) with ESMTP id i2PJMW701809 |
|
for <joe@carat.ruby-stuff.com>; Thu, 25 Mar 2008 13:22:32 -0600 |
|
Received: by facet.ruby-stuff.com (Postfix, from userid 502) |
|
id 4AF228B1BD; Thu, 25 Mar 2008 13:22:36 -0600 (CST) |
|
To: joe@carat.ruby-stuff.com |
|
Subject: Try out the new features! |
|
Message-Id: <20080325192236.4AF228B1BD@facet.ruby-stuff.com> |
|
Date: Thu, 25 Mar 2008 13:22:36 -0600 (CST) |
|
From: dave@facet.ruby-stuff.com (Dave Thomas) |
|
Status: RO |
|
|
|
Ruby 1.9 has even more new features, both in |
|
the core language and in the supplied libraries. |
|
|
|
Try it out! |
Library Net::SMTP: Simple SMTP Client
The net/smtp library provides a simple client to send electronic mail using the Simple Mail Transfer Protocol (SMTP). It does not assist in the creation of the message payload—it simply delivers messages once an RFC 822 message has been constructed. The TMail gem provides an interface for creating and parsing email messages.
- Sends an e-mail from a string:
|
require 'net/smtp' |
|
|
|
msg = "Subject: Test\n\nNow is the time\n" |
|
Net::SMTP.start('pragprog.com') do |smtp| |
|
smtp.send_message(msg, 'dave@pragprog.com', ['dave']) |
|
end |
- Sends an e-mail using an SMTP object and an adapter:
|
require 'net/smtp' |
|
|
|
Net::SMTP::start('pragprog.com', 25, "pragprog.com") do |smtp| |
|
smtp.open_message_stream('dave@pragprog.com', # from |
|
[ 'dave' ] # to |
|
) do |stream| |
|
stream.puts "Subject: Test1" |
|
stream.puts |
|
stream.puts "And so is this" |
|
end |
|
end |
- Sends an e-mail to a server requiring CRAM-MD5 authentication:
|
require 'net/smtp' |
|
|
|
msg = "Subject: Test\n\nNow is the time\n" |
|
Net::SMTP.start('pragprog.com', 25, 'pragprog.com', |
|
'user', 'password', :cram_md5) do |smtp| |
|
smtp.send_message(msg, 'dave@pragprog.com', ['dave']) |
|
end |
Library Net::Telnet: Telnet Client
The net/telnet library provides a complete implementation of a telnet client and includes features that make it a convenient mechanism for interacting with nontelnet services.
- Connects to localhost, runs the date command, and disconnects:
|
require 'net/telnet' |
|
tn = Net::Telnet.new({}) |
|
tn.login "testuser", "wibble" |
|
tn.cmd "date" # => "date\nMon May 27 12:33:29 CDT 2013\nlight-boy:~ testuser$ " |
- The methods new , cmd , login , and waitfor take an optional block. If present, the block is passed output from the server as it is received by the routine. This can be used to provide real-time output, rather than waiting (for example) for a login to complete before displaying the server’s response.
|
require 'net/telnet' |
|
tn = Net::Telnet.new({}) {|str| print str } |
|
tn.login("testuser", "wibble") {|str| print str } |
|
tn.cmd("date") {|str| print str } |
- Produces:
|
Trying localhost... |
|
Connected to localhost. |
|
|
|
Darwin/BSD (light-boy.local) (ttys007) |
|
|
|
login: testuser |
|
Password: |
|
Last login: Mon May 27 12:33:29 on ttys007 |
|
light-boy:~ testuser$ date |
|
Mon May 27 12:33:29 CDT 2013 |
|
light-boy:~ testuser$ |
- Query a WHOIS server on port 43.
|
require 'net/telnet' |
|
tn = Net::Telnet.new('Host' => 'whois.domain.com', |
|
'Port' => '43', |
|
'Timeout' => 5, |
|
'Telnetmode' => false) |
|
tn.write("pragprog.com\r\n") |
|
puts tn.sock.grep(/ on /) |
- Produces:
|
Record last updated on 15-Oct-2012. |
|
Record expires on 19-Jan-2016. |
|
Record created on 19-Jan-1999. |
Library NKF: Interface to Network Kanji Filter
The NKF module is a wrapper around Itaru Ichikawa’s Network Kanji Filter (NKF) library (version 1.7). It provides functions to guess at the encoding of JIS, EUC, and SJIS streams and to convert from one encoding to another. Even though Ruby 1.9 now supports these encodings natively, this library is still useful for guessing encodings.
- As of Ruby 1.9, NFK uses the built-in encoding objects:
|
require 'nkf' |
|
NKF::AUTO # => nil |
|
NKF::JIS # => #<Encoding:ISO-2022-JP (dummy)> |
|
NKF::EUC # => #<Encoding:EUC-JP> |
|
NKF::SJIS # => #<Encoding:Shift_JIS> |
- Guesses at the encoding of a string. (Thanks to Nobu Nakada for the examples on this page.)
|
require 'nkf' |
|
p NKF.guess("Yukihiro Matsumoto") |
|
p NKF.guess("\e$B$^$D$b$H$f$-$R$m\e(B") |
|
p NKF.guess("\244\336\244\304\244\342\244\310\244\346\244\255\244\322\244\355") |
|
p NKF.guess("\202\334\202\302\202\340\202\306\202\344\202\253\202\320\202\353") |
- Produces:
|
#<Encoding:US-ASCII> |
|
#<Encoding:ISO-2022-JP (dummy)> |
|
#<Encoding:EUC-JP> |
|
#<Encoding:Shift_JIS> |
- TheNFK.nkf method takes two parameters. The first is a set of options, passed on to the NKF library. The second is the string to translate. The following examples assume that your console is set up to accommodate Japanese characters. The text at the end of the three ruby commands is Yukihiro Matsumoto in Hiragana.
Library Observable: The Observer Pattern
The Observer pattern, also known as Publish/Subscribe, provides a simple mechanism for one object (the source) to inform a set of interested third-party objects when its state changes (see Design Patterns [GHJV95]). In the Ruby implementation, the notifying class mixes in the module Observable, which provides the methods for managing the associated observer objects. The observers must implement the update method to receive notifications.
|
require 'observer' |
|
|
|
class CheckWaterTemperature # Periodically check the water |
|
include Observable |
|
|
|
def run |
|
last_temp = nil |
|
loop do |
|
temp = Temperature.fetch # external class... |
|
puts "Current temperature: #{temp}" |
|
if temp != last_temp |
|
changed # notify observers |
|
notify_observers(Time.now, temp) |
|
last_temp = temp |
|
end |
|
end |
|
end |
|
end |
|
|
|
class Warner |
|
def initialize(&limit) |
|
@limit = limit |
|
end |
|
def update(time, temp) # callback for observer |
|
if @limit.call(temp) |
|
puts "--- #{time.to_s}: Temperature outside range: #{temp}" |
|
end |
|
end |
|
end |
|
|
|
checker = CheckWaterTemperature.new |
|
checker.add_observer(Warner.new {|t| t < 80}) |
|
checker.add_observer(Warner.new {|t| t > 120}) |
|
checker.run |
Produces:
|
Current temperature: 83 |
|
Current temperature: 75 |
|
--- 2013-05-27 12:33:30 -0500: Temperature outside range: 75 |
|
Current temperature: 90 |
|
Current temperature: 134 |
|
--- 2013-05-27 12:33:30 -0500: Temperature outside range: 134 |
|
Current temperature: 134 |
|
Current temperature: 112 |
|
Current temperature: 79 |
|
--- 2013-05-27 12:33:30 -0500: Temperature outside range: 79 |
Library open-uri: Treat FTP and HTTP Resources as Files
The open-uri library extends Object#open, allowing it to accept URIs for FTP and HTTP as well as local filenames. Once opened, these resources can be treated as if they were local files, accessed using conventional IO methods. The URI passed to open is either a string containing an HTTP or FTP URL or a URI object (see the URI library). When opening an HTTP resource, the method automatically handles redirection and proxies. When using an FTP resource, the method logs in as an anonymous user.
The IO object returned by open in these cases is extended to support methods that return metainformation from the request: content_type , charset , content_encoding , last_modified , status , base_uri , meta .
- URI
|
require 'open-uri' |
|
require 'pp' |
|
|
|
open('http://ruby-lang.org') do |f| |
|
puts "URI: #{f.base_uri}" |
|
puts "Content-type: #{f.content_type}, charset: #{f.charset}" |
|
puts "Encoding: #{f.content_encoding}" |
|
puts "Last modified: #{f.last_modified}" |
|
puts "Status: #{f.status.inspect}" |
|
pp f.meta |
|
puts "----" |
|
3.times {|i| puts "#{i}: #{f.gets}" } |
|
end |
Produces:
|
URI: http://www.ruby-lang.org/ |
|
Content-type: text/html, charset: iso-8859-1 |
|
Encoding: [] |
|
Last modified: 2013-05-22 16:31:36 -0500 |
|
Status: ["200", "OK"] |
|
{"date"=>"Mon, 27 May 2013 17:33:23 GMT", |
|
"server"=>"nginx/0.7.67", |
|
"content-type"=>"text/html", |
|
"content-length"=>"748", |
|
"last-modified"=>"Wed, 22 May 2013 21:31:36 GMT", |
|
"accept-ranges"=>"bytes", |
|
"via"=>"1.1 www.ruby-lang.org"} |
|
---- |
|
0: <html> |
|
1: <head> |
|
2: <script type="text/javascript"> |
Library Open3: Run Subprocess and Connect to All Streams
Runs a command in a subprocess. Data written to stdin can be read by the subprocess, and data written to standard output and standard error in the subprocess will be available on the stdout and stderr streams. The subprocess is actually run as a grandchild, and as a result, Process#waitallcannot be used to wait for its termination (hence the sleep in the following example). Note also that you probably cannot assume that the application’s output and error streams will not be buffered, so output may not arrive when you expect it to arrive.
|
require 'open3' |
|
|
|
def read_from(label, stream) |
|
while line = stream.gets |
|
puts "#{label}: #{line}" |
|
end |
|
end |
|
|
|
Open3.popen3('bc') do | stdin, stdout, stderr | |
|
t1 = Thread.new { read_from('STDOUT', stdout) } |
|
t2 = Thread.new { read_from('STDERR', stderr) } |
|
stdin.puts "3 * 4" |
|
stdin.puts "1 / 0" |
|
stdin.puts "2 ^ 5" |
|
stdin.close |
|
t1.join |
|
t2.join |
|
end |
Produces:
|
STDOUT: 12 |
|
STDERR: Runtime error (func=(main), adr=3): Divide by zero |
|
STDOUT: 32 |
Library OpenSSL: SSL Library
OpenSSL library available
The Ruby OpenSSL extension wraps the freely available OpenSSL library.[128] It provides the Secure Sockets Layer and Transport Layer Security (SSL and TLS) protocols, allowing for secure communications over networks. The library provides functions for certificate creation and management, message signing, and encryption/decryption. It also provides wrappers to simplify access to HTTPS servers, along with secure FTP. The interface to the library is large (roughly 330 methods), but the average Ruby user will probably use only a small subset of the library’s capabilities.
- Net::FTP
- Net::HTTP
- Socket
- Accesses a secure website using HTTPS. Note that SSL is used to tunnel to the site, but the requested page also requires standard HTTP basic authorization.
|
require 'net/https' |
|
|
|
USER = "xxx" |
|
PW = "yyy" |
|
|
|
site = Net::HTTP.new("www.securestuff.com", 443) |
|
site.use_ssl = true |
|
response = site.get2("/cgi-bin/cokerecipe.cgi", |
|
'Authorization' => 'Basic ' + |
|
["#{USER}:#{PW}"].pack('m').strip) |
- Creates a socket that uses SSL. This isn’t a good example of accessing a website. However, it illustrates how a socket can be encrypted.
|
require 'socket' |
|
require 'openssl' |
|
|
|
socket = TCPSocket.new("www.secure-stuff.com", 443) |
|
|
|
ssl_context = OpenSSL::SSL::SSLContext.new() |
|
|
|
unless ssl_context.verify_mode |
|
warn "warning: peer certificate won't be verified this session." |
|
ssl_context.verify_mode = OpenSSL::SSL::VERIFY_NONE |
|
end |
|
sslsocket = OpenSSL::SSL::SSLSocket.new(socket, ssl_context) |
|
sslsocket.sync_close = true |
|
sslsocket.connect |
|
|
|
sslsocket.puts("GET /secret-info.shtml") |
|
while line = sslsocket.gets |
|
p line |
|
end |
Library OptionParser: Option Parsing
OptionParser is a flexible and extensible way to parse command-line arguments. It has a particularly rich abstraction of the concept of an option.
- An option can have multiple short names (options preceded by a single hyphen) and multiple long names (options preceded by two hyphens). Thus, an option that displays help may be available as -h, -?, --help, and --about. Users may abbreviate long option names to the shortest nonambiguous prefix.
- An option may be specified as having no argument, an optional argument, or a required argument. Arguments can be validated against patterns or lists of valid values.
- Arguments may be returned as objects of any type (not just strings). The argument type system is extensible (we add Date handling in the example).
- Arguments can have one or more lines of descriptive text, used when generating usage information.
Options are specified using the on and def methods. These methods take a variable number of arguments that cumulatively build a definition of each option. The arguments accepted by these methods are:
"-x" "-xARG" "-x=ARG" "-x[OPT]" "-x[=OPT]" "-x PLACE"
Option has short name x. First form has no argument, next two have mandatory argument, next two have optional argument, last specifies argument follows option. The short names may also be specified as a range (such as "-[a-c]").
"--switch" "--switch=ARG" "--switch=[OPT]" "--switch PLACE"
Option has long name switch. First form has no argument, next has a mandatory argument, the next has an optional argument, and the last specifies the argument follows the switch.
"--no-switch"
Defines a option whose default value is false.
"=ARG" "=[OPT]"
Argument for this option is mandatory or optional. For example, the following code says there’s an option known by the aliases -x, -y, and -z that takes a mandatory argument, shown in the usage as N:
|
opt.on("-x", "-y", "-z", "=N") |
"description"
Any string that doesn’t start - or = is used as a description for this option in the summary. Multiple descriptions may be given; they’ll be shown on additional lines.
/pattern/
Any argument must match the given pattern.
array
Argument must be one of the values from array.
proc or method
Argument type conversion is performed by the given proc or method (rather than using the block associated with the on or def method call).
ClassName
Argument must match that defined for ClassName, which may be predefined or added using OptionParser.accept. Built-in argument classes are
Object:
Any string. No conversion. This is the default.
String:
Any nonempty string. No conversion.
Integer:
Ruby/C-like integer with optional sign (0ddd is octal, 0bddd binary, 0xddd hexadecimal). Converts to Integer.
Float:
Float number format. Converts to Float.
Numeric:
Generic numeric format. Converts to Integer for integers, Float for floats.
Array:
Argument must be of list of strings separated by a comma.
OptionParser::DecimalInteger:
Decimal integer. Converted to Integer.
OptionParser::OctalInteger:
Ruby/C-like octal/hexadecimal/binary integer.
OptionParser::DecimalNumeric:
Decimal integer/float number. Integers converted to Integer, floats to Float.
TrueClass, FalseClass:
Boolean switch.
- GetoptLong
|
require 'optparse' |
|
require 'date' |
|
|
|
# Add Dates as a new option type |
|
OptionParser.accept(Date, /(\d+)-(\d+)-(\d+)/) do |d, mon, day, year| |
|
Date.new(year.to_i, mon.to_i, day.to_i) |
|
end |
|
|
|
opts = OptionParser.new |
|
opts.on("-x") {|val| puts "-x seen" } |
|
opts.on("-s", "--size VAL", Integer) {|val| puts "-s #{val}" } |
|
opts.on("-a", "--at DATE", Date) {|val| puts "-a #{val}" } |
|
|
|
my_argv = [ "--size", "1234", "-x", "-a", "12-25-2008", "fred", "wilma" ] |
|
|
|
rest = opts.parse(*my_argv) |
|
puts "Remainder = #{rest.join(', ')}" |
|
puts opts.to_s |
Produces:
|
-s 1234 |
|
-x seen |
|
-a 2008-12-25 |
|
Remainder = fred, wilma |
|
Usage: prog [options] |
|
-x |
|
-s, --size VAL |
|
-a, --at DATE |
Library OpenStruct: Open (dynamic) Structure
An open structure is an object whose attributes are created dynamically when first assigned. In other words, if obj is an instance of an OpenStruct, then the statement obj.abc=1 will create the attribute abc in obj and then assign the value 1 to it.
|
require 'ostruct' |
|
|
|
os = OpenStruct.new( "f1" => "one", :f2 => "two" ) |
|
os.f3 = "cat" |
|
os.f4 = 99 |
|
os.f1 # => "one" |
|
os.f2 # => "two" |
|
os.f3 # => "cat" |
|
os.f4 # => 99 |
OpenStruct uses method_missing to intercept calls. This might cause a problem, because calls to a method defined in class Object will not invoke method_missing —they’ll simply call the method in Object. In practice, this isn’t a problem, because you typically call a setter before calling a getting, and when you do call the setter method, ostruct will defined getter and setter methods, overriding those in Object. Here’s a typical example; because we call ice.freeze= first, the freeze= and freeze methods will be dynamically created in the ostruct, and the getter will work as expected.
|
require 'ostruct' |
|
|
|
ice = OpenStruct.new |
|
ice.freeze = "yes" |
|
ice.freeze # => #<OpenStruct freeze="yes"> |
However, if you don’t first call the setter, the freeze getter will not invoke method_missing —it’ll simply call the underlying freeze method in Object.
|
require 'ostruct' |
|
|
|
ice = OpenStruct.new |
|
p ice.freeze |
|
ice.freeze = "yes" |
Produces:
|
#<OpenStruct> |
|
prog.rb:5:in `<main>': can't modify frozen OpenStruct (TypeError) |
Library Pathname: Representation of File Paths
A Pathname represents the absolute or relative name of a file. It has two distinct uses. First, it allows manipulation of the parts of a file path (extracting components, building new paths, and so on). Second (and somewhat confusingly), it acts as a façade for some methods in classes Dir, File, and module FileTest, forwarding on calls for the file named by the Pathname object.
- File
- Path name manipulation:
|
require 'pathname' |
|
|
|
p1 = Pathname.new("/usr/bin") |
|
p2 = Pathname.new("ruby") |
|
p3 = p1 + p2 |
|
p4 = p2 + p1 |
|
p3.parent # => #<Pathname:/usr/bin> |
|
p3.parent.parent # => #<Pathname:/usr> |
|
p1.absolute? # => true |
|
p2.absolute? # => false |
|
p3.split # => [#<Pathname:/usr/bin>, #<Pathname:ruby>] |
|
p5 = Pathname.new("testdir") |
|
puts p5.realpath |
|
puts p5.children |
- Produces:
|
/Users/dave/BS2/published/ruby4/Book/testdir |
|
testdir/.svn |
|
testdir/config.h |
|
testdir/main.rb |
- Path name as proxy for file and directory status requests:
|
require 'pathname' |
|
|
|
p1 = Pathname.new("/usr/bin/ruby") |
|
p1.file? # => true |
|
p1.directory? # => false |
|
p1.executable? # => true |
|
p1.size # => 34752 |
|
|
|
p2 = Pathname.new("testfile") # => #<Pathname:testfile> |
|
|
|
p2.read # => "This is line one\nThis is line two\nThis is |
|
# .. line three\nAnd so on...\n" |
|
p2.readlines # => ["This is line one\n", "This is line two\n", |
|
# .. "This is line three\n", "And so on...\n"] |
Library PP: Pretty-print Objects
PP uses the PrettyPrint library to format the results of inspecting Ruby objects. As well as the methods in the class, it defines a global function, pp , which works like the existing p method but formats its output.
PP has a default layout for all Ruby objects. However, you can override the way it handles a class by defining the method pretty_print , which takes a PP object as a parameter. It should use that PP object’s methods text , breakable , nest , group , and pp to format its output (see PrettyPrint for details).
- JSON
- PrettyPrint
- YAML
- Compares “p” and “pp”:
|
require 'pp' |
|
|
|
Customer = Struct.new(:name, :sex, :dob, :country) |
|
cust = Customer.new("Walter Wall", "Male", "12/25/1960", "Niue") |
|
|
|
puts "Regular print" |
|
p cust |
|
|
|
puts "\nPretty print" |
|
pp cust |
- Produces:
|
Regular print |
|
#<struct Customer name="Walter Wall", sex="Male", dob="12/25/1960", |
|
country="Niue"> |
|
|
|
Pretty print |
|
#<struct Customer |
|
name="Walter Wall", |
|
sex="Male", |
|
dob="12/25/1960", |
|
country="Niue"> |
- You can tell PP not to display an object if it has already displayed it:
|
require 'pp' |
|
|
|
a = "string" |
|
b = [ a ] |
|
c = [ b, b ] |
|
PP.sharing_detection = false |
|
pp c |
|
|
|
PP.sharing_detection = true |
|
pp c |
- Produces:
|
[["string"], ["string"]] |
|
[["string"], [...]] |
Library PrettyPrint: General Pretty Printer
PrettyPrint implements a pretty printer for structured text. It handles details of wrapping, grouping, and indentation. The PP library uses PrettyPrint to generate more legible dumps of Ruby objects.
- PP
The following program prints a chart of Ruby’s classes, showing subclasses as a bracketed list following the parent. To save some space, we show just the classes in the Numeric branch of the tree.
|
require 'prettyprint' |
|
|
|
@children = Hash.new { |h,k| h[k] = Array.new } |
|
ObjectSpace.each_object(Class) do |cls| |
|
@children[cls.superclass] << cls if cls <= Numeric |
|
end |
|
def print_children_of(printer, cls) |
|
printer.text(cls.name) |
|
kids = @children[cls].sort_by(&:name) |
|
unless kids.empty? |
|
printer.group(0, " [", "]") do |
|
printer.nest(3) do |
|
printer.breakable |
|
kids.each_with_index do |k, i| |
|
printer.breakable unless i.zero? |
|
print_children_of(printer, k) |
|
end |
|
end |
|
printer.breakable |
|
end |
|
end |
|
end |
|
printer = PrettyPrint.new(STDOUT, 30) |
|
print_children_of(printer, Object) |
|
printer.flush |
Produces:
|
Object [ |
|
Numeric [ |
|
Complex |
|
Float |
|
Integer [ |
|
Bignum |
|
Fixnum |
|
] |
|
Rational |
|
] |
|
] |
Library prime: Prime Numbers
Provides facilities for generating prime numbers, as well as factoring numbers. Note that the Prime class is a singleton.
- mathn
- The prime library extends the number classes to include new functionality and adds a new class Prime:
|
require 'prime' |
|
# 60 = 2**2 * 3 * 5 |
|
60.prime? # => false |
|
60.prime_division # => [[2, 2], [3, 1], [5, 1]] |
- You can also use it to generate sequences of primes:
|
require 'prime' |
|
Prime.each {|p| breakif p > 20; print p, " " } |
- Produces:
|
2 3 5 7 11 13 17 19 |
- Because Prime.each returns an enumerator if no block is present, we can write the previous example more concisely.
|
require 'prime' |
|
puts Prime.each.take_while {|p| p <= 20 }.join(" ") |
- Produces:
|
2 3 5 7 11 13 17 19 |
Library Profile: Profile Execution of a Ruby Program
The profile library is a trivial wrapper around the Profiler module, making it easy to profile the execution of an entire program. Profiling can be enabled from the command line using the -r profile option or from within a source program by requiring the profile module.
Unlike Ruby 1.8, Ruby 1.9 does not profile primitive methods such as Fixnum#== and Fixnum#+. This helps boost Ruby’s performance.
- Benchmark
- Profiler__
|
require 'profile' |
|
def ackerman(m, n) |
|
if m == 0 then n+1 |
|
elsif n == 0 and m > 0 then ackerman(m-1, 1) |
|
else ackerman(m-1, ackerman(m, n-1)) |
|
end |
|
end |
|
ackerman(3, 3) |
Produces:
|
% cumulative self self total |
|
time seconds seconds calls ms/call ms/call name |
|
100.00 0.04 0.04 2432 0.02 0.64 Object#ackerman |
|
0.00 0.04 0.00 1 0.00 0.00 TracePoint#enable |
|
0.00 0.04 0.00 1 0.00 0.00 Module#method_added |
|
0.00 0.04 0.00 1 0.00 0.00 TracePoint#disable |
|
0.00 0.04 0.00 1 0.00 40.00 #toplevel |
Library Profiler__: Control Execution Profiling
The Profiler__ module can be used to collect a summary of the number of calls to, and the time spent in, methods in a Ruby program. The output is sorted by the total time spent in each method. The profile library is a convenience wrapper that profiles an entire program.
- Benchmark
- profile
|
require 'profiler' |
|
|
|
# ...Omit definition of connection and fetching methods... |
|
|
|
def calc_discount(qty, price) |
|
case qty |
|
when 0..10 then 0.0 |
|
when 11..99 then price * 0.05 |
|
else price * 0.1 |
|
end |
|
end |
|
|
|
def calc_sales_totals(rows) |
|
total_qty = total_price = total_disc = 0 |
|
rows.each do |row| |
|
total_qty += row.qty |
|
total_price += row.price |
|
total_disc += calc_discount(row.qty, row.price) |
|
end |
|
end |
|
|
|
connect_to_database |
|
rows = read_sales_data |
|
|
|
Profiler__::start_profile |
|
calc_sales_totals(rows) |
|
Profiler__::stop_profile |
|
Profiler__::print_profile(STDOUT) |
Produces:
|
% cumulative self self total |
|
time seconds seconds calls ms/call ms/call name |
|
28.57 0.02 0.02 648 0.03 0.03 Range#include? |
|
28.57 0.04 0.02 1 20.00 70.00 Array#each |
|
14.29 0.05 0.01 325 0.03 0.37 Object#calc_sales_totals |
|
14.29 0.06 0.01 324 0.03 0.12 Object#calc_discount |
|
14.29 0.07 0.01 648 0.02 0.05 Range#=== |
|
0.00 0.07 0.00 1 0.00 0.00 TracePoint#enable |
|
0.00 0.07 0.00 648 0.00 0.00 Float#<=> |
|
0.00 0.07 0.00 648 0.00 0.00 Fixnum#<=> |
|
0.00 0.07 0.00 648 0.00 0.00 SalesData#price |
|
0.00 0.07 0.00 3 0.00 0.00 Fixnum#+ |
|
0.00 0.07 0.00 648 0.00 0.00 SalesData#qty |
|
0.00 0.07 0.00 1 0.00 0.00 TracePoint#disable |
|
0.00 0.07 0.00 1 0.00 70.00 #toplevel |
Library PStore: Persistent Object Storage
The PStore class provides transactional, file-based, persistent storage of Ruby objects. Each PStore can store several object hierarchies. Each hierarchy has a root, identified by a key (often a string). At the start of a PStore transaction, these hierarchies are read from a disk file and made available to the Ruby program. At the end of the transaction, the hierarchies are written back to the file. Any changes made to objects in these hierarchies are therefore saved on disk, to be read at the start of the next transaction that uses that file.
In normal use, a PStore object is created and then is used one or more times to control a transaction. Within the body of the transaction, any object hierarchies that had previously been saved are made available, and any changes to object hierarchies, and any new hierarchies, are written back to the file at the end.
The following example stores two hierarchies in a PStore. The first, identified by the key "names", is an array of strings. The second, identified by "tree", is a simple binary tree.
|
require 'pstore' |
|
require 'pp' |
|
class T |
|
def initialize(val, left=nil, right=nil) |
|
@val, @left, @right = val, left, right |
|
end |
|
def to_a |
|
[ @val, @left.to_a, @right.to_a ] |
|
end |
|
end |
|
|
|
def T(*args) |
|
T.new(*args) |
|
end |
|
|
|
store = PStore.new("/tmp/store") |
|
store.transaction do |
|
store['names'] = [ 'Douglas', 'Barenberg', 'Meyer' ] |
|
store['tree'] = T('top', |
|
T('A', T('B')), |
|
T('C', T('D', nil, T('E')))) |
|
end |
|
|
|
# now read it back in |
|
store.transaction do |
|
puts "Roots: #{store.roots.join(', ')}" |
|
puts store['names'].join(', ') |
|
pp store['tree'].to_a |
|
end |
Produces:
|
Roots: names, tree |
|
Douglas, Barenberg, Meyer |
|
["top", |
|
["A", ["B", [], []], []], |
|
["C", ["D", [], ["E", [], []]], []]] |
Library PTY: Pseudo-Terminal Interface: Interact with External Processes
Unix with pty support
Many Unix platforms support a pseudo-terminal —a device pair where one end emulates a process running on a conventional terminal, and the other end can read and write that terminal as if it were a user looking at a screen and typing on a keyboard.
The PTY library provides the method spawn , which starts the given command (by default a shell), connecting it to one end of a pseudo-terminal. It then returns the reader and writer streams connected to that terminal, allowing your process to interact with the running process.
Working with pseudo-terminals can be tricky. See IO#expect for a convenience method that makes life easier. You might also want to track down Ara T. Howard’s Session module for an even simpler approach to driving subprocesses.[129]
- expect
This example runs irb in a subshell and asks it to convert the string “cat” to uppercase:
|
require 'pty' |
|
require 'expect' |
|
|
|
$expect_verbose = true |
|
|
|
PTY.spawn("irb") do |reader, writer, pid| |
|
reader.expect(/> /) |
|
writer.puts "'cat'.upcase" |
|
reader.expect("=> ") |
|
answer = reader.gets |
|
puts "Answer = #{answer}" |
|
end |
Produces:
|
2.0.0p0 :001 > 'cat'.upcase |
|
=> Answer = "CAT" |
Library Rational: Rational Numbers
The Rational class is now built in to Ruby. The vestigial Rational library simply defines a few aliases for backward compatibility. For the classes Fixnum and Bignum, the following aliases are defined:
Floating-point division
quof is an alias for fdiv .
Rational division
rdiv is an alias for quo .
Exponentiation
power! and rpower are aliases for ** .
Library Readline: Interface to GNU Readline Library
GNU readline present
The Readline module allows programs to prompt for and receive lines of user input. The module allows lines to be edited during entry, and command history allows previous commands to be recalled and edited. The history can be searched, allowing the user to (for example) recall a previous command containing the text ruby. Command completion allows context-sensitive shortcuts: tokens can be expanded in the command line under control of the invoking application. In typical GNU fashion, the underlying readline library supports more options than any user could need and emulates both vi and emacs key bindings.
This meaningless program implements a trivial interpreter that can increment and decrement a value. It uses the Abbrev module to expand abbreviated commands when the Tab key is pressed.
sl_readline/readline.rb |
|
|
require 'abbrev' |
|
require 'readline' |
|
include Readline |
|
|
|
ABBREV = %w{ exit inc dec }.abbrev |
|
Readline.completion_proc = -> string { ABBREV[string] } |
|
|
|
value = 0 |
|
loop do |
|
cmd = readline("wibble [#{value}]: ", true) || "exit" |
|
case cmd.strip |
|
when "exit" thenbreak |
|
when "inc" then value += 1 |
|
when "dec" then value -= 1 |
|
else puts "Invalid command #{cmd}" |
|
end |
|
end |
|
wibble [0]: inc |
|
wibble [1]: <up-arrow> => inc |
|
wibble [2]: d<tab> => dec |
|
wibble [1]: in<esc><p> => inc |
|
wibble [2]: exit |
Library Resolv: DNS Client Library
The resolv library is a pure-Ruby implementation of a DNS client—it can be used to convert domain names into corresponding IP addresses. It also supports reverse lookups and the resolution of names in the local hosts file.
Loading the additional library resolv-replace insinuates the resolv library into Ruby’s socket library.
Basic name lookups are already built-in to the standard socket libraries. The resolv library exists because, prior to Ruby 1.9, calling the operating system to do a name lookup would suspend all interpreter threads. That is no longer the case.
Library REXML: XML Processing Library
REXML is a pure-Ruby XML processing library, including DTD-compliant document parsing, XPath querying, and document generation. It supports both tree-based and stream-based document processing. Because it is written in Ruby, it is available on all platforms supporting Ruby. REXML has a full and complex interface—this section contains a few small examples.
- Assume the file demo.xml contains this:
|
<classes language="ruby"> |
|
<class name="Numeric"> |
|
Numeric represents all numbers. |
|
<class name="Float"> |
|
Floating point numbers have a fraction and a mantissa. |
|
</class> |
|
<class name="Integer"> |
|
Integers contain exact integral values. |
|
<class name="Fixnum"> |
|
Fixnums are stored as machine ints. |
|
</class> |
|
<class name="Bignum"> |
|
Bignums store arbitraty-sized integers. |
|
</class> |
|
</class> |
|
</class> |
|
</classes> |
- Reads and processes the XML:
|
require 'rexml/document' |
|
|
|
xml = REXML::Document.new(File.open("code/sl_rexml/demo.xml")) |
|
|
|
puts "Root element: #{xml.root.name}" |
|
print "The names of all classes: " |
|
xml.elements.each("//class") {|c| print c.attributes["name"], " " } |
|
|
|
print "\nDescription of Fixnum: " |
|
p xml.elements["//class[@name='Fixnum']"].text |
- Produces:
|
Root element: classes |
|
The names of all classes: Numeric Float Integer Fixnum Bignum |
|
Description of Fixnum: "\n Fixnums are stored as machine ints.\n " |
- Reads in a document, adds and deletes elements, and manipulates attributes before writing it back out:
|
require 'rexml/document' |
|
include REXML |
|
|
|
xml = Document.new(File.open("code/sl_rexml/demo.xml")) |
|
|
|
cls = Element.new("class") |
|
cls.attributes["name"] = "Rational" |
|
cls.text = "Represents complex numbers" |
|
|
|
# Remove Integer's children, and add our new node as |
|
# the one after Integer |
|
int = xml.elements["//class[@name='Integer']"] |
|
int.delete_at(1) |
|
int.delete_at(2) |
|
int.next_sibling = cls |
|
|
|
# Change all the 'name' attributes to class_name |
|
xml.elements.each("//class") do |c| |
|
c.attributes['class_name'] = c.attributes['name'] |
|
c.attributes.delete('name') |
|
end |
|
|
|
# and write it out with a XML declaration at the front |
|
xml << XMLDecl.new |
|
xml.write(STDOUT, 2) |
- Produces:
|
<?xml version='1.0'?> |
|
<classes language='ruby'> |
|
<class class_name='Numeric'> |
|
Numeric represents all numbers. |
|
<class class_name='Float'> |
|
Floating point numbers have a fraction and a mantissa. |
|
</class> |
|
<class class_name='Integer'> |
|
Integers contain exact integral values. |
|
</class> |
|
<class class_name='Rational'> |
|
Represents complex numbers |
|
</class> |
|
</class> |
|
</classes> |
Library Rinda: Tuplespace Implementation
Tuplespaces are a distributed blackboard system. Processes may add tuples to the blackboard, and other processes may remove tuples from the blackboard that match a certain pattern. Originally presented by David Gelernter, tuplespaces offer an interesting scheme for distributed cooperation among heterogeneous processes.
Rinda, the Ruby implementation of tuplespaces, offers some interesting additions to the concept. In particular, the Rinda implementation uses the === operator to match tuples. This means that tuples may be matched using regular expressions, the classes of their elements, and the element values.
- DRb
- The blackboard is a DRb server that offers a shared tuplespace:
|
require 'rinda/tuplespace' |
|
MY_URI = "druby://127.0.0.1:12131" |
|
DRb.start_service(MY_URI, Rinda::TupleSpace.new) |
|
DRb.thread.join |
- The arithmetic agent accepts messages containing an arithmetic operator and two numbers. It stores the result back on the blackboard.
|
require 'rinda/rinda' |
|
MY_URI = "druby://127.0.0.1:12131" |
|
DRb.start_service |
|
ts = Rinda::TupleSpaceProxy.new(DRbObject.new(nil, MY_URI)) |
|
loop do |
|
op, v1, v2 = ts.take([ %r{^[-+/*]$}, Numeric, Numeric]) |
|
ts.write(["result", v1.send(op, v2)]) |
|
end |
- The client places tuples on the blackboard and reads back the result of each:
|
require 'rinda/rinda' |
|
MY_URI = "druby://127.0.0.1:12131" |
|
DRb.start_service |
|
ts = Rinda::TupleSpaceProxy.new(DRbObject.new(nil, MY_URI)) |
|
|
|
queries = [[ "+", 1, 2 ], [ "*", 3, 4 ], [ "/", 8, 2 ]] |
|
queries.each do |q| |
|
ts.write(q) |
|
ans = ts.take(["result", nil]) |
|
puts "#{q[1]} #{q[0]} #{q[2]} = #{ans[1]}" |
|
end |
- Produces:
|
1 + 2 = 3 |
|
3 * 4 = 12 |
|
8 / 2 = 4 |
Library Ripper: Parse Ruby Source
The ripper library gives you access to Ruby’s parser. It can tokenize input, return lexical tokens, and return a nested S-expression. It also supports event-based parsing.
- Tokenize a line of Ruby code:
|
require "ripper" |
|
content = "a=1;b=2;puts a+b" |
|
p Ripper.tokenize(content) |
- Produces:
|
["a", "=", "1", ";", "b", "=", "2", ";", "puts", " ", "a", "+", "b"] |
- Does a lexical analysis, returning token types, values, and line and column numbers:
|
require "ripper" |
|
require "pp" |
|
content = "a=1;b=2;puts a+b" |
|
pp Ripper.lex(content)[0,5] |
- Produces:
|
[[[1, 0], :on_ident, "a"], |
|
[[1, 1], :on_op, "="], |
|
[[1, 2], :on_int, "1"], |
|
[[1, 3], :on_semicolon, ";"], |
|
[[1, 4], :on_ident, "b"]] |
- Returns the sexp representing a chunk of code:
|
require "ripper" |
|
require "pp" |
|
content = "a=1;b=2;puts a+b" |
|
pp Ripper.sexp(content) |
- Produces:
|
[:program, |
|
[[:assign, [:var_field, [:@ident, "a", [1, 0]]], [:@int, "1", [1, 2]]], |
|
[:assign, [:var_field, [:@ident, "b", [1, 4]]], [:@int, "2", [1, 6]]], |
|
[:command, |
|
[:@ident, "puts", [1, 8]], |
|
[:args_add_block, |
|
[[:binary, |
|
[:var_ref, [:@ident, "a", [1, 13]]], |
|
:+, |
|
[:var_ref, [:@ident, "b", [1, 15]]]]], |
|
false]]]] |
- As a (silly) example of event-based lexical analysis, here’s a program that finds class definitions and their associated comment blocks. For each, it outputs the class name and the comment. It might be considered the zeroth iteration of an RDoc-like program.
The parameter to parse is an accumulator—it is passed between event handlers and can be used to construct the result.
|
require 'ripper' |
|
|
|
# This class handles parser events, extracting |
|
# comments and attaching them to class definitions |
|
class BabyRDoc < Ripper::Filter |
|
def initialize(*) |
|
super |
|
reset_state |
|
end |
|
|
|
def on_default(event, token, output) |
|
reset_state |
|
output |
|
end |
|
|
|
def on_sp(token, output) |
|
output |
|
end |
|
alias on_nil on_sp |
|
|
|
def on_comment(comment, output) |
|
@comment << comment.sub(/^\s*#\s*/, " ") |
|
output |
|
end |
|
|
|
def on_kw(name, output) |
|
@expecting_class_name = (name == 'class') |
|
output |
|
end |
|
|
|
def on_const(name, output) |
|
if @expecting_class_name |
|
output << "#{name}:\n" |
|
output << @comment |
|
end |
|
reset_state |
|
output |
|
end |
|
|
|
private |
|
|
|
def reset_state |
|
@comment = "" |
|
@expecting_class_name = false |
|
end |
|
end |
|
|
|
BabyRDoc.new(File.read(__FILE__)).parse(STDOUT) |
Produces:
|
BabyRDoc: |
|
This class handles parser events, extracting |
|
comments and attaching them to class definitions |
Library RSS: RSS Feed Generation and Parsing
Rich Site Summary or RDF Site Summary or Really Simple Syndication—take your pick. RSS is the protocol of choice for disseminating news on the Internet. The Ruby RSS library supports creating and parsing streams compliant with RSS 0.9, RSS 1.0, and RSS 2.0.
- Reads and summarizes the latest stories from http://ruby-lang.org :
|
require 'rss/2.0' |
|
require 'open-uri' |
|
|
|
open('http://ruby-lang.org/en/feeds/news.rss') do |http| |
|
response = http.read |
|
result = RSS::Parser.parse(response, false) |
|
puts "Channel: " + result.channel.title |
|
result.items.each_with_index do |item, i| |
|
puts "#{i+1}. #{item.title}" if i < 3 |
|
end |
|
end |
- Produces:
|
Channel: Ruby News |
|
1. Ruby 1.9.3-p429 is released |
|
2. Ruby 2.0.0-p195 is released |
|
3. Object taint bypassing in DL and Fiddle in Ruby (CVE-2013-2065) |
- Generates some RSS information:
|
require 'rss/0.9' |
|
|
|
rss = RSS::Rss.new("0.9") |
|
chan = RSS::Rss::Channel.new |
|
chan.title = "The Daily Dave" |
|
chan.description = "Dave's Feed" |
|
chan.language = "en-US" |
|
chan.link = "http://pragdave.pragprog.com" |
|
rss.channel = chan |
|
|
|
image = RSS::Rss::Channel::Image.new |
|
image.url = "http://pragprog.com/pragdave.gif" |
|
image.title = "PragDave" |
|
image.link = chan.link |
|
chan.image = image |
|
|
|
3.times do |i| |
|
item = RSS::Rss::Channel::Item.new |
|
item.title = "My News Number #{i}" |
|
item.link = "http://pragprog.com/pragdave/story_#{i}" |
|
item.description = "This is a story about number #{i}" |
|
chan.items << item |
|
end |
|
|
|
puts rss.to_s |
Library Scanf: Input Format Conversion
Implements a version of the C library scanf function, which extracts values from a string under the control of a format specifier.
The Ruby version of the library adds a scanf method to both class IO and class String. The version in IO applies the format string to the next line read from the receiver. The version in String applies the format string to the receiver. The library also adds the global method Object#scanf, which uses as its source the next line of standard input.
Scanf has one main advantage over using regular expressions to break apart a string: a regular expression extracts strings, whereas scanf will return objects converted to the correct type.
- Splits a date string into its constituents:
|
require 'scanf' |
|
|
|
date = "2010-12-15" |
|
year, month, day = date.scanf("%4d-%2d-%2d") |
|
year # => 2010 |
|
month # => 12 |
|
day # => 15 |
|
year.class # => Fixnum |
- The block form of scanf applies the format multiple times to the input string, returning each set of results to the block. The numbers are returned as integers, not strings:
|
require 'scanf' |
|
|
|
data = "cat:7 dog:9 cow:17 walrus:31" |
|
|
|
data.scanf("%[^:]:%d ") do |animal, value| |
|
puts "A #{animal.strip} has #{value}" |
|
end |
- Produces:
|
A cat has 7 |
|
A dog has 9 |
|
A cow has 17 |
|
A walrus has 31 |
- Extracts hex numbers:
|
require 'scanf' |
|
|
|
data = "decaf bad" |
|
data.scanf("%3x%2x%x") # => [3564, 175, 2989] |
Library SDBM: Interface to SDBM Database
The SDBM database implements a simple key/value persistence mechanism. Because the underlying SDBM library itself is provided with Ruby, there are no external dependencies, and SDBM should be available on all platforms supported by Ruby. SDBM database keys and values must be strings. SDBM databases are effectively hashlike.
- DBM
- GDBM
The example that follows stores a record in a new database and then fetches it back. Unlike the DBM library, all values to SDBM must be strings (or implement to_str ).
|
require 'sdbm' |
|
require 'date' |
|
|
|
SDBM.open("data.dbm") do |dbm| |
|
dbm['name'] = "Walter Wombat" |
|
dbm['dob'] = Date.new(1997, 12,25).to_s |
|
dbm['uses'] = "Ruby" |
|
end |
|
|
|
SDBM.open("data.dbm", nil) do |dbm| |
|
p dbm.keys |
|
p dbm['dob'] |
|
end |
Produces:
|
["name", "dob", "uses"] |
|
"1997-12-25" |
Library SecureRandom: Access to Secure Random Number Generators
Provides access to one of your operating system’s secure random number generators. If the OpenSSL library is installed, the module uses its random_bytes method. Otherwise, the module looks for and uses /dev/urandom or the CryptGenRandom method in the Windows API.
- Generates some random numbers:
|
require 'securerandom' |
|
# Random floats such that 0.0 <= rand < 1.0 |
|
SecureRandom.random_number(0) # => 0.26256698786247024 |
|
SecureRandom.random_number(0) # => 0.6885743213737645 |
|
|
|
# Random integers such that 0 <= rand < 1000 |
|
SecureRandom.random_number(1000) # => 112 |
|
SecureRandom.random_number(1000) # => 273 |
- Generates ten random bytes, returning the result as a hex string, a Base64 string, and a string of binary data. A different random string is returned for each call.
|
require 'securerandom' |
|
SecureRandom.hex(10) # => "bf4262e94d093ffbb4a7" |
|
SecureRandom.base64(10) # => "X/8YpCbCEyO2zA==" |
|
SecureRandom.random_bytes(10) # => "\x7FO\0r\r\xC1?\xB7b#" |
Library Set: Implement Various Forms of Set
A Set is a collection of unique values (where uniqueness is determined using eql? and hash ). Convenience methods let you build sets from enumerable objects.
- Basic set operations:
|
require 'set' |
|
|
|
set1 = Set.new([:bear, :cat, :deer]) |
|
|
|
set1.include?(:bat) # => false |
|
set1.add(:fox) # => #<Set: {:bear, :cat, :deer, :fox}> |
|
|
|
partition = set1.classify {|element| element.to_s.length } |
|
|
|
partition # => {4=>#<Set: {:bear, :deer}>, 3=>#<Set: {:cat, :fox}>} |
|
|
|
set2 = [ :cat, :dog, :cow ].to_set |
|
set1 | set2 # => #<Set: {:bear, :cat, :deer, :fox, :dog, :cow}> |
|
set1 & set2 # => #<Set: {:cat}> |
|
set1 - set2 # => #<Set: {:bear, :deer, :fox}> |
|
set1 ^ set2 # => #<Set: {:dog, :cow, :bear, :deer, :fox}> |
- Partitions the users in our /etc/passwd file into subsets where members of each subset have adjacent user IDs:
|
require 'etc' |
|
require 'set' |
|
|
|
users = [] |
|
Etc.passwd {|u| users << u } |
|
|
|
related_users = users.to_set.divide do |u1, u2| |
|
(u1.uid - u2.uid).abs <= 1 |
|
end |
|
|
|
related_users.each do |relatives| |
|
relatives.each {|u| print "#{u.uid}/#{u.name} " } |
|
puts "\n=======" |
|
end |
- Produces:
|
235/_assetcache 234/_krb_anonymous 233/_krb_kerberos 232/_krb_changepw |
|
231/_krb_kadmin 230/_krb_krbtgt 229/_avbdeviced 228/_netstatistics 227/_dovenull |
|
|
|
======= |
|
93/_calendar 92/_securityagent 91/_tokend |
|
======= |
|
202/_coreaudiod 203/_screensaver 201/Guest 200/_softwareupdate |
|
======= |
|
... |
Library Shellwords: Manipulate Shell Lines Using POSIX Semantics
Given a string representative of a shell command line, splits it into word tokens according to POSIX semantics. Also allows you to create properly escaped shell lines from individual words.
- Spaces between double or single quotes are treated as part of a word.
- Double quotes may be escaped using a backslash.
- Spaces escaped by a backslash are not used to separate words.
- Otherwise, tokens separated by whitespace are treated as words.
|
require 'shellwords' |
|
include Shellwords |
|
|
|
line = %{Code Ruby Be Happy!} |
|
shellwords(line) # => ["Code", "Ruby", "Be", "Happy!"] |
|
|
|
line = %{"Code Ruby" 'Be Happy'!} |
|
shellwords(line) # => ["Code Ruby", "Be Happy!"] |
|
|
|
line = %q{Code\ Ruby "Be Happy"!} |
|
shellwords(line) # => ["Code Ruby", "Be Happy!"] |
|
|
|
shelljoin(["Code Ruby", "Be Happy"]) # => Code\ Ruby Be\ Happy |
In addition, the library adds shellsplit and shelljoin methods to classes String and Array, respectively:
|
require 'shellwords' |
|
include Shellwords |
|
%{Code\\ Ruby Be Happy!}.shellsplit # => ["Code Ruby", "Be", "Happy!"] |
|
["Code Ruby", "Be Happy"].shelljoin # => "Code\\ Ruby Be\\ Happy" |
Library Singleton: The Singleton Pattern
The Singleton design pattern ensures that only one instance of a particular class may be created for the lifetime of a program (see Design Patterns [GHJV95]).
The singleton library makes this simple to implement. Mix the Singleton module into each class that is to be a singleton, and that class’s new method will be made private. In its place, users of the class call the method instance , which returns a singleton instance of that class.
In this example, the two instances of MyClass are the same object:
|
require 'singleton' |
|
|
|
class MyClass |
|
|
|
attr_accessor :data |
|
include Singleton |
|
end |
|
|
|
a = MyClass.instance # => #<MyClass:0x007feb190604d0> |
|
b = MyClass.instance # => #<MyClass:0x007feb190604d0> |
|
a.data = 123 # => 123 |
|
b.data # => 123 |
|
a.object_id # => 70323856933480 |
|
b.object_id # => 70323856933480 |
Library Socket: IP, TCP, Unix, and SOCKS Socket Access
The socket extension defines nine classes for accessing the socket-level communications of the underlying system. All of these classes are (indirect) subclasses of class IO, meaning that IO’s methods can be used with socket connections.
The hierarchy of socket classes reflects the reality of network programming and hence is somewhat confusing. The BasicSocket class largely contains methods common to data transfer for all socket-based connections. It is subclassed to provide protocol-specific implementations: IPSocket and UNIXSocket (for domain sockets). These in turn are subclassed by TCPSocket, UDPSocket, and SOCKSSocket.
BasicSocket is also subclassed by class Socket, which is a more generic interface to socket-oriented networking. Although classes such as TCPSocket are specific to a protocol, Socket objects can, with some work, be used regardless of protocol.
TCPSocket, SOCKSSocket, and UNIXSocket are each connection oriented. Each has a corresponding xxxxServer class, which implements the server end of a connection.
The socket libraries are something that you may never use directly. However, if you do use them, you’ll need to know the details. For that reason, we’ve put a reference section online at http://pragprog.com/book/ruby3/programming-ruby-1-9?tab=tab-contents .
The following code shows a trivial UDP server and client:
|
# Simple logger prints messages received on UDP port 12121 |
|
require 'socket' |
|
socket = UDPSocket.new |
|
socket.bind("127.0.0.1", 12121) |
|
loop do |
|
msg, sender = socket.recvfrom(100) |
|
host = sender[3] |
|
puts "#{Time.now}: #{host} '#{msg}'" |
|
STDOUT.flush |
|
end |
|
# Exercise the logger |
|
require 'socket' |
|
log = UDPSocket.new |
|
log.connect("127.0.0.1", 12121) |
|
log.print "Up and Running!" |
|
# process ... process .. |
|
log.print "Done!" |
Produces:
|
2013-05-27 12:33:39 -0500: 127.0.0.1 'Up and Running!' |
|
2013-05-27 12:33:39 -0500: 127.0.0.1 'Done!' |
Library StringIO: Treat Strings as IO Objects
In some ways, the distinction between strings and file contents is artificial: the contents of a file are basically a string that happens to live on disk, not in memory. The StringIO library aims to unify the two concepts, making strings act as if they were opened IO objects. Once a string is wrapped in a StringIO object, it can be read from and written to as if it were an open file. This can make unit testing a lot easier. It also lets you pass strings into classes and methods that were originally written to work with files. StringIO objects take their encoding from the string you pass in or the default external encoding is that no string is passed.
- Reads and writes from a string:
|
require 'stringio' |
|
|
|
sio = StringIO.new("time flies like an arrow") |
|
sio.read(5) # => "time " |
|
sio.read(5) # => "flies" |
|
sio.pos = 19 |
|
sio.read(5) # => "arrow" |
|
sio.rewind # => 0 |
|
sio.write("fruit") # => 5 |
|
sio.pos = 16 |
|
sio.write("a banana") # => 8 |
|
sio.rewind # => 0 |
|
sio.read # => "fruitflies like a banana" |
- Uses StringIO as a testing aid:
|
require 'stringio' |
|
require 'csv' |
|
require 'test/unit' |
|
|
|
class TestCSV < Test::Unit::TestCase |
|
def test_simple |
|
StringIO.open do |op| |
|
CSV(op) do |csv| |
|
csv << [ 1, "line 1", 27 ] |
|
csv << [ 2, nil, 123 ] |
|
end |
|
assert_equal("1,line 1,27\n2,,123\n", op.string) |
|
end |
|
end |
|
end |
- Produces:
|
Run options: |
|
# Running tests: |
|
. |
|
Finished tests in 0.004047s, 247.0966 tests/s, 247.0966 assertions/s. |
|
1 tests, 1 assertions, 0 failures, 0 errors, 0 skips |
|
|
|
ruby -v: ruby 2.0.0p0 (2013-02-24 revision 39474) [x86_64-darwin12.2.0] |
Library StringScanner: Basic String Tokenizer
StringScanner objects progress through a string, matching (and optionally returning) tokens that match a given pattern. Unlike the built-in scan methods, StringScanner objects maintain a current position pointer in the string being examined, so each call resumes from the position in the string where the previous call left off. Pattern matches are anchored to this previous point.
- Implements a simple language:
|
require 'strscan' |
|
|
|
# Handle the language: |
|
# set <var> = <value> |
|
# get <var> |
|
|
|
values = {} |
|
|
|
while line = gets |
|
|
|
scanner = StringScanner.new(line.chomp) |
|
|
|
scanner.scan(/(get|set)\s+/) or fail "Missing command" |
|
cmd = scanner[1] |
|
|
|
var_name = scanner.scan(/\w+/) or fail "Missing variable" |
|
|
|
case cmd |
|
when "get" |
|
puts "#{var_name} => #{values[var_name].inspect}" |
|
|
|
when "set" |
|
scanner.skip(/\s+=\s+/) or fail "Missing '='" |
|
value = scanner.rest |
|
values[var_name] = value |
|
else |
|
fail cmd |
|
end |
|
end |
- Run this from the command line, typing in phrases from the language:
|
$ ruby strscan.rb |
|
set a = dave |
|
set b = hello |
|
get b |
|
b => "hello" |
|
get a |
|
a => "dave" |
Library Syslog: Interface to Unix System Logging
Unix system with syslog
The Syslog class is a simple wrapper around the Unix syslog(3) library. It allows messages to be written at various severity levels to the logging daemon, where they are disseminated according to the configuration in syslog.conf. Ruby 2.0 adds support for Syslog::Logger, which is compatible with the Logger API.«2.0»
The following examples assume the log file is /var/log/system.log.
- Adds to our local system log. We’ll log all the levels configured for the user facility for our system (which is every level except debug and info messages).
|
require 'syslog' |
|
log = Syslog.open("test") # "test" is the app name |
|
log.debug("Warm and fuzzy greetings from your program") |
|
log.info("Program starting") |
|
log.notice("I said 'Hello!'") |
|
log.warning("If you don't respond soon, I'm quitting") |
|
log.err("You haven't responded after %d milliseconds", 7) |
|
log.alert("I'm telling your mother...") |
|
log.emerg("I'm feeling totally crushed") |
|
log.crit("Aarrgh....") |
|
system("tail -6 /var/log/system.log") |
- Produces:
|
Sep 16 12:48:44 dave-4 test[35121]: Warm and fuzzy greetings from your program |
|
Sep 16 12:48:44 dave-4 test[35121]: Program starting |
|
Sep 16 12:48:44 dave-4 test[35121]: I said 'Hello!' |
|
Sep 16 12:48:44 dave-4 test[35121]: If you don't respond soon, I'm quitting |
|
Sep 16 12:48:44 dave-4 test[35121]: You haven't responded after 7 milliseconds |
|
Sep 16 12:48:44 dave-4 test[35121]: I'm telling your mother... |
|
Sep 16 12:48:44 dave-4 test[35121]: I'm feeling totally crushed |
|
Sep 16 12:48:44 dave-4 test[35121]: Aarrgh.... |
- Logs only errors and above:
|
require 'syslog' |
|
log = Syslog.open("test") |
|
log.mask = Syslog::LOG_UPTO(Syslog::LOG_ERR) |
|
log.debug("Warm and fuzzy greetings from your program") |
|
log.info("Program starting") |
|
log.notice("I said 'Hello!'") |
|
log.warning("If you don't respond soon, I'm quitting") |
|
log.err("You haven't responded after %d milliseconds", 7) |
|
log.alert("I'm telling your mother...") |
|
log.emerg("I'm feeling totally crushed") |
|
log.crit("Aarrgh....") |
|
|
|
system("tail -4 /var/log/system.log") |
- Produces:
|
Sep 16 12:48:44 dave-4 test[35124]: You haven't responded after 7 milliseconds |
|
Sep 16 12:48:44 dave-4 test[35124]: I'm telling your mother... |
|
Sep 16 12:48:44 dave-4 test[35124]: I'm feeling totally crushed |
|
Sep 16 12:48:44 dave-4 test[35124]: Aarrgh.... |
Library Tempfile: Temporary File Support
Class Tempfile creates managed temporary files. Although they behave the same as any other IO objects, temporary files are automatically deleted when the Ruby program terminates. Once a Tempfile object has been created, the underlying file may be opened and closed a number of times in succession.
Tempfile does not directly inherit from IO. Instead, it delegates calls to a File object. From the programmer’s perspective, apart from the unusual new , open, and close semantics, a Tempfile object behaves as if it were an IO object.
If you don’t specify a directory to hold temporary files when you create them, the tmpdir library will be used to find a system-dependent location.
- tmpdir
|
require 'tempfile' |
|
tf = Tempfile.new("afile") |
|
tf.path # => "/var/folders/44/j19_ml3n3dx7bwrb_qmbcjyc0000gn/T/afile20130527-24 |
|
# .. 867-1greefy" |
|
tf.puts("Cosi Fan Tutte") |
|
tf.close |
|
tf.open |
|
tf.gets # => "Cosi Fan Tutte\n" |
|
tf.close(true) |
Library Test::Unit: Unit Testing Framework
Test::Unit is a unit testing framework based on the original SUnit Smalltalk framework. It provides a structure in which unit tests may be organized, selected, and run. Tests can be run from the command line or using one of several GUI-based interfaces.
Chapter 13, Unit Testing contains a tutorial on Test::Unit.
Maybe we have a simple playlist class, designed to store and retrieve songs:
|
require_relative 'song.rb' |
|
require 'forwardable' |
|
|
|
class Playlist |
|
extend Forwardable |
|
def_delegator(:@list, :<<, :add_song) |
|
def_delegators(:@list, :size, :empty?) |
|
def initialize |
|
@list = [] |
|
end |
|
def find(title) |
|
@list.find {|song| song.title == title} |
|
end |
|
end |
We can write unit tests to exercise this class. The Test::Unit framework is smart enough to run the tests in a test class if no main program is supplied.
|
require 'test/unit' |
|
require_relative 'playlist.rb' |
|
|
|
class TestPlaylist < Test::Unit::TestCase |
|
def test_adding |
|
pl = Playlist.new |
|
assert_empty(pl) |
|
assert_nil(pl.find("My Way")) |
|
pl.add_song(Song.new("My Way", "Sinatra")) |
|
assert_equal(1, pl.size) |
|
s = pl.find("My Way") |
|
refute_nil(s) |
|
assert_equal("Sinatra", s.artist) |
|
assert_nil(pl.find("Chicago")) |
|
# .. and so on |
|
end |
|
end |
Produces:
|
Run options: |
|
# Running tests: |
|
. |
|
Finished tests in 0.004140s, 241.5459 tests/s, 1690.8213 assertions/s. |
|
1 tests, 7 assertions, 0 failures, 0 errors, 0 skips |
|
|
|
ruby -v: ruby 2.0.0p0 (2013-02-24 revision 39474) [x86_64-darwin12.2.0] |
Library thread: Utility Functionality for Threading
The thread library adds some utility functions and classes for supporting threads. Much of this has been superseded by the Monitor class, but the thread library contains two classes, Queue and SizedQueue, that are still useful. Both classes implement a thread-safe queue that can be used to pass objects between producers and consumers in multiple threads. The Queue object implements a unbounded queue. A SizedQueue is told its capacity; any producer that tries to add an object when the queue is at that capacity will block until a consumer has removed an object.
The following example was provided by Robert Kellner. It has three consumers taking objects from an unsized queue. Those objects are provided by two producers, which each add three items.
|
require 'thread' |
|
queue = Queue.new |
|
|
|
consumers = (1..3).map do |i| |
|
Thread.new("consumer #{i}") do |name| |
|
begin |
|
obj = queue.deq |
|
print "#{name}: consumed #{obj.inspect}\n" |
|
enduntil obj == :END_OF_WORK |
|
end |
|
end |
|
|
|
producers = (1..2).map do |i| |
|
Thread.new("producer #{i}") do |name| |
|
3.times do |j| |
|
queue.enq("Item #{j} from #{name}") |
|
end |
|
end |
|
end |
|
|
|
producers.each(&:join) |
|
consumers.size.times { queue.enq(:END_OF_WORK) } |
|
consumers.each(&:join) |
Produces:
|
consumer 1: consumed "Item 0 from producer 1" |
|
consumer 1: consumed "Item 1 from producer 1" |
|
consumer 1: consumed "Item 2 from producer 1" |
|
consumer 1: consumed "Item 0 from producer 2" |
|
consumer 2: consumed "Item 1 from producer 2" |
|
consumer 3: consumed "Item 2 from producer 2" |
|
consumer 1: consumed :END_OF_WORK |
|
consumer 3: consumed :END_OF_WORK |
|
consumer 2: consumed :END_OF_WORK |
Library ThreadsWait: Wait for Multiple Threads to Terminate
Class ThreadsWait handles the termination of a group of thread objects. It provides methods to allow you to check for termination of any managed thread and to wait for all managed threads to terminate.
The following example kicks off a number of threads that each wait for a slightly shorter length of time before terminating and returning their thread number. Using ThreadsWait, we can capture these threads as they terminate, either individually or as a group.
|
require 'thwait' |
|
|
|
group = ThreadsWait.new |
|
|
|
# construct threads that wait for 1 second, .9 second, etc. |
|
# add each to the group |
|
|
|
9.times do |i| |
|
thread = Thread.new(i) {|index| sleep 1.0 - index/10.0; index } |
|
group.join_nowait(thread) |
|
end |
|
|
|
# any threads finished? |
|
group.finished? # => false |
|
|
|
# wait for one to finish |
|
group.next_wait.value # => 8 |
|
|
|
# wait for 5 more to finish |
|
5.times { group.next_wait } # => 5 |
|
|
|
# wait for next one to finish |
|
group.next_wait.value # => 2 |
|
|
|
# and then wait for all the rest |
|
group.all_waits # => nil |
Library Time: Extended Functionality for Class Time
The time library adds functionality to the built-in class Time, supporting date and/or time formats used by RFC 2822 (e-mail), RFC 2616 (HTTP), and ISO 8601 (the subset used by XML schema).
|
require 'time' |
|
|
|
# Convert external formats into Time objects |
|
|
|
Time.rfc2822("Thu, 1 Apr 2010 16:32:45 CST") # => 2010-04-01 17:32:45 -0500 |
|
Time.rfc2822("Thu, 1 Apr 2010 16:32:45 -0600") # => 2010-04-01 17:32:45 -0500 |
|
|
|
Time.httpdate("Thu, 01 Apr 2010 16:32:45 GMT") # => 2010-04-01 11:32:45 -0500 |
|
Time.httpdate("Thursday, 01-Apr-04 16:32:45 GMT") # => 2004-04-01 16:32:45 UTC |
|
Time.httpdate("Thu Apr 1 16:32:45 2010") # => 2010-04-01 16:32:45 UTC |
|
|
|
Time.xmlschema("2010-04-01T16:32:45") # => 2010-04-01 16:32:45 -0500 |
|
Time.xmlschema("2010-04-01T16:32:45.12-06:00") # => 2010-04-01 22:32:45 UTC |
|
# Convert time objects into external formats |
|
|
|
Time.now.rfc2822 # => "Mon, 27 May 2013 12:33:41 -0500" |
|
Time.now.httpdate # => "Mon, 27 May 2013 17:33:41 GMT" |
|
Time.now.xmlschema # => "2013-05-27T12:33:41-05:00" |
Library Timeout: Run a Block with Timeout
The Timeout.timeout method takes a parameter representing a timeout period in seconds, an optional exception parameter, and a block. The block is executed, and a timer is run concurrently. If the block terminates before the timeout, timeout returns the value of the block. Otherwise, the exception (default Timeout::Error) is raised.
|
require 'timeout' |
|
|
|
for snooze in 1..2 |
|
puts "About to sleep for #{snooze}" |
|
begin |
|
Timeout::timeout(1.5) do |timeout_length| |
|
puts "Timeout period is #{timeout_length}" |
|
sleep(snooze) |
|
puts "That was refreshing" |
|
end |
|
rescue Timeout::Error |
|
puts "Woken up early!!" |
|
end |
|
end |
Produces:
|
About to sleep for 1 |
|
Timeout period is 1.5 |
|
That was refreshing |
|
About to sleep for 2 |
|
Timeout period is 1.5 |
|
Woken up early!! |
Be careful when using timeouts—you may find them interrupting system calls that you cannot reliably restart, resulting in possible data loss.
Library Tk: Wrapper for Tcl/Tk
Tk library installed
Of all the Ruby options for creating GUIs, the Tk library is probably the most widely supported, running on Windows, Linux, Mac OS X, and other Unix-like platforms.[130] Although it doesn’t produce the prettiest interfaces, Tk is functional and relatively simple to program.
sl_tk/curves.rb |
|
|
# encoding: utf-8 |
|
require 'tk' |
|
include Math |
|
|
|
def plot(val) |
|
Integer(val * 180 + 200) |
|
end |
|
|
|
TkRoot.new do |root| |
|
title "Curves" |
|
geometry "400x400" |
|
|
|
TkCanvas.new(root) do |canvas| |
|
width 400 |
|
height 400 |
|
pack('side'=>'top', 'fill'=>'both', 'expand'=>'yes') |
|
|
|
points = [ ] |
|
a = 2 |
|
b = 3 |
|
0.0.step(8, 0.1) do |t| |
|
x = Math.sin(a*t) |
|
y = Math.cos(b*t) |
|
points << plot(x) << plot(y) |
|
end |
|
TkcLine.new(canvas, *(points), smooth: 'on', width: 10, fill: 'blue') |
|
end |
|
end |
|
Tk.mainloop |
produces:
Library tmpdir: System-Independent Temporary Directory Location
The tmpdir library adds the tmpdir method to class Dir. This method returns the path to a temporary directory that should be writable by the current process. (This will not be true if none of the well-known temporary directories is writable and if the current working directory is also not writable.) Candidate directories include those referenced by the environment variables TMPDIR, TMP, TEMP, and USERPROFILE; the directory /tmp; and (on Windows boxes) the temp subdirectory of the Windows or System directory.
|
require 'tmpdir' |
|
|
|
Dir.tmpdir # => "/var/folders/44/j19_ml3n3dx7bwrb_qmbcjyc0000gn/T" |
|
|
|
ENV['TMPDIR'] = "/wibble" # doesn't exist |
|
ENV['TMP'] = "/sbin" # not writable |
|
ENV['TEMP'] = "/Users/dave/tmp" # just right |
|
|
|
Dir.tmpdir # => "/Users/dave/tmp" |
The mktmpdir method can be used to create a new temporary directory:
|
require 'tmpdir' |
|
|
|
name = Dir.mktmpdir |
|
|
|
# .. process, process, process .. |
|
|
|
Dir.rmdir(name) |
Library Tracer: Trace Program Execution
The tracer library uses Object#set_trace_func to trace all or part of a Ruby program’s execution. The traced lines show the thread number, file, line number, class, event, and source line. The events shown are - for a change of line, < for a call, > for a return, C for a class definition, and E for the end of a definition.
- You can trace an entire program by including the tracer library from the command line:
|
class Account |
|
def initialize(balance) |
|
@balance = balance |
|
end |
|
def debit(amt) |
|
if @balance < amt |
|
fail "Insufficient funds" |
|
else |
|
@balance -= amt |
|
end |
|
end |
|
end |
|
acct = Account.new(100) |
|
acct.debit(40) |
|
$ ruby -r tracer account.rb |
|
#0:prog.rb:15::-: acct = Account.new(100) |
|
#0:prog.rb:3:Account:>: def initialize(balance) |
|
#0:prog.rb:4:Account:-: @balance = balance |
|
#0:prog.rb:5:Account:<: end |
|
#0:prog.rb:16::-: acct.debit(40) |
|
#0:prog.rb:6:Account:>: def debit(amt) |
|
#0:prog.rb:7:Account:-: if @balance < amt |
|
#0:prog.rb:10:Account:-: @balance -= amt |
|
#0:prog.rb:12:Account:<: end |
- You can also use tracer objects to trace just a portion of your code and use filters to select what to trace:
|
require 'tracer' |
|
tracer = Tracer.new |
|
tracer.add_filter lambda {|event, *rest| event == "line" } |
|
acct = Account.new(100) |
|
tracer.on { acct.debit(40) } |
- Produces:
|
#0:prog.rb:18::-: tracer.on { acct.debit(40) } |
|
#0:prog.rb:6:Account:-: if @balance < amt |
|
#0:prog.rb:9:Account:-: @balance -= amt |
Library TSort: Topological Sort
Given a set of dependencies between nodes (where each node depends on zero or more other nodes and there are no cycles in the graph of dependencies), a topological sort will return a list of the nodes ordered such that no node follows a node that depends on it. One use for this is scheduling tasks, where the order means that you complete the dependencies before you start any task that depends on them. The make program uses a topological sort to order its execution.
In the Ruby implementation, you mix in the TSort module and define two methods: tsort_each_node , which yields each node in turn, and tsort_each_child , which, given a node, yields each of that node’s dependencies.
- Given the set of dependencies among the steps for making a piña colada, what is the optimum order for undertaking the steps?
|
require 'tsort' |
|
|
|
class Tasks |
|
include TSort |
|
def initialize |
|
@dependencies = {} |
|
end |
|
def add_dependency(task, *relies_on) |
|
@dependencies[task] = relies_on |
|
end |
|
def tsort_each_node(&block) |
|
@dependencies.each_key(&block) |
|
end |
|
def tsort_each_child(node, &block) |
|
deps = @dependencies[node] |
|
deps.each(&block) if deps |
|
end |
|
end |
|
|
|
tasks = Tasks.new |
|
tasks.add_dependency(:add_rum, :open_blender) |
|
tasks.add_dependency(:add_pc_mix, :open_blender) |
|
tasks.add_dependency(:add_ice, :open_blender) |
|
tasks.add_dependency(:close_blender, :add_rum, :add_pc_mix, :add_ice) |
|
tasks.add_dependency(:blend_mix, :close_blender) |
|
tasks.add_dependency(:pour_drink, :blend_mix) |
|
tasks.add_dependency(:pour_drink, :open_blender) |
|
puts tasks.tsort |
- Produces:
|
open_blender |
|
add_rum |
|
add_pc_mix |
|
add_ice |
|
close_blender |
|
blend_mix |
|
pour_drink |
Library un: Command-Line Interface to FileUtils
Why un? When you invoke it from the command line with the -r option to Ruby, it spells -run. This pun gives a hint as to the intent of the library: it lets you run commands (in this case, a subset of the methods in FileUtils) from the command line. In theory this gives you an operating system--independent set of file manipulation commands, possibly useful when writing portable Makefiles.
- FileUtils
- The available commands are as follows:
$ ruby -run -e cp -- <options>* source dest $ ruby -run -e ln -- <options>* target linkname $ ruby -run -e mv -- <options>* source dest $ ruby -run -e rm -- <options>* file $ ruby -run -e mkdir -- <options>* dirs $ ruby -run -e rmdir --<options>* dirs $ ruby -run -e install -- <options>* source dest $ ruby -run -e chmod -- <options>* octal_mode file $ ruby -run -e touch -- <options>* file
Note the use of -- to tell the Ruby interpreter that options to the program follow.
You can get a list of all available commands with this:
|
$ ruby -run -e help |
For help on a particular command, append the command’s name:
|
$ ruby -run -e help mkdir |
Library URI: RFC 2396 Uniform Resource Identifier (URI) Support
URI encapsulates the concept of a Uniform Resource Identifier (URI), a way of specifying some kind of (potentially networked) resource. URIs are a superset of URLs: URLs (such as the addresses of web pages) allow specification of addresses by location, and URIs also allow specification by name.
URIs consist of a scheme (such as http, mailto, ftp, and so on), followed by structured data identifying the resource within the scheme.
URI has factory methods that take a URI string and return a subclass of URI specific to the scheme. The library explicitly supports the ftp, http, https, ldap, and mailto schemes; others will be treated as generic URIs. The module also has convenience methods to escape and unescape URIs. The class Net::HTTP accepts URI objects where a URL parameter is expected.
- open-uri
- Net::HTTP
|
require 'uri' |
|
|
|
uri = URI.parse("http://pragprog.com:1234/mypage.cgi?q=ruby") |
|
uri.class # => URI::HTTP |
|
uri.scheme # => "http" |
|
uri.host # => "pragprog.com" |
|
uri.port # => 1234 |
|
uri.path # => "/mypage.cgi" |
|
uri.query # => "q=ruby" |
|
|
|
uri = URI.parse("mailto:ruby@pragprog.com?Subject=help&body=info") |
|
uri.class # => URI::MailTo |
|
uri.scheme # => "mailto" |
|
uri.to # => "ruby@pragprog.com" |
|
uri.headers # => [["Subject", "help"], ["body", "info"]] |
|
|
|
uri = URI.parse("ftp://dave@anon.com:/pub/ruby;type=i") |
|
uri.class # => URI::FTP |
|
uri.scheme # => "ftp" |
|
uri.host # => "anon.com" |
|
uri.port # => 21 |
|
uri.path # => "pub/ruby" |
|
uri.typecode # => "i" |
Library WeakRef: Support for Weak References
In Ruby, objects are not eligible for garbage collection if references still exist to them. Normally, this is a Good Thing—it would be disconcerting to have an object simply evaporate while you were using it. However, sometimes you may need more flexibility. For example, you might want to implement an in-memory cache of commonly used file contents. As you read more files, the cache grows. At some point, you may run low on memory. The garbage collector will be invoked, but the objects in the cache are all referenced by the cache data structures and so will not be deleted.
A weak reference behaves like any normal object reference with one important exception—the referenced object may be garbage collected, even while references to it exist. In the cache example, if the cached files were accessed using weak references, once memory runs low, they will be garbage collected, freeing memory for the rest of the application.
- Weak references introduce a slight complexity. Because the object referenced can be deleted by garbage collection at any time, code that accesses these objects must take care to ensure that the references are valid. Two techniques can be used. First, the code can reference the objects normally. Any attempt to reference an object that has been garbage collected will raise a WeakRef::RefError exception.
|
require 'weakref' |
|
# Generate lots of small strings. Hopefully the early ones will have |
|
# been garbage collected... |
|
refs = (1..10000).map {|i| WeakRef.new("#{i}") } |
|
puts "Last element is #{refs.last}" |
|
puts "First element is #{refs.first}" |
- Produces:
|
Last element is 10000 |
|
prog.rb:6:in `<main>': Invalid Reference - probably recycled (WeakRef::RefError) |
- Alternatively, use theWeakRef#weakref_alive? method to check that a reference is valid before using it. Garbage collection must be disabled during the test and subsequent reference to the object. In a single-threaded program, you could use something like this:
|
ref = WeakRef.new(some_object) |
|
|
|
# .. some time later |
|
|
|
gc_was_disabled = GC.disable |
|
if ref.weakref_alive? |
|
# do stuff with 'ref' |
|
end |
|
GC.enable unless gc_was_disabled |
Library WEBrick: Web Server Toolkit
WEBrick is a pure-Ruby framework for implementing HTTP-based servers. The Ruby standard library includes WEBrick services that implement a standard web server (serving files and directory listings) and servlets supporting CGI, erb, file download, and the mounting of Ruby lambdas.
The Web programming chapter has more examples of WEBrick.
- The following code mounts two Ruby procs on a web server.
Requests to http://localhost:2000/hello run one proc, and the other proc is invoked by requests to http://localhost:2000/bye.
|
#!/usr/bin/ruby |
|
|
|
require 'webrick' |
|
include WEBrick |
|
|
|
hello_proc = lambda do |req, resp| |
|
resp['Content-Type'] = "text/html" |
|
resp.body = %{ |
|
<html><body> |
|
Hello. You're calling from a #{req['User-Agent']} |
|
<p> |
|
I see parameters: #{req.query.keys.join(', ')} |
|
</body></html> |
|
} |
|
end |
|
|
|
bye_proc = lambda do |req, resp| |
|
resp['Content-Type'] = "text/html" |
|
resp.body = %{ |
|
<html><body> |
|
<h3>Goodbye!</h3> |
|
</body></html> |
|
} |
|
end |
|
|
|
|
|
hello = HTTPServlet::ProcHandler.new(hello_proc) |
|
bye = HTTPServlet::ProcHandler.new(bye_proc) |
|
|
|
s = HTTPServer.new(:Port => 2000) |
|
s.mount("/hello", hello) |
|
s.mount("/bye", bye) |
|
|
|
trap("INT"){ s.shutdown } |
|
s.start |
Library WIN32OLE: Windows Automation
Windows
This is an interface to Windows automation, allowing Ruby code to interact with Windows applications. The Ruby interface to Windows is discussed in more detail in Chapter 21, Ruby and Microsoft Windows.
- Opens Internet Explorer and asks it to display our home page:
|
require 'win32ole' |
|
|
|
ie = WIN32OLE.new('InternetExplorer.Application') |
|
ie.visible = true |
|
ie.navigate("http://www.pragprog.com") |
- Creates a new chart in Microsoft Excel and then rotates it. This code is one of the samples that comes with the library.
|
require 'win32ole' |
|
|
|
# -4100 is the value for the Excel constant xl3DColumn. |
|
ChartTypeVal = -4100; |
|
|
|
# Creates OLE object to Excel |
|
#excel = WIN32OLE.new("excel.application.5") |
|
excel = WIN32OLE.new("excel.application") |
|
|
|
# Create and rotate the chart |
|
|
|
excel.visible = TRUE; |
|
excel.Workbooks.Add(); |
|
excel.Range("a1").value = 3; |
|
excel.Range("a2").value = 2; |
|
excel.Range("a3").value = 1; |
|
excel.Range("a1:a3").Select(); |
|
excelchart = excel.Charts.Add(); |
|
excelchart.type = ChartTypeVal; |
|
|
|
i = 30 |
|
i.step(180, 10) do |rot| |
|
# excelchart['Rotation'] = rot; |
|
excelchart.rotation=rot; |
|
end |
|
# Done, bye |
|
|
|
excel.ActiveWorkbook.Close(0); |
|
excel.Quit(); |
Library XMLRPC: Remote Procedure Calls using XML-RPC
XMLRPC allows clients to invoke methods on networked servers using the XML-RPC protocol. Communications take place over HTTP. The server may run in the context of a web server, in which case ports 80 or 443 (for SSL) will typically be used. The server may also be run stand-alone. The Ruby XML-RPC server implementation supports operation as a CGI script, as a mod_ruby script, as a WEBrick handler, and as a stand-alone server. Basic authentication is supported, and clients can communicate with servers via proxies. Servers may throw FaultException errors—these generate the corresponding exception on the client (or optionally may be flagged as a status return to the call).
- dRuby
- WEBrick
- The following simple server accepts a temperature in Celsius and converts it to Fahrenheit. It runs within the context of the WEBrick web server.
sl_xmlrpc/xmlserver.rb |
|
|
require 'webrick' |
|
require 'xmlrpc/server' |
|
xml_servlet = XMLRPC::WEBrickServlet.new |
|
xml_servlet.add_handler("convert_celcius") do |celcius| |
|
celcius*1.8 + 32 |
|
end |
|
xml_servlet.add_multicall # Add support for multicall |
|
server = WEBrick::HTTPServer.new(:Port => 2000) |
|
server.mount("/RPC2", xml_servlet) |
|
trap("INT"){ server.shutdown } |
|
server.start |
- This client makes calls to the temperature conversion server. Note that in the output we show both the server’s logging and the client program’s output.
|
require 'xmlrpc/client' |
|
server = XMLRPC::Client.new("localhost", "/RPC2", 2000) |
|
puts server.call("convert_celcius", 0) |
|
puts server.call("convert_celcius", 100) |
|
puts server.multicall(['convert_celcius', -10], ['convert_celcius', 200]) |
- Produces:
|
[2013-05-27 12:33:44] INFO WEBrick 1.3.1 |
|
[2013-05-27 12:33:44] INFO ruby 2.0.0 (2013-02-24) [x86_64-darwin12.2.0] |
|
[2013-05-27 12:33:44] INFO WEBrick::HTTPServer#start: pid=24895 port=2000 |
|
localhost - - [27/May/2013:12:33:45 CDT] "POST /RPC2 HTTP/1.1" 200 124 |
|
- -> /RPC2 |
|
localhost - - [27/May/2013:12:33:45 CDT] "POST /RPC2 HTTP/1.1" 200 125 |
|
- -> /RPC2 |
|
localhost - - [27/May/2013:12:33:45 CDT] "POST /RPC2 HTTP/1.1" 200 290 |
|
- -> /RPC2 |
|
32.0 |
|
212.0 |
|
14.0 |
|
392.0 |
Library YAML: Object Serialization/Deserialization
The YAML library (also described in the tutorial) serializes and deserializes Ruby object trees to and from an external, readable, plain-text format. YAML can be used as a portable object marshaling scheme, allowing objects to be passed in plain text between separate Ruby processes. In some cases, objects may also be exchanged between Ruby programs and programs in other languages that also have YAML support. While Ruby 1.9.2 can use libyaml if it is available, Ruby 2.0«2.0 makes it a requirement, and bundles it with the interpreter.
- json
- YAML can be used to store an object tree in a flat file:
|
require 'yaml' |
|
tree = { name: 'ruby', |
|
uses: [ 'scripting', 'web', 'testing', 'etc' ] |
|
} |
|
|
|
File.open("tree.yml", "w") {|f| YAML.dump(tree, f)} |
- Once stored, it can be read by another program:
|
require 'yaml' |
|
tree = YAML.load_file("tree.yml") |
|
tree[:uses][1] # => "web" |
- The YAML format is also a convenient way to store configuration information for programs. Because it is readable, it can be maintained by hand using a normal editor and then read as objects by programs. For example, a configuration file may contain the following:
sl_yaml/config.yml |
|
|
--- |
|
username: dave |
|
prefs: |
|
background: dark |
|
foreground: cyan |
|
timeout: 30 |
- We can use this in a program:
|
require 'yaml' |
|
|
|
config = YAML.load_file("code/sl_yaml/config.yml") |
|
config["username"] # => "dave" |
|
config["prefs"]["timeout"] * 10 # => 300 |
Library Zlib: Read and Write Compressed Files
zlib library available
The Zlib module is home to a number of classes for compressing and decompressing streams and for working with gzip-format compressed files. They also calculate zip checksums.
- Compresses /etc/passwd as a gzip file and then reads the result back:
|
require 'zlib' |
|
|
|
# These methods can take a filename |
|
Zlib::GzipWriter.open("passwd.gz") do |gz| |
|
gz.write(File.read("/etc/passwd")) |
|
end |
|
|
|
system("ls -l /etc/passwd passwd.gz") |
|
puts |
|
|
|
# or a stream |
|
File.open("passwd.gz") do |f| |
|
gzip = Zlib::GzipReader.new(f) |
|
data = gzip.read.split(/\n/) |
|
puts data[15,3] |
|
end |
- Produces:
|
-rw-r--r-- 1 root wheel 5086 Jul 20 2011 /etc/passwd |
|
-rw-rw-r-- 1 dave staff 1621 May 27 12:33 passwd.gz |
|
|
|
_installassistant:*:25:25:Install Assistant:/var/empty:/usr/bin/false |
|
_lp:*:26:26:Printing Services:/var/spool/cups:/usr/bin/false |
|
_postfix:*:27:27:Postfix Mail Server:/var/spool/postfix:/usr/bin/false |
- Compresses data sent between two processes:
|
require 'zlib' |
|
|
|
rd, wr = IO.pipe |
|
|
|
if fork |
|
rd.close |
|
zipper = Zlib::Deflate.new |
|
zipper << "This is a string " |
|
data = zipper.deflate("to compress", Zlib::FINISH) |
|
wr.write(data) |
|
wr.close |
|
Process.wait |
|
else |
|
wr.close |
|
text = Zlib.inflate(rd.read) |
|
puts "We got: #{text}" |
|
end |
- Produces:
|
We got: This is a string to compress |
Footnotes
[125] |
http://www.faqs.org/rfcs/rfc2045.html and http://www.faqs.org/rfcs/rfc4648.html |
[126] |
http://www.gnu.org/software/gdbm/gdbm.html |
[127] |
http://www.ietf.org/rfc/rfc4627.txt |
[128] |
http://www.openssl.org |
[129] |
Currently found at http://www.codeforpeople.com/lib/ruby/session/ |
[130] |
All these environments require that the Tcl/Tk libraries are installed before the Ruby Tk extension can be used. |