<NME> option_parser.rb
<BEF> # http://www.ruby-doc.org/stdlib/libdoc/optparse/rdoc/classes/OptionParser.html
require 'optparse'

module RSpec::Core
  # @private
  class Parser
    def self.parse(args, source=nil)
      new(args).parse(source)
    end

    attr_reader :original_args

    def initialize(original_args)
      @original_args = original_args
    end

    def parse(source=nil)
      return { :files_or_directories_to_run => [] } if original_args.empty?
      args = original_args.dup

      options = args.delete('--tty') ? { :tty => true } : {}
      begin
        parser(options).parse!(args)
      rescue OptionParser::InvalidOption => e
        abort "#{e.message}#{" (defined in #{source})" if source}\n\n" \
              "Please use --help for a listing of valid options"
      end

      options[:files_or_directories_to_run] = args
      options
    end

  private

    # rubocop:disable Metrics/AbcSize
    # rubocop:disable Metrics/MethodLength
    # rubocop:disable Metrics/CyclomaticComplexity
    # rubocop:disable Metrics/PerceivedComplexity
    def parser(options)
      OptionParser.new do |parser|
        parser.summary_width = 34

        parser.banner = "Usage: rspec [options] [files or directories]\n\n"

        parser.on('-I PATH', 'Specify PATH to add to $LOAD_PATH (may be used more than once).') do |dirs|
          options[:libs] ||= []
          options[:libs].concat(dirs.split(File::PATH_SEPARATOR))
        end

        parser.on('-r', '--require PATH', 'Require a file.') do |path|
          options[:requires] ||= []
          options[:requires] << path
        end

        parser.on('-O', '--options PATH', 'Specify the path to a custom options file.') do |path|
          options[:custom_options_file] = path
        end

        parser.on('--order TYPE[:SEED]', 'Run examples by the specified order type.',
                  '  [defined]           examples and groups are run in the order they are defined',
                  '  [rand]              randomize the order of groups and examples',
                  '  [random]            alias for rand',
                  '  [random:SEED]       e.g. --order random:123',
                  '  [recently-modified] run the most recently modified files first') do |o|
          options[:order] = o
        end

        parser.on('--seed SEED', Integer, 'Equivalent of --order rand:SEED.') do |seed|
          options[:order] = "rand:#{seed}"
        end

        parser.on('--bisect[=verbose]', 'Repeatedly runs the suite in order to isolate the failures to the ',
                  '  smallest reproducible case.') do |argument|
          options[:bisect] = argument || true
          options[:runner] = RSpec::Core::Invocations::Bisect.new
        end

        parser.on('--[no-]fail-fast[=COUNT]', 'Abort the run after a certain number of failures (1 by default).') do |argument|
          if argument == true
            value = 1
          elsif argument == false || argument == 0
            value = false
          else
            begin
              value = Integer(argument)
            rescue ArgumentError
              RSpec.warning "Expected an integer value for `--fail-fast`, got: #{argument.inspect}", :call_site => nil
            end
          end
          set_fail_fast(options, value)
        end
                '  [d]ocumentation (group and example names)',
                '  [h]tml',
                '  [t]extmate',
                '  custom formatter class name') do |o|
          options[:formatters] ||= []
          options[:formatters] << [o]
                  'Override the exit code used when there are errors loading or running specs outside of examples.') do |code|
          options[:error_exit_code] = code
        end

        parser.on('-X', '--[no-]drb', 'Run examples via DRb.') do |use_drb|
          options[:drb] = use_drb
          options[:runner] = RSpec::Core::Invocations::DRbWithFallback.new if use_drb
        end

        parser.on('--drb-port PORT', 'Port to connect to the DRb server.') do |o|
          options[:drb_port] = o.to_i
        end

        parser.separator("\n  **** Output ****\n\n")

        parser.on('-f', '--format FORMATTER', 'Choose a formatter.',
                  '  [p]rogress (default - dots)',
                  '  [d]ocumentation (group and example names)',
                  '  [h]tml',
                  '  [j]son',
                  '  [f]ailures ("file:line:reason", suitable for editors integration)',
                  '  custom formatter class name') do |o|
          options[:formatters] ||= []
          options[:formatters] << [o]
        end

        parser.on('-o', '--out FILE',
                  'Write output to a file instead of $stdout. This option applies',
                  '  to the previously specified --format, or the default format',
                  '  if no format is specified.'
                 ) do |o|
          options[:formatters] ||= [['progress']]
          options[:formatters].last << o
        end

        parser.on('--deprecation-out FILE', 'Write deprecation warnings to a file instead of $stderr.') do |file|
          options[:deprecation_stream] = file
        end

        parser.on('-b', '--backtrace', 'Enable full backtrace.') do |_o|
          options[:full_backtrace] = true
        end

        parser.on('-c', '--color', '--colour', '') do |_o|
          # flag will be excluded from `--help` output because it is deprecated
          options[:color] = true
          options[:color_mode] = :automatic
        end

        parser.on('--force-color', '--force-colour', 'Force the output to be in color, even if the output is not a TTY') do |_o|
          if options[:color_mode] == :off
            abort "Please only use one of `--force-color` and `--no-color`"
          end
          options[:color_mode] = :on
        end

        parser.on('--no-color', '--no-colour', 'Force the output to not be in color, even if the output is a TTY') do |_o|
          if options[:color_mode] == :on
            abort "Please only use one of --force-color and --no-color"
          end
          options[:color_mode] = :off
        end

        parser.on('-p', '--[no-]profile [COUNT]',
                  'Enable profiling of examples and list the slowest examples (default: 10).') do |argument|
          options[:profile_examples] = if argument.nil?
                                         true
                                       elsif argument == false
                                         false
                                       else
                                         begin
                                           Integer(argument)
                                         rescue ArgumentError
                                           RSpec.warning "Non integer specified as profile count, separate " \
                                                       "your path from options with -- e.g. " \
                                                       "`rspec --profile -- #{argument}`",
                                                         :call_site => nil
                                           true
                                         end
                                       end
        end

        parser.on('--dry-run', 'Print the formatter output of your suite without',
                  '  running any examples or hooks') do |_o|
          options[:dry_run] = true
        end

        parser.on('-w', '--warnings', 'Enable ruby warnings') do
          if Object.const_defined?(:Warning) && Warning.respond_to?(:[]=)
            Warning[:deprecated] = true
          end
          $VERBOSE = true
        end

        parser.separator <<-FILTERING

  **** Filtering/tags ****

    In addition to the following options for selecting specific files, groups, or
    examples, you can select individual examples by appending the line number(s) to
    the filename:

      rspec path/to/a_spec.rb:37:87

    You can also pass example ids enclosed in square brackets:

      rspec path/to/a_spec.rb[1:5,1:6] # run the 5th and 6th examples/groups defined in the 1st group

