Login
Documentation
Login
/* -*- 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.
 */
#define VERBOSEN(N,pfexp)                                             \
  if(fcli.verbose>=(N)) do {                    \
    fcli_printf("VERBOSE %d: ", (N));                              \
    fcli_printf pfexp;                                                   \
  } while(0)

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

/**
 ** Convenience form of VERBOSEN for level-1 verbosity. Level 1
 ** is intended for application-level use.
 */
#define VERBOSE(pfexp) VERBOSEN(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 and fcli.userName 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;
    /**
     ** fsl_malloc()'d repo db name string.
     */
    char * repoDb;
    /**
     ** 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;
    /**
     ** The default user name, as provided by the --user|-U flags or
     ** (if one of those is not provided) fsl_guess_user_name().
     */
    char * userName;
    /**
     ** 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 {
      /**
       ** 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;
      /**
       ** Set to true if fcli_setup() detects -? or --help in the
       ** argument list, or if the first non-flag argument is "help".
       */
      char helpRequested;
    } transient;
  };
  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.
   */
  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 );

  /**
   ** 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, ...);

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

  /**
   ** 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_clear();

  /**
   ** 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 which formatting the
   ** string).
   */
  int fcli_err_set(int code, char const * fmt, ...);

  
  /**
   ** If ::fcli has any error state, it outputs its and returns the
   ** error code, else returns 0. If clear is true the error state is
   ** cleared. 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. Use fcli_err_report()
   ** to get this.
   **
   ** The format of the output depends partially on fcli.verbose. In
   ** verbose mode, the file/line info is included, otherwise it is
   ** elided.
   */
  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.
   */
  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);

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


#endif
/* _NET_FOSSIL_SCM_FOSSILAPP_H_INCLUDED_ */