OptionParser 클래스

OptionParser 클래스

명령줄 옵션 분석을 위한 클래스예요. GetoptLong보다 훨씬 진보했으면서도 쓰기 더 쉽고, 더 Ruby스러운 해결책이에요.

출처: Ruby 3.3 API

본문

OptionParser

OptionParser가 처음인가요?

Tutorial을 보세요.

소개 (Introduction)

OptionParser는 명령줄 옵션 분석을 위한 클래스예요. GetoptLong보다 훨씬 진보했으면서도 쓰기 더 쉽고, 더 Ruby스러운 해결책이에요.

기능 (Features)

  • 인자 명세와 그걸 처리하는 코드를 같은 곳에 써요.
  • 옵션 요약을 출력할 수 있어요. 그 문자열을 따로 유지할 필요가 없죠.
  • 선택 인자와 필수 인자를 우아하게 지정할 수 있어요.
  • 인자를 지정된 클래스로 자동 변환할 수 있어요.
  • 인자를 특정 집합으로 제한할 수 있어요.

이 모든 기능은 아래 예시에서 보여줘요. 전체 문서는 make_switch를 보세요.

최소 예시 (Minimal example)

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

도움말 생성 (Generating Help)

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)하는 기능을 지원해요.

OptionParser에는 몇 가지 바로 쓸 수 있는 타입 강제 변환이 있어요:

  • DateDate.parse가 받는 모든 것(optparse/date를 require해야 해요)
  • DateTimeDateTime.parse가 받는 모든 것(optparse/date를 require해야 해요)
  • TimeTime.httpdate 또는 Time.parse가 받는 모든 것(optparse/time을 require해야 해요)
  • URIURI.parse가 받는 모든 것(optparse/uri를 require해야 해요)
  • ShellwordsShellwords.shellwords가 받는 모든 것(optparse/shellwords를 require해야 해요)
  • String – 비어 있지 않은 모든 문자열
  • Integer – 모든 정수. 8진수를 변환해요. (예: 124, -3, 040)
  • Float – 모든 실수. (예: 10, 3.14, -100E+13)
  • Numeric – 모든 정수, 실수 또는 유리수 (1, 3.4, 1/3)
  • DecimalIntegerInteger와 같지만 8진수 형식이 없어요.
  • OctalIntegerInteger와 같지만 10진수 형식이 없어요.
  • DecimalNumeric – 10진 정수 또는 실수.
  • TrueClass'+, yes, true, -, no, false'를 받아들이고 기본값은 true
  • FalseClassTrueClass와 같지만 기본값은 false
  • Array,로 구분된 문자열 (예: 1,2,3)
  • Regexp – 정규표현식. 옵션도 포함해요.

자체 강제 변환을 추가할 수도 있는데, 아래에서 다룰게요.

내장 변환 사용 (Using Built-in Conversions)

예시로 내장 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

커스텀 변환 만들기 (Creating Custom Conversions)

OptionParseraccept 메서드로 변환기를 만들 수 있어요. 클래스가 지정될 때마다 호출할 변환 블록을 지정해요. 아래 예시는 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)

옵션을 Hash에 저장 (Store options to a Hash)

order, parse 등의 메서드의 into 옵션은 명령줄 옵션을 Hash에 저장해요.

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}

완전한 예시 (Complete example)

다음 예시는 완전한 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 등)에서는 명령줄 옵션에 셸 완성(shell completion)을 쓸 수 있어요.

추가 문서 (Further documentation)

위 예시와 함께 제공되는 Tutorial이면 이 클래스를 쓰는 법을 배우기에 충분해요. 질문이 있으면 bugs.ruby-lang.org에 티켓을 올려 주세요.

상수 (Constants)

  • DecimalInteger

Attributes

banner

요약 앞에 오는 헤딩 배너예요.

default_argv

기본적으로 파싱할 문자열들.

program_name

에러 메시지와 기본 배너에 출력될 프로그램 이름. 기본값은 $0이에요.

raise_unknown

알 수 없는 옵션에서 raise할지 여부.

release

릴리스 코드.

require_exact

옵션이 정확히 일치해야 하는지 여부(축약된 긴 옵션을 짧은 옵션으로 제공하는 것을 허용하지 않음).

set_banner

요약 앞에 오는 헤딩 배너예요.

set_program_name

에러 메시지와 기본 배너에 출력될 프로그램 이름. 기본값은 $0이에요.

set_summary_indent

요약의 들여쓰기. String이어야 해요(+ String 메서드가 있거나).

set_summary_width

요약의 옵션 목록 부분 너비. Numeric이어야 해요.

summary_indent

요약의 들여쓰기. String이어야 해요(+ String 메서드가 있거나).

summary_width

요약의 옵션 목록 부분 너비. Numeric이어야 해요.

version

버전.

Public Class Methods

accept (*args, &blk)

accept 참고.

each_const (path, base = ::Object)

상수들을 반복해요.

getopts (*args, symbolize_names: false)

getopts 참고.

inc (arg, default = nil)

arg에 따라 default의 증가된 값을 반환해요.

new (banner = nil, width = 32, indent = ' ' * 4) { |self| ... }

인스턴스를 초기화하고, 블록과 함께 호출되면 자기 자신을 넘겨줘요. banner

reject (*args, &blk)

reject 참고.

search_const (klass, name) { |klass, cname, const| ... }

상수를 탐색해요.

show_version (*pkgs)

버전을 보여 줘요.

terminate (arg = nil)

옵션 파싱을 종료해요.

top ()

옵션 목록의 대상.

with (*args, &block)

새 인스턴스를 초기화하고 인스턴스 문맥에서 선택 블록을 평가해요. 인자 argsnew로 전달되며, 파라미터 설명은 거길 보세요.

