Login
Artifact [1e147925ab]
Login

Artifact 1e147925abff2ab5bef45900b95bda1feab0eb1f:


/* -*- 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. It is primarily
    intended for use with CLI apps implementing features similar to
    those in fossil(1).  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 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.

    - 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

  /**
      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)();
    /**
        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.
       */
      char 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_ */