/* -*- 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_ */