Login
Artifact [881f8797c8]
Login

Artifact 881f8797c8574d65f11ec198371c327741c840c3:


/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ 
/* vim: set ts=2 et sw=2 tw=80: */
#if !defined(_NET_FOSSIL_SCM_FOSSILAPP_H_INCLUDED_)
#define _NET_FOSSIL_SCM_FOSSILAPP_H_INCLUDED_
/*
  Copyright (c) 2013 D. Richard Hipp

  This program is free software; you can redistribute it and/or
  modify it under the terms of the Simplified BSD License (also
  known as the "2-Clause License" or "FreeBSD License".)

  This program is distributed in the hope that it will be useful,
  but without any warranty; without even the implied warranty of
  merchantability or fitness for a particular purpose.

  Author contact information:
  drh@hwaci.com
  http://www.hwaci.com/drh/

  *****************************************************************************
  This file provides a basis for basic libfossil-using apps. It is intended
  to be used as the basis for test/demo apps, and not necessarily full-featured
  applications.
*/

/* Force assert() to always work... */
#if defined(NDEBUG)
#undef NDEBUG
#define DEBUG 1
#endif
#include "fossil-scm/fossil.h"
#include <assert.h> /* for the benefit of test apps */
#include <stdlib.h> /* EXIT_SUCCESS and friends */

/** @page page_fcli fcli (formerly FossilApp)

    ::fcli (formerly FossilApp) provides a small framework for
    bootstrapping simple libfossil applications which only need a
    single fsl_cx instance managing a single checkout and/or
    repository. It is primarily intended for use with CLI apps
    implementing features similar to those in fossil(1), but can also
    be used with GUI apps. It provides the following basic services to
    applications:

    - The global ::fcli struct holds global state.

    - fcli_setup() bootstraps the environment. This must be the
    first call made into the API, as this replaces the libfossil
    memory allocator with a fail-fast variant to simplify app-level
    code a bit (by removing the need to check for OOM errors). This
    also registers an atexit(3) handler to clean up fcli-owned
    resources at app shutdown.

    - Automatically tries to open a checkout (and its associated
    repository) under the current directory, but not finding one
    is not an error (the app needs to check for this if it should
    be an error: use fsl_cx_db_repo() and fsl_cx_db_checkout()).

    - fcli_flag(), fcli_next_arg(), and friends provide uniform access
    to CLI arguments.

    - A (very) basic help subsystem, triggered by the --help or -? CLI
    flags, or if the first non-flag argument is "help".  Applications
    may optionally assign fcli.appHelp to a function which outputs
    app-specific help. If help is triggered and the --global boolean
    flag is passed then framework-level help text will be output,
    otherwise a hint about the --global option will be output instead.

    - Basic error reporting mechanism. See fcli_err_report().

    The source tree contains several examples of using fcli
    in the files named f-*.c.
*/

/**
   Ouputs the given printf-type expression (2nd arg) to
   fcli_printf() if fcli.verbose>=N, else it does nothing.

   Reminder: this uses a while(0) look so that the macro can end
   with a semicolon without triggering a warning.
*/
#define FCLI_VN(N,pfexp)                        \
  if(fcli.verbose>=(N)) do {                    \
      fcli_printf("VERBOSE %d: ", (int)(N));    \
      fcli_printf pfexp;                        \
    } while(0)

/**
   Convenience form of FCLI_VN for level-2 verbosity.
*/
#define FCLI_V2(pfexp) FCLI_VN(2,pfexp)

/**
   Convenience form of FCLI_VN for level-1 verbosity. Levels 1
   and 2 are intended for application-level use.
*/
#define FCLI_V(pfexp) FCLI_VN(1,pfexp)

