|
- //////////////////////////////////////////////////////////////////////
- // C++ Base Application Object
- // Written by Jonathan A. Foster <jon@jfpossibilities.com
- // Started August 31st, 2020
- //
- // This is a base application object for traditional C/C++
- // applications with a "int main(int argc, char//argv)" function.
- //////////////////////////////////////////////////////////////////////
- #ifndef __IDS_CLI_H__
- #define __IDS_CLI_H__
- #include <string>
- #include <ostream>
- #include <stdexcept>
-
-
-
- //////////////////////////////////////////////////////////////////////
- // Busy indicator aka. "Live Bug"
- //
- // This makes it stupid simple to step through a string that contains
- // a single character per "frame". You can either call next() to get
- // the next char in a sequence or use << with an ostream.
- //////////////////////////////////////////////////////////////////////
-
- struct LiveBug {
- std::string seq;
- int p;
- LiveBug(): seq("|/-\\"), p(0) {}
- inline char next() { if(p>=seq.size()) p=0; return seq[p++]; }
- };
- inline std::ostream &operator<<(std::ostream &o, LiveBug &bug) {
- return o << bug.next();
- }
-
-
-
- //////////////////////////////////////////////////////////////////////
- // Base Unix Application
- //
- // Its apparent to me tha all applications should be objects. Therefor
- // running an app should consist of setting up an object from a class,
- // running something on it. And then tearing it back down. With that
- // in mind the intent of this class is to provide the boiler plate
- // needed for this model and the bridge from the typical C/C++ main().
- //
- // How to use:
- // 1. Create a sublcass of this class with your app's functionality.
- // 2. Override do_switch(), do_switch_arg() and do_arg() to process
- // arguments passed from the OS. See main() for the built-in
- // processing.
- // 3. Override main to provide functionality not triggered by args,
- // including setup, teardown or even to replace the argument
- // processing. Don't call the inherited method if you don't need
- // the argument handling.
- // 4. override crash() if you want to do something more than dump
- // the exception string to cerr.
- // 5. Tell the compiler about your class with the MAIN(class)
- // macro.
- //
- // I'm calling this a "Unix" application because its what developed
- // the current main() interface concept. This is also emulated /
- // provided on other platforms, but it started in Unix.
- //////////////////////////////////////////////////////////////////////
- // globally accessible pointer to allow polymorphism
- struct cBaseApp; extern cBaseApp *baseapp;
- struct cBaseApp {
- int command_argc; // main(argc ...)
- char **command_args; // main(... argv)
- int ExitCode; // App's code returned to OS.
-
- /// STARTUP ///
-
- //cBaseApp();
- cBaseApp():
- command_argc(0),
- command_args(0),
- ExitCode(0)
- { if(!baseapp) baseapp = this; } // Only the first gets registered
-
-
- /// Initialize ///
- //
- // This is called, prior to this->main(), with the argc & args from the C
- // main() entry point. Override this to perform any initialization here that
- // can't be done before the CLI args are known.
- //
- virtual cBaseApp &init(int argc, char **argv);
-
- /// argument handlers ///
- //
- // Override these routines to implement argument processing. These are char//
- // instead of string since this is how they come to us from the OS.
- //
-
- // how many args needed for val
- virtual unsigned do_switch(const char *arg);
- // proccess a val for switch
- virtual void do_switch_arg(const char *sw, const std::string &val) { }
- // process a non-switch arg.
- virtual void do_arg(const char *arg) { }
- // TODO: switch lookup methods ("has switch?", "what's switch val?", ...)
-
- /// new main() - loop through args ///
- //
- // This implementation runs through the arguments passed in using the usual
- // GNU semantics. It calls do_switch() and do_arg(), depending on the
- // presence of a leading "-", passing the current argument (without leading
- // dash(es)). if do_switch() returns > 0 then that many arguments are passed
- // to do_switch_val() with the corresponding switch that triggered the
- // calls. Typically do_switch() would only return 0 or 1.
- //
- // Switches are defined as arguments starting with one or two dashes. A
- // single "--" switch will terminate switch processing, sending all
- // remaining args to do_arg(). Switches with names longer than one char must
- // be preceded with two dashes ("--" long switches).
- //
- // A switch starting with a single dash is a short switch, consisting of a
- // single character. More than one character can follow a dash in which case
- // they will be treated as a series of single char switches
- // (Ex: -abc = -a -b -c). Only the last one is allowed to have an argument
- // (do_switch()>0).
- //
- // This should be overridden to provide any functionality not directly
- // triggered by switches or args and its functionality can be replaced
- // completely if you don't need arguments.
- //
-
- virtual int main();
-
- /// Provide help text for CLI arg parse errors
- //
- // This is intended to show a command line help message on the terminal
- // about what the proper CLI syntax is. The return is the desired exit
- // code. The default is 1. This implementation will provide the app meta
- // data, if present. This simplified method is used so an exception is
- // not required to call it.
-
- virtual int help();
-
- /// Catch exceptions ///
- //
- // This is called by the boiler plate main() (see bottom) when an excpetion
- // falls out. This is meant to handle std::exception descendents. You can
- // catch other odd bals in main() above and deal with then accordingly. But
- // good style dictates all exceptions should descend from std::exception.
- // The return value is the application's exit code.
- //
-
- virtual int crash(const std::exception &e);
-
- /// virtualize destructor ///
-
- virtual ~cBaseApp() { }
-
- };
-
-
-
- //////////////////////////////////////////////////////////////////////
- // An exception class to signal CLI errors
- //////////////////////////////////////////////////////////////////////
-
- struct CLIerror: public std::runtime_error {
- CLIerror(const std::string &s): runtime_error(s) {}
- };
-
-
-
- //////////////////////////////////////////////////////////////////////
- // Macro to bridge the C/C++ main() to the app class' main()
- //
- // This sets up a global static variable named "app" that contains
- // your application object. It then copies the CLI args into it and
- // calls main. An std::exception handler is setup to pass exceptions
- // to the "app" object for further handling prior to exiting the app.
- // The app's exit code is returned from app.main() or app.crash().
- //////////////////////////////////////////////////////////////////////
-
- #define MAIN(__APPCLASS__) \
- __APPCLASS__ app; \
- int main(int argc, char **args) { \
- try { \
- return app.init(argc, args).main();\
- } catch(const std::exception &e) { \
- return app.crash(e); \
- } \
- }
-
- #endif
|