Programming Ruby 3.2 (5th Edition): B1.0 page 248, Rake consumes options, not OptionParser in task

@noelrappin

page 248, top of page, code using OptionParser. If I put traces in Rakefile :

puts "*** in Rakefile #{Time.now.strftime('%H:%M:%S')} ***"

def delete(dir, pattern)
    puts "in delete dir=#{dir}, pattern=#{pattern}"
    files = Dir["#{dir}/#{pattern}"]
    rm(files, verbose: true) unless files.empty?
end

desc "Remove files whose names end with a tilde"
task :delete_unix_backups do
    dir = "."
    puts 'before new'
    parser = OptionParser.new
    puts 'before on'
    parser.on("-d") { |opt| dir = opt }
    puts 'before parse!'
    parser.parse!
    delete(dir, "*~")
end

and execute rake without parameter, it loads the Rakefile :

% rake delete_unix_backups
*** in Rakefile 19:02:18 ***
before new
before on
before parse!
in delete dir=., pattern=*~

But with an invalid parameter, it doesn’t even load the Rakefile :

% rake delete_unix_backups -x    
invalid option: -x

Despite the fact that the documentation rake/command_line_usage.rdoc at master · ruby/rake · GitHub puts [options ...] before [targets ...], obviously rake takes the options for him, as with --dry-run (-n) :

% rake delete_unix_backups -n
*** in Rakefile 19:03:07 ***
** Invoke delete_unix_backups (first_time)
** Execute (dry run) delete_unix_backups

Paragraph after the code :

Then you’d call this with rake delete_unix_backups -d code The parse! command removes the
options from ARGV, preventing Rake from considering them as their own tasks.

Unfortunately it’s impossible to pass -d to OptionParser in task :delete_unix_backups :

% rake --trace delete_unix_backups -d del
rake aborted!
OptionParser::AmbiguousOption: ambiguous option: -d
/Users/b/.rvm/gems/ruby-3.0.0/gems/rake-13.0.6/lib/rake/application.rb:659:in `handle_options'
/Users/b/.rvm/gems/ruby-3.0.0/gems/rake-13.0.6/lib/rake/application.rb:92:in `block in init'
/Users/b/.rvm/gems/ruby-3.0.0/gems/rake-13.0.6/lib/rake/application.rb:186:in `standard_exception_handling'
/Users/b/.rvm/gems/ruby-3.0.0/gems/rake-13.0.6/lib/rake/application.rb:89:in `init'
/Users/b/.rvm/gems/ruby-3.0.0/gems/rake-13.0.6/lib/rake/application.rb:81:in `block in run'
/Users/b/.rvm/gems/ruby-3.0.0/gems/rake-13.0.6/lib/rake/application.rb:186:in `standard_exception_handling'
/Users/b/.rvm/gems/ruby-3.0.0/gems/rake-13.0.6/lib/rake/application.rb:80:in `run'
/Users/b/.rvm/gems/ruby-3.0.0/gems/rake-13.0.6/exe/rake:27:in `<top (required)>'
/Users/b/.rbenv/versions/3.2.0/bin/rake:25:in `load'
/Users/b/.rbenv/versions/3.2.0/bin/rake:25:in `<main>'

Caused by:
OptionParser::InvalidOption: invalid option: d
Did you mean?  D
...

The reason is handle_options in rake’s application.rb 92 :

87    # Initialize the command line parameters and app name.
88    def init(app_name="rake", argv = ARGV)
89      standard_exception_handling do
90        @name = app_name
91        begin
92          args = handle_options argv
93        rescue ArgumentError
94          # Backward compatibility for capistrano
95          args = handle_options
96        end
97        collect_command_line_tasks(args)
98      end
99    end
641    # Read and handle the command line options.  Returns the command line
642    # arguments that we didn't understand, which should (in theory) be just
643    # task names and env vars.
644    def handle_options(argv) # :nodoc:
645      set_default_options
646
647      OptionParser.new do |opts||
...
657        standard_rake_options.each { |args| opts.on(*args) }
658        opts.environment("RAKEOPT")
659      end.parse(argv)
660    end

which parses the options before you have a chance to add a new one in the task.

Okay, after a little bit of scrambling, I’ve got this

def delete(dir, pattern)
  files = Dir["#{dir}/#{pattern}"]
  rm(files, verbose: true) unless files.empty?
end

desc "Remove files whose names end with a tilde"
task :delete_unix_backups do
  dir = "."
  parser = OptionParser.new
  parser.on("-d DIR") { |opt| dir = opt }
  args = parser.order!(ARGV) {}
  parser.parse!(args)
  puts dir
  delete(dir, "*~")
end

Which works if you call it with rake delete_unix_backups -- -d ../rake

Thanks!