#if defined(__cplusplus)
extern "C" {
#endif


/**
   Describes help text for an application flag.
*/
struct fcli_help_arg_t {
  /** "Short" flag, with no leading dashes. */
  char const * flagShort;
  /** "Long" flag, with no leading dashes. */
  char const * flagLong;
  /** Brief description of value type, e.g. "int" or "string",
      or 0 if the flag is a boolean (has no value).
  */
  char const * valType;
  /**
     If not 0 and (this->brief!=0) then this is called to render the
     help text. It should simply render the text to f_out().
  */
  void (*callback)();

  /**
     Brief description text.
  */
  char const * brief;
};
typedef struct fcli_help_arg_t fcli_help_arg_t;

/**
   Structure for holding app-level --help info.
*/
struct fcli_help_t {
  /** Brief description of what the app does. */
  char const * briefDescription;
  /** Brief usage text. It will be prefixed by the
      app's name. e.g. "options file1 ... fileN".
  */
  char const * briefUsage;
  /**
     If not NULL, it must include a sentry entry at the end of the
     list with a NULL for both flagShort and flagLong pointer
     values.
  */
  fcli_help_arg_t const * flags;
  /**
     If not 0 then this is called after outputing any flags' help.
     It should output any additional help text using f_out().
  */
  void (*callback)();
};
typedef struct fcli_help_t fcli_help_t;


/**
   The fcli_t type, accessed by clients via the ::fcli
   global instance, contains various data for managing a basic
   Fossil SCM application build using libfossil.

   Usage notes:

   - All non-const pointer members are owned and managed by the
   fcli API. Clients may reference them but must be aware of
   lifetimes if they plan to hold the reference for long.

*/
struct fcli_t {
  /**
     Holds a help function callback which should output any
     app-specific help. Should be set by client applications BEFORE
     calling fcli_setup() so that the ::fcli help subsystem can
     integrate the app. fcli.appName will be set by the time this
     is called.
  */
  void (*appHelp)();
  /**
     If not NULL, it must be a pointer to fcli_help_t holding help
     info for the app.  It will be formated and output when --help
     is triggered, in place of appHelp.
  */
  fcli_help_t const * appHelp2;
  /**
     The shared fsl_cx instance. It gets initialized by
     fcli_setup() and cleaned up post-main().

     this->f is owned by this object and will be cleaned up at app
     shutdown (post-main).
  */
  fsl_cx * f;
  /**
     A verbosity level counter. Starts at 0 (no verbosity) and goes
     up for higher verbosity levels. Currently levels 1 and 2 are intended
     for app-level use and level 3 for library-level use.
  */
  int verbose;
  /**
     If not NULL then fcli_setup() will attempt to open the
     checkout for the given dir, including its associated repo
     db. By default this is "." (the current directory).

     Applications can set this to NULL _before_ calling
     fcli_setup() in order to disable the automatic attemp to
     open a checkout under the current directory.  Doing so is
     equivalent to using the --no-checkout|-C flags.
  */
  char const * checkoutDir;
  /**
     The current list of CLI arguments. This list gets modified
     by fcli_flag() and friends.
  */
  char ** argv;
  /**
     Current number of items in this->argv.
  */
  int argc;
  /**
     Application's name. Currently argv[0] but needs to be
     adjusted for Windows platforms.
  */
  char const * appName;
  /**
     True if the --dry-run flag is seen during initial arguments
     processing. ::fcli does not use this itself - it is
     intended as a convenience for applications. Those which want
     to support a short-form flag can implement that like this:

     @code
     char dryRun = fcli.fDryRun ? fcli.fDryRun :
     fcli_flag("n", NULL);
     @endcode

  */
  char fDryRun;
  /**
     Transient settings and flags. These are bits which are used
     during (or very shortly after) fcli_setup() but have no effect
     if modified after that.
  */
  struct {
    /**
       fsl_malloc()'d repo db name string made by args processing.
    */
    char * repoDbArg;
    /**
       Set to true if fcli_setup() detects -? or --help in the
       argument list, or if the first non-flag argument is "help".
    */
    char helpRequested;
    /**
       Don't use this - it will likely go away. The following text
       was at some point true but is currently a lie:

       If the --gmt flag is found, this is set to true.  That
       causes the FSL_CX_F_LOCALTIME_GMT flag to be set on this->f.
    */
    char gmtTime;
  } transient;
  /**
     Holds bits which can/should be configured by clients BEFORE
     calling fcli_setup().
  */
  struct {
    /**
       Whether or not to enable fossil's SQL tracing.
       This should start with a negative value, which helps
       fcli_process_args() process it. Setting this after
       initialization has no effect.
    */
    int traceSql;
    /**
       This output channel is used when initializing this->f. The
       default implementation uses fsl_outputer_FILE to output to
       stdout.
    */
    fsl_outputer outputer;

  } config;
};
typedef struct fcli_t fcli_t;

/** @var fcli

    This fcli_t instance is intended to act as a singleton.  It
    holds all fcli-related global state.  It gets initialized with
    default/empty state at app startup and gets fully initialized
    via fcli_setup().

    Application startup with this API typically looks like:

    @code
    // from early on in main():
    int rc;
    fcli.appHelp = fcli_local_help;
    rc = fcli_setup(argv, argc);
    if(FSL_RC_BREAK==rc) return 0; // --help|-?|help
    else if(rc) goto end;

    // Else proceed. At the end do:

    end:
    return (fcli_err_report(0)||rc) ? EXIT_FAILURE : EXIT_SUCCESS;

    @endcode

    fcli.f holds the API's fsl_cx instance. It will be non-NULL (but
    might not have an opened checkout/repository) if fsl_setup()
    succeeds.
*/
extern fcli_t fcli;

/**
   Should be called early on in main(), passed the arguments passed
   to main(). Returns 0 on success.  Sets up the ::fcli instance
   and opens a checkout in the current dir by default.

   MUST BE CALLED BEFORE fsl_malloc() and friends are used, as this
   swaps out the allocator with one which aborts on OOM.

   If argument processing finds either of the (--help, -?) flags,
   or the first non-flag argument is "help", it sets
   fcli.transient.helpRequested to a true value, calls fcli_help(),
   and returns FSL_RC_BREAK, in which case the application should
   exit/return from main with code 0 immediately.
   fcli.transient.helpRequested is set to 1 if --help or -? are
   seen, 2 if "help" is the first non-flag argument, so clients can
   (if they care to) further distinguish between the two, e.g. by
   checking for a command after "help" in the latter case and
   showing command-specific help.

   Returns 0 on success. Errors other than FSL_RC_BREAK should be
   treated as fatal to the app, and fcli.f's error state
   _might_ contain info about the error.
*/
int fcli_setup(int argc, char * const * argv );

/**
   Returns the libfossil context associated with the fcli API.
*/
fsl_cx * fcli_cx();

/**
   Calls fcli_local_help() then displays global help options
   to stdout.
*/
void fcli_help();

/**
   Works like printf() but sends its output to fsl_outputf() using
   the fcli.f fossil conext (if set) or fsl_fprintf() (to
   stdout).
*/
void fcli_printf(char const * fmt, ...)
#if 0
/* Would be nice, but complains about our custom format options: */
  __attribute__ ((__format__ (__printf__, 1, 2)))
#endif
  ;

/**
   f_out() is a shorthand for fcli_printf().
*/
#define f_out fcli_printf  

/**
   Returns the verbosity level set via CLI args. 0 is no verbosity.
*/
int fcli_is_verbose();

/**
   Searches fcli.argv for the given flag (pass it without leading
   dashes). If found, this function returns true, else it returns
   false. If value is not NULL then the flag, if found, is assumed to
   have a value, otherwise the flag is assumed to be a boolean. A flag
   with a value may take either one of these forms:

   -flag=value
   -flag value

   *value gets assigned to a COPY OF value part of the first form or a
   COPY OF the subsequent argument for the second form (copies are
   required in order to avoid trickier memory management here). On
   success it removes the flag (and its value, if any) from
   fcli.argv.  Thus by removing all flags early on, the CLI
   arguments are left only with non-flag arguments to sift through.

   If it returns a string, the caller must eventually free it with
   fsl_free().

   Flags may start with either one or two dashes - they are
   equivalent.
*/
char fcli_flag(char const * opt, char ** value);

/**
   Works like fcli_flag() but tries two argument
   forms, in order. It is intended to be passed short and long
   forms, but can be passed two aliases or similar.

   @code
   char * v = NULL;
   fcli_flag2("n", "limit", &v);
   if(v) { ...; fsl_free(v); }
   @endcode
*/
char fcli_flag2(char const * opt1, char const * opt2,
                char ** value);

/**
   Works similarly to fcli_flag2(), but if no flag is found and
   value is not NULL then *value is assigned to the return value of
   fcli_next_arg(1). In that case:

   - The return value will specify whether or not fcli_next_arg()
   returned a value or not.

   - If it returns true then *value is owned by the caller and it
   must eventually be freed using fsl_free().

   - If it returns false, *value is not modified.

   The opt2 parameter may be NULL, but op1 may not.
*/
char fcli_flag_or_arg(char const * opt1, char const * opt2,
                      char ** value);


/**
   Clears any error state in fcli.f.
*/
void fcli_err_reset();

/**
   Sets fcli.f's error state, analog to fsl_cx_err_set().
   Returns the code argument on success, some other non-0 value on
   a more serious error (e.g. FSL_RC_OOM when formatting the
   string).
*/
int fcli_err_set(int code, char const * fmt, ...);

/**
   Returns the internally-used fsl_error instance which is used for
   propagating errors.  The object is owned by ::fcli and MUST NOT
   be freed or otherwise abused by clients. It may, however, be
   passed to routines which take a fsl_error parameter to report
   errors (e.g. fsl_deck_output().

   Returns NULL if fcli_setup() has not yet been called or after
   fcli has been cleaned up (post-main()).

*/
fsl_error * fcli_error();


/**
   If ::fcli has any error state, this outputs it and returns the
   error code, else returns 0. If clear is true the error state is
   cleared/reset, otherwise it is left in place. Returns 0 if
   ::fcli has not been initialized. The 2nd and 3rd arguments are
   assumed to be the __FILE__ and __LINE__ macro values of the call
   point. See fcli_err_report() for a convenience form of this
   function.

   The format of the output depends partially on fcli_is_verbose(). In
   verbose mode, the file/line info is included, otherwise it is
   elided.

   @see fcli_err_report()
*/
int fcli_err_report2(char clear, char const * file, int line);

/**
   Convenience macro for using fcli_err_report2().
*/
#define fcli_err_report(CLEAR) fcli_err_report2((CLEAR), __FILE__, __LINE__)

/**
   Peeks at or takes the next argument from the CLI args.
   If take is true, it is removed from the args list and transfered
   to the caller (who must fsl_free() it), else ownership is not
   modified.
*/
char * fcli_next_arg(char take);

/**
   If fcli.argv contains what looks like any flag arguments,
   this updates the fossil error state and returns true, else
   returns false. If outputError is true and an unused flag is
   found then the error state is immediately output (but not
   cleared).
*/
char fcli_has_unused_flags(char outputError);

/**
   Typedef for general-purpose fcli call-by-name commands.

   @see fcli_dispatch_commands()
*/
typedef int (*FossilCommand_f)();

/**
   Describes a named callback command.

   @see fcli_dispatch_commands()
*/
struct FossilCommand {
  /** The name of the command. */
  char const * name;
  /** The callback for this command. */
  FossilCommand_f f;
};
typedef struct FossilCommand FossilCommand;

/**
   Expects an array of FossilCommands which contain a trailing
   sentry entry with a NULL name and callback. It searches the list
   for a command matching fcli_next_arg(). If found, it
   removes that argument from the list, calls the callback, and
   returns its result. If no command is found FSL_RC_NOT_FOUND is
   returned, the argument list is not modified, and the error state
   is updated with a description of the problem and a list of all
   command names in cmdList.

   If reportErrors is true then on error this function outputs
   the error result but it keeps the error state in place
   for the downstream use.
*/
int fcli_dispatch_commands( FossilCommand const * cmdList,
                            char reportErrors);

/**
   A minor helper function intended to be passed the pending result
   code of the main() routine. This function outputs any pending
   error state in fcli. Returns one of EXIT_SUCCESS if mainRc is 0
   and fcli had no pending error report, otherwise it returns
   EXIT_FAILURE. This function does not clean up fcli - that is
   handled via an atexit() handler.

   It is intended to be called once at the very end of main:

   @code
   int main(){
   int rc;

   ...set up fcli...assign rc...

   return fcli_end_of_main(rc);
   }
   @endcode

   @see fcli_error()
   @see fcli_err_set()
   @see fcli_err_report()
   @see fcli_err_reset()
*/
int fcli_end_of_main(int mainRc);

#if defined(__cplusplus)
} /*extern "C"*/
#endif


#endif
/* _NET_FOSSIL_SCM_FOSSILAPP_H_INCLUDED_ */