阅读lib的文档,做个笔记。OptionParser 这个类用于,在写一些command line工具的时候,设置命令行参数选项。GetoptLong有类似的功能,不过文中建议使用OptionParser.

一、例:简单示例 (主要是入个门,顺便演示了下不带参数的选项该怎么处理,像-v, -f 那种))

ruby 数据结构 rubystdlib_Code

ruby 数据结构 rubystdlib_ruby_02

1 #直接存成test.rb, 运行 ruby test.rb -v somethingnouse 能正常运行
 2 #运行 ruby test.rb -v somethingnouse -s,因为没有设置-s的选项处理,
 3 #会报错"invalid option: -s (OptionParser::InvalidOption)"
 4 require 'optparse'
 5  
 6 p ARGV # => ["-v", "somethingnouse"]
 7 options = {} #自己设置用来接收输入参数的hash,用数组应该也可以,随便,
 8              #也可以说和OptionParser没什么关系
 9 OptionParser.new do |opts|
10   opts.banner = "Usage: example.rb [options]"
11  
12   opts.on("-v", "--[no-]verbose", "Run verbosely") do |v| #选项-v后面没有值,看来有的话会赋值成true
13     puts "value of v"#不写-v选项的话程序根本不到这一行,可以理解成if has "-v" 这种感觉吧
14     puts v #=> true
15     options[:verbose] = v #这里写一些功能上的code
16   end
17 end.parse! #shift一个参数, Code:a = [1,2,3]; a.shift ##output=> a=[2,3] 应该是类似这种机制吧,细节没看
18  
19 p options #{:verbose=>true}
20 p ARGV #["somethingnouse"]

View Code

 

二、例:生成help

基本就是边调试边注释来学习的。在文档里例子的基础上改了一些小地方用作调试。

ruby 数据结构 rubystdlib_Code

ruby 数据结构 rubystdlib_ruby_02

1 require 'optparse'
 2  
 3 Options = Struct.new(:name)
 4  
 5 class Parser
 6   def self.parse(options)
 7     args = Options.new("world") #Struct类args的参数name默认值为"world"
 8  
 9     opt_parser = OptionParser.new do |opts| #开始使用OptionParser类处理参数
10       opts.banner = "Usage:example.rb [option]" #输出help时最上面那一行
11  
12       opts.on("-n NAME", "--name=NAME", "Name to say hello to") do |n|
13       #输入参数选项为-n时,需要带参数NAME, 用法:ruby test.rb --name="A puppy"
14       #例子里-nNAME之间没有空格,测了一下参数写成"-nDog"是好用的。
15       #同样,长参数--name写成--name NAME,而在调用的时候 ruby test.rb --name="A puppy"也成立,非常灵活
16         args.name = n #这里其实就是把参数值传到别的地方去,加工,打印,随你怎么处理
17         puts n       
18         puts "#{ARGV}" #可以用例查看一下ARGV的变化,命令为 ruby test.rb -n Dog -h 运行时,这里输出["-h"]
19                        #和预想的有点出入,有时间可以看看OptionParser的Code深入理解一下
20       end
21  
22       opts.on("-h", "--help", "Prints this help") do
23         puts opts #打印所有的opts内容,其实就是我们设置的每项opt.on的加上banner
24         #exit #打印完help就退出了,不处理其他参数。注意exit是退出程序
25         #比如我在line17 打印了name的值。如果我运行 ruby test.rb -n Dog -h
26         #output:
27         #Dog
28         #Usage:example.rb [option]
29         #-n, --name=NAME                  Name to say hello to
30         #-h, --help                       Prints this help
31         #显然先处理了 -n 选项,又处理了-h再退出的
32  
33       end
34     end
35  
36     opt_parser.parse!(options) #!=>shift 继续处理下一个参数
37     return args  #看到这里就明白,在实际应用中,可以用这个Struct来收集所有传入的参数值
38   end
39 end
40  
41 #options = Parser.parse %w[-h] #%w 用于表示其中元素被引号括起的数组,元素用空格分离。如 %w[a b] => ["a", "b"]
42 options = Parser.parse ARGV #直接处理运行命令行传入的参数
43 puts options

View Code

 

  顺便搜了一下ruby的选项源码:就是ruby --help列举的那些:https://github.com/ruby/ruby/blob/ruby_2_3/ruby.c 好像也没用什么库之类的(主要搂了一眼没看着include<getopt.h>),应该是直接宏+printf(这几行code位置line168-237):

ruby 数据结构 rubystdlib_Code

