什么是RubyGem



RubyGems包管理系统(也称为Gems)已经成为Ruby代码包发布和管理的标准,在Ruby 1.9中已经打包在Ruby中提供。

每个gem有名称、版本和平台。比如rake gem版本是0.8.7,平台是ruby,可以在Ruby的任意平台上运行,其他平台还包括java(比如nokogiri)和mswin32(比如sqlite-ruby)。

Gems的结构,一般包括三部分:

代码,包括测试和支持工具。 文档
gemspec

每个gem有同样的标准代码组织结构:

% tree freewill
freewill/
├── bin/
│ └── freewill
├── lib/
│ └── freewill.rb
├── test/
│ └── test_freewill.rb
├── README
├── Rakefile
└── freewill.gemspec
`
lib包含gem的代码。
test或spec包含测试,取决于采用测试框架。 一个gem通常有一个Rakefile,用于rake程序自动化测试、生成代码和执行其他一些任务。
一般在bin目录下会有一个执行文件,gem安装后,该bin目录通常会在PATH路径中。 文档一般包含在README和代码中。当安装gem时,文档会自动生成,大多数gems包括RDoc文档,有的也用YARD
* 最后一部分是gemspec,包含有关这个gem的规格信息。包括文件、测试、平台、版本号和其他的作者邮件和姓名等等。

RubyGems修改了Ruby的装载路径,控制你的Ruby代码能被require找到。

一旦使用require请求一个gem,RubyGems

自动把lib目录加入到$LOAD_PATH中,有些gems也会增加其他目录,比如bin。这些是可选的,你可以把一个gem的多个目录加入到装载路径中。

Gemspec提供基本的信息,让你知道gem包含的内容,一个简单例子:

`
% cat freewill.gemspec
Gem::Specification.new do |s|
s.name = ‘freewill’
s.version = ‘1.0.0’
s.date = ‘2010-04-27’
s.summary = “Freewill!”
s.description = “I will choose Freewill!”
s.authors = [“Nick Quaranto”]
s.email = 'nick@quaran.to
s.homepage = ‘http://example.com'
s.files = [“lib/freewill.rb”]
end

来源What is a gem?

使用optparse改写post



之前写过”编写markdown文档直接发布到wordpress上“,当时还是自己解析ARGV,看了optparse后,把代码改一改。

# Define the options, and what they do
opts.on( ‘-s’, ‘–title [BlogTitle]’, ‘Blog title’ ) do|title|
options[:title] = title
end

options[:tags] = nil
opts.on( ‘-t’, ‘–tags tag1,tag2,tag3’, Array, ‘Tags blog’ ) do|tags|
options[:tags] = tags
end

options[:categories] = nil
opts.on( ‘-c’, ‘–categories cat1,cat2,cat3’, Array, ‘Blog categories’ ) do|categories|
options[:categories] = categories
end

第一个参数可选,后面的可以指定列表,挺好。修改后的代码

ruby OptionParser



原先在看的Programming Ruby版本有点低,其中的代码有些在1.9中也不适合,比如说Net::HTTP.get,找了本Programming Ruby 1.9的,目录结构也有所不同了,所以补看部分。不过最近事情多,效率变低,每天都只是看一会,也缺少总结了。

今天记录一下看”Organizing Your Source”中的一个例子用的optparse,optparse是Ruby的一个库,不是gem。OptionParser类用于对命令行选项进行解析,有一些特色:

参数规范说明和处理代码写在一起,便于关联。 可以自动生成选项的汇总,不需要独立维护选项信息。
可选和必选参数指定很简单优雅。 输入参数能自动转换成所需类。
输入参数能限制数据集。

有个简单例子:

require ‘optparse’
options = {}
optparse = OptionParser.new do |opts|
opts.banner = “Usage: example.rb [options]”

