Implemented ticket #1098: CLI framework
* pjlib-util: * implement CLI and CLI console front-end * build: * added CLI framework support on VS2005 and Makefile git-svn-id: https://svn.pjsip.org/repos/pjproject/branches/projects/cli@3211 74dad513-b988-da41-8d7b-12977e46ad98
This commit is contained in:
parent
f414b9411c
commit
b94730283e
|
@ -27,9 +27,10 @@ export _LDFLAGS := $(subst /,$(HOST_PSEP),$(PJLIB_UTIL_LIB)) \
|
|||
#
|
||||
export PJLIB_UTIL_SRCDIR = ../src/pjlib-util
|
||||
export PJLIB_UTIL_OBJS += $(OS_OBJS) $(M_OBJS) $(CC_OBJS) $(HOST_OBJS) \
|
||||
base64.o crc32.o errno.o dns.o dns_dump.o dns_server.o \
|
||||
getopt.o hmac_md5.o hmac_sha1.o http_client.o md5.o pcap.o resolver.o \
|
||||
scanner.o sha1.o srv_resolver.o string.o stun_simple.o \
|
||||
base64.o cli.o cli_console.o crc32.o errno.o dns.o \
|
||||
dns_dump.o dns_server.o getopt.o hmac_md5.o hmac_sha1.o \
|
||||
http_client.o md5.o pcap.o resolver.o scanner.o sha1.o \
|
||||
srv_resolver.o string.o stun_simple.o \
|
||||
stun_simple_client.o xml.o
|
||||
export PJLIB_UTIL_CFLAGS += $(_CFLAGS)
|
||||
|
||||
|
|
|
@ -2768,6 +2768,14 @@
|
|||
RelativePath="..\src\pjlib-util\base64.c"
|
||||
>
|
||||
</File>
|
||||
<File
|
||||
RelativePath="..\src\pjlib-util\cli.c"
|
||||
>
|
||||
</File>
|
||||
<File
|
||||
RelativePath="..\src\pjlib-util\cli_console.c"
|
||||
>
|
||||
</File>
|
||||
<File
|
||||
RelativePath="..\src\pjlib-util\crc32.c"
|
||||
>
|
||||
|
@ -4713,6 +4721,18 @@
|
|||
RelativePath="..\include\pjlib-util\base64.h"
|
||||
>
|
||||
</File>
|
||||
<File
|
||||
RelativePath="..\include\pjlib-util\cli.h"
|
||||
>
|
||||
</File>
|
||||
<File
|
||||
RelativePath="..\include\pjlib-util\cli_console.h"
|
||||
>
|
||||
</File>
|
||||
<File
|
||||
RelativePath="..\include\pjlib-util\cli_imp.h"
|
||||
>
|
||||
</File>
|
||||
<File
|
||||
RelativePath="..\include\pjlib-util\config.h"
|
||||
>
|
||||
|
|
|
@ -0,0 +1,577 @@
|
|||
/* $Id$ */
|
||||
/*
|
||||
* Copyright (C) 2010 Teluu Inc. (http://www.teluu.com)
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 2 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* 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. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; if not, write to the Free Software
|
||||
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
*/
|
||||
#ifndef __PJLIB_UTIL_CLI_H__
|
||||
#define __PJLIB_UTIL_CLI_H__
|
||||
|
||||
/**
|
||||
* @file cli.h
|
||||
* @brief Command Line Interface
|
||||
*/
|
||||
|
||||
#include <pjlib-util/types.h>
|
||||
#include <pj/list.h>
|
||||
|
||||
|
||||
PJ_BEGIN_DECL
|
||||
|
||||
/**
|
||||
* @defgroup PJLIB_UTIL_CLI Command Line Interface Framework
|
||||
* @{
|
||||
*
|
||||
*/
|
||||
|
||||
/**
|
||||
* Maximum length of command buffer.
|
||||
*/
|
||||
#ifndef PJ_CLI_MAX_CMDBUF
|
||||
# define PJ_CLI_MAX_CMDBUF 120
|
||||
#endif
|
||||
|
||||
/**
|
||||
* Maximum command arguments.
|
||||
*/
|
||||
#ifndef PJ_CLI_MAX_ARGS
|
||||
# define PJ_CLI_MAX_ARGS 8
|
||||
#endif
|
||||
|
||||
/**
|
||||
* Maximum short name version (shortcuts) for a command.
|
||||
*/
|
||||
#ifndef PJ_CLI_MAX_SHORTCUTS
|
||||
# define PJ_CLI_MAX_SHORTCUTS 4
|
||||
#endif
|
||||
|
||||
/*
|
||||
* New error constants (note: to be placed in errno.h with new values)
|
||||
*/
|
||||
/**
|
||||
* @hideinitializer
|
||||
* End the current session. This is a special error code returned by
|
||||
* pj_cli_exec() to indicate that "exit" or equivalent command has been
|
||||
* called to end the current session.
|
||||
*/
|
||||
#define PJ_CLI_EEXIT -101
|
||||
/**
|
||||
* @hideinitializer
|
||||
* A required CLI argument is not specified.
|
||||
*/
|
||||
#define PJ_CLI_EMISSINGARG -104
|
||||
/**
|
||||
* @hideinitializer
|
||||
* Too many CLI arguments.
|
||||
*/
|
||||
#define PJ_CLI_ETOOMANYARGS -105
|
||||
/**
|
||||
* @hideinitializer
|
||||
* Invalid CLI argument. Typically this is caused by extra characters
|
||||
* specified in the command line which does not match any arguments.
|
||||
*/
|
||||
#define PJ_CLI_EINVARG -106
|
||||
/**
|
||||
* @hideinitializer
|
||||
* CLI command with the specified name already exist.
|
||||
*/
|
||||
#define PJ_CLI_EBADNAME -107
|
||||
|
||||
/**
|
||||
* This opaque structure represents a CLI application. A CLI application is
|
||||
* the root placeholder of other CLI objects. In an application, one (and
|
||||
* normally only one) CLI application instance needs to be created.
|
||||
*/
|
||||
typedef struct pj_cli_t pj_cli_t;
|
||||
|
||||
/**
|
||||
* Type of command id.
|
||||
*/
|
||||
typedef int pj_cli_cmd_id;
|
||||
|
||||
/**
|
||||
* Reserved command id constants.
|
||||
*/
|
||||
typedef enum pj_cli_std_cmd_id
|
||||
{
|
||||
/**
|
||||
* Constant to indicate an invalid command id.
|
||||
*/
|
||||
PJ_CLI_INVALID_CMD_ID = -1,
|
||||
|
||||
/**
|
||||
* A special command id to indicate that a command id denotes
|
||||
* a command group.
|
||||
*/
|
||||
PJ_CLI_CMD_ID_GROUP = -2
|
||||
|
||||
} pj_cli_std_cmd_id;
|
||||
|
||||
|
||||
/**
|
||||
* This describes the parameters to be specified when creating a CLI
|
||||
* application with pj_cli_create(). Application MUST initialize this
|
||||
* structure by calling pj_cli_cfg_default() before using it.
|
||||
*/
|
||||
typedef struct pj_cli_cfg
|
||||
{
|
||||
/**
|
||||
* The application name, which will be used in places such as logs.
|
||||
* This field is mandatory.
|
||||
*/
|
||||
pj_str_t name;
|
||||
|
||||
/**
|
||||
* Optional application title, which will be used in places such as
|
||||
* window title. If not specified, the application name will be used
|
||||
* as the title.
|
||||
*/
|
||||
pj_str_t title;
|
||||
|
||||
/**
|
||||
* The pool factory where all memory allocations will be taken from.
|
||||
* This field is mandatory.
|
||||
*/
|
||||
pj_pool_factory *pf;
|
||||
|
||||
/**
|
||||
* Specify whether only exact matching command will be executed. If
|
||||
* PJ_FALSE, the framework will accept any unique abbreviations of
|
||||
* the command. Please see the description of pj_cli_parse() function
|
||||
* for more info.
|
||||
*
|
||||
* Default: PJ_FALSE
|
||||
*/
|
||||
pj_bool_t exact_cmd;
|
||||
|
||||
} pj_cli_cfg;
|
||||
|
||||
|
||||
/**
|
||||
* This describes the type of an argument (pj_cli_arg_spec).
|
||||
*/
|
||||
typedef enum pj_cli_arg_type
|
||||
{
|
||||
/**
|
||||
* Unformatted string.
|
||||
*/
|
||||
PJ_CLI_ARG_TEXT,
|
||||
|
||||
/**
|
||||
* An integral number.
|
||||
*/
|
||||
PJ_CLI_ARG_INT
|
||||
|
||||
} pj_cli_arg_type;
|
||||
|
||||
/**
|
||||
* This structure describe the specification of a command argument.
|
||||
*/
|
||||
typedef struct pj_cli_arg_spec
|
||||
{
|
||||
/**
|
||||
* Argument name.
|
||||
*/
|
||||
pj_str_t name;
|
||||
|
||||
/**
|
||||
* Helpful description of the argument. This text will be used when
|
||||
* displaying help texts for the command/argument.
|
||||
*/
|
||||
pj_str_t desc;
|
||||
|
||||
/**
|
||||
* Argument type, which will be used for rendering the argument and
|
||||
* to perform basic validation against an input value.
|
||||
*/
|
||||
pj_cli_arg_type type;
|
||||
|
||||
} pj_cli_arg_spec;
|
||||
|
||||
|
||||
/**
|
||||
* Forward declaration of pj_cli_cmd_spec structure.
|
||||
*/
|
||||
typedef struct pj_cli_cmd_spec pj_cli_cmd_spec;
|
||||
|
||||
/**
|
||||
* Forward declaration for pj_cli_sess, which will be declared in cli_imp.h.
|
||||
*/
|
||||
typedef struct pj_cli_sess pj_cli_sess;
|
||||
|
||||
/**
|
||||
* Forward declaration for CLI front-end.
|
||||
*/
|
||||
typedef struct pj_cli_front_end pj_cli_front_end;
|
||||
|
||||
/**
|
||||
* This structure contains the command to be executed by command handler.
|
||||
*/
|
||||
typedef struct pj_cli_cmd_val
|
||||
{
|
||||
/** The session on which the command was executed on. */
|
||||
pj_cli_sess *sess;
|
||||
|
||||
/** The command specification being executed. */
|
||||
const pj_cli_cmd_spec *cmd;
|
||||
|
||||
/** Number of argvs. */
|
||||
int argc;
|
||||
|
||||
/** Array of args, with argv[0] specifies the name of the cmd. */
|
||||
pj_str_t argv[PJ_CLI_MAX_ARGS];
|
||||
|
||||
} pj_cli_cmd_val;
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* This specifies the callback type for command handlers, which will be
|
||||
* executed when the specified command is invoked.
|
||||
*
|
||||
* @param sess The CLI session where the command is invoked.
|
||||
* @param cmd_val The command that is specified by the user.
|
||||
*
|
||||
* @return Return the status of the command execution.
|
||||
*/
|
||||
typedef pj_status_t (*pj_cli_cmd_handler)(pj_cli_cmd_val *cval);
|
||||
|
||||
/**
|
||||
* This structure describes the full specification of a CLI command. A CLI
|
||||
* command mainly consists of the name of the command, zero or more arguments,
|
||||
* and a callback function to be called to execute the command.
|
||||
*
|
||||
* Application can create this specification by forming an XML document and
|
||||
* calling pj_cli_create_cmd_from_xml() to instantiate the spec. A sample XML
|
||||
* document containing a command spec is as follows:
|
||||
*
|
||||
\verbatim
|
||||
<CMD name='makecall' id='101' sc='m,mc' desc='Make outgoing call'>
|
||||
<ARGS>
|
||||
<ARG name='target' type='text' desc='The destination'/>
|
||||
</ARGS>
|
||||
</CMD>
|
||||
\endverbatim
|
||||
|
||||
*/
|
||||
struct pj_cli_cmd_spec
|
||||
{
|
||||
/**
|
||||
* To make list of child cmds.
|
||||
*/
|
||||
PJ_DECL_LIST_MEMBER(struct pj_cli_cmd_spec);
|
||||
|
||||
/**
|
||||
* Command ID assigned to this command by the application during command
|
||||
* creation. If this value is PJ_CLI_CMD_ID_GROUP (-2), then this is
|
||||
* a command group and it can't be executed.
|
||||
*/
|
||||
pj_cli_cmd_id id;
|
||||
|
||||
/**
|
||||
* The command name.
|
||||
*/
|
||||
pj_str_t name;
|
||||
|
||||
/**
|
||||
* The full description of the command.
|
||||
*/
|
||||
pj_str_t desc;
|
||||
|
||||
/**
|
||||
* Number of optional shortcuts
|
||||
*/
|
||||
unsigned sc_cnt;
|
||||
|
||||
/**
|
||||
* Optional array of shortcuts, if any. Shortcut is a short name version
|
||||
* of the command. If the command doesn't have any shortcuts, this
|
||||
* will be initialized to NULL.
|
||||
*/
|
||||
pj_str_t *sc;
|
||||
|
||||
/**
|
||||
* The command handler, to be executed when a command matching this command
|
||||
* specification is invoked by the end user. The value may be NULL if this
|
||||
* is a command group.
|
||||
*/
|
||||
pj_cli_cmd_handler handler;
|
||||
|
||||
/**
|
||||
* Number of arguments.
|
||||
*/
|
||||
unsigned arg_cnt;
|
||||
|
||||
/**
|
||||
* Array of arguments.
|
||||
*/
|
||||
pj_cli_arg_spec *arg;
|
||||
|
||||
/**
|
||||
* Child commands, if any. A command will only have subcommands if it is
|
||||
* a group. If the command doesn't have subcommands, this field will be
|
||||
* initialized with NULL.
|
||||
*/
|
||||
pj_cli_cmd_spec *sub_cmd;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* This contains extra parameters to be specified when calling pj_cli_exec().
|
||||
* Upon return from the function, various other fields in this structure will
|
||||
* be set by the function.
|
||||
*
|
||||
* Application must call pj_cli_exec_info_default() to initialize this
|
||||
* structure with its default values.
|
||||
*/
|
||||
typedef struct pj_cli_exec_info
|
||||
{
|
||||
/**
|
||||
* If command parsing failed, on return this will point to the location
|
||||
* where the failure occurs, otherwise the value will be set to -1.
|
||||
*/
|
||||
int err_pos;
|
||||
|
||||
/**
|
||||
* If a command matching the command in the cmdline was found, on return
|
||||
* this will be set to the command id of the command, otherwise it will be
|
||||
* set to PJ_CLI_INVALID_CMD_ID.
|
||||
*/
|
||||
pj_cli_cmd_id cmd_id;
|
||||
|
||||
/**
|
||||
* If a command was executed, on return this will be set to the return
|
||||
* value of the command, otherwise it will contain PJ_SUCCESS.
|
||||
*/
|
||||
pj_status_t cmd_ret;
|
||||
|
||||
/**
|
||||
* If pj_cli_exec() fails because an argument is missing (the function
|
||||
* returned PJ_CLI_EMISSINGARG error), this field will be set to the
|
||||
* index of the missing argument. This is useful to give more helpful
|
||||
* error info to the end user, or to facilitate a more interactive
|
||||
* input display.
|
||||
*/
|
||||
int arg_idx;
|
||||
|
||||
} pj_cli_exec_info;
|
||||
|
||||
|
||||
/**
|
||||
* Initialize a pj_cli_cfg with its default values.
|
||||
*
|
||||
* @param param The instance to be initialized.
|
||||
*/
|
||||
PJ_DECL(void) pj_cli_cfg_default(pj_cli_cfg *param);
|
||||
|
||||
/**
|
||||
* Initialize pj_cli_exec_info with its default values.
|
||||
*
|
||||
* @param param The param to be initialized.
|
||||
*/
|
||||
PJ_DECL(void) pj_cli_exec_info_default(pj_cli_exec_info *param);
|
||||
|
||||
/**
|
||||
* Create a new CLI application instance.
|
||||
*
|
||||
* @param cfg CLI application creation parameters.
|
||||
* @param p_cli Pointer to receive the returned instance.
|
||||
*
|
||||
* @return PJ_SUCCESS on success, or the appropriate error code.
|
||||
*/
|
||||
PJ_DECL(pj_status_t) pj_cli_create(pj_cli_cfg *cfg,
|
||||
pj_cli_t **p_cli);
|
||||
|
||||
/**
|
||||
* Get the internal parameter of the CLI instance.
|
||||
*
|
||||
* @param cli The CLI application instance.
|
||||
*
|
||||
* @return CLI parameter instance.
|
||||
*/
|
||||
PJ_DECL(pj_cli_cfg*) pj_cli_get_param(pj_cli_t *cli);
|
||||
|
||||
|
||||
/**
|
||||
* Call this to signal application shutdown. Typically application would
|
||||
* call this from it's "Quit" menu or similar command to quit the
|
||||
* application.
|
||||
*
|
||||
* See also pj_cli_end_session() to end a session instead of quitting the
|
||||
* whole application.
|
||||
*
|
||||
* @param cli The CLI application instance.
|
||||
* @param req The session on which the shutdown request is
|
||||
* received.
|
||||
* @param restart Indicate whether application restart is wanted.
|
||||
*/
|
||||
PJ_DECL(void) pj_cli_quit(pj_cli_t *cli, pj_cli_sess *req,
|
||||
pj_bool_t restart);
|
||||
|
||||
/**
|
||||
* Check if application shutdown or restart has been requested.
|
||||
*
|
||||
* @param cli The CLI application instance.
|
||||
*
|
||||
* @return PJ_TRUE if pj_cli_quit() has been called.
|
||||
*/
|
||||
PJ_DECL(pj_bool_t) pj_cli_is_quitting(pj_cli_t *cli);
|
||||
|
||||
|
||||
/**
|
||||
* Check if application restart has been requested.
|
||||
*
|
||||
* @param cli The CLI application instance.
|
||||
*
|
||||
* @return PJ_TRUE if pj_cli_quit() has been called with
|
||||
* restart parameter set.
|
||||
*/
|
||||
PJ_DECL(pj_bool_t) pj_cli_is_restarting(pj_cli_t *cli);
|
||||
|
||||
|
||||
/**
|
||||
* Destroy a CLI application instance. This would also close all sessions
|
||||
* currently running for this CLI application.
|
||||
*
|
||||
* @param cli The CLI application.
|
||||
*/
|
||||
PJ_DECL(void) pj_cli_destroy(pj_cli_t *cli);
|
||||
|
||||
|
||||
/**
|
||||
* End the specified session, and destroy it to release all resources used
|
||||
* by the session.
|
||||
*
|
||||
* See also pj_cli_quit() to quit the whole application instead.
|
||||
*
|
||||
* @param sess The CLI session to be destroyed.
|
||||
*/
|
||||
PJ_DECL(void) pj_cli_end_session(pj_cli_sess *sess);
|
||||
|
||||
/**
|
||||
* Register a front end to the CLI application.
|
||||
*
|
||||
* @param CLI The CLI application.
|
||||
* @param fe The CLI front end to be registered.
|
||||
*/
|
||||
PJ_DECL(void) pj_cli_register_front_end(pj_cli_t *cli,
|
||||
pj_cli_front_end *fe);
|
||||
|
||||
/**
|
||||
* Create a new complete command specification from an XML node text and
|
||||
* register it to the CLI application.
|
||||
*
|
||||
* @param cli The CLI application.
|
||||
* @param group Optional group to which this command will be added
|
||||
* to, or specify NULL if this command is a root
|
||||
* command.
|
||||
* @param xml Input string containing XML node text for the
|
||||
* command.
|
||||
* @param handler Function handler for the command. This must be NULL
|
||||
* if the command specifies a command group.
|
||||
* @param p_cmd Optional pointer to store the newly created
|
||||
* specification.
|
||||
*
|
||||
* @return PJ_SUCCESS on success, or the appropriate error code.
|
||||
*/
|
||||
PJ_DECL(pj_status_t) pj_cli_add_cmd_from_xml(pj_cli_t *cli,
|
||||
pj_cli_cmd_spec *group,
|
||||
const pj_str_t *xml,
|
||||
pj_cli_cmd_handler handler,
|
||||
pj_cli_cmd_spec *p_cmd);
|
||||
|
||||
/**
|
||||
* Parse an input cmdline string. The first word of the command line is the
|
||||
* command itself, which will be matched against command specifications
|
||||
* registered in the CLI application.
|
||||
*
|
||||
* By default, a command may be matched by any shorter abbreviations of the
|
||||
* command that uniquely identify the command. For example, suppose two
|
||||
* commands "help" and "hold" are currently the only commands registered in
|
||||
* the CLI application. In this case, specifying "he" and "hel" would also
|
||||
* match "help" command, and similarly "ho" and "hol" would also match "hold"
|
||||
* command, but specifying "h" only would yield an error as it would match
|
||||
* more than one commands. This matching behavior can be turned off by
|
||||
* setting \a pj_cli_cfg.exact_cmd to PJ_TRUE.
|
||||
*
|
||||
* Zero or more arguments follow the command name. Arguments are separated by
|
||||
* one or more whitespaces. Argument may be placed inside a pair of quotes,
|
||||
* double quotes, '{' and '}', or '[' and ']' pairs. This is useful when the
|
||||
* argument itself contains whitespaces or other restricted characters. If
|
||||
* the quote character itself is to appear in the argument, the argument then
|
||||
* must be quoted with different quote characters. There is no character
|
||||
* escaping facility provided by this function (such as the use of backslash
|
||||
* '\' character).
|
||||
*
|
||||
* The cmdline may be followed by an extra newline (LF or CR-LF characters),
|
||||
* which simply will be ignored. However any more characters following this
|
||||
* newline will cause an error to be returned.
|
||||
*
|
||||
* @param sess The CLI session.
|
||||
* @param cmdline The command line string to be parsed.
|
||||
* @param val Structure to store the parsing result.
|
||||
* @param info Additional info to be returned regarding the parsing.
|
||||
*
|
||||
* @return This function returns the status of the parsing,
|
||||
* which can be one of the following :
|
||||
* - PJ_SUCCESS: a command was executed successfully.
|
||||
* - PJ_EINVAL: invalid parameter to this function.
|
||||
* - PJ_ENOTFOUND: command is not found.
|
||||
* - PJ_CLI_EMISSINGARG: missing argument.
|
||||
* - PJ_CLI_EINVARG: invalid command argument.
|
||||
* - PJ_CLI_EEXIT: "exit" has been called to end
|
||||
* the current session. This is a signal for the
|
||||
* application to end it's main loop.
|
||||
*/
|
||||
PJ_DECL(pj_status_t) pj_cli_parse(pj_cli_sess *sess,
|
||||
char *cmdline,
|
||||
pj_cli_cmd_val *val,
|
||||
pj_cli_exec_info *info);
|
||||
|
||||
/**
|
||||
* Execute a command line. This function will parse the input string to find
|
||||
* the appropriate command and verify whether the string matches the command
|
||||
* specifications. If matches, the command will be executed, and the return
|
||||
* value of the command will be set in the \a cmd_ret field of the \a eparam
|
||||
* argument, if specified.
|
||||
*
|
||||
* Please also see pj_cli_parse() for more info regarding the cmdline format.
|
||||
*
|
||||
* @param sess The CLI session.
|
||||
* @param cmdline The command line string to be executed. See the
|
||||
* description of pj_cli_parse() API for more info
|
||||
* regarding the cmdline format.
|
||||
* @param info Optional pointer to receive additional information
|
||||
* related to the execution of the command (such as
|
||||
* the command return value).
|
||||
*
|
||||
* @return This function returns the status of the command
|
||||
* parsing and execution (note that the return value
|
||||
* of the handler itself will be returned in \a info
|
||||
* argument, if specified). Please see the return value
|
||||
* of pj_cli_parse() for possible return values.
|
||||
*/
|
||||
pj_status_t pj_cli_exec(pj_cli_sess *sess,
|
||||
char *cmdline,
|
||||
pj_cli_exec_info *info);
|
||||
|
||||
|
||||
/**
|
||||
* @}
|
||||
*/
|
||||
|
||||
PJ_END_DECL
|
||||
|
||||
#endif /* __PJLIB_UTIL_CLI_H__ */
|
|
@ -0,0 +1,109 @@
|
|||
/* $Id$ */
|
||||
/*
|
||||
* Copyright (C) 2010 Teluu Inc. (http://www.teluu.com)
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 2 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* 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. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; if not, write to the Free Software
|
||||
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
*/
|
||||
#ifndef __PJLIB_UTIL_CLI_CONSOLE_H__
|
||||
#define __PJLIB_UTIL_CLI_CONSOLE_H__
|
||||
|
||||
/**
|
||||
* @file cli_console.h
|
||||
* @brief Command Line Interface Console Front End API
|
||||
*/
|
||||
|
||||
#include <pjlib-util/cli_imp.h>
|
||||
|
||||
|
||||
PJ_BEGIN_DECL
|
||||
|
||||
/**
|
||||
* @ingroup PJLIB_UTIL_CLI_IMP
|
||||
* @{
|
||||
*
|
||||
*/
|
||||
|
||||
|
||||
/**
|
||||
* This structure contains various options for CLI console front-end.
|
||||
* Application must call pj_cli_console_cfg_default() to initialize
|
||||
* this structure with its default values.
|
||||
*/
|
||||
typedef struct pj_cli_console_cfg
|
||||
{
|
||||
/**
|
||||
* Default log verbosity level for the session.
|
||||
*
|
||||
* Default value: PJ_CLI_CONSOLE_LOG_LEVEL
|
||||
*/
|
||||
int log_level;
|
||||
|
||||
} pj_cli_console_cfg;
|
||||
|
||||
|
||||
/**
|
||||
* Initialize pj_cli_console_cfg with its default values.
|
||||
*
|
||||
* @param param The structure to be initialized.
|
||||
*/
|
||||
PJ_DECL(void) pj_cli_console_cfg_default(pj_cli_console_cfg *param);
|
||||
|
||||
|
||||
/**
|
||||
* Create a console front-end for the specified CLI application, and return
|
||||
* the only session instance for the console front end. On Windows operating
|
||||
* system, this may open a new console window.
|
||||
*
|
||||
* @param cli The CLI application instance.
|
||||
* @param param Optional console CLI parameters. If this value is
|
||||
* NULL, default parameters will be used.
|
||||
* @param p_sess Pointer to receive the session instance for the
|
||||
* console front end.
|
||||
* @param p_fe Optional pointer to receive the front-end instance
|
||||
* of the console front-end just created.
|
||||
*
|
||||
* @return PJ_SUCCESS on success, or the appropriate error code.
|
||||
*/
|
||||
PJ_DECL(pj_status_t) pj_cli_console_create(pj_cli_t *cli,
|
||||
const pj_cli_console_cfg *param,
|
||||
pj_cli_sess **p_sess,
|
||||
pj_cli_front_end **p_fe);
|
||||
|
||||
/**
|
||||
* Retrieve a cmdline from console stdin. Application should use this
|
||||
* instead of the standard gets() or fgets(), since unlike these functions,
|
||||
* this is able to end the blocking when pj_cli_quit() is called by other
|
||||
* session.
|
||||
*
|
||||
* Note that this function would also remove the trailing newlines from the
|
||||
* input, if any.
|
||||
*
|
||||
* @param sess The CLI session.
|
||||
* @param buf Pointer to receive the buffer.
|
||||
* @param maxlen Maximum length to read.
|
||||
*
|
||||
* @return PJ_SUCCESS if an input was read
|
||||
*/
|
||||
PJ_DECL(pj_status_t) pj_cli_console_readline(pj_cli_sess *sess,
|
||||
char *buf,
|
||||
unsigned maxlen);
|
||||
|
||||
/**
|
||||
* @}
|
||||
*/
|
||||
|
||||
PJ_END_DECL
|
||||
|
||||
#endif /* __PJLIB_UTIL_CLI_CONSOLE_H__ */
|
|
@ -0,0 +1,208 @@
|
|||
/* $Id$ */
|
||||
/*
|
||||
* Copyright (C) 2010 Teluu Inc. (http://www.teluu.com)
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 2 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* 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. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; if not, write to the Free Software
|
||||
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
*/
|
||||
#ifndef __PJLIB_UTIL_CLI_IMP_H__
|
||||
#define __PJLIB_UTIL_CLI_IMP_H__
|
||||
|
||||
/**
|
||||
* @file cli_imp.h
|
||||
* @brief Command Line Interface Implementor's API
|
||||
*/
|
||||
|
||||
#include <pjlib-util/cli.h>
|
||||
|
||||
|
||||
PJ_BEGIN_DECL
|
||||
|
||||
/**
|
||||
* @defgroup PJLIB_UTIL_CLI_IMP Command Line Interface Implementor's API
|
||||
* @ingroup PJLIB_UTIL_CLI
|
||||
* @{
|
||||
*
|
||||
*/
|
||||
|
||||
/**
|
||||
* Default log level for console sessions.
|
||||
*/
|
||||
#ifndef PJ_CLI_CONSOLE_LOG_LEVEL
|
||||
# define PJ_CLI_CONSOLE_LOG_LEVEL PJ_LOG_MAX_LEVEL
|
||||
#endif
|
||||
|
||||
/**
|
||||
* Default log level for telnet sessions.
|
||||
*/
|
||||
#ifndef PJ_CLI_TELNET_LOG_LEVEL
|
||||
# define PJ_CLI_TELNET_LOG_LEVEL 3
|
||||
#endif
|
||||
|
||||
/**
|
||||
* Default port number for telnet daemon.
|
||||
*/
|
||||
#ifndef PJ_CLI_TELNET_PORT
|
||||
# define PJ_CLI_TELNET_PORT 23
|
||||
#endif
|
||||
|
||||
/**
|
||||
* This enumeration specifies front end types.
|
||||
*/
|
||||
typedef enum pj_cli_front_end_type
|
||||
{
|
||||
PJ_CLI_CONSOLE_FRONT_END, /**< Console front end. */
|
||||
PJ_CLI_TELNET_FRONT_END, /**< Telnet front end. */
|
||||
PJ_CLI_HTTP_FRONT_END, /**< HTTP front end. */
|
||||
PJ_CLI_GUI_FRONT_END /**< GUI front end. */
|
||||
} pj_cli_front_end_type;
|
||||
|
||||
|
||||
/**
|
||||
* Front end operations. Only the CLI should call these functions
|
||||
* directly.
|
||||
*/
|
||||
typedef struct pj_cli_front_end_op
|
||||
{
|
||||
/**
|
||||
* Callback to write a log message to the appropriate sessions belonging
|
||||
* to this front end. The front end would only send the log message to
|
||||
* the session if the session's log verbosity level is greater than the
|
||||
* level of this log message.
|
||||
*
|
||||
* @param fe The front end.
|
||||
* @param level Verbosity level of this message message.
|
||||
* @param data The message itself.
|
||||
* @param len Length of this message.
|
||||
*/
|
||||
void (*on_write_log)(pj_cli_front_end *fe, int level,
|
||||
const char *data, int len);
|
||||
|
||||
/**
|
||||
* Callback to be called when the application is quitting, to signal the
|
||||
* front-end to end its main loop or any currently blocking functions,
|
||||
* if any.
|
||||
*
|
||||
* @param fe The front end.
|
||||
* @param req The session which requested the application quit.
|
||||
*/
|
||||
void (*on_quit)(pj_cli_front_end *fe, pj_cli_sess *req);
|
||||
|
||||
/**
|
||||
* Callback to be called to close and self destroy the front-end. This
|
||||
* must also close any active sessions created by this front-ends.
|
||||
*
|
||||
* @param fe The front end.
|
||||
*/
|
||||
void (*on_destroy)(pj_cli_front_end *fe);
|
||||
|
||||
} pj_cli_front_end_op;
|
||||
|
||||
|
||||
/**
|
||||
* This structure describes common properties of CLI front-ends. A front-
|
||||
* end is a mean to interact with end user, for example the CLI application
|
||||
* may interact with console, telnet, web, or even a GUI.
|
||||
*
|
||||
* Each end user's interaction will create an instance of pj_cli_sess.
|
||||
*
|
||||
* Application instantiates the front end by calling the appropriate
|
||||
* function to instantiate them.
|
||||
*/
|
||||
struct pj_cli_front_end
|
||||
{
|
||||
/**
|
||||
* Linked list members
|
||||
*/
|
||||
PJ_DECL_LIST_MEMBER(struct pj_cli_front_end);
|
||||
|
||||
/**
|
||||
* Front end type.
|
||||
*/
|
||||
pj_cli_front_end_type type;
|
||||
|
||||
/**
|
||||
* The CLI application.
|
||||
*/
|
||||
pj_cli_t *cli;
|
||||
|
||||
/**
|
||||
* Front end operations.
|
||||
*/
|
||||
pj_cli_front_end_op *op;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Session operations.
|
||||
*/
|
||||
typedef struct pj_cli_sess_op
|
||||
{
|
||||
/**
|
||||
* Callback to be called to close and self destroy the session.
|
||||
*
|
||||
* @param sess The session to destroy.
|
||||
*/
|
||||
void (*destroy)(pj_cli_sess *sess);
|
||||
|
||||
} pj_cli_sess_op;
|
||||
|
||||
|
||||
/**
|
||||
* This structure describes common properties of a CLI session. A CLI session
|
||||
* is the interaction of an end user to the CLI application via a specific
|
||||
* renderer, where the renderer can be console, telnet, web, or a GUI app for
|
||||
* mobile. A session is created by its renderer, and it's creation procedures
|
||||
* vary among renderer (for example, a telnet session is created when the
|
||||
* end user connects to the application, while a console session is always
|
||||
* instantiated once the program is run).
|
||||
*/
|
||||
struct pj_cli_sess
|
||||
{
|
||||
/**
|
||||
* Linked list members
|
||||
*/
|
||||
PJ_DECL_LIST_MEMBER(struct pj_cli_sess);
|
||||
|
||||
/**
|
||||
* Pointer to the front-end instance which created this session.
|
||||
*/
|
||||
pj_cli_front_end *fe;
|
||||
|
||||
/**
|
||||
* Session operations.
|
||||
*/
|
||||
pj_cli_sess_op *op;
|
||||
|
||||
/**
|
||||
* Text containing session info, which is filled by the renderer when
|
||||
* the session is created.
|
||||
*/
|
||||
pj_str_t info;
|
||||
|
||||
/**
|
||||
* Log verbosity of this session.
|
||||
*/
|
||||
int log_level;
|
||||
|
||||
};
|
||||
|
||||
/**
|
||||
* @}
|
||||
*/
|
||||
|
||||
|
||||
PJ_END_DECL
|
||||
|
||||
#endif /* __PJLIB_UTIL_CLI_IMP_H__ */
|
|
@ -0,0 +1,492 @@
|
|||
/* $Id$ */
|
||||
/*
|
||||
* Copyright (C) 2010 Teluu Inc. (http://www.teluu.com)
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 2 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* 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. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; if not, write to the Free Software
|
||||
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
*/
|
||||
|
||||
#include <pjlib-util/cli_imp.h>
|
||||
#include <pj/assert.h>
|
||||
#include <pj/errno.h>
|
||||
#include <pj/except.h>
|
||||
#include <pj/hash.h>
|
||||
#include <pj/os.h>
|
||||
#include <pj/pool.h>
|
||||
#include <pj/string.h>
|
||||
#include <pjlib-util/errno.h>
|
||||
#include <pjlib-util/scanner.h>
|
||||
#include <pjlib-util/xml.h>
|
||||
|
||||
#if 1
|
||||
/* Enable some tracing */
|
||||
#define THIS_FILE "cli.c"
|
||||
#define TRACE_(arg) PJ_LOG(3,arg)
|
||||
#else
|
||||
#define TRACE_(arg)
|
||||
#endif
|
||||
|
||||
#define CMD_HASH_TABLE_SIZE 63 /* Hash table size */
|
||||
|
||||
struct pj_cli_t
|
||||
{
|
||||
pj_pool_t *pool; /* Pool to allocate memory from */
|
||||
pj_cli_cfg cfg; /* CLI configuration */
|
||||
pj_cli_cmd_spec root; /* Root of command tree structure */
|
||||
pj_cli_front_end fe_head; /* List of front-ends */
|
||||
pj_hash_table_t *hash; /* Command hash table */
|
||||
|
||||
pj_bool_t is_quitting;
|
||||
pj_bool_t is_restarting;
|
||||
};
|
||||
|
||||
PJ_DEF(void) pj_cli_cfg_default(pj_cli_cfg *param)
|
||||
{
|
||||
pj_assert(param);
|
||||
pj_bzero(param, sizeof(*param));
|
||||
pj_strset2(¶m->name, "");
|
||||
}
|
||||
|
||||
PJ_DEF(void) pj_cli_exec_info_default(pj_cli_exec_info *param)
|
||||
{
|
||||
pj_assert(param);
|
||||
pj_bzero(param, sizeof(*param));
|
||||
param->err_pos = -1;
|
||||
param->cmd_id = PJ_CLI_INVALID_CMD_ID;
|
||||
param->cmd_ret = PJ_SUCCESS;
|
||||
}
|
||||
|
||||
PJ_DEF(pj_status_t) pj_cli_create(pj_cli_cfg *cfg,
|
||||
pj_cli_t **p_cli)
|
||||
{
|
||||
pj_pool_t *pool;
|
||||
pj_cli_t *cli;
|
||||
|
||||
PJ_ASSERT_RETURN(cfg && cfg->pf && p_cli, PJ_EINVAL);
|
||||
|
||||
pool = pj_pool_create(cfg->pf, "cli", 1024, 1024, NULL);
|
||||
cli = PJ_POOL_ZALLOC_T(pool, struct pj_cli_t);
|
||||
if (!cli)
|
||||
return PJ_ENOMEM;
|
||||
|
||||
pj_memcpy(&cli->cfg, cfg, sizeof(*cfg));
|
||||
cli->pool = pool;
|
||||
pj_list_init(&cli->fe_head);
|
||||
|
||||
cli->hash = pj_hash_create(pool, CMD_HASH_TABLE_SIZE);
|
||||
|
||||
cli->root.sub_cmd = PJ_POOL_ZALLOC_T(pool, pj_cli_cmd_spec);
|
||||
pj_list_init(cli->root.sub_cmd);
|
||||
|
||||
*p_cli = cli;
|
||||
|
||||
return PJ_SUCCESS;
|
||||
}
|
||||
|
||||
PJ_DEF(pj_cli_cfg*) pj_cli_get_param(pj_cli_t *cli)
|
||||
{
|
||||
PJ_ASSERT_RETURN(cli, NULL);
|
||||
|
||||
return &cli->cfg;
|
||||
}
|
||||
|
||||
PJ_DEF(void) pj_cli_register_front_end(pj_cli_t *cli,
|
||||
pj_cli_front_end *fe)
|
||||
{
|
||||
pj_list_push_back(&cli->fe_head, fe);
|
||||
}
|
||||
|
||||
PJ_DEF(void) pj_cli_quit(pj_cli_t *cli, pj_cli_sess *req,
|
||||
pj_bool_t restart)
|
||||
{
|
||||
pj_cli_front_end *fe;
|
||||
|
||||
pj_assert(cli);
|
||||
pj_assert(!cli->is_quitting);
|
||||
|
||||
cli->is_quitting = PJ_TRUE;
|
||||
cli->is_restarting = restart;
|
||||
|
||||
fe = cli->fe_head.next;
|
||||
while (fe != &cli->fe_head) {
|
||||
if (fe->op && fe->op->on_quit)
|
||||
(*fe->op->on_quit)(fe, req);
|
||||
fe = fe->next;
|
||||
}
|
||||
}
|
||||
|
||||
PJ_DEF(pj_bool_t) pj_cli_is_quitting(pj_cli_t *cli)
|
||||
{
|
||||
PJ_ASSERT_RETURN(cli, PJ_FALSE);
|
||||
|
||||
return cli->is_quitting;
|
||||
}
|
||||
|
||||
PJ_DEF(pj_bool_t) pj_cli_is_restarting(pj_cli_t *cli)
|
||||
{
|
||||
PJ_ASSERT_RETURN(cli, PJ_FALSE);
|
||||
|
||||
return cli->is_restarting;
|
||||
}
|
||||
|
||||
PJ_DEF(void) pj_cli_destroy(pj_cli_t *cli)
|
||||
{
|
||||
pj_cli_front_end *fe;
|
||||
|
||||
pj_assert(cli);
|
||||
|
||||
if (!pj_cli_is_quitting(cli))
|
||||
pj_cli_quit(cli, NULL, PJ_FALSE);
|
||||
|
||||
fe = cli->fe_head.next;
|
||||
while (fe != &cli->fe_head) {
|
||||
pj_list_erase(fe);
|
||||
if (fe->op && fe->op->on_destroy)
|
||||
(*fe->op->on_destroy)(fe);
|
||||
fe = cli->fe_head.next;
|
||||
}
|
||||
|
||||
cli->is_quitting = PJ_FALSE;
|
||||
|
||||
pj_pool_release(cli->pool);
|
||||
}
|
||||
|
||||
PJ_DEF(void) pj_cli_end_session(pj_cli_sess *sess)
|
||||
{
|
||||
pj_assert(sess);
|
||||
|
||||
if (sess->op->destroy)
|
||||
(*sess->op->destroy)(sess);
|
||||
}
|
||||
|
||||
/* Syntax error handler for parser. */
|
||||
static void on_syntax_error(pj_scanner *scanner)
|
||||
{
|
||||
PJ_UNUSED_ARG(scanner);
|
||||
PJ_THROW(PJ_EINVAL);
|
||||
}
|
||||
|
||||
PJ_DEF(pj_status_t) pj_cli_add_cmd_from_xml(pj_cli_t *cli,
|
||||
pj_cli_cmd_spec *group,
|
||||
const pj_str_t *xml,
|
||||
pj_cli_cmd_handler handler,
|
||||
pj_cli_cmd_spec *p_cmd)
|
||||
{
|
||||
pj_pool_t *pool;
|
||||
pj_xml_node *root;
|
||||
pj_xml_attr *attr;
|
||||
pj_xml_node *sub_node;
|
||||
pj_cli_cmd_spec *cmd;
|
||||
pj_cli_arg_spec args[PJ_CLI_MAX_ARGS];
|
||||
pj_str_t sc[PJ_CLI_MAX_SHORTCUTS];
|
||||
pj_status_t status = PJ_SUCCESS;
|
||||
|
||||
PJ_ASSERT_RETURN(cli && xml, PJ_EINVAL);
|
||||
|
||||
/* Parse the xml */
|
||||
pool = pj_pool_create(cli->cfg.pf, "xml", 1024, 1024, NULL);
|
||||
root = pj_xml_parse(pool, xml->ptr, xml->slen);
|
||||
if (!root) {
|
||||
TRACE_((THIS_FILE, "Error: unable to parse XML"));
|
||||
status = PJ_EINVAL;
|
||||
goto on_exit;
|
||||
}
|
||||
|
||||
if (pj_stricmp2(&root->name, "CMD")) {
|
||||
status = PJ_EINVAL;
|
||||
goto on_exit;
|
||||
}
|
||||
|
||||
/* Initialize the command spec */
|
||||
cmd = PJ_POOL_ZALLOC_T(cli->pool, struct pj_cli_cmd_spec);
|
||||
if (!cmd) {
|
||||
status = PJ_ENOMEM;
|
||||
goto on_exit;
|
||||
}
|
||||
|
||||
/* Get the command attributes */
|
||||
attr = root->attr_head.next;
|
||||
while (attr != &root->attr_head) {
|
||||
if (!pj_stricmp2(&attr->name, "name")) {
|
||||
pj_strltrim(&attr->value);
|
||||
pj_strrtrim(&attr->value);
|
||||
/* Check whether command with the specified name already exists */
|
||||
if (!attr->value.slen ||
|
||||
pj_hash_get(cli->hash, attr->value.ptr,
|
||||
attr->value.slen, NULL))
|
||||
{
|
||||
status = PJ_CLI_EBADNAME;
|
||||
goto on_exit;
|
||||
}
|
||||
|
||||
pj_strdup(cli->pool, &cmd->name, &attr->value);
|
||||
} else if (!pj_stricmp2(&attr->name, "id")) {
|
||||
cmd->id = (pj_cli_cmd_id)pj_strtol(&attr->value);
|
||||
} else if (!pj_stricmp2(&attr->name, "sc")) {
|
||||
pj_scanner scanner;
|
||||
pj_str_t str;
|
||||
|
||||
PJ_USE_EXCEPTION;
|
||||
|
||||
pj_scan_init(&scanner, attr->value.ptr, attr->value.slen,
|
||||
PJ_SCAN_AUTOSKIP_WS, &on_syntax_error);
|
||||
|
||||
PJ_TRY {
|
||||
while (!pj_scan_is_eof(&scanner)) {
|
||||
pj_scan_get_until_ch(&scanner, ',', &str);
|
||||
pj_strrtrim(&str);
|
||||
if (!pj_scan_is_eof(&scanner))
|
||||
pj_scan_advance_n(&scanner, 1, PJ_TRUE);
|
||||
if (!str.slen)
|
||||
continue;
|
||||
|
||||
if (cmd->sc_cnt >= PJ_CLI_MAX_SHORTCUTS) {
|
||||
PJ_THROW(PJ_CLI_ETOOMANYARGS);
|
||||
}
|
||||
/* Check whether the shortcuts are already used */
|
||||
if (pj_hash_get(cli->hash, str.ptr,
|
||||
str.slen, NULL))
|
||||
{
|
||||
PJ_THROW(PJ_CLI_EBADNAME);
|
||||
}
|
||||
|
||||
pj_strassign(&sc[cmd->sc_cnt++], &str);
|
||||
}
|
||||
}
|
||||
PJ_CATCH_ANY {
|
||||
pj_scan_fini(&scanner);
|
||||
status = PJ_GET_EXCEPTION();
|
||||
goto on_exit;
|
||||
}
|
||||
PJ_END;
|
||||
|
||||
} else if (!pj_stricmp2(&attr->name, "desc")) {
|
||||
pj_strdup(cli->pool, &cmd->desc, &attr->value);
|
||||
}
|
||||
|
||||
attr = attr->next;
|
||||
}
|
||||
|
||||
/* Get the command arguments */
|
||||
sub_node = root->node_head.next;
|
||||
while (sub_node != (pj_xml_node*)&root->node_head) {
|
||||
if (!pj_stricmp2(&sub_node->name, "ARGS")) {
|
||||
pj_xml_node *arg_node;
|
||||
|
||||
arg_node = sub_node->node_head.next;
|
||||
while (arg_node != (pj_xml_node*)&sub_node->node_head) {
|
||||
if (!pj_stricmp2(&arg_node->name, "ARG")) {
|
||||
pj_cli_arg_spec *arg;
|
||||
|
||||
if (cmd->arg_cnt >= PJ_CLI_MAX_ARGS) {
|
||||
status = PJ_CLI_ETOOMANYARGS;
|
||||
goto on_exit;
|
||||
}
|
||||
arg = &args[cmd->arg_cnt];
|
||||
pj_bzero(arg, sizeof(*arg));
|
||||
attr = arg_node->attr_head.next;
|
||||
while (attr != &arg_node->attr_head) {
|
||||
if (!pj_stricmp2(&attr->name, "name")) {
|
||||
pj_strassign(&arg->name, &attr->value);
|
||||
} else if (!pj_stricmp2(&attr->name, "type")) {
|
||||
if (!pj_stricmp2(&attr->value, "text")) {
|
||||
arg->type = PJ_CLI_ARG_TEXT;
|
||||
} else if (!pj_stricmp2(&attr->value, "int")) {
|
||||
arg->type = PJ_CLI_ARG_INT;
|
||||
}
|
||||
} else if (!pj_stricmp2(&attr->name, "desc")) {
|
||||
pj_strassign(&arg->desc, &attr->value);
|
||||
}
|
||||
|
||||
attr = attr->next;
|
||||
}
|
||||
cmd->arg_cnt++;
|
||||
}
|
||||
|
||||
arg_node = arg_node->next;
|
||||
}
|
||||
}
|
||||
sub_node = sub_node->next;
|
||||
}
|
||||
|
||||
if (cmd->id == PJ_CLI_CMD_ID_GROUP) {
|
||||
/* Command group shouldn't have any shortcuts nor arguments */
|
||||
if (!cmd->sc_cnt || !cmd->arg_cnt) {
|
||||
status = PJ_EINVAL;
|
||||
goto on_exit;
|
||||
}
|
||||
cmd->sub_cmd = PJ_POOL_ALLOC_T(cli->pool, struct pj_cli_cmd_spec);
|
||||
pj_list_init(cmd->sub_cmd);
|
||||
}
|
||||
|
||||
if (!cmd->name.slen) {
|
||||
status = PJ_CLI_EBADNAME;
|
||||
goto on_exit;
|
||||
}
|
||||
|
||||
if (cmd->arg_cnt) {
|
||||
unsigned i;
|
||||
|
||||
cmd->arg = (pj_cli_arg_spec *)pj_pool_zalloc(cli->pool, cmd->arg_cnt *
|
||||
sizeof(pj_cli_arg_spec));
|
||||
if (!cmd->arg) {
|
||||
status = PJ_ENOMEM;
|
||||
goto on_exit;
|
||||
}
|
||||
for (i = 0; i < cmd->arg_cnt; i++) {
|
||||
pj_strdup(cli->pool, &cmd->arg[i].name, &args[i].name);
|
||||
pj_strdup(cli->pool, &cmd->arg[i].desc, &args[i].desc);
|
||||
cmd->arg[i].type = args[i].type;
|
||||
}
|
||||
}
|
||||
|
||||
if (cmd->sc_cnt) {
|
||||
unsigned i;
|
||||
|
||||
cmd->sc = (pj_str_t *)pj_pool_zalloc(cli->pool, cmd->sc_cnt *
|
||||
sizeof(pj_str_t));
|
||||
if (!cmd->sc) {
|
||||
status = PJ_ENOMEM;
|
||||
goto on_exit;
|
||||
}
|
||||
for (i = 0; i < cmd->sc_cnt; i++) {
|
||||
pj_strdup(cli->pool, &cmd->sc[i], &sc[i]);
|
||||
pj_hash_set(cli->pool, cli->hash, cmd->sc[i].ptr,
|
||||
cmd->sc[i].slen, 0, cmd);
|
||||
}
|
||||
}
|
||||
|
||||
pj_hash_set(cli->pool, cli->hash, cmd->name.ptr, cmd->name.slen, 0, cmd);
|
||||
|
||||
cmd->handler = handler;
|
||||
|
||||
if (group)
|
||||
pj_list_push_back(group->sub_cmd, cmd);
|
||||
else
|
||||
pj_list_push_back(cli->root.sub_cmd, cmd);
|
||||
|
||||
if (p_cmd)
|
||||
p_cmd = cmd;
|
||||
|
||||
on_exit:
|
||||
pj_pool_release(pool);
|
||||
|
||||
return status;
|
||||
}
|
||||
|
||||
PJ_DEF(pj_status_t) pj_cli_parse(pj_cli_sess *sess,
|
||||
char *cmdline,
|
||||
pj_cli_cmd_val *val,
|
||||
pj_cli_exec_info *info)
|
||||
{
|
||||
pj_scanner scanner;
|
||||
pj_str_t str;
|
||||
int len;
|
||||
pj_cli_exec_info einfo;
|
||||
|
||||
PJ_USE_EXCEPTION;
|
||||
|
||||
PJ_ASSERT_RETURN(sess && cmdline && val, PJ_EINVAL);
|
||||
|
||||
if (!info)
|
||||
info = &einfo;
|
||||
pj_cli_exec_info_default(info);
|
||||
|
||||
/* Parse the command line. */
|
||||
len = pj_ansi_strlen(cmdline);
|
||||
pj_scan_init(&scanner, cmdline, len, PJ_SCAN_AUTOSKIP_WS,
|
||||
&on_syntax_error);
|
||||
PJ_TRY {
|
||||
pj_scan_get_until_chr(&scanner, " \t\r\n", &str);
|
||||
|
||||
/* Find the command from the hash table */
|
||||
val->cmd = pj_hash_get(sess->fe->cli->hash, str.ptr,
|
||||
str.slen, NULL);
|
||||
|
||||
/* Error, command not found */
|
||||
if (!val->cmd)
|
||||
PJ_THROW(PJ_ENOTFOUND);
|
||||
|
||||
info->cmd_id = val->cmd->id;
|
||||
|
||||
/* Parse the command arguments */
|
||||
val->argc = 1;
|
||||
pj_strassign(&val->argv[0], &str);
|
||||
while (!pj_scan_is_eof(&scanner)) {
|
||||
if (*scanner.curptr == '\'' || *scanner.curptr == '"' ||
|
||||
*scanner.curptr == '[' || *scanner.curptr == '{')
|
||||
{
|
||||
pj_scan_get_quotes(&scanner, "'\"[{", "'\"]}", 4, &str);
|
||||
/* Remove the quotes */
|
||||
str.ptr++;
|
||||
str.slen -= 2;
|
||||
} else {
|
||||
pj_scan_get_until_chr(&scanner, " \t\r\n", &str);
|
||||
}
|
||||
if (val->argc == PJ_CLI_MAX_ARGS)
|
||||
PJ_THROW(PJ_CLI_ETOOMANYARGS);
|
||||
pj_strassign(&val->argv[val->argc++], &str);
|
||||
}
|
||||
|
||||
if (!pj_scan_is_eof(&scanner)) {
|
||||
pj_scan_get_newline(&scanner);
|
||||
if (!pj_scan_is_eof(&scanner))
|
||||
PJ_THROW(PJ_CLI_EINVARG);
|
||||
}
|
||||
}
|
||||
PJ_CATCH_ANY {
|
||||
pj_scan_fini(&scanner);
|
||||
return PJ_GET_EXCEPTION();
|
||||
}
|
||||
PJ_END;
|
||||
|
||||
if ((val->argc - 1) < (int)val->cmd->arg_cnt) {
|
||||
info->arg_idx = val->argc;
|
||||
return PJ_CLI_EMISSINGARG;
|
||||
} else if ((val->argc - 1) > (int)val->cmd->arg_cnt) {
|
||||
info->arg_idx = val->cmd->arg_cnt + 1;
|
||||
return PJ_CLI_ETOOMANYARGS;
|
||||
}
|
||||
|
||||
val->sess = sess;
|
||||
|
||||
return PJ_SUCCESS;
|
||||
}
|
||||
|
||||
pj_status_t pj_cli_exec(pj_cli_sess *sess,
|
||||
char *cmdline,
|
||||
pj_cli_exec_info *info)
|
||||
{
|
||||
pj_cli_cmd_val val;
|
||||
pj_status_t status;
|
||||
pj_cli_exec_info einfo;
|
||||
|
||||
PJ_ASSERT_RETURN(sess && cmdline, PJ_EINVAL);
|
||||
|
||||
if (!info)
|
||||
info = &einfo;
|
||||
status = pj_cli_parse(sess, cmdline, &val, info);
|
||||
|
||||
if (status != PJ_SUCCESS)
|
||||
return status;
|
||||
|
||||
if (val.cmd->handler) {
|
||||
info->cmd_ret = (*val.cmd->handler)(&val);
|
||||
if (info->cmd_ret == PJ_CLI_EINVARG ||
|
||||
info->cmd_ret == PJ_CLI_EEXIT)
|
||||
return info->cmd_ret;
|
||||
}
|
||||
|
||||
return PJ_SUCCESS;
|
||||
}
|
|
@ -0,0 +1,170 @@
|
|||
/* $Id$ */
|
||||
/*
|
||||
* Copyright (C) 2010 Teluu Inc. (http://www.teluu.com)
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 2 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* 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. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; if not, write to the Free Software
|
||||
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
*/
|
||||
|
||||
#include <pjlib-util/cli_imp.h>
|
||||
#include <pjlib-util/cli_console.h>
|
||||
#include <pj/assert.h>
|
||||
#include <pj/errno.h>
|
||||
#include <pj/os.h>
|
||||
#include <pj/pool.h>
|
||||
#include <pj/string.h>
|
||||
#include <pjlib-util/errno.h>
|
||||
|
||||
struct cli_console_fe
|
||||
{
|
||||
pj_cli_front_end base;
|
||||
pj_pool_t *pool;
|
||||
pj_cli_sess *sess;
|
||||
pj_thread_t *input_thread;
|
||||
pj_bool_t thread_quit;
|
||||
pj_sem_t *thread_sem;
|
||||
|
||||
struct async_input_t
|
||||
{
|
||||
char *buf;
|
||||
unsigned maxlen;
|
||||
pj_sem_t *sem;
|
||||
} input;
|
||||
};
|
||||
|
||||
static void cli_console_quit(pj_cli_front_end *fe, pj_cli_sess *req)
|
||||
{
|
||||
struct cli_console_fe * cfe = (struct cli_console_fe *)fe;
|
||||
|
||||
PJ_UNUSED_ARG(req);
|
||||
|
||||
pj_assert(cfe);
|
||||
if (cfe->input_thread) {
|
||||
cfe->thread_quit = PJ_TRUE;
|
||||
pj_sem_post(cfe->input.sem);
|
||||
pj_sem_post(cfe->thread_sem);
|
||||
}
|
||||
}
|
||||
|
||||
static void cli_console_destroy(pj_cli_front_end *fe)
|
||||
{
|
||||
struct cli_console_fe * cfe = (struct cli_console_fe *)fe;
|
||||
|
||||
pj_assert(cfe);
|
||||
cli_console_quit(fe, NULL);
|
||||
|
||||
pj_sem_destroy(cfe->thread_sem);
|
||||
pj_sem_destroy(cfe->input.sem);
|
||||
pj_pool_release(cfe->pool);
|
||||
}
|
||||
|
||||
PJ_DEF(void) pj_cli_console_cfg_default(pj_cli_console_cfg *param)
|
||||
{
|
||||
pj_assert(param);
|
||||
|
||||
param->log_level = PJ_CLI_CONSOLE_LOG_LEVEL;
|
||||
}
|
||||
|
||||
PJ_DEF(pj_status_t) pj_cli_console_create(pj_cli_t *cli,
|
||||
const pj_cli_console_cfg *param,
|
||||
pj_cli_sess **p_sess,
|
||||
pj_cli_front_end **p_fe)
|
||||
{
|
||||
pj_cli_sess *sess;
|
||||
struct cli_console_fe *fe;
|
||||
pj_cli_console_cfg cfg;
|
||||
pj_pool_t *pool;
|
||||
|
||||
PJ_ASSERT_RETURN(cli && p_sess, PJ_EINVAL);
|
||||
|
||||
pool = pj_pool_create(pj_cli_get_param(cli)->pf, "console_fe",
|
||||
256, 256, NULL);
|
||||
sess = PJ_POOL_ZALLOC_T(pool, pj_cli_sess);
|
||||
fe = PJ_POOL_ZALLOC_T(pool, struct cli_console_fe);
|
||||
if (!sess || !fe)
|
||||
return PJ_ENOMEM;
|
||||
|
||||
if (!param) {
|
||||
pj_cli_console_cfg_default(&cfg);
|
||||
param = &cfg;
|
||||
}
|
||||
sess->fe = &fe->base;
|
||||
sess->log_level = param->log_level;
|
||||
fe->base.cli = cli;
|
||||
fe->base.type = PJ_CLI_CONSOLE_FRONT_END;
|
||||
fe->base.op = PJ_POOL_ZALLOC_T(pool, struct pj_cli_front_end_op);
|
||||
fe->base.op->on_quit = &cli_console_quit;
|
||||
fe->base.op->on_destroy = &cli_console_destroy;
|
||||
fe->pool = pool;
|
||||
fe->sess = sess;
|
||||
pj_sem_create(pool, "console_fe", 0, 1, &fe->thread_sem);
|
||||
pj_sem_create(pool, "console_fe", 0, 1, &fe->input.sem);
|
||||
pj_cli_register_front_end(cli, &fe->base);
|
||||
|
||||
*p_sess = sess;
|
||||
if (p_fe)
|
||||
*p_fe = &fe->base;
|
||||
|
||||
return PJ_SUCCESS;
|
||||
}
|
||||
|
||||
static int readline_thread(void * p)
|
||||
{
|
||||
struct cli_console_fe * fe = (struct cli_console_fe *)p;
|
||||
int i;
|
||||
|
||||
while (!fe->thread_quit) {
|
||||
fgets(fe->input.buf, fe->input.maxlen, stdin);
|
||||
for (i = pj_ansi_strlen(fe->input.buf) - 1; i >= 0; i--) {
|
||||
if (fe->input.buf[i] == '\n')
|
||||
fe->input.buf[i] = 0;
|
||||
else
|
||||
break;
|
||||
}
|
||||
pj_sem_post(fe->input.sem);
|
||||
/* Sleep until the next call of pj_cli_console_readline() */
|
||||
pj_sem_wait(fe->thread_sem);
|
||||
}
|
||||
pj_sem_post(fe->input.sem);
|
||||
fe->input_thread = NULL;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
PJ_DEF(pj_status_t) pj_cli_console_readline(pj_cli_sess *sess,
|
||||
char *buf,
|
||||
unsigned maxlen)
|
||||
{
|
||||
struct cli_console_fe *fe = (struct cli_console_fe *)sess->fe;
|
||||
|
||||
PJ_ASSERT_RETURN(sess && buf, PJ_EINVAL);
|
||||
|
||||
fe->input.buf = buf;
|
||||
fe->input.maxlen = maxlen;
|
||||
|
||||
if (!fe->input_thread) {
|
||||
if (pj_thread_create(fe->pool, NULL, &readline_thread, fe,
|
||||
0, 0, &fe->input_thread) != PJ_SUCCESS)
|
||||
{
|
||||
return PJ_EUNKNOWN;
|
||||
}
|
||||
} else {
|
||||
/* Wake up readline thread */
|
||||
pj_sem_post(fe->thread_sem);
|
||||
}
|
||||
|
||||
pj_sem_wait(fe->input.sem);
|
||||
|
||||
return (fe->thread_quit ? PJ_CLI_EEXIT : PJ_SUCCESS);
|
||||
}
|
Loading…
Reference in New Issue