I wanted to build an awesome place for people to discuss module specific issues, but I don't have any more time for this, and there are much better places to discuss Perl-related issues. I'd recommend asking your question on Stack Overflow or on Perl Monks.
If you are looking for a Perl tutorial or Perl-related news, I hope these links will serve you well.
Posted on 2010-05-20 19:53:16.760194-07 by chengwei
Can I return error (exit) code to system after a command is run?
Hi,

First of all, I want to express my appreciation to this framework. I've been using this framework for a while and I really like this framework because just like the Overview says, "It assumes the responsibility of implementing details that are common to all command-line applications, making it possible for new applications adhering to well-defined conventions". Other than that, adding new commands to this framework is so easy, I just have to fill in some "holes" in the framework (e.g. usage text, option spec and validation, and run() subroutine) and I can focus on the real business logic of the command in the run() and forget about many tedious things. Moreover, with the help of Inline-C and Inline-CPP, I can even write the main business logic of commands in C/C++, call it from the run() subroutine, and link it with my legacy C/C++ production code. This way, I don't have to be a Perl writer to use this Peral CLI framework. I can use this framework for non-Perl languages. That's cool!

As of this writing, the only thing I don't know how to do (and I'm not sure whether it can be done) is how to return error (exit) code to system after a command run is finished. Right now, the run() subroutine in the samples seems to always return output messages for display. Is there any way that, in addition to the output messages, I can also return error (exit) code to the system? I need this capability for scenarios such as test automation - by checking the command exit code my test program can determine its flow accordingly.

Thank you very much!

Chengwei
Direct Responses: 12716 | Write a response
Posted on 2010-05-23 20:00:02.505281-07 by kerisman in response to 12710
Re: Can I return error (exit) code to system after a command is run?
I'm very glad CLIF is helping you to succeed. Thanks for expressing your appreciation! I like your idea to call out to non-Perl code within your commands. One could imagine building a custom menu-driven app based on a collection of independent tools.

CLIF is designed to handle error conditions using exceptions. If an error condition occurs during a command's execution, the command should throw an exception (even if only a die). CLIF displays error messages based on that exception.

I recommend using exception handling over error codes -- it will lead to much cleaner and more reliable code. If your tests need to take different actions based on what kind of error occurred, you can use Exception::Class to define an exception object hierarchy. Your commands can throw the proper exception based on what happens. Your test code can check the type of the exception (check out Exception::Class::TryCatch for a nice interface for catching exceptions) and act accordingly.

In order to make this work properly, you should do the following:

1. Create an exception hierarchy in a package such as "My::Custom::Exceptions" and make sure it uses CLI::Framework::Exceptions. Define new exceptions based on your error conditions. These are the exceptions that may be raised by your running commands. Each of these custom exceptions should inherit from CLI::Framework::Exception::CmdRunException.
2. In the run() method for your commands, instead of trying to return error codes, throw exceptions of the types you've defined.
3. In your application, define handle_exception to rethrow the exception (i.e. just die) instead of rendering it. For your test code, you might force that to happen with some symbol table manipulation (should be acceptable for a test script). Of course, you generally might want to handle exceptions more gracefully.
4. In your calling code (test code in your case), catch the exceptions and take the appropriate actions.
As I typed this, I realized that the need to have custom command exceptions inherit from CLI::Framework::Exception::CmdRunException is not documented. I'm adding that to my list for the next release.

And now, to switch from abstract mode to concrete mode, here's some demo code:

