Ruby stdlib 学习 —— OptionParser
阅读lib的文档,做个笔记。OptionParser 这个类用于,在写一些command line工具的时候,设置命令行参数选项。GetoptLong有类似的功能,不过文中建议使用OptionParser.
一、例:简单示例 (主要是入个门,顺便演示了下不带参数的选项该怎么处理,像-v, -f 那种))
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"]
二、例:生成help
基本就是边调试边注释来学习的。在文档里例子的基础上改了一些小地方用作调试。
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
顺便搜了一下ruby的选项源码:就是ruby --help列举的那些:https://github.com/ruby/ruby/blob/ruby_2_3/ruby.c 好像也没用什么库之类的(主要搂了一眼没看着include<getopt.h>),应该是直接宏+printf(这几行code位置line168-237):
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 }
用宏定义函数这方面的知识我基本为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的例子:
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 被自动处理了
四 op.accept用法。
理解上可以用accept关联任何自定义类型的实例,再通过OptionParser和外界交互。文档里举得这个例子,可以联想到gem install 的用法。比如:gem install libA。gem 要去查询libA的安装路径时,每个lib和安装路径就是key和value的关系。如果找不到这个libA,当然也就报错了。先这么理解着。
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 #如果不包含目标元素,运行代码块->{}中的内容
五 一个完整的例子
例子的题目是Complete example,应该比较长也比较完整
英文注释是原文里的,起手可以运行 test.rb --help 看看效果
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
浙公网安备 33010602011771号