OptionParser
OptionParser
OptionParser는 명령줄 옵션을 분석하는 클래스예요. GetoptLong보다 훨씬 발전했으면서도 쓰기 쉽고, 더 Ruby스러운 해법이에요.
출처: Ruby 4.0 API
본문
특징
- 인자 지정과 그것을 처리하는 코드가 같은 곳에 쓰여요.
- 옵션 요약(summary)을 자동으로 출력할 수 있어요. 문자열을 별도로 유지 관리할 필요가 없죠.
- 선택적 인자와 필수 인자를 아주 우아하게 지정할 수 있어요.
- 인자를 지정된 클래스로 자동 변환할 수 있어요.
- 인자를 특정 집합으로 제한할 수 있어요.
이 모든 특징은 아래 예제에 나와 있어요. 전체 문서는 make_switch를 참고하세요.
최소 예제
require 'optparse'
options = {}
OptionParser.new do |parser|
parser.banner = "Usage: example.rb [options]"
parser.on("-v", "--[no-]verbose", "Run verbosely") do |v|
options[:verbose] = v
end
end.parse!
p options
p ARGV
도움말 생성하기
OptionParser는 여러분이 작성한 명령의 도움말을 자동으로 생성할 수 있어요.
require 'optparse'
Options = Struct.new(:name)
class Parser
def self.parse(options)
args = Options.new("world")
opt_parser = OptionParser.new do |parser|
parser.banner = "Usage: example.rb [options]"
parser.on("-nNAME", "--name=NAME", "Name to say hello to") do |n|
args.name = n
end
parser.on("-h", "--help", "Prints this help") do
puts parser
exit
end
end
opt_parser.parse!(options)
return args
end
end
options = Parser.parse %w[--help]
#=>
# Usage: example.rb [options]
# -n, --name=NAME Name to say hello to
# -h, --help Prints this help
필수 인자(Required Arguments)
인자가 필요한 옵션의 경우, 옵션 지정 문자열에 옵션 이름을 대문자로 넣을 수 있어요. 옵션을 필수 인자 없이 쓰면 예외가 발생해요.
require 'optparse'
options = {}
OptionParser.new do |parser|
parser.on("-r", "--require LIBRARY",
"Require the LIBRARY before executing your script") do |lib|
puts "You required #{lib}!"
end
end.parse!
실행 결과:
$ ruby optparse-test.rb -r
optparse-test.rb:9:in '<main>': missing argument: -r (OptionParser::MissingArgument)
$ ruby optparse-test.rb -r my-library
You required my-library!
타입 강제 변환(Type Coercion)
OptionParser는 명령줄 인자를 객체로 강제 변환(coerce)해 주는 기능을 지원해요. 기본으로 쓸 수 있는 강제 변환 종류는 다음과 같아요.
Date–Date.parse가 받아들이는 것 (optparse/daterequire 필요)DateTime–DateTime.parse가 받아들이는 것 (optparse/daterequire 필요)Time–Time.httpdate나Time.parse가 받아들이는 것 (optparse/timerequire 필요)URI–URI.parse가 받아들이는 것 (optparse/urirequire 필요)Shellwords–Shellwords.shellwords가 받아들이는 것 (optparse/shellwordsrequire 필요)String– 비어 있지 않은 어떤 문자열이든Integer– 어떤 정수든. 8진수도 변환해요. (예: 124, -3, 040)Float– 어떤 부동소수든. (예: 10, 3.14, -100E+13)Numeric– 어떤 정수·부동소수·유리수든 (1, 3.4, 1/3)DecimalInteger–Integer와 같지만 8진수 형식은 없어요.OctalInteger–Integer와 같지만 10진수 형식은 없어요.DecimalNumeric– 10진 정수 또는 부동소수.TrueClass– '+ yes true - no false'를 받고 기본값은 trueFalseClass–TrueClass와 같지만 기본값은 falseArray– ','로 구분된 문자열 (예: 1,2,3)Regexp– 정규표현식. 옵션도 포함돼요.
자체 강제 변환을 추가할 수도 있는데, 아래에서 다뤄요.
내장 변환 사용하기
내장 Time 변환을 예로 들어 볼게요. 다른 내장 변환도 동일하게 동작해요. OptionParser는 인자를 Time으로 파싱하려 시도해요. 성공하면 그 시간을 핸들러 블록에 넘기고, 실패하면 예외를 발생시켜요.
require 'optparse'
require 'optparse/time'
OptionParser.new do |parser|
parser.on("-t", "--time [TIME]", Time, "Begin execution at given time") do |time|
p time
end
end.parse!
실행 결과:
$ ruby optparse-test.rb -t nonsense
... invalid argument: -t nonsense (OptionParser::InvalidArgument)
$ ruby optparse-test.rb -t 10-11-12
2010-11-12 00:00:00 -0500
$ ruby optparse-test.rb -t 9:30
2014-08-13 09:30:00 -0400
커스텀 변환 만들기
OptionParser의 accept 메서드로 변환기를 만들 수 있어요. 클래스가 지정될 때마다 어떤 변환 블록을 호출할지 지정하는 거예요. 아래 예제는 on 핸들러가 받기 전에 User 객체를 가져오는 데 사용해요.
require 'optparse'
User = Struct.new(:id, :name)
def find_user id
not_found = ->{ raise "No User Found for id #{id}" }
[ User.new(1, "Sam"),
User.new(2, "Gandalf") ].find(not_found) do |u|
u.id == id
end
end
op = OptionParser.new
op.accept(User) do |user_id|
find_user user_id.to_i
end
op.on("--user ID", User) do |user|
puts user
end
op.parse!
실행 결과:
$ ruby optparse-test.rb --user 1
#<struct User id=1, name="Sam">
$ ruby optparse-test.rb --user 2
#<struct User id=2, name="Gandalf">
$ ruby optparse-test.rb --user 3
optparse-test.rb:15:in 'block in find_user': No User Found for id 3 (RuntimeError)
옵션을 해시로 저장하기
order, parse 등의 메서드의 into 옵션은 명령줄 옵션을 해시에 저장해요.
require 'optparse'
options = {}
OptionParser.new do |parser|
parser.on('-a')
parser.on('-b NUM', Integer)
parser.on('-v', '--verbose')
end.parse!(into: options)
p options
실행 결과:
$ ruby optparse-test.rb -a
{:a=>true}
$ ruby optparse-test.rb -a -v
{:a=>true, :verbose=>true}
$ ruby optparse-test.rb -a -b 100
{:a=>true, :b=>100}
완전한 예제
다음 예제는 완전한 Ruby 프로그램이에요. 직접 실행하며 다양한 옵션의 효과를 볼 수 있어요. optparse의 특징을 배우는 가장 좋은 방법일 거예요.
require 'optparse'
require 'optparse/time'
require 'ostruct'
require 'pp'
class OptparseExample
Version = '1.0.0'
CODES = %w[iso-2022-jp shift_jis euc-jp utf8 binary]
CODE_ALIASES = { "jis" => "iso-2022-jp", "sjis" => "shift_jis" }
class ScriptOptions
attr_accessor :library, :inplace, :encoding, :transfer_type,
:verbose, :extension, :delay, :time, :record_separator,
:list
def initialize
self.library = []
self.inplace = false
self.encoding = "utf8"
self.transfer_type = :auto
self.verbose = false
end
def define_options(parser)
parser.banner = "Usage: example.rb [options]"
parser.separator ""
parser.separator "Specific options:"
# add additional options
perform_inplace_option(parser)
delay_execution_option(parser)
execute_at_time_option(parser)
specify_record_separator_option(parser)
list_example_option(parser)
specify_encoding_option(parser)
optional_option_argument_with_keyword_completion_option(parser)
boolean_verbose_option(parser)
parser.separator ""
parser.separator "Common options:"
# No argument, shows at tail. This will print an options summary.
# Try it and see!
parser.on_tail("-h", "--help", "Show this message") do
puts parser
exit
end
# Another typical switch to print the version.
parser.on_tail("--version", "Show version") do
puts Version
exit
end
end
def perform_inplace_option(parser)
# Specifies an optional option argument
parser.on("-i", "--inplace [EXTENSION]",
"Edit ARGV files in place",
"(make backup if EXTENSION supplied)") do |ext|
self.inplace = true
self.extension = ext || ''
self.extension.sub!(/\A\.?(?=.)/, ".") # Ensure extension begins with dot.
end
end
def delay_execution_option(parser)
# Cast 'delay' argument to a Float.
parser.on("--delay N", Float, "Delay N seconds before executing") do |n|
self.delay = n
end
end
def execute_at_time_option(parser)
# Cast 'time' argument to a Time object.
parser.on("-t", "--time [TIME]", Time, "Begin execution at given time") do |time|
self.time = time
end
end
def specify_record_separator_option(parser)
# Cast to octal integer.
parser.on("-F", "--irs [OCTAL]", OptionParser::OctalInteger,
"Specify record separator (default \\0)") do |rs|
self.record_separator = rs
end
end
def list_example_option(parser)
# List of arguments.
parser.on("--list x,y,z", Array, "Example 'list' of arguments") do |list|
self.list = list
end
end
def specify_encoding_option(parser)
# Keyword completion. We are specifying a specific set of arguments (CODES
# and CODE_ALIASES - notice the latter is a Hash), and the user may provide
# the shortest unambiguous text.
code_list = (CODE_ALIASES.keys + CODES).join(', ')
parser.on("--code CODE", CODES, CODE_ALIASES, "Select encoding",
"(#{code_list})") do |encoding|
self.encoding = encoding
end
end
def optional_option_argument_with_keyword_completion_option(parser)
# Optional '--type' option argument with keyword completion.
parser.on("--type [TYPE]", [:text, :binary, :auto],
"Select transfer type (text, binary, auto)") do |t|
self.transfer_type = t
end
end
def boolean_verbose_option(parser)
# Boolean switch.
parser.on("-v", "--[no-]verbose", "Run verbosely") do |v|
self.verbose = v
end
end
end
#
# Return a structure describing the options.
#
def parse(args)
# The options specified on the command line will be collected in
# *options*.
@options = ScriptOptions.new
@args = OptionParser.new do |parser|
@options.define_options(parser)
parser.parse!(args)
end
@options
end
attr_reader :parser, :options
end # class OptparseExample
example = OptparseExample.new
options = example.parse(ARGV)
pp options # example.options
pp ARGV
셸 완성(Shell Completion)
bash, zsh 같은 현대적인 셸에서는 명령줄 옵션에 대해 셸 완성을 사용할 수 있어요.
추가 문서
위 예제들과 함께 제공되는 Tutorial이면 이 클래스를 쓰는 법을 배우기에 충분해요. 질문이 있다면 bugs.ruby-lang.org에 티켓을 올려 주세요.
Constants
DecimalInteger
10진 정수 형식. Integer로 변환돼요.
DecimalNumeric
10진 정수/부동소수 형식. 정수 형식은 Integer로, 부동소수 형식은 Float로 변환돼요.
OctalInteger
Ruby/C 스타일 8진수/16진수/2진수 정수 형식. Integer로 변환돼요.
VERSION
버전 문자열
Version
호환용 별칭
Attributes
요약 앞에 붙는 제목 배너(banner). 기본적으로 파싱될 문자열들. 오류 메시지와 기본 배너에 나타날 프로그램 이름(기본 $0). 알 수 없는 옵션에서 예외를 던질지 여부. 릴리스 코드. 옵션이 정확히 일치해야 하는지(축약된 긴 옵션을 짧은 옵션으로 제공하는 것을 허용하지 않음) 여부. 요약 앞에 붙는 제목 배너. 오류 메시지와 기본 배너에 나타날 프로그램 이름(기본 $0) — program_name. 요약 들여쓰기(String 또는 + String 메서드를 가져야 함) — summary_indent. 요약의 옵션 목록 부분 너비(Numeric이어야 함) — summary_width. 요약 들여쓰기 — summary_indent. 요약의 옵션 목록 부분 너비 — summary_width. 버전.
Public Class Methods
accept(*args, &blk)
accept 메서드를 참고하세요.
getopts(*args, symbolize_names: false)
getopts 메서드를 참고하세요.
inc(arg, default = nil)
arg에 따라 default를 증가시킨 값을 돌려줘요.
new(banner = nil, width = 32, indent = ' ' * 4) { |self| ... }
인스턴스를 초기화하고, 블록과 함께 호출되면 self를 yield 해요. 파라미터는 banner(배너 메시지), width(요약 너비), indent(요약 들여쓰기)예요.
reject(*args, &blk)
reject 메서드를 참고하세요.
show_version(*pkgs)
Version이 정의된 패키지에서 버전 문자열을 보여줘요. pkgs는 패키지 목록이에요.
terminate(arg = nil)
terminate 메서드를 참고하세요.
top()
전역 top 옵션 목록을 돌려줘요. 직접 쓰지 마세요.
with(*args, &block)
새 인스턴스를 초기화하고 인스턴스의 컨텍스트에서 선택적 블록을 평가해요. 인자 args는 new에 전달돼요. 이 메서드는 deprecated이고, 그 동작은 이전 new 메서드에 해당해요.
Public Instance Methods
abort(mesg = $!)
프로그램 이름과 함께 메시지를 보여준 후 중단해요. mesg는 메시지로, 기본 $!이에요. Kernel#abort를 참고하세요. 슈퍼클래스 메서드 Kernel#abort를 호출해요.
accept(*args, &blk)
지정된 클래스 t를 받아들이도록 지시해요. 인자 문자열은 그 클래스로 변환돼야 하는 블록에 전달돼요. t는 인자 클래스 지정자로, Class를 포함한 어떤 객체든 가능해요. pat는 인자의 패턴으로, t가 match에 응답하면 기본 t예요.
accept(t, pat, &block)
additional_message(typ, opt)
추가 정보를 돌려줘요.
banner()
요약 앞에 붙는 제목 배너.
base()
on_tail의 대상(subject).
candidate(word)
word에 대한 후보를 돌려줘요.
define(*params, &block)
주어진 파라미터 params로 옵션을 만들어요. 블록이 주어지면 만들어진 옵션의 핸들러예요. 명령줄 파싱 중 그 옵션을 만나면, 블록은 그 옵션에 주어진 인자(있다면)와 함께 호출돼요.
define_by_keywords(options, method, **params)
주어진 파라미터 params로 옵션을 만들어요. method의 키워드 파라미터에 대한 options로 설정되는 옵션들을 정의해요. 각 키워드의 파라미터는 params의 요소로 주어져요.
define_head(*params, &block)
define과 같지만, 만들어진 옵션이 요약의 머리(head)에 추가돼요.
define_tail(*params, &block)
define과 같지만, 만들어진 옵션이 요약의 꼬리(tail)에 추가돼요.
environment(env = File.basename($0, '.*'), **keywords)
환경 변수 env(또는 그 대문자)를 셸처럼 쪼개서 파싱해요. env는 기본적으로 프로그램의 basename이에요.
getopts(*args, symbolize_names: false, **keywords)
getopts.rb의 래퍼 메서드예요.
params = ARGV.getopts("ab:", "foo", "bar:", "zot:Z;zot option")
# params["a"] = true # -a
# params["b"] = "1" # -b1
# params["foo"] = "1" # --foo
# params["bar"] = "x" # --bar x
# params["zot"] = "z" # --zot Z
symbolize_names(불리언) 옵션은 돌려받는 해시 키가 심볼이어야 하는지 지정해요. 기본값은 false(문자열 사용)예요.
params = ARGV.getopts("ab:", "foo", "bar:", "zot:Z;zot option", symbolize_names: true)
# params[:a] = true # -a
# params[:b] = "1" # -b1
# params[:foo] = "1" # --foo
# params[:bar] = "x" # --bar x
# params[:zot] = "z" # --zot Z
help()
옵션 요약 문자열을 돌려줘요.
inc(*args)
self.inc를 참고하세요.
load(filename = nil, **keywords)
filename 파일에서 옵션을 로드해요. 파일이 없으면 아무것도 하지 않아요. 성공적으로 로드됐는지 돌려줘요. filename은 기본적으로 XDG와 Haiku 표준 위치 아래의 ~/.options 디렉터리에서 프로그램 basename을 접미사 없이 쓴 다음, '.options' 접미사를 붙인 basename을 사용해요. 선택적 into 키워드 인자는 parse 메서드에서 받는 것과 똑같이 동작해요.
make_switch(params, block = nil)
주어진 파라미터 params로 옵션을 만들어요. 블록이 주어지면 만들어진 옵션의 핸들러예요.
new() { |self| ... }
새 List를 push 해요. 블록이 주어지면 self를 yield 하고 블록의 결과를 돌려주고, 아니면 self를 돌려줘요.
on(*params, &block)
주어진 파라미터 params로 옵션을 만들어요. 블록은 옵션의 핸들러예요.
on_head(*params, &block)
on과 같지만, 새 옵션이 요약의 머리에 추가돼요.
on_tail(*params, &block)
on과 같지만, 새 옵션이 요약의 꼬리에 추가돼요.
order(*argv, **keywords, &nonopt)
명령줄 인자 argv를 순서대로 파싱해요. 블록이 주어지면 각 비옵션 인자를 yield 해요. 선택적 into 키워드 인자가 주어지면 파싱된 옵션 값이 []= 메서드로 그곳에 저장돼요(Hash, OpenStruct, 또는 비슷한 객체일 수 있어요). 파싱되지 않고 남은 argv의 나머지를 돌려줘요.
order!(argv = default_argv, into: nil, **keywords, &nonopt)
order와 같지만 스위치를 파괴적으로 제거해요. 비옵션 인자는 argv에 남아요.
parse(*argv, **keywords)
POSIXLY_CORRECT 환경 변수가 설정돼 있으면 인자를 순서대로 파싱하고, 그 외에는 permutation 모드로 파싱해요. 선택적 into 키워드 인자가 주어지면 파싱된 옵션 값이 []= 메서드로 저장돼요.
parse!(argv = default_argv, **keywords)
parse와 같지만 스위치를 파괴적으로 제거해요. 비옵션 인자는 argv에 남아요.
permute(*argv, **keywords)
명령줄 인자를 permutation 모드로 파싱하고 비옵션 인자 목록을 돌려줘요. 선택적 into 키워드 인자가 주어지면 파싱된 옵션 값이 []= 메서드로 저장돼요.
permute!(argv = default_argv, **keywords)
permute와 같지만 스위치를 파괴적으로 제거해요. 비옵션 인자는 argv에 남아요.
program_name()
오류 메시지와 기본 배너에 나타날 프로그램 이름. 기본 $0.
reject(*args, &blk)
지정된 클래스 인자를 거부하도록 지시해요. type은 인자 클래스 지정자로, Class를 포함한 어떤 객체든 가능해요.
reject(type)
release()
릴리스 코드.
remove()
마지막 List를 제거해요.
separator(string)
요약에 구분자를 추가해요.
summarize(to = [], width = @summary_width, max = width - 1, indent = @summary_indent, &blk)
옵션 요약을 to에 넣고 to를 돌려줘요. 블록이 주어지면 각 줄을 yield 해요. to는 << 메서드를 가져야 하는 출력 대상(기본 []), width는 왼쪽 너비(기본 @summary_width), max는 왼쪽에 허용되는 최대 길이(기본 width - 1), indent는 들여쓰기(기본 @summary_indent)예요.
terminate(arg = nil)
옵션 파싱을 종료해요. 선택적 인자 arg는 첫 비옵션 인자가 될 문자열로 다시 밀어 넣어져요.
to_a()
옵션 요약 목록을 돌려줘요.
to_s()
top()
on/on_head, accept/reject의 대상(subject).
ver()
program_name, version, release에서 버전 문자열을 돌려줘요.
version()
버전.
warn(mesg = $!)
프로그램 이름과 함께 경고 메시지를 보여줘요. mesg는 메시지로, 기본 $!이에요. Kernel#warn을 참고하세요. 슈퍼클래스 메서드 Kernel#warn을 호출해요.