This is the package defining your custom exceptions:
package My::Custom::Exceptions; # import the CLIF exception class definitions... use CLI::Framework::Exceptions; use Exporter qw( import ); our @EXPORT_OK = qw( throw_moon_phase_exception ); our %EXPORT_TAGS = ( all => \@EXPORT_OK ); use Exception::Class ( 'My::Moon::Phase::Exception' => { description => 'demo exception', alias => 'throw_moon_phase_exception', isa => 'CLI::Framework::Exception::CmdRunException' } ); 1;
...and the following (which can be in a single file, e.g. "clif-custom-exception-test.pl") are your CLIF Application/Command packages:
use Exception::Class::TryCatch; my $app = My::App->new(); eval { $app->run() }; if( catch my $e ) { if( $e->isa('My::Moon::Phase::Exception') ) { my $type = ref $e; print "caught exception of type '$type'; error: $e\n"; } #else if( $e->isa('Some::Other::Exception::Type') { # do something else... #} } #------- package My::App; use base qw(CLI::Framework); sub command_map { { cmd => 'My::Command::Cmd', } } sub init { print "Initializing...\n"; } sub handle_exception { my ($app, $e) = @_; die $e; } #------- package My::Command::Cmd; use base qw( CLI::Framework::Command ); use Exception::Class::TryCatch; use My::Custom::Exceptions qw( :all ); sub run { my ($self, $opts, @args) = @_; throw_moon_phase_exception(error => "wrong phase of moon"); }
Running "clif-custom-exception-test.pl cmd" will show that the custom exception is caught.
Direct Responses: 12717 | 12726 | Write a response
Posted on 2010-05-23 20:08:34.289138-07 by kerisman in response to 12716
Re: Can I return error (exit) code to system after a command is run?
If you really need exit codes, another option would be to write a command superclass (all your commands would inherit from it) that might have methods such as get_last_err_code, clear_err_code, set_err_code. The commands would set the error code inside run(). Other external entities (such as your test scripts) could access the error code (by first accessing the command object, which CLIF allows via get_current_command, registered_command_object, etc.) and take actions based on its value.

...but, as I explained, I recommend using proper exception handling instead.
Direct Responses: Write a response
Posted on 2010-05-26 03:46:05.850706-07 by chengwei in response to 12716
Re: Can I return error (exit) code to system after a command is run?
Thank you very much for the detailed explanation and sample code. Your information is very helpful! And yes, I agree with you that using exception is a better and more structured way of handling error conditions than returning error code.

However, in my test-automation scenario, the test program is a program using CLIF scripts to control and test my production program. That is, the test program could be a separate program from the CLIF scripts, and it could even be written in C and not Perl. (In that case, the test program will invoke the CLIF scripts by using "system()" C function, or "CreateProcess()" Win32 API.) Under those circumstances, I'm afraid that the test program won't be able to catch the exceptions the CLIF scripts throw - that is the reason I need the CLIF scripts to return its exit code to the system, so I can get that exit code from the system in my test program.

Having said that, your suggestion still inspired me a lot, and I still agree with you in using exception handling in the CLIF scripts. What I can think of a solution for my scenario, is to follow your demo code to throw and catch exceptions within a CLIF script. But when the CLIF script is about to exit, I will return proper exit code to the system according to the exception catched, like the following:

use Exception::Class::TryCatch; my $app = My::App->new(); eval { $app->run() }; if( catch my $e ) { if( $e->isa('My::Moon::Phase::Exception') ) { my $type = ref $e; print "caught exception of type '$type'; error: $e\n"; exit 1; # non-0 means error } #else if( $e->isa('Some::Other::Exception::Type') { # do something else... # exit 2; # non-0 means error #} } exit 0; # 0 means success

Not sure if this is a good solution, though? Since I'm new to Perl.
It would be nice if I can have your advices.

Thank you very much!

Chengwei
Direct Responses: 12727 | Write a response
Posted on 2010-05-26 08:19:28.613866-07 by kerisman in response to 12726
Re: Can I return error (exit) code to system after a command is run?
That solution seems reasonable for what you're trying to do, although it can be simplified. CLI::Framework::Application uses the handle_exception method as a simple way for you to define an error-handling policy. Exceptions are passed to that method, where they can be handled however you wish. I think your needs will be met by overriding handle_exception to exit with the proper code based on the type of exception.

Note that, with either of these approaches, the exit() will cause interactive mode to exit abruptly during error conditions. That may be fine in your case. However, to avoid having that happen, your implementation of handle_exception() could check whether the application is in interactive mode using get_interactivity_mode and exit only when it is not interactive.
Direct Responses: 12728 | Write a response
Posted on 2010-05-26 09:44:15.201194-07 by chengwei in response to 12727
Re: Can I return error (exit) code to system after a command is run?
Thanks a lot for the advices! Both of them are valuable to me.
I think I have gotten the solution I wanted.

Thank you very much,
Chengwei
Direct Responses: Write a response