FILTERING

        parser.on('--only-failures', "Filter to just the examples that failed the last time they ran.") do
          configure_only_failures(options)
        end

        parser.on("-n", "--next-failure", "Apply `--only-failures` and abort after one failure.",
                  "  (Equivalent to `--only-failures --fail-fast --order defined`)") do
          configure_only_failures(options)
          set_fail_fast(options, 1)
          options[:order] ||= 'defined'
        end

        parser.on('-P', '--pattern PATTERN', 'Load files matching pattern (default: "spec/**/*_spec.rb").') do |o|
          if options[:pattern]
            options[:pattern] += ',' + o
          else
            options[:pattern] = o
          end
        end

        parser.on('--exclude-pattern PATTERN',
                  'Load files except those matching pattern. Opposite effect of --pattern.') do |o|
          options[:exclude_pattern] = o
        end

        parser.on('-e', '--example STRING', "Run examples whose full nested names include STRING (may be",
                  "  used more than once)") do |o|
          (options[:full_description] ||= []) << Regexp.compile(Regexp.escape(o))
        end

        parser.on('-E', '--example-matches REGEX', "Run examples whose full nested names match REGEX (may be",
                  "  used more than once)") do |o|
          (options[:full_description] ||= []) << Regexp.compile(o)
        end

        parser.on('-t', '--tag TAG[:VALUE]',
                  'Run examples with the specified tag, or exclude examples',
                  'by adding ~ before the tag.',
                  '  - e.g. ~slow',
                  '  - TAG is always converted to a symbol') do |tag|
          filter_type = tag =~ /^~/ ? :exclusion_filter : :inclusion_filter

          name, value = tag.gsub(/^(~@|~|@)/, '').split(':', 2)
          name = name.to_sym

          parsed_value = case value
                         when  nil        then true # The default value for tags is true
                         when 'true'      then true
                         when 'false'     then false
                         when 'nil'       then nil
                         when /^:/        then value[1..-1].to_sym
                         when /^\d+$/     then Integer(value)
                         when /^\d+.\d+$/ then Float(value)
                         else
                           value
                         end

          add_tag_filter(options, filter_type, name, parsed_value)
        end

        parser.on('--default-path PATH', 'Set the default path where RSpec looks for examples (can',
                  '  be a path to a file or a directory).') do |path|
          options[:default_path] = path
        end

        parser.separator("\n  **** Utility ****\n\n")

        parser.on('--init', 'Initialize your project with RSpec.') do |_cmd|
          options[:runner] = RSpec::Core::Invocations::InitializeProject.new
        end

        parser.on('-v', '--version', 'Display the version.') do
          options[:runner] = RSpec::Core::Invocations::PrintVersion.new
        end

        # These options would otherwise be confusing to users, so we forcibly
        # prevent them from executing.
        #
        #   * --I is too similar to -I.
        #   * -d was a shorthand for --debugger, which is removed, but now would
        #     trigger --default-path.
        invalid_options = %w[-d --I]

        hidden_options = invalid_options + %w[-c]

        parser.on_tail('-h', '--help', "You're looking at it.") do
          options[:runner] = RSpec::Core::Invocations::PrintHelp.new(parser, hidden_options)
        end

        # This prevents usage of the invalid_options.
        invalid_options.each do |option|
          parser.on(option) do
            raise OptionParser::InvalidOption.new
          end
        end
      end
    end
    # rubocop:enable Metrics/AbcSize
    # rubocop:enable Metrics/MethodLength
    # rubocop:enable Metrics/CyclomaticComplexity
    # rubocop:enable Metrics/PerceivedComplexity

    def add_tag_filter(options, filter_type, tag_name, value=true)
      (options[filter_type] ||= {})[tag_name] = value
    end

    def set_fail_fast(options, value)
      options[:fail_fast] = value
    end

    def configure_only_failures(options)
      options[:only_failures] = true
      add_tag_filter(options, :inclusion_filter, :last_run_status, 'failed')
    end
  end