ruby 数据结构 rubystdlib_ruby_02

1 static void
 2 usage(const char *name, int help)
 3 {
 4     /* This message really ought to be max 23 lines.
 5      * Removed -h because the user already knows that option. Others? */
 6 
 7     struct message {
 8     const char *str;
 9     unsigned short namelen, secondlen;
10     };
11 #define M(shortopt, longopt, desc) { \
12     shortopt " " longopt " " desc, \
13     (unsigned short)sizeof(shortopt), \
14     (unsigned short)sizeof(longopt), \
15 }
16     static const struct message usage_msg[] = {
17     M("-0[octal]",       "",               "specify record separator (\\0, if no argument)"),
18     M("-a",           "",               "autosplit mode with -n or -p (splits $_ into $F)"),
19     M("-c",           "",               "check syntax only"),
20     M("-Cdirectory",   "",               "cd to directory before executing your script"),
21     M("-d",           ", --debug",           "set debugging flags (set $DEBUG to true)"),
22     M("-e 'command'",  "",               "one line of script. Several -e's allowed. Omit [programfile]"),
23     M("-Eex[:in]",     ", --encoding=ex[:in]", "specify the default external and internal character encodings"),
24     M("-Fpattern",       "",               "split() pattern for autosplit (-a)"),
25     M("-i[extension]", "",               "edit ARGV files in place (make backup if extension supplied)"),
26     M("-Idirectory",   "",               "specify $LOAD_PATH directory (may be used more than once)"),
27     M("-l",           "",               "enable line ending processing"),
28     M("-n",           "",               "assume 'while gets(); ... end' loop around your script"),
29     M("-p",           "",               "assume loop like -n but print line also like sed"),
30     M("-rlibrary",       "",               "require the library before executing your script"),
31     M("-s",           "",               "enable some switch parsing for switches after script name"),
32     M("-S",           "",               "look for the script using PATH environment variable"),
33     M("-T[level=1]",   "",               "turn on tainting checks"),
34     M("-v",           ", --verbose",       "print version number, then turn on verbose mode"),
35     M("-w",           "",               "turn warnings on for your script"),
36     M("-W[level=2]",   "",               "set warning level; 0=silence, 1=medium, 2=verbose"),
37     M("-x[directory]", "",               "strip off text before #!ruby line and perhaps cd to directory"),
38     M("-h",           "",               "show this message, --help for more info"),
39     };
40     static const struct message help_msg[] = {
41     M("--copyright",                   "", "print the copyright"),
42     M("--enable=feature[,...]",       ", --disable=feature[,...]",
43       "enable or disable features"),
44     M("--external-encoding=encoding",  ", --internal-encoding=encoding",
45       "specify the default external or internal character encoding"),
46     M("--version",                     "", "print the version"),
47     M("--help",               "", "show this message, -h for short message"),
48     };
49     static const struct message features[] = {
50     M("gems",    "",        "rubygems (default: "DEFAULT_RUBYGEMS_ENABLED")"),
51     M("did_you_mean", "",   "did_you_mean (default: "DEFAULT_RUBYGEMS_ENABLED")"),
52     M("rubyopt", "",        "RUBYOPT environment variable (default: enabled)"),
53     M("frozen-string-literal", "", "freeze all string literals (default: disabled)"),
54     };
55     int i;
56     const int num = numberof(usage_msg) - (help ? 1 : 0);
57 #define SHOW(m) show_usage_line((m).str, (m).namelen, (m).secondlen, help)
58 
59     printf("Usage: %s [switches] [--] [programfile] [arguments]\n", name);
60     for (i = 0; i < num; ++i)
61     SHOW(usage_msg[i]);
62 
63     if (!help) return;
64 
65     for (i = 0; i < numberof(help_msg); ++i)
66     SHOW(help_msg[i]);
67     puts("Features:");
68     for (i = 0; i < numberof(features); ++i)
69     SHOW(features[i]);
70 }

View Code

  用宏定义函数这方面的知识我基本为0。有兴趣可以观摩一下。

 

三、参数有一些内置类型可以直接拿来用

Date – Anything accepted by Date.parse

DateTime – Anything accepted by DateTime.parse

Time – Anything accepted by Time.httpdate or Time.parse

URI – Anything accepted by URI.parse

Shellwords – Anything accepted by Shellwords.shellwords

String – Any non-empty string

Integer – Any integer. Will convert octal. (e.g. 124, -3, 040)

Float – Any float. (e.g. 10, 3.14, -100E+13)

Numeric – Any integer, float, or rational (1, 3.4, 1/3)