이 메서드는 deprecated예요. 동작이 옛 new 메서드와 대응해요.

Public Instance Methods

abort (mesg = $!)

메시지를 출력하고 상태로 종료해요.

accept (*args, &blk)

지정된 클래스 t를 받아들이도록 지시해요. 인자 문자열은 그걸 원하는 클래스로 변환해야 하는 블록에 전달돼요. t

accept(t, pat, &block)

additional_message (typ, opt)

추가 정보를 반환해요.

banner ()

요약 앞에 오는 헤딩 배너.

base ()

on_tail의 대상.

candidate (word)

후보를 구해요.

def_head_option / def_option / def_tail_option

옵션 정의 헬퍼.

define(*params, &block)

주어진 파라미터 params로 옵션을 만들어요. Parameters for New Options 참고.

블록이 주어지면(조건부) 생성된 옵션의 핸들러예요. 명령줄 파싱 중 옵션이 만나지면, 블록은 그 옵션에 주어진 인자(있을 때)와 함께 호출돼요. Option Handlers 참고.

define_by_keywords(options, method, **params)

주어진 파라미터 params로 옵션을 만들어요. Parameters for New Options 참고.

블록이 주어지면(조건부) 생성된 옵션의 핸들러예요. 옵션이 만나지면 주어진 인자와 함께 호출돼요. Option Handlers 참고.

define_head(*params, &block) / define_tail(*params, &block)

주어진 파라미터 params로 옵션을 만들어요. (define과 동일; on_head/on_tail처럼 요약의 머리/꼬리에 추가돼요)

environment (env = File.basename($0, '.*'))

환경변수 env(또는 그 대문자)를 셸처럼 분할해 파싱해요.

env는 기본적으로 프로그램의 basename이에요.

getopts (*args, symbolize_names: false)

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(boolean)은 반환되는 Hash 키가 Symbol이어야 하는지 지정해요. 기본값은 false(String 사용)예요.

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)

증가된 값을 반환해요.

load (filename = nil, into: nil)

filename처럼 파일에서 옵션을 불러와요. 파일이 없으면 아무것도 하지 않아요. 성공적으로 불러왔는지 반환해요.

filename은 기본적으로 ~/.options 디렉터리에서 확장자 없는 프로그램 basename, 그다음 XDG와 Haiku 표준 위치 아래에서 .options 접미사를 가진 basename이에요.

선택 into 키워드 인자는 parse 메서드에서 받는 것과 정확히 같이 동작해요.

make_switch(params, block = nil)

주어진 파라미터 params로 옵션을 만들어요. Parameters for New Options 참고.

블록이 주어지면(조건부) 생성된 옵션의 핸들러예요. Option Handlers 참고.

new () { |self| ... }

새 List를 푸시해요.

on(*params, &block)

주어진 파라미터 params로 옵션을 만들어요. Parameters for New Options 참고.

블록이 주어지면(조건부) 생성된 옵션의 핸들러예요. Option Handlers 참고.

on_head(*params, &block)

on과 같지만 새 옵션이 요약의 머리에 추가돼요.

on_tail(*params, &block)

on과 같지만 새 옵션이 요약의 꼬리에 추가돼요.

order (*argv, into: nil, &nonopt)

명령줄 인자 argv를 순서대로 파싱해요. 블록이 주어지면 각 비옵션 인자를 넘겨줘요. 선택 into 키워드 인자가 주어지면 파싱된 옵션 값들이 거기에 []= 메서드로 저장돼요(Hash, OpenStruct, 또는 그와 비슷한 객체가 될 수 있어요).

파싱되지 않고 남은 argv의 나머지를 반환해요.

order! (argv = default_argv, into: nil, &nonopt)

order와 같지만 스위치를 파괴적으로 제거해요. 비옵션 인자는 argv에 남아요.

parse (*argv, into: nil)

환경변수 POSIXLY_CORRECT가 설정돼 있으면 명령줄 인자 argv를 순서대로, 그 외에는 permutation 모드로 파싱해요. 선택 into 키워드 인자가 주어지면 파싱된 옵션 값들이 거기에 []= 메서드로 저장돼요.

parse! (argv = default_argv, into: nil)

parse와 같지만 스위치를 파괴적으로 제거해요. 비옵션 인자는 argv에 남아요.

permute (*argv, into: nil)

명령줄 인자 argv를 permutation 모드로 파싱하고 비옵션 인자 목록을 반환해요. 선택 into 키워드 인자가 주어지면 파싱된 옵션 값들이 거기에 저장돼요.

permute! (argv = default_argv, into: nil)

permute와 같지만 스위치를 파괴적으로 제거해요. 비옵션 인자는 argv에 남아요.

program_name ()

에러 메시지와 기본 배너에 출력될 프로그램 이름. 기본값은 $0이에요.

reject (*args, &blk)

지정된 클래스 인자를 거부하도록 지시해요. t

reject(t)

release ()

릴리스 코드.

remove ()

마지막 List를 제거해요.

separator (string)

요약에 구분자를 추가해요.

summarize (to = [], width = @summary_width, max = width - 1, indent = @summary_indent, &blk)

옵션 요약을 to에 넣고 to를 반환해요. 블록이 주어지면 각 줄을 넘겨줘요. to

terminate (arg = nil)

옵션 파싱을 종료해요. 선택 파라미터 arg는 첫 비옵션 인자로 밀어 넣어지는 문자열이에요.

to_a ()

옵션 요약 목록을 반환해요.

to_s ()

문자열 표현을 반환해요.

top ()

on / on_head, accept / reject의 대상.

ver ()

program_name, version, release에서 버전 문자열을 반환해요.

version ()

버전.

warn (mesg = $!)

경고 메시지를 출력해요.