Index: /pjproject/branches/projects/cli/pjlib-util/build/Makefile
===================================================================
--- /pjproject/branches/projects/cli/pjlib-util/build/Makefile	(revision 3210)
+++ /pjproject/branches/projects/cli/pjlib-util/build/Makefile	(revision 3211)
@@ -28,7 +28,8 @@
 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)
Index: /pjproject/branches/projects/cli/pjlib-util/build/pjlib_util.vcproj
===================================================================
--- /pjproject/branches/projects/cli/pjlib-util/build/pjlib_util.vcproj	(revision 3210)
+++ /pjproject/branches/projects/cli/pjlib-util/build/pjlib_util.vcproj	(revision 3211)
@@ -2770,4 +2770,12 @@
 			</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"
 				>
@@ -4715,4 +4723,16 @@
 			</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"
 				>
Index: /pjproject/branches/projects/cli/pjlib-util/include/pjlib-util/cli.h
===================================================================
--- /pjproject/branches/projects/cli/pjlib-util/include/pjlib-util/cli.h	(revision 3211)
+++ /pjproject/branches/projects/cli/pjlib-util/include/pjlib-util/cli.h	(revision 3211)
@@ -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__ */
Index: /pjproject/branches/projects/cli/pjlib-util/include/pjlib-util/cli_console.h
===================================================================
--- /pjproject/branches/projects/cli/pjlib-util/include/pjlib-util/cli_console.h	(revision 3211)
+++ /pjproject/branches/projects/cli/pjlib-util/include/pjlib-util/cli_console.h	(revision 3211)
@@ -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__ */
Index: /pjproject/branches/projects/cli/pjlib-util/include/pjlib-util/cli_imp.h
===================================================================
--- /pjproject/branches/projects/cli/pjlib-util/include/pjlib-util/cli_imp.h	(revision 3211)
+++ /pjproject/branches/projects/cli/pjlib-util/include/pjlib-util/cli_imp.h	(revision 3211)
@@ -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__ */
Index: /pjproject/branches/projects/cli/pjlib-util/src/pjlib-util/cli.c
===================================================================
--- /pjproject/branches/projects/cli/pjlib-util/src/pjlib-util/cli.c	(revision 3211)
+++ /pjproject/branches/projects/cli/pjlib-util/src/pjlib-util/cli.c	(revision 3211)
@@ -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(&param->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;
+}
Index: /pjproject/branches/projects/cli/pjlib-util/src/pjlib-util/cli_console.c
===================================================================
--- /pjproject/branches/projects/cli/pjlib-util/src/pjlib-util/cli_console.c	(revision 3211)
+++ /pjproject/branches/projects/cli/pjlib-util/src/pjlib-util/cli_console.c	(revision 3211)
@@ -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);
+}
