######临时文件的支持
类tempfile创建受控制的临时文件. 虽然它们和其他IO对象的行为相同,但是当Ruby程序结束时会自动删除这些临时文建. 当Tempfile对象被创建时,底层的文建可能多次被打开和关闭.
Tempfile并不直接继承于IO.相反,它将所有调用委托给一个File对象.从程序员的角度来看,除了不通的new,open和close语义外,Tempfile对象的行为就像是一个IO对象.
当你创建它们时如果没有指定保存临时文件的目录,则会使用tmpdir来得到一个系统相关的位置.
require 'tempfile'
tf = Tempfile.new("afile")
tf.path # => "/var/folders/.../Tmp/afile2009041386464iwdzcf0"
tf.puts("Cosi Fan Tutte")
tf.close
tf.open
tf.gets # => "Cosi Fan Tutte\n"
tf.close(true)
######单元测试框架
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.
We could have a simple playlist class, designed to store and retrieve songs:
require 'code/testunit/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 'code/testunit/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
#输出结果:
Loaded suite /tmp/prog
Started
.
Finished in 0.002641 seconds.
1 tests, 7 assertions, 0 failures, 0 errors, 0 skips
######多线程的实用功能
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"
end until 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)
#输出结果:
consumer 1: consumed "Item 0 from producer 2"
consumer 1: consumed "Item 0 from producer 1"
consumer 2: consumed "Item 1 from producer 2"
consumer 1: consumed "Item 1 from producer 1"
consumer 2: consumed "Item 2 from producer 2"
consumer 3: consumed "Item 2 from producer 1"
consumer 2: consumed :END_OF_WORK
consumer 3: consumed :END_OF_WORK
consumer 1: consumed :END_OF_WORK
######等待多线程终止
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
######扩展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).
``ruby
require ’time’
Time.rfc2822("Thu, 1 Apr 2008 16:32:45 CST") # => 2008-04-01 17:32:45 0500
Time.rfc2822("Thu, 1 Apr 2008 16:32:45 0600") # => 2008-04-01 17:32:45 0500
Time.now.rfc2822 # => Mon, 13 Apr 2009 13:27:18 -0500
Time.httpdate("Thu, 01 Apr 2008 16:32:45 GMT") # => 2008-04-01 11:32:45 0500
Time.httpdate("Thursday, 01Apr04 16:32:45 GMT") # => 2004-04-01 16:32:45 UTC
Time.httpdate("Thu Apr 1 16:32:45 2008") # => 2008-04-01 16:32:45 UTC
Time.now.httpdate # => Mon, 13 Apr 2009 18:27:18 GMT
Time.xmlschema("2008-04-01T16:32:45") # => 2008-04-01 16:32:45 0500
Time.xmlschema("2008-04-01T16:32:45.1206:00") # => 2008-04-01 22:32:45 UTC
Time.now.xmlschema # => 2009-04-13T13:27:18-05:00
[Timeout](#timeout)
--------------------------------------
######使用Timeout运行一个block
> 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.
```ruby
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
#输出结果:
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.
######Tcl/Tk的包装
Of all the Ruby options for creating GUIs, the Tk library is probably Only if: Tk the most widely suplibrary
installed ported, running on Windows, Linux, Mac OS X, and other Unix-like platforms.5 Although
it doesn’t produce the prettiest interfaces, Tk is functional and relatively simple to program.
require 'tk'
include Math
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 = [ ]
10.upto(30) do |scale|
(0.0).step(2*PI,0.1) do |i|
new_x = 5*scale*sin(i) + 200 + scale*sin(i*2)
new_y = 5*scale*cos(i) + 200 + scale*cos(i*6)
points << [ new_x, new_y ]
f = scale/5.0
r = (Math.sin(f)+1)*127.0
g = (Math.cos(2*f)+1)*127.0
b = (Math.sin(3*f)+1)*127.0
col = sprintf("#%02x%02x%02x", r.to_i, g.to_i, b.to_i)
if points.size == 3
TkcLine.new(canvas,
points[0][0], points[0][1],
points[1][0], points[1][1],
points[2][0], points[2][1],
'smooth'=>'on',
'width'=> 7,
'fill' => col,
'capstyle' => 'round')
points.shift
end
end
end
end
end
Tk.mainloop
######系统无关的临时目录位置
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/a4/a4daQQOG4anplm9DAY+
TE+++TI/Tmp"
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)
######追踪程序的执行
The tracer library uses Kernel.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) #0:account.rb:1::-: class Account
@balance = balance #0:account.rb:1:Class:>: class Account
end #0:account.rb:1:Class:<: class Account
def debit(amt) #0:account.rb:1::C: class Account
if @balance < amt #0:account.rb:2::-: def initialize(balance)
fail "Insufficient funds" #0:account.rb:2:Module:>: def initialize(balance)
else #0:account.rb:2:Module:<: def initialize(balance)
@balance -= amt #0:account.rb:5::-: def debit(amt)
end #0:account.rb:5:Module:>: def debit(amt)
end #0:account.rb:5:Module:<: def debit(amt)
end #0:account.rb:1::E: class Account
acct = Account.new(100) #0:account.rb:13::-: acct = Account.new(100)
acct.debit(40) #0:account.rb:13:Class:>: acct = Account.new(100)
#0:account.rb:2:Account:>: def initialize(balance)
#0:account.rb:3:Account:: @balance = balance
#0:account.rb:13:Account:<: acct = Account.new(100)
#0:account.rb:13:Class:<: acct = Account.new(100)
#0:account.rb:14::-: acct.debit(40)
#0:account.rb:5:Account:>: def debit(amt)
#0:account.rb:6:Account:: if @balance < amt
#0:account.rb:6:Account:: if @balance < amt
#0:account.rb:6:Fixnum:>: if @balance < amt
#0:account.rb:6:Fixnum:<: if @balance < amt
#0:account.rb:9:Account:-: @balance -= amt
#0:account.rb:9:Fixnum:>: @balance -=amt
#0:account.rb:9:Fixnum:<: @balance -= amt
#0:account.rb:9:Account:<: @balance -= amt
- You can also use tracer objects to trace just a portion of your code and use filters to select what to trace:
require 'tracer' #0:account.rb:20::-: acct.debit(40)
class Account #0:account.rb:8:Account:-: if @balance < amt
def initialize(balance) #0:account.rb:8:Account:-: if @balance < amt
@balance = balance #0:account.rb:11:Account:: @balance -= amt
end
def debit(amt)
if @balance < amt
fail "Insufficient funds"
else
@balance =
amt
end
end
end
tracer = Tracer.new
tracer.add_filter lambda {|event, *rest| event == "line" }
acct = Account.new(100)
tracer.on do
acct.debit(40)
end
######拓扑(Topological)排序
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 will complete the dependencies before you start any task that depends on them. The make program uses a topological sort to order its execution.
In this library’s 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 nodes 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
#输出结果:
open_blender
add_rum
add_pc_mix
add_ice
close_blender
blend_mix
pour_drink