DecimalInteger -- Like Integer, but no octal format.

OctalInteger -- Like Integer, but no decimal format.

DecimalNumeric -- Decimal integer or float.

TrueClass – Accepts ‘+, yes, true, -, no, false’ and defaults as true

FalseClass – Same as TrueClass, but defaults to false

Array – Strings separated by ‘,’ (e.g. 1,2,3)

Regexp – Regular expressions. Also includes options.

 

一个使用time的例子:

ruby 数据结构 rubystdlib_Code

ruby 数据结构 rubystdlib_ruby_02

1 require 'optparse'
 2 require 'optparse/time'
 3  
 4 OptionParser.new do |parser|
 5   #例子比较简单,重点应该就在第三个参数,输入之后相关类会自动解析?
 6   parser.on("-t", "--time [TIME]", Time, "Begin execution at given time") do |time|
 7     p time
 8   end
 9 end.parse!
10 # 比如运行:ruby test.rb  -t 2000-1-1
11 # output: 2000-01-01 00:00:00 +0800
12 # time 被自动处理了

View Code

 

四 op.accept用法。

理解上可以用accept关联任何自定义类型的实例,再通过OptionParser和外界交互。文档里举得这个例子,可以联想到gem install 的用法。比如:gem install libA。gem 要去查询libA的安装路径时,每个lib和安装路径就是key和value的关系。如果找不到这个libA,当然也就报错了。先这么理解着。

ruby 数据结构 rubystdlib_Code

ruby 数据结构 rubystdlib_ruby_02

1 require 'optparse'
 2 
 3 User = Struct.new(:id, :name) #自定义对象User
 4 
 5 def find_user id
 6   not_found = ->{ raise "No User Found for id #{id}" }
 7   [ User.new(1, "Sam"),
 8     User.new(2, "Gandalf") ].find(not_found) do |u|
 9     u.id == id  #在判断是否存在某个的id时候顺便初始化了两个User实例
10   end
11 end
12 
13 op = OptionParser.new
14   op.accept(User) do |user_id|  #运行ruby test.rb --user 1
15     puts find_user user_id.to_i #output:#<struct User id=1, name="Sam">
16     #User.new(3, "SomeOne")     #如果注释掉前一行,输入参数为 --user 1也会返回
17   end                           ##<struct User id=3, name="SomeOne">
18 
19   op.on("--user ID", User) do |user|
20     puts user                   ##<struct User id=1, name="Sam">
21   end                           #看来这里最终打印的user就是op.accept的返回值
22 op.parse!
23 
24 #简化了一下find_user中[].find这段代码。
25 [1, 2].find(->{ raise "throw Exception"}) do |a|
26     a == 1 #如果在这行之前加puts,输出 false true;
27 end        #但这样的话find会接收不到返回值:true or false。
28            #会导致 ->{}中的代码必然运行
29 #可见这段代码的作用是:
30 #遍历数组元素,如果包含目标元素(所有返回值中只要有一个true),正常运行
31 #如果不包含目标元素,运行代码块->{}中的内容

View Code

五 一个完整的例子

例子的题目是Complete example,应该比较长也比较完整

英文注释是原文里的,起手可以运行 test.rb --help 看看效果

ruby 数据结构 rubystdlib_ruby_11

 

ruby 数据结构 rubystdlib_Code

ruby 数据结构 rubystdlib_ruby_02

