Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Ruby/Thor exit status in case of an error

Tags:

ruby

thor

I'm new to Thor (and to Ruby) and I'm considering using it within a build script, as it's said it can be a replacement to Rake (thus to Make). However after a short trial, I'm confused about the error status it returns. I quickly went through the wiki but haven't seen any mention about that.

With just the first "Simple Example", test.thor:

class Test < Thor
  desc "example", "an example task"
  def example
    puts "I'm a thor task!"
  end
end

version #:

eruve>thor version
Thor 0.18.1

I tried the following, an erroneous command on purpose:

eruve>ruby --version; thor test:example badarg; echo exit status: $?

ruby 2.0.0p195 (2013-05-14 revision 40734) [x86_64-darwin10.8.0]
ERROR: thor example was called with arguments ["badarg"]
Usage: "thor test:example".
exit status: 0

So, there was an error but it exits with status 0 nonetheless... meaning I'd rather not use it in a (non-ruby) script, or else the script would continue running even though it should terminate. Subsequent errors might be difficult to analyse.

I must be missing something, hence my questions:

  • Is there an easy way to get a non-zero status by default in case of an error (config file, etc.)?

  • If not, what am I supposed to do to get it right?

Thank you.

like image 852
eruve Avatar asked Jun 21 '13 18:06

eruve


3 Answers

I know this has been answered already but I think this is a better answer so I thought I'd contribute it anyway.

Thor has a method you can use to change the behavior so errors cause non-zero exit codes. It's not documented very well (IMHO).

class Test < Thor
  def self.exit_on_failure?
    true
  end

  desc "example", "an example task"
  def example
    puts "I'm a thor task!"
  end
end

The default for this is inexplicably false. I don't know why anyone would want it to behave like that personally. Thor issue 244 addresses this as well.

Update: As of Thor 1.0.0, you will get a deprecation warning if you do not provide your own exit_on_failure? method. This was to deal with the confusing default behavior.

like image 114
ZombieDev Avatar answered Nov 17 '22 19:11

ZombieDev


Based on bundler's solution (many thanks @fontno), and more investigation on my side, here is a hack in order to have it working with a normal shell. WARNING: it's not elegant, prints out the exception stack crap, but I think that works (please don't hesitate to tell me otherwise).

class Thorough < Thor
  ENV["THOR_DEBUG"] = "1"
  check_unknown_options!
private
  def subcommand(*_) super subcommand(*_)
  rescue Thor::Error => e
    $stderr.puts e.message
    exit 1
  end
end

class Test < Thor#ough
  desc "example", "an example task"
  def example
    puts "I'm a thor task!"
  end
end

Written as above, it has the same behavior as before (I believe). Now, after removing the # from Thor#ough, it should exit with status 1 if Thor has raised an Error, thus allowing some control from e.g. a non-ruby shell.

eruve>thor test:example badarg; echo $?
/Users/eruve/.rvm/gems/ruby-2.0.0-p195/gems/thor-0.18.1/lib/thor/base.rb:482:in `handle_argument_error': ERROR: thor example was called with arguments ["badarg"] (Thor::InvocationError)
Usage: "thor test:example".
    from /Users/eruve/.rvm/gems/ruby-2.0.0-p195/gems/thor-0.18.1/lib/thor/command.rb:35:in `rescue in run'
    from /Users/eruve/.rvm/gems/ruby-2.0.0-p195/gems/thor-0.18.1/lib/thor/command.rb:21:in `run'
    from /Users/eruve/.rvm/gems/ruby-2.0.0-p195/gems/thor-0.18.1/lib/thor/invocation.rb:120:in `invoke_command'
    from /Users/eruve/.rvm/gems/ruby-2.0.0-p195/gems/thor-0.18.1/lib/thor.rb:363:in `dispatch'
    from /Users/eruve/.rvm/gems/ruby-2.0.0-p195/gems/thor-0.18.1/lib/thor/base.rb:439:in `start'
    from /Users/eruve/.rvm/gems/ruby-2.0.0-p195/gems/thor-0.18.1/lib/thor/runner.rb:36:in `method_missing'
    from /Users/eruve/.rvm/gems/ruby-2.0.0-p195/gems/thor-0.18.1/lib/thor/command.rb:29:in `run'
    from /Users/eruve/.rvm/gems/ruby-2.0.0-p195/gems/thor-0.18.1/lib/thor/command.rb:128:in `run'
    from /Users/eruve/.rvm/gems/ruby-2.0.0-p195/gems/thor-0.18.1/lib/thor/invocation.rb:120:in `invoke_command'
    from /Users/eruve/.rvm/gems/ruby-2.0.0-p195/gems/thor-0.18.1/lib/thor.rb:363:in `dispatch'
    from /Users/eruve/.rvm/gems/ruby-2.0.0-p195/gems/thor-0.18.1/lib/thor/base.rb:439:in `start'
    from /Users/eruve/.rvm/gems/ruby-2.0.0-p195/gems/thor-0.18.1/bin/thor:6:in `<top (required)>'
    from /Users/eruve/.rvm/gems/ruby-2.0.0-p195/bin/thor:23:in `load'
    from /Users/eruve/.rvm/gems/ruby-2.0.0-p195/bin/thor:23:in `<main>'
    from /Users/eruve/.rvm/gems/ruby-2.0.0-p195/bin/ruby_noexec_wrapper:14:in `eval'
    from /Users/eruve/.rvm/gems/ruby-2.0.0-p195/bin/ruby_noexec_wrapper:14:in `<main>'
1

eruve>thor test:example; echo $?
I'm a thor task!
0

eruve>thor test:example badarg 2>/dev/null; echo $?
1

Cheers. PS: I wonder, are there many gotchas like this in Thor? If it's an expected behavior, its purpose/philosophy is incompatible with the needs of my project... hacks are not a reliable solution.

like image 3
eruve Avatar answered Nov 17 '22 18:11

eruve


Good question. I also noticed this when looking a thor to use for a project. As far as I can tell this is expected behavior. This pull request for bundler has an interesting solution that may be appropriate for you.

They enabled the thor debug flag so they could catch the error and set the appropriate exit status

# bin/bundle

Bundler.with_friendly_errors {
    # Set debug flag so we can rescue Thor::error's
    # and set the correct exit code.
    ENV["THOR_DEBUG"] = "1"
    Bundler::CLI.start
}


# friendly_errors

rescue Thor::UndefinedCommandError => e
    Bundler.ui.error e.message
    exit 15
  rescue Thor::Error => e
    Bundler.ui.error e.message
    exit 1
like image 1
fontno Avatar answered Nov 17 '22 18:11

fontno