end

<MSG> add --format json to the command-line option parser

<DFF> @@ -92,6 +92,7 @@ module RSpec::Core
                 '  [d]ocumentation (group and example names)',
                 '  [h]tml',
                 '  [t]extmate',
+                '  [j]son',
                 '  custom formatter class name') do |o|
           options[:formatters] ||= []
           options[:formatters] << [o]

{'commit': "diff --git a/lib/rspec/core/option_parser.rb b/lib/rspec/core/option_parser.rb\nindex 2f71de44..e82574c5 100644\n--- a/lib/rspec/core/option_parser.rb\n+++ b/lib/rspec/core/option_parser.rb\n@@ -92,6 +92,7 @@ module RSpec::Core\n                 '  [d]ocumentation (group and example names)',\n                 '  [h]tml',\n                 '  [t]extmate',\n+                '  [j]son',\n                 '  custom formatter class name') do |o|\n           options[:formatters] ||= []\n           options[:formatters] << [o]\n", 'commit_subject': 'add --format json to the command-line option parser', 'repo_name': 'rspec/rspec-core', 'committer': {'name': 'Alex Chaffee', 'email': '60c6d277a8bd81de7fdde19201bf9c58a3df08f4@stinky.com', 'time_sec': '1345184417', 'tz_offset': '-420', 'date': {'seconds': '1345184417'}}, 'commit_tree': 'cb031b87e8acb0fb0c09d8383a4be869c810451a', 'commit_message': 'add --format json to the command-line option parser\n', 'language_name': 'Ruby', 'license': 'mit', 'hunks': "@@ -92,6 +92,7 @@ module RSpec::Core\n                 '  [d]ocumentation (group and example names)',\n                 '  [h]tml',\n                 '  [t]extmate',\n+                '  [j]son',\n                 '  custom formatter class name') do |o|\n           options[:formatters] ||= []\n           options[:formatters] << [o]\n", 'addition_count': 1, 'deletion_count': 0, 'src_file': 'a/lib/rspec/core/option_parser.rb', 'tgt_file': 'b/lib/rspec/core/option_parser.rb', 'file_extension': '.rb', 'before_file': '# http://www.ruby-doc.org/stdlib/libdoc/optparse/rdoc/classes/OptionParser.html\nrequire \'optparse\'\n\nmodule RSpec::Core\n  # @private\n  class Parser\n    def self.parse(args, source=nil)\n      new(args).parse(source)\n    end\n\n    attr_reader :original_args\n\n    def initialize(original_args)\n      @original_args = original_args\n    end\n\n    def parse(source=nil)\n      return { :files_or_directories_to_run => [] } if original_args.empty?\n      args = original_args.dup\n\n      options = args.delete(\'--tty\') ? { :tty => true } : {}\n      begin\n        parser(options).parse!(args)\n      rescue OptionParser::InvalidOption => e\n        abort "#{e.message}#{" (defined in #{source})" if source}\\n\\n" \\\n              "Please use --help for a listing of valid options"\n      end\n\n      options[:files_or_directories_to_run] = args\n      options\n    end\n\n  private\n\n    # rubocop:disable Metrics/AbcSize\n    # rubocop:disable Metrics/MethodLength\n    # rubocop:disable Metrics/CyclomaticComplexity\n    # rubocop:disable Metrics/PerceivedComplexity\n    def parser(options)\n      OptionParser.new do |parser|\n        parser.summary_width = 34\n\n        parser.banner = "Usage: rspec [options] [files or directories]\\n\\n"\n\n        parser.on(\'-I PATH\', \'Specify PATH to add to $LOAD_PATH (may be used more than once).\') do |dirs|\n          options[:libs] ||= []\n          options[:libs].concat(dirs.split(File::PATH_SEPARATOR))\n        end\n\n        parser.on(\'-r\', \'--require PATH\', \'Require a file.\') do |path|\n          options[:requires] ||= []\n          options[:requires] << path\n        end\n\n        parser.on(\'-O\', \'--options PATH\', \'Specify the path to a custom options file.\') do |path|\n          options[:custom_options_file] = path\n        end\n\n        parser.on(\'--order TYPE[:SEED]\', \'Run examples by the specified order type.\',\n                  \'  [defined]           examples and groups are run in the order they are defined\',\n                  \'  [rand]              randomize the order of groups and examples\',\n                  \'  [random]            alias for rand\',\n                  \'  [random:SEED]       e.g. --order random:123\',\n                  \'  [recently-modified] run the most recently modified files first\') do |o|\n          options[:order] = o\n        end\n\n        parser.on(\'--seed SEED\', Integer, \'Equivalent of --order rand:SEED.\') do |seed|\n          options[:order] = "rand:#{seed}"\n        end\n\n        parser.on(\'--bisect[=verbose]\', \'Repeatedly runs the suite in order to isolate the failures to the \',\n                  \'  smallest reproducible case.\') do |argument|\n          options[:bisect] = argument || true\n          options[:runner] = RSpec::Core::Invocations::Bisect.new\n        end\n\n        parser.on(\'--[no-]fail-fast[=COUNT]\', \'Abort the run after a certain number of failures (1 by default).\') do |argument|\n          if argument == true\n            value = 1\n          elsif argument == false || argument == 0\n            value = false\n          else\n            begin\n              value = Integer(argument)\n            rescue ArgumentError\n              RSpec.warning "Expected an integer value for `--fail-fast`, got: #{argument.inspect}", :call_site => nil\n            end\n          end\n          set_fail_fast(options, value)\n        end\n                \'  [d]ocumentation (group and example names)\',\n                \'  [h]tml\',\n                \'  [t]extmate\',\n                \'  custom formatter class name\') do |o|\n          options[:formatters] ||= []\n          options[:formatters] << [o]\n                  \'Override the exit code used when there are errors loading or running specs outside of examples.\') do |code|\n          options[:error_exit_code] = code\n        end\n\n        parser.on(\'-X\', \'--[no-]drb\', \'Run examples via DRb.\') do |use_drb|\n          options[:drb] = use_drb\n          options[:runner] = RSpec::Core::Invocations::DRbWithFallback.new if use_drb\n        end\n\n        parser.on(\'--drb-port PORT\', \'Port to connect to the DRb server.\') do |o|\n          options[:drb_port] = o.to_i\n        end\n\n        parser.separator("\\n  **** Output ****\\n\\n")\n\n        parser.on(\'-f\', \'--format FORMATTER\', \'Choose a formatter.\',\n                  \'  [p]rogress (default - dots)\',\n                  \'  [d]ocumentation (group and example names)\',\n                  \'  [h]tml\',\n                  \'  [j]son\',\n                  \'  [f]ailures ("file:line:reason", suitable for editors integration)\',\n                  \'  custom formatter class name\') do |o|\n          options[:formatters] ||= []\n          options[:formatters] << [o]\n        end\n\n        parser.on(\'-o\', \'--out FILE\',\n                  \'Write output to a file instead of $stdout. This option applies\',\n                  \'  to the previously specified --format, or the default format\',\n                  \'  if no format is specified.\'\n                 ) do |o|\n          options[:formatters] ||= [[\'progress\']]\n          options[:formatters].last << o\n        end\n\n        parser.on(\'--deprecation-out FILE\', \'Write deprecation warnings to a file instead of $stderr.\') do |file|\n          options[:deprecation_stream] = file\n        end\n\n        parser.on(\'-b\', \'--backtrace\', \'Enable full backtrace.\') do |_o|\n          options[:full_backtrace] = true\n        end\n\n        parser.on(\'-c\', \'--color\', \'--colour\', \'\') do |_o|\n          # flag will be excluded from `--help` output because it is deprecated\n          options[:color] = true\n          options[:color_mode] = :automatic\n        end\n\n        parser.on(\'--force-color\', \'--force-colour\', \'Force the output to be in color, even if the output is not a TTY\') do |_o|\n          if options[:color_mode] == :off\n            abort "Please only use one of `--force-color` and `--no-color`"\n          end\n          options[:color_mode] = :on\n        end\n\n        parser.on(\'--no-color\', \'--no-colour\', \'Force the output to not be in color, even if the output is a TTY\') do |_o|\n          if options[:color_mode] == :on\n            abort "Please only use one of --force-color and --no-color"\n          end\n          options[:color_mode] = :off\n        end\n\n        parser.on(\'-p\', \'--[no-]profile [COUNT]\',\n                  \'Enable profiling of examples and list the slowest examples (default: 10).\') do |argument|\n          options[:profile_examples] = if argument.nil?\n                                         true\n                                       elsif argument == false\n                                         false\n                                       else\n                                         begin\n                                           Integer(argument)\n                                         rescue ArgumentError\n                                           RSpec.warning "Non integer specified as profile count, separate " \\\n                                                       "your path from options with -- e.g. " \\\n                                                       "`rspec --profile -- #{argument}`",\n                                                         :call_site => nil\n                                           true\n                                         end\n                                       end\n        end\n\n        parser.on(\'--dry-run\', \'Print the formatter output of your suite without\',\n                  \'  running any examples or hooks\') do |_o|\n          options[:dry_run] = true\n        end\n\n        parser.on(\'-w\', \'--warnings\', \'Enable ruby warnings\') do\n          if Object.const_defined?(:Warning) && Warning.respond_to?(:[]=)\n            Warning[:deprecated] = true\n          end\n          $VERBOSE = true\n        end\n\n        parser.separator <<-FILTERING\n\n  **** Filtering/tags ****\n\n    In addition to the following options for selecting specific files, groups, or\n    examples, you can select individual examples by appending the line number(s) to\n    the filename:\n\n      rspec path/to/a_spec.rb:37:87\n\n    You can also pass example ids enclosed in square brackets:\n\n      rspec path/to/a_spec.rb[1:5,1:6] # run the 5th and 6th examples/groups defined in the 1st group\n\nFILTERING\n\n        parser.on(\'--only-failures\', "Filter to just the examples that failed the last time they ran.") do\n          configure_only_failures(options)\n        end\n\n        parser.on("-n", "--next-failure", "Apply `--only-failures` and abort after one failure.",\n                  "  (Equivalent to `--only-failures --fail-fast --order defined`)") do\n          configure_only_failures(options)\n          set_fail_fast(options, 1)\n          options[:order] ||= \'defined\'\n        end\n\n        parser.on(\'-P\', \'--pattern PATTERN\', \'Load files matching pattern (default: "spec/**/*_spec.rb").\') do |o|\n          if options[:pattern]\n            options[:pattern] += \',\' + o\n          else\n            options[:pattern] = o\n          end\n        end\n\n        parser.on(\'--exclude-pattern PATTERN\',\n                  \'Load files except those matching pattern. Opposite effect of --pattern.\') do |o|\n          options[:exclude_pattern] = o\n        end\n\n        parser.on(\'-e\', \'--example STRING\', "Run examples whose full nested names include STRING (may be",\n                  "  used more than once)") do |o|\n          (options[:full_description] ||= []) << Regexp.compile(Regexp.escape(o))\n        end\n\n        parser.on(\'-E\', \'--example-matches REGEX\', "Run examples whose full nested names match REGEX (may be",\n                  "  used more than once)") do |o|\n          (options[:full_description] ||= []) << Regexp.compile(o)\n        end\n\n        parser.on(\'-t\', \'--tag TAG[:VALUE]\',\n                  \'Run examples with the specified tag, or exclude examples\',\n                  \'by adding ~ before the tag.\',\n                  \'  - e.g. ~slow\',\n                  \'  - TAG is always converted to a symbol\') do |tag|\n          filter_type = tag =~ /^~/ ? :exclusion_filter : :inclusion_filter\n\n          name, value = tag.gsub(/^(~@|~|@)/, \'\').split(\':\', 2)\n          name = name.to_sym\n\n          parsed_value = case value\n                         when  nil        then true # The default value for tags is true\n                         when \'true\'      then true\n                         when \'false\'     then false\n                         when \'nil\'       then nil\n                         when /^:/        then value[1..-1].to_sym\n                         when /^\\d+$/     then Integer(value)\n                         when /^\\d+.\\d+$/ then Float(value)\n                         else\n                           value\n                         end\n\n          add_tag_filter(options, filter_type, name, parsed_value)\n        end\n\n        parser.on(\'--default-path PATH\', \'Set the default path where RSpec looks for examples (can\',\n                  \'  be a path to a file or a directory).\') do |path|\n          options[:default_path] = path\n        end\n\n        parser.separator("\\n  **** Utility ****\\n\\n")\n\n        parser.on(\'--init\', \'Initialize your project with RSpec.\') do |_cmd|\n          options[:runner] = RSpec::Core::Invocations::InitializeProject.new\n        end\n\n        parser.on(\'-v\', \'--version\', \'Display the version.\') do\n          options[:runner] = RSpec::Core::Invocations::PrintVersion.new\n        end\n\n        # These options would otherwise be confusing to users, so we forcibly\n        # prevent them from executing.\n        #\n        #   * --I is too similar to -I.\n        #   * -d was a shorthand for --debugger, which is removed, but now would\n        #     trigger --default-path.\n        invalid_options = %w[-d --I]\n\n        hidden_options = invalid_options + %w[-c]\n\n        parser.on_tail(\'-h\', \'--help\', "You\'re looking at it.") do\n          options[:runner] = RSpec::Core::Invocations::PrintHelp.new(parser, hidden_options)\n        end\n\n        # This prevents usage of the invalid_options.\n        invalid_options.each do |option|\n          parser.on(option) do\n            raise OptionParser::InvalidOption.new\n          end\n        end\n      end\n    end\n    # rubocop:enable Metrics/AbcSize\n    # rubocop:enable Metrics/MethodLength\n    # rubocop:enable Metrics/CyclomaticComplexity\n    # rubocop:enable Metrics/PerceivedComplexity\n\n    def add_tag_filter(options, filter_type, tag_name, value=true)\n      (options[filter_type] ||= {})[tag_name] = value\n    end\n\n    def set_fail_fast(options, value)\n      options[:fail_fast] = value\n    end\n\n    def configure_only_failures(options)\n      options[:only_failures] = true\n      add_tag_filter(options, :inclusion_filter, :last_run_status, \'failed\')\n    end\n  end\nend\n', 'message': 'add --format json to the command-line option parser\n', 'author_name': 'Alex Chaffee', 'lang': 'rb'}