opts.on(“-v”, “–[no-]verbose”, “Run verbosely”) do |v|
options[:verbose] = v
end
end
optparse.parse!
p options
p ARGV
`

OptionParser.parse! 用于从ARGV中抽取选项,被抽取的选项会从ARGV中删除。

在上例中,options是一个空的hash,当命令行中遇到-v或–verbose,则会设置options[:verbose]为true。

optparse是一个OptionParser对象实例,传递了一个块,这个块构造了内部的数据结构,然后调用parse!。

有两篇不错的文章:
OptionParser–Parsing Command-line Options the Ruby Way
* How do I make a command-line tool in Ruby?

关于选项是可选还是必选,只需要看在选项后面的内容是否加上[]即可,比如下面的-r必选,-i可选。

# Mandatory argument.
          opts.on("-r", "--require LIBRARY",
                  "Require the LIBRARY before executing your script") do |lib|
            options.library


对于必选的选项,如果在输入的命令行未指定选项参数值,则会报错:

optparsetest.rb:44:in‘: missing argument: -r (OptionParser::MissingArgument)

代码中可以捕获OptionParser::MissingArgument。stack overflow有个问题How do you specify a required switch (not argument) with Ruby OptionParser?专门描述了对于强制选项的处理方法。

Ruby Tk



《Programming Ruby》 有两章提到Web和Tk,这都是很大的篇章,还是需要专题去看。以前也曾用tcl/tk写过一个界面,用来解释当时写的计费汇总科目的配置,不过这都已经随着代码的退休也已忘却。

所以在看Ruby Tk章节时回忆一下。

先是需要一个容器widget(比如TkFrame或TkRoot),然后在容器中创建widget,比如说按钮或文本,准备好后调用Tk.mainloop启动界面,Tk引擎就会控制程序,显示widget并调用响应GUI事件的代码。

看一段简单的代码:

require ‘tk’
root = TkRoot.new { title “Ex1” }
TkLabel.new(root) {
text ‘Hello, World!’
pack { padx 15 ; pady 15; side ‘left’ }
}
Tk.mainloop

我们可以通过回调和绑定变量从widget传回信息。

回调callback:command选项有Proc对象,回调时触发。 采用TkVariable proxy模式,绑定Ruby变量到Tk widget的值。在TKVariable.new中采用TkVariable引用作为参数,这样你就可以直接用TkVariable的值get/set方法影响widget的内容。
每个widget都有configure方法,使用Hash或代码块,和new的方式一致。 可以使用cget返回特定的选项值。

GUI中很重要的一点就是界面元素的排布,Geometry管理器提供了三种命令:

pack:灵活的,基于约束的排布 place:绝对位置排布
* grid:表格(行列)方式排布

绑定事件:b.bind(“Enter”) { b.configure(‘image’=>image2) }

至于一堆的widget和事件,需要用的时候理理。

ruby的世界



一个Ruby命令包括三部分,Ruby解释器的参数、运行程序文件名和运行程序的参数。

ruby [
options
] [–] [
programfile
] [
arguments
]
</pre> Ruby选项在碰到第一个不是“-”符号的单词时结束,如果没有指定运行程序文件名,或者文件名是"-",Ruby从标准输入读取程序代码,而在程序名后跟着的是程序运行参数。 一些命令行参数: * -C directory 执行前切换工作目录。 * -c 只检查语法。 * -d, --debug 调试模式。 * -e 'command' 作为一行执行命令。比如: ruby -e 'puts $:'。 * -I directories 指定$LOAD_PATH($:),类unix系统使用":"分隔。 * -r library 执行前require库。 * -v, --verbose verbose模式,打印编译错误。如果不指定文件名,Ruby退出。 * -w verbose模式,与-v不同的是,如果不指定文件名,Ruby从标准输入获取,建议。 命令行参数保存在ARGV中,不过不像c,程序名不是保存在ARGV[0],而是保存在全局变量$0中。 Kernel#exit终止程序,返回状态值。但是exit时并非直接退出,而是触发SystemExit异常,可以捕获做清理操作。 可以从变量ENV存取操作系统的环境变量,一些环境变量在Ruby启动时就会被获取,包括RUBYOPT, RUBYLIB, RUBYPATH, RUBYSHELL, DLN_LIBRARY_PATH, RUBLIB_PREFIX。 如果你想知道Ruby特定编译情况下的设置,可以使用“rbconfig.rb”中的Config模块。 <pre>
1.9.3-p125 :070 > CONFIG[“host”]
=> “x86_64-apple-darwin12.2.0”

写了段有实际用途的代码



之前写过一次代码解小学数学题,最近每天学Ruby,第一个写的有点实际用途的,发现还是给小乐解数学题,而且性质都是属于拼凑类型的题目。

来看两道题目:

1. 一个四位数乘以4,结果还是一个四位数,乘积和被乘数刚好相反,比如ABCD变成了DCBA。

1000.upto(3000) do |i|
j = i 4
puts “#{i}
4 = #{j}” if i.to_s == j.to_s.reverse
end
</pre> 2. AB/C = DE/F = GH/I, 其中的字母用1到9的数字代替,不能重复。 <pre>
1.upto(30) do |i|
1.upto(10) do |l|
l.upto(10) do |m|
m.upto(10) do |n|
a, b, c = i l, i m, i n
arr = []
if (a>9&&a<100) and (b>9&&b<100) and (c>9&&c<100)
arr[0] = a.to_s[0]
arr[1] = a.to_s[1]
arr[2] = b.to_s[0]
arr[3] = b.to_s[1]
arr[4] = c.to_s[0]
arr[5] = c.to_s[1]
arr[6] = l.to_s
arr[7] = m.to_s
arr[8] = n.to_s
puts “#{a}/#{l}=#{b}/#{m}=#{c}/#{n}” if arr.sort.join == “123456789”
end
end
end
end
end
</pre> 答案分别是: <pre>
1. ABCD
4 = DCBA
2178 * 4 = 8712
2. AB/C = DE/F = GH/I, fill with 1..9
21/3=49/7=56/8
27/3=54/6=81/9
[Finished in 0.1s]

第一个题目我觉得可以分析,有点意思。代码写起来也简单,四句话就搞定了。

第二个题目我就没理解了,在凑上有什么特殊技巧吗?很难给小乐做解释,唯一可以和她说的是,要不咱们学写代码得了。或者这数学题目倒是让小乐学习代码的好动机。看来这可以让我学习Ruby更有动力了,下次可以教她。

Ruby写代码须知



新手须知外,写代码除了有个编辑器外,考虑多的就是怎么调试代码。

Ruby Debugger,Ruby本身提供了调试功能,可以使用-r debug选项,调试时的命令参见When Trouble Strikes,我看也gdb有点像,break, where, list, cont。

当然也肯定离不开irb,交互式Ruby。不过现在有个两者的结合体,又可以象irb一样交互,又提供调试功能,那就是pry这里有篇简单的入门文章,看起来不错,后续在实践中熟悉。pry-nav提供了上一步、下一步的功能。

还有两个性能方面的分析工具,一个是benchmark,分析整体消耗的时间,一个是profier,能分析到每个方法的执行结果数据,就能方便定位性能瓶颈了。

Ruby新手须知



有鉴于写代码老是犯低级错误,先看一下新手须知。原帖在Things That Newcomers to Ruby Should Know ,选了几个我感兴趣和看明白的。

1. 使用”ruby -w”能得到更有帮助警告,可以设置RUBYOPT环境变量为’w’。
2. ri 查看Ruby文档。
3. 注意hash,如果key值的对象变化的,需要Hash#rehash。

s = “mutable”
arr = [s]
hsh = { arr => “object” }
s.upcase!
p hsh[arr] # -> nil (maybe not what was expected)
hsh.rehash
p hsh[arr] # -> “object”
</pre> 4. 从文件中读到的数据是string类型,需要to_i, to_f转换为数值,否则+做的是字符串连接。 5. ruby没有++,--,需要使用x+=1。 6. ruby有两类逻辑运算符[!, &amp;&amp;, ||] 和 [not, and, or],牵着的优先级比赋值运算符高,而后者比赋值运算符低。并且注意&amp;&amp;比||优先级高,但and和or是一样的。这会有有趣的现象: <pre>
a = ‘test’
b = nil
both = a && b # both == nil
both = a and b # both == ‘test’
both = (a and b) # both == nil
</pre> 7. case语句调用的是===方法,而不是==。 8. 建议不要在方法调用'('加上空格。 9. "0..k"表示的是一个Range对象,所以从0到k的循环用的是(0..k).each,而不是[0..k].each。 10. 只有false和mil才表示false,0,"",[],{}都bei被认为是true。 11. Ruby变量保存是对象引用,赋值拷贝的是引用。举例: <pre>
a = ‘aString’
c = a
a += ‘ modified using +=’
puts c # -> “aString”
a = ‘aString’
c = a
a << ‘ modified using <<’
puts c # -> “aString modified using <<”

12. 还是引用的问题,没有built-in copy标准。一种方式是采用serialization/marshalling,序列化和反序列化达到类似效果。

Ruby多进程



Ruby有很多调用和管理独立进程的方法。

### system和反引号最简单的生成一个独立的进程的方式是运行一些命令,然后等它完成。可以采用system和反引号方法,比如:

system(“ls”)
result = date
result
</pre> Kernel::system在子进程中执行指定的命令,如果命令能被找到并执行返回true,否则返回false,子进程的返回状态放在全局变量$?中。 system的问题是命令的输出结果和程序的输出混在一起,可能不是你所预期。而要获得子进程的标准输出,可以使用反引号,记得返回结果使用string.chomp 移去换行字符。 ### popen 有时我们需要多一点的控制,比如和子进程进行通信,传个数据啥的。IO.popen可以实现,在子进程运行命令,并且将子进程的标准输入和输出连接到Ruby IO对象。 而在popen的参数为"-"时,popen会fork一个新的Ruby解释器,这个解释器和原先的解释器在popen调用返回后都继续运行,原先的进程接收到一个IO对象,而子进程则接收nil,比如: <pre>
pipe = IO.popen(“-“,”w+”)
if pipe
pipe.puts “Get a job!”
$stderr.puts “Child #{Process.pid} says ‘#{pipe.gets.chomp}’”
else
$stderr.puts “Dad #{Process.pid} says ‘#{gets.chomp}’”
puts “OK”
end
</pre> 相对于popen,传统的Unix采用Kernel::fork, Kernel::exe和IO.pipe方式也被支持。 ### 独立的子进程 fork 在父进程中返回进程编号,在子进程中返回nil。子进程执行exec,我们可以在父进程中wait。 <pre>
exec(“sort testfile > output.txt”) if fork == nil
# The sort is now running in a child process
# carry on processing in the main program

# then wait for the sort to finish
Process.wait
</pre> 如果你想在子进程退出时接到通知,而不是等,可以使用Kernel.trap 设置信号处理,比如: <pre>
trap(“CLD”) {
pid = Process.wait
puts “Child pid #{pid}: terminated”
exit
}

exec(“sort testfile > output.txt”) if fork == nil

# do other stuff…
</pre> 写循环的时候想当然地写了i++,却怎么都报错,才发现[Ruby原来并不支持++,--运算符](http://stackoverflow.com/questions/3660563/why-doesnt-ruby-support-i-or-i-increment-decrement-operators)。 ### 块和子进程 IO.popen 可以像File.open一样使用块的方式,比如: <pre>
IO.popen (“date”) { |f| puts “Date is #{f.gets}” }
</pre> 这样IO对象会在块代码退出时自动关闭。 而对于Kernel.fork来说,fork后的块在子进程里运行,块后的代码在父进程中运行,这个比判断返回的进程Id要方便多了。 <pre>
fork do
puts “In child, pid = #$$”
exit 99
end
pid = Process.wait
puts “Child terminated, pid = #{pid}, exit code = #{$? >> 8}”

小技巧:$?按位右移8个二进制位,是Posix中表示返回值中低8位是程序退出的原因,高8位是真实的返回代码。

第四周的总结



坚持100天学习Ruby,第四周,说是第四周,其实是一个月了。小结一下。

继续看Programming Ruby - The Pragmatic Programmer’s Guide三章,”Exceptions, Catch, and Throw”,”Modules”和”Basic Input and Output”,数量上还行,并且多了点练习。 时间还是在中午休息时间,时间每天都要在40分钟以上。
为了写代码,折腾了一下Sublime Text2配置Ruby的环境 看的一些需要记录的点:
>- Ruby中不仅有异常(rescue/raise),还有正常(throw/catch),可以用于优雅地退出一堆的处理环节。
>- Modules中介绍了mixin的方式,提供多重继承的实现机制。
>- 基本的输入输出倒没啥,不过练习中开始碰到问题,慢慢求解的一个过程。