1 require 'optparse'
  2 require 'optparse/time'
  3 require 'ostruct'
  4 require 'pp'
  5 
  6 class OptparseExample
  7   Version = '1.0.0'
  8 
  9   CODES = %w[iso-2022-jp shift_jis euc-jp utf8 binary]
 10   CODE_ALIASES = { "jis" => "iso-2022-jp", "sjis" => "shift_jis" }
 11 
 12   class ScriptOptions
 13     attr_accessor :library, :inplace, :encoding, :transfer_type,
 14                   :verbose, :extension, :delay, :time, :record_separator,
 15                   :list
 16 
 17     def initialize
 18       self.library = []
 19       self.inplace = false
 20       self.encoding = "utf8"
 21       self.transfer_type = :auto
 22       self.verbose = false
 23     end
 24 
 25     def define_options(parser)
 26       parser.banner = "Usage: example.rb [options]"
 27       parser.separator "" #看上去跟在banner后面纯换行用的
 28       parser.separator "Specific options:" #打印字面内容
 29 
 30       # add additional options
 31       perform_inplace_option(parser) #每个选项的具体内容,由各个具体的函数分担
 32       delay_execution_option(parser)
 33       execute_at_time_option(parser)
 34       specify_record_separator_option(parser)
 35       list_example_option(parser)
 36       specify_encoding_option(parser)
 37       optional_option_argument_with_keyword_completion_option(parser)
 38       boolean_verbose_option(parser)
 39 
 40       parser.separator "" #空行
 41       parser.separator "Common options:" #打印字面内容
 42       # No argument, shows at tail.  This will print an options summary.
 43       # Try it and see!
 44       parser.on_tail("-h", "--help", "Show this message") do
 45         puts parser
 46         exit
 47       end
 48       # Another typical switch to print the version.
 49       parser.on_tail("--version", "Show version") do
 50         puts Version
 51         exit
 52       end
 53     end
 54     #从这里开始是处理每个选项的函数
 55     def perform_inplace_option(parser)
 56       # Specifies an optional option argument
 57       parser.on("-i", "--inplace [EXTENSION]",
 58                 "Edit ARGV files in place",
 59                 "(make backup if EXTENSION supplied)") do |ext|
 60         self.inplace = true        #test.rb -i txt 这个工具是用来创建文件的?@inplace=true
 61         self.extension = ext || '' #扩展名?这里显示@extension=".txt"
 62         self.extension.sub!(/\A\.?(?=.)/, ".")  # Ensure extension begins with dot.
 63       end
 64     end
 65 
 66     def delay_execution_option(parser) #deley多长时间后运行?
 67       # Cast 'delay' argument to a Float.
 68       parser.on("--delay N", Float, "Delay N seconds before executing") do |n|
 69         self.delay = n
 70       end
 71     end
 72 
 73     def execute_at_time_option(parser) #开始运行的时间,是个内置time类型
 74       # Cast 'time' argument to a Time object.
 75       parser.on("-t", "--time [TIME]", Time, "Begin execution at given time") do |time|
 76         self.time = time
 77       end
 78     end
 79 
 80     def specify_record_separator_option(parser)
 81       # Cast to octal integer. 转换成8进制的数?
 82       parser.on("-F", "--irs [OCTAL]", OptionParser::OctalInteger,
 83                 "Specify record separator (default \\0)") do |rs|
 84         self.record_separator = rs
 85       end
 86     end
 87 
 88     def list_example_option(parser) #参数Array类型
 89       # List of arguments.
 90       parser.on("--list x,y,z", Array, "Example 'list' of arguments") do |list|
 91         self.list = list
 92       end
 93     end
 94 
 95     def specify_encoding_option(parser) #可以写code 或者code_aliases. test.rb --code jis => @encoding="iso-2022-jp"
 96       # Keyword completion.  We are specifying a specific set of arguments (CODES
 97       # and CODE_ALIASES - notice the latter is a Hash), and the user may provide
 98       # the shortest unambiguous text.
 99       code_list = (CODE_ALIASES.keys + CODES).join(', ')
100       parser.on("--code CODE", CODES, CODE_ALIASES, "Select encoding",
101                 "(#{code_list})") do |encoding|
102         self.encoding = encoding
103       end
104     end
105 
106     def optional_option_argument_with_keyword_completion_option(parser)
107       # Optional '--type' option argument with keyword completion.
108       parser.on("--type [TYPE]", [:text, :binary, :auto], #设置转换类型?参数必须是[]里这几个
109                 "Select transfer type (text, binary, auto)") do |t| #比较神奇的是 --type t或 a 或 b(key首字母)
110         self.transfer_type = text                                   #也成功了,ruby 的特色吧应该^_^ 
111       end
112     end
113 
114     def boolean_verbose_option(parser) #bool类型选项
115       # Boolean switch.
116       parser.on("-v", "--[no-]verbose", "Run verbosely") do |v|
117         self.verbose = v
118       end
119     end
120   end
121 
122   #
123   # Return a structure describing the options.
124   #
125   def parse(args) #参数处理函数,可以注意一下这里的实例变量是怎么用的
126     # The options specified on the command line will be collected in
127     # *options*.
128 
129     @options = ScriptOptions.new
130     @args = OptionParser.new do |parser| #这部分是前面几个例子里用到OptionParser参数处理主体
131       @options.define_options(parser)    #无非是调用define_options去一个一个处理了
132       parser.parse!(args)
133     end
134     @options
135   end
136 
137   attr_reader :parser, :options
138 end  # class OptparseExample
139 
140 example = OptparseExample.new
141 options = example.parse(ARGV)
142 pp options # example.options
143 pp ARGV

View Code