[asterisk-commits] twilson: branch group/aco_xmldocs r373739 - in /team/group/aco_xmldocs: apps/...

SVN commits to the Asterisk project asterisk-commits at lists.digium.com
Tue Sep 25 16:13:59 CDT 2012


Author: twilson
Date: Tue Sep 25 16:13:56 2012
New Revision: 373739

URL: http://svnview.digium.com/svn/asterisk?view=rev&rev=373739
Log:
Initial patch

TODO: write config option docs for modules using ACO

Modified:
    team/group/aco_xmldocs/apps/app_skel.c
    team/group/aco_xmldocs/doc/appdocsxml.dtd
    team/group/aco_xmldocs/include/asterisk/_private.h
    team/group/aco_xmldocs/include/asterisk/config_options.h
    team/group/aco_xmldocs/include/asterisk/xml.h
    team/group/aco_xmldocs/include/asterisk/xmldoc.h
    team/group/aco_xmldocs/main/asterisk.c
    team/group/aco_xmldocs/main/config_options.c
    team/group/aco_xmldocs/main/xml.c
    team/group/aco_xmldocs/main/xmldoc.c

Modified: team/group/aco_xmldocs/apps/app_skel.c
URL: http://svnview.digium.com/svn/asterisk/team/group/aco_xmldocs/apps/app_skel.c?view=diff&rev=373739&r1=373738&r2=373739
==============================================================================
--- team/group/aco_xmldocs/apps/app_skel.c (original)
+++ team/group/aco_xmldocs/apps/app_skel.c Tue Sep 25 16:13:56 2012
@@ -77,6 +77,51 @@
 		from. It shows you the basic structure to create your own Asterisk applications.</para>
 		</description>
 	</application>
+
+	<configInfo name="app_skel" language="en_US">
+		<configFile name="app_skel.conf">
+			<configObject name="globals">
+				<synopsis>Options that apply globally to app_skel</synopsis>
+				<configOption name="games">
+					<synopsis>The number of games a single execution of SkelGuessNumber will play</synopsis>
+				</configOption>
+				<configOption name="cheat">
+					<synopsis>Should the computer cheat?</synopsis>
+					<description><para>If enabled, the computer will ignore winning guesses.</para></description>
+				</configOption>
+			</configObject>
+			<configObject name="sounds">
+				<synopsis>Prompts for SkelGuessNumber to play</synopsis>
+				<configOption name="prompt" default="please-enter-your&amp;number&amp;queue-less-than">
+					<synopsis>A prompt directing the user to enter a number less than the max number</synopsis>
+				</configOption>
+				<configOption name="wrong_guess" default="vm-pls-try-again">
+					<synopsis>The sound file to play when a wrong guess is made</synopsis>
+				</configOption>
+				<configOption name="right_guess" default="auth-thankyou">
+					<synopsis>The sound file to play when a correct guess is made</synopsis>
+				</configOption>
+				<configOption name="too_low">
+					<synopsis>The sound file to play when a guess is too low</synopsis>
+				</configOption>
+				<configOption name="too_high">
+					<synopsis>The sound file to play when a guess is too high</synopsis>
+				</configOption>
+				<configOption name="lose" default="vm-goodbye">
+					<synopsis>The sound file to play when a player loses</synopsis>
+				</configOption>
+			</configObject>
+			<configObject name="level">
+				<synopsis>Defined levels for the SkelGuessNumber game</synopsis>
+				<configOption name="max_number">
+					<synopsis>The maximum in the range of numbers to guess (1 is the implied minimum)</synopsis>
+				</configOption>
+				<configOption name="max_guesses">
+					<synopsis>The maximum number of guesses before a game is considered lost</synopsis>
+				</configOption>
+			</configObject>
+		</configFile>
+	</configInfo>
  ***/
 
 static char *app = "SkelGuessNumber";
@@ -188,6 +233,7 @@
 /*! \brief An aco_type structure to link the "general" category to the skel_global_config type */
 static struct aco_type global_option = {
 	.type = ACO_GLOBAL,
+	.name = "globals",
 	.item_offset = offsetof(struct skel_config, global),
 	.category_match = ACO_WHITELIST,
 	.category = "^general$",
@@ -198,6 +244,7 @@
 /*! \brief An aco_type structure to link the "sounds" category to the skel_global_config type */
 static struct aco_type sound_option = {
 	.type = ACO_GLOBAL,
+	.name = "sounds",
 	.item_offset = offsetof(struct skel_config, global),
 	.category_match = ACO_WHITELIST,
 	.category = "^sounds$",
@@ -208,6 +255,7 @@
 /*! \brief An aco_type structure to link the everything but the "general" and "sounds" categories to the skel_level type */
 static struct aco_type level_option = {
 	.type = ACO_ITEM,
+	.name = "level",
 	.category_match = ACO_BLACKLIST,
 	.category = "^(general|sounds)$",
 	.item_alloc = skel_level_alloc,

Modified: team/group/aco_xmldocs/doc/appdocsxml.dtd
URL: http://svnview.digium.com/svn/asterisk/team/group/aco_xmldocs/doc/appdocsxml.dtd?view=diff&rev=373739&r1=373738&r2=373739
==============================================================================
--- team/group/aco_xmldocs/doc/appdocsxml.dtd (original)
+++ team/group/aco_xmldocs/doc/appdocsxml.dtd Tue Sep 25 16:13:56 2012
@@ -1,13 +1,13 @@
-  <!ELEMENT docs (application|function|agi|manager|managerEvent|info)*>
+  <!ELEMENT docs (application|function|agi|manager|managerEvent|info|configInfo)*>
   <!ATTLIST docs xmlns:xi CDATA #FIXED "http://www.w3.org/2001/XInclude">
 
   <!ELEMENT xi:include (xi:fallback?) >
-  <!ATTLIST xi:include 
+  <!ATTLIST xi:include
   xmlns:xi	CDATA       #FIXED    "http://www.w3.org/2001/XInclude"
   href		CDATA       #IMPLIED
   parse		(xml|text)  "xml"
   xpointer	CDATA       #IMPLIED
-  encoding	CDATA       #IMPLIED 
+  encoding	CDATA       #IMPLIED
   accept	CDATA       #IMPLIED
   accept-language CDATA  #IMPLIED >
 
@@ -36,6 +36,34 @@
   <!ELEMENT managerEventInstance (synopsis?,syntax?,description?,see-also?)*>
   <!ATTLIST managerEventInstance class CDATA #REQUIRED>
 
+  <!ELEMENT configInfo (configFile+)>
+  <!ATTLIST configInfo name CDATA #REQUIRED>
+  <!ATTLIST configInfo language CDATA #REQUIRED>
+
+  <!ELEMENT configFile (configObject+)>
+  <!ATTLIST configFile name CDATA #REQUIRED>
+
+  <!ELEMENT configObject (synopsis?|description?|syntax?|configOption)*>
+  <!ATTLIST configObject name ID #REQUIRED>
+
+  <!ELEMENT configOption (synopsis,description?,syntax?)*>
+  <!ATTLIST configOption name CDATA #REQUIRED>
+  <!ATTLIST configOption regex (yes|no|true|false) "false">
+  <!ATTLIST configOption default CDATA #IMPLIED>
+
+  <!ELEMENT matchInfo (category|field?)>
+
+  <!ELEMENT category (#PCDATA)>
+  <!ATTLIST category match (yes|no|true|false) #REQUIRED>
+
+  <!ELEMENT field (#PCDATA)>
+  <!ATTLIST field name CDATA #REQUIRED>
+
+  <!ELEMENT dataType EMPTY>
+  <!ATTLIST dataType type (string|uint|integer|boolean|float|enum) #REQUIRED>
+  <!ATTLIST dataType max CDATA #IMPLIED>
+  <!ATTLIST dataType min CDATA #IMPLIED>
+
   <!ELEMENT info (para|note|warning|variablelist|enumlist|info|xi:include)*>
   <!ATTLIST info name CDATA #REQUIRED>
   <!ATTLIST info language CDATA #REQUIRED>
@@ -48,7 +76,7 @@
 
   <!ELEMENT synopsis (#PCDATA)>
 
-  <!ELEMENT syntax (parameter|xi:include)*>
+  <!ELEMENT syntax (parameter|dataType|category|matchInfo|xi:include)*>
   <!ATTLIST syntax argsep CDATA ",">
 
   <!ELEMENT description (para|note|warning|variablelist|enumlist|info|xi:include)*>
@@ -87,7 +115,7 @@
   <!ELEMENT replaceable (#PCDATA)>
   <!ELEMENT directory (#PCDATA)>
   <!ELEMENT astcli (#PCDATA)>
- 
+
   <!ELEMENT note (para+|xi:include*)>
   <!ELEMENT warning (para+|xi:include*)>
 

Modified: team/group/aco_xmldocs/include/asterisk/_private.h
URL: http://svnview.digium.com/svn/asterisk/team/group/aco_xmldocs/include/asterisk/_private.h?view=diff&rev=373739&r1=373738&r2=373739
==============================================================================
--- team/group/aco_xmldocs/include/asterisk/_private.h (original)
+++ team/group/aco_xmldocs/include/asterisk/_private.h Tue Sep 25 16:13:56 2012
@@ -49,6 +49,7 @@
 int ast_test_init(void);            /*!< Provided by test.c */
 int ast_msg_init(void);             /*!< Provided by message.c */
 void ast_msg_shutdown(void);        /*!< Provided by message.c */
+int aco_init(void);             /*!< Provided by config_options.c */
 
 /*!
  * \brief Reload asterisk modules.

Modified: team/group/aco_xmldocs/include/asterisk/config_options.h
URL: http://svnview.digium.com/svn/asterisk/team/group/aco_xmldocs/include/asterisk/config_options.h?view=diff&rev=373739&r1=373738&r2=373739
==============================================================================
--- team/group/aco_xmldocs/include/asterisk/config_options.h (original)
+++ team/group/aco_xmldocs/include/asterisk/config_options.h Tue Sep 25 16:13:56 2012
@@ -109,6 +109,7 @@
 struct aco_type {
 	/* common stuff */
 	enum aco_type_t type;   /*!< Whether this is a global or item type */
+	const char *name;       /*!< The name of this type (must match XML documentation) */
 	const char *category;   /*!< A regular expression for matching categories to be allowed or denied */
 	const char *matchfield; /*!< An option name to match for this type (i.e. a 'type'-like column) */
 	const char *matchvalue; /*!< The value of the option to require for matching (i.e. 'peer' for type= in sip.conf) */

Modified: team/group/aco_xmldocs/include/asterisk/xml.h
URL: http://svnview.digium.com/svn/asterisk/team/group/aco_xmldocs/include/asterisk/xml.h?view=diff&rev=373739&r1=373738&r2=373739
==============================================================================
--- team/group/aco_xmldocs/include/asterisk/xml.h (original)
+++ team/group/aco_xmldocs/include/asterisk/xml.h Tue Sep 25 16:13:56 2012
@@ -23,6 +23,7 @@
 
 struct ast_xml_node;
 struct ast_xml_doc;
+struct ast_xml_xpath_results;
 
 /*!
  * \brief Initialize the XML library implementation.
@@ -207,6 +208,18 @@
  * \brief Dump the specified document to a file. */
 int ast_xml_doc_dump_file(FILE *output, struct ast_xml_doc *doc);
 
+/*! \brief Free the XPath results */
+void ast_xml_xpath_results_free(struct ast_xml_xpath_results *results);
+
+/*! \brief Return the number of results from an XPath query */
+int ast_xml_xpath_num_results(struct ast_xml_xpath_results *results);
+
+/*! \brief Return the first result node of an XPath query */
+struct ast_xml_node *ast_xml_xpath_get_first_result(struct ast_xml_xpath_results *results);
+
+/*! \brief Execute an XPath query on an XML document */
+struct ast_xml_xpath_results *ast_xml_query(struct ast_xml_doc *doc, const char *xpath_str);
+
 /* Features using ast_xml_ */
 #ifdef HAVE_LIBXML2
 #define AST_XML_DOCS

Modified: team/group/aco_xmldocs/include/asterisk/xmldoc.h
URL: http://svnview.digium.com/svn/asterisk/team/group/aco_xmldocs/include/asterisk/xmldoc.h?view=diff&rev=373739&r1=373738&r2=373739
==============================================================================
--- team/group/aco_xmldocs/include/asterisk/xmldoc.h (original)
+++ team/group/aco_xmldocs/include/asterisk/xmldoc.h Tue Sep 25 16:13:56 2012
@@ -61,10 +61,40 @@
 		AST_STRING_FIELD(name);
 		/*! The type of the item */
 		AST_STRING_FIELD(type);
+		/*! Reference to another field */
+		AST_STRING_FIELD(ref);
+		/*! Default value */
+		AST_STRING_FIELD(default_value);
 	);
 	/*! The next XML documentation item that matches the same name/item type */
 	struct ast_xml_doc_item *next;
+	unsigned int regex;
 };
+
+/*! \brief Update the XML for an ACO option
+ * \param module The module name
+ * \param name The option name
+ * \param object_name The config type name
+ * \param default_value The option's default value
+ * \param regex Whether or not the value is matched via regex (non-zero = yes)
+ *
+ * \retval non-zero failure
+ * \retval 0 success
+ */
+int ast_xmldoc_update_config_option(const char *module, const char *name, const char *object_name, const char *default_value, unsigned int regex);
+
+/*! \brief Update the XML for an ACO type
+ * \param module The module name
+ * \param name The config type name
+ * \param category The category regex to match against
+ * \param matchfield The field that needs to be matched, if applicable
+ * \param matchvalue The value of the matchfield needed for a match, if applicable
+ * \param matches Whether the category regex specifies a whitelist or blacklist (non-zero = whitelist)
+ *
+ * \retval non-zero failure
+ * \retval 0 success
+ */
+int ast_xmldoc_update_config_type(const char *module, const char *name, const char *category, const char *matchfield, const char *matchvalue, unsigned int matches);
 
 /*!
  *  \brief Get the syntax for a specified application or function.

Modified: team/group/aco_xmldocs/main/asterisk.c
URL: http://svnview.digium.com/svn/asterisk/team/group/aco_xmldocs/main/asterisk.c?view=diff&rev=373739&r1=373738&r2=373739
==============================================================================
--- team/group/aco_xmldocs/main/asterisk.c (original)
+++ team/group/aco_xmldocs/main/asterisk.c Tue Sep 25 16:13:56 2012
@@ -4007,6 +4007,8 @@
 	ast_xmldoc_load_documentation();
 #endif
 
+	aco_init();
+
 	if (astdb_init()) {
 		printf("%s", term_quit());
 		exit(1);

Modified: team/group/aco_xmldocs/main/config_options.c
URL: http://svnview.digium.com/svn/asterisk/team/group/aco_xmldocs/main/config_options.c?view=diff&rev=373739&r1=373738&r2=373739
==============================================================================
--- team/group/aco_xmldocs/main/config_options.c (original)
+++ team/group/aco_xmldocs/main/config_options.c Tue Sep 25 16:13:56 2012
@@ -31,11 +31,14 @@
 
 #include <regex.h>
 
+#include "asterisk/_private.h"
 #include "asterisk/config.h"
 #include "asterisk/config_options.h"
 #include "asterisk/stringfields.h"
 #include "asterisk/acl.h"
 #include "asterisk/frame.h"
+#include "asterisk/xmldoc.h"
+#include "asterisk/cli.h"
 
 #ifdef LOW_MEMORY
 #define CONFIG_OPT_BUCKETS 5
@@ -70,6 +73,10 @@
 	intptr_t args[0];
 };
 
+#ifdef AST_XML_DOCS
+static struct ao2_container *xmldocs;
+#endif /* AST_XML_DOCS */
+
 void *aco_pending_config(struct aco_info *info)
 {
 	if (!(info && info->internal)) {
@@ -142,7 +149,7 @@
 	return regex;
 }
 
-static int link_option_to_types(struct aco_type **types, struct aco_option *opt)
+static int link_option_to_types(struct aco_info *info, struct aco_type **types, struct aco_option *opt)
 {
 	size_t idx = 0;
 	struct aco_type *type;
@@ -152,7 +159,11 @@
 			ast_log(LOG_ERROR, "Attempting to register option using uninitialized type\n");
 			return -1;
 		}
-		if (!ao2_link(type->internal->opts, opt)) {
+		if (!ao2_link(type->internal->opts, opt)
+#ifdef AST_XML_DOCS
+				|| ast_xmldoc_update_config_option(info->module, opt->name, type->name, opt->default_val, opt->match_type == ACO_REGEX)
+#endif /* AST_XML_DOCS */
+		) {
 			while (--idx) {
 				ao2_unlink(types[idx]->internal->opts, opt);
 			}
@@ -179,13 +190,56 @@
 	opt->deprecated = 1;
 	opt->match_type = ACO_EXACT;
 
-	if (link_option_to_types(types, opt)) {
+	if (link_option_to_types(info, types, opt)) {
 		ao2_ref(opt, -1);
 		return -1;
 	}
 
 	return 0;
 }
+
+#ifdef AST_XML_DOCS
+/*! \brief Find a particular ast_xml_doc_item by module, types, and name
+ */
+static struct ast_xml_doc_item *find_xmldoc_option(struct ast_xml_doc_item *config_info, struct aco_type **types, const char *name)
+{
+	struct ast_xml_doc_item *iter = config_info;
+
+	if (!iter) {
+		return NULL;
+	}
+	/* First is just the configInfo, we can skip it */
+	while ((iter = iter->next)) {
+		size_t x;
+		if (strcasecmp(iter->name, name)) {
+			continue;
+		}
+		for (x = 0; types[x]; x++) {
+			/* All we care about is that at least one type has the option */
+			if (!strcasecmp(types[x]->name, iter->ref)) {
+				return iter;
+			}
+		}
+	}
+	return NULL;
+}
+
+static struct ast_xml_doc_item *find_xmldoc_type(struct ast_xml_doc_item *config_info, const char *name)
+{
+	struct ast_xml_doc_item *iter = config_info;
+	if (!iter) {
+		return NULL;
+	}
+	/* First is just the config Info, skip it */
+	while ((iter = iter->next)) {
+		if (strcasecmp(iter->type, "configObject") || strcasecmp(iter->name, name)) {
+			continue;
+		}
+		break;
+	}
+	return iter;
+}
+#endif /* AST_XML_DOCS */
 
 int __aco_option_register(struct aco_info *info, const char *name, enum aco_matchtype matchtype, struct aco_type **types,
 	const char *default_val, enum aco_option_type kind, aco_option_handler handler, unsigned int flags, size_t argc, ...)
@@ -233,10 +287,25 @@
 		return -1;
 	};
 
-	if (link_option_to_types(types, opt)) {
+	if (link_option_to_types(info, types, opt)) {
 		ao2_ref(opt, -1);
 		return -1;
 	}
+
+#ifdef AST_XML_DOCS
+	{
+		RAII_VAR(struct ast_xml_doc_item *, config_info, ao2_find(xmldocs, info->module, OBJ_KEY), ao2_cleanup);
+		struct ast_xml_doc_item *config_option;
+
+		if (!config_info || !(config_option = find_xmldoc_option(config_info, types, name))) {
+			ast_log(LOG_ERROR, "XML Documentation for option '%s' in modules '%s' not found!\n", name, info->module);
+			ao2_ref(opt, -1);
+			return -1;
+		}
+		config_option->regex = opt->match_type == ACO_REGEX;
+		ast_string_field_set(config_option, default_value, opt->default_val);
+	}
+#endif /* AST_XML_DOCS */
 
 	return 0;
 }
@@ -533,10 +602,10 @@
 	}
 
 	if (res != ACO_PROCESS_OK) {
-	   goto end;
-	}
-
-	if (info->pre_apply_config && (info->pre_apply_config()))  {
+		goto end;
+	}
+
+	if (info->pre_apply_config && (info->pre_apply_config())) {
 		res = ACO_PROCESS_ERROR;
 		goto end;
 	}
@@ -644,19 +713,47 @@
 	return 0;
 }
 
+#ifdef AST_XML_DOCS
+static void update_runtime_type_docs(const char *module, struct aco_type *type)
+{
+	RAII_VAR(struct ast_xml_doc_item *, config_info, ao2_find(xmldocs, module, OBJ_KEY), ao2_cleanup);
+	struct ast_xml_doc_item *config_type;
+
+	if (!config_info || !(config_type = find_xmldoc_type(config_info, type->name))) {
+		return;
+	}
+	if (ast_str_strlen(config_type->syntax)) {
+		return;
+	}
+
+	ast_str_set(&config_type->syntax, 0, "category %s /%s/\n", type->category_match == ACO_WHITELIST ? "=~" : "!~", type->category);
+	if (!ast_strlen_zero(type->matchfield)) {
+		ast_str_append(&config_type->syntax, 0, "matchfield: %s = %s\n", type->matchfield, type->matchvalue);
+	}
+}
+#endif /* AST_XML_DOCS */
+
 int aco_info_init(struct aco_info *info)
 {
-	size_t x, y;
+	size_t x = 0, y = 0;
+	struct aco_file *file;
+	struct aco_type *type;
 
 	if (!(info->internal = ast_calloc(1, sizeof(*info->internal)))) {
 		return -1;
 	}
 
-	for (x = 0; info->files[x]; x++) {
-		for (y = 0; info->files[x]->types[y]; y++) {
-			if (internal_type_init(info->files[x]->types[y])) {
+	while ((file = info->files[x++])) {
+		while ((type = file->types[y++])) {
+			if (internal_type_init(type)) {
 				goto error;
 			}
+#ifdef AST_XML_DOCS
+			if (ast_xmldoc_update_config_type(info->module, type->name, type->category, type->matchfield, type->matchvalue, type->category_match == ACO_WHITELIST)) {
+				goto error;
+			}
+			update_runtime_type_docs(info->module, type);
+#endif /* AST_XML_DOCS */
 		}
 	}
 
@@ -709,6 +806,260 @@
 	}
 	ao2_iterator_destroy(&iter);
 
+	return 0;
+}
+
+#ifdef AST_XML_DOCS
+static char *complete_config_module(const char *word, int pos, int state)
+{
+	char *c = NULL;
+	size_t wordlen = strlen(word);
+	int which = 0;
+	struct ao2_iterator i;
+	struct ast_xml_doc_item *cur;
+
+	if (pos != 3) {
+		return NULL;
+	}
+
+	i = ao2_iterator_init(xmldocs, 0);
+	while ((cur = ao2_iterator_next(&i))) {
+		if (!strncasecmp(word, cur->name, wordlen) && ++which > state) {
+			c = ast_strdup(cur->name);
+			ao2_ref(cur, -1);
+			break;
+		}
+		ao2_ref(cur, -1);
+	}
+	ao2_iterator_destroy(&i);
+
+	return c;
+}
+
+static char *complete_config_type(const char *module, const char *word, int pos, int state)
+{
+	char *c = NULL;
+	size_t wordlen = strlen(word);
+	int which = 0;
+	RAII_VAR(struct ast_xml_doc_item *, info, NULL, ao2_cleanup);
+	struct ast_xml_doc_item *cur = info;
+
+	if (pos != 4) {
+		return NULL;
+	}
+
+	if (!(cur = ao2_find(xmldocs, module, OBJ_KEY))) {
+		return NULL;
+	}
+
+	while ((cur = cur->next)) {
+		if (!strcasecmp(cur->type, "configObject") && !strncasecmp(word, cur->name, wordlen) && ++which > state) {
+			c = ast_strdup(cur->name);
+			break;
+		}
+	}
+	return c;
+}
+
+static char *complete_config_option(const char *module, const char *option, const char *word, int pos, int state)
+{
+	char *c = NULL;
+	size_t wordlen = strlen(word);
+	int which = 0;
+	RAII_VAR(struct ast_xml_doc_item *, info, NULL, ao2_cleanup);
+	struct ast_xml_doc_item *cur = info;
+
+	if (pos != 5) {
+		return NULL;
+	}
+
+	if (!(cur = ao2_find(xmldocs, module, OBJ_KEY))) {
+		return NULL;
+	}
+
+	while ((cur = cur->next)) {
+		if (!strcasecmp(cur->type, "configOption") && !strcasecmp(cur->ref, option) && !strncasecmp(word, cur->name, wordlen) && ++which > state) {
+			c = ast_strdup(cur->name);
+			break;
+		}
+	}
+	return c;
+}
+
+static char *cli_show_types(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a)
+{
+	RAII_VAR(struct ast_xml_doc_item *, item, NULL, ao2_cleanup);
+	struct ast_xml_doc_item *tmp;
+
+	switch (cmd) {
+	case CLI_INIT:
+		e->command = "config show types";
+		e->usage =
+			"Usage: config show types <module>\n"
+			"   Lists a module's different config object types.\n";
+		return NULL;
+	case CLI_GENERATE:
+		return complete_config_module(a->word, a->pos, a->n);
+	}
+
+	if (a->argc != 4) {
+		return CLI_SHOWUSAGE;
+	}
+
+	if (!(item = ao2_find(xmldocs, a->argv[3], OBJ_KEY))) {
+		return NULL;
+	}
+	tmp = item;
+	while ((tmp = tmp->next)) {
+		if (!strcasecmp(tmp->type, "configObject")) {
+			ast_cli(a->fd, "%s\n", tmp->name);
+		}
+	}
+	return CLI_SUCCESS;
+}
+
+static char *cli_show_type(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a)
+{
+	RAII_VAR(struct ast_xml_doc_item *, item, NULL, ao2_cleanup);
+	struct ast_xml_doc_item *tmp;
+
+	switch (cmd) {
+	case CLI_INIT:
+		e->command = "config show type";
+		e->usage =
+			"Usage: config show type <module> <type>\n"
+			"   Display detailed information about a config object type.\n";
+		return NULL;
+	case CLI_GENERATE:
+		switch(a->pos) {
+		case 3: return complete_config_module(a->word, a->pos, a->n);
+		case 4: return complete_config_type(a->argv[3], a->word, a->pos, a->n);
+		default: return NULL;
+		}
+	}
+
+	if (a->argc != 5) {
+		return CLI_SHOWUSAGE;
+	}
+
+	if (!(item = ao2_find(xmldocs, a->argv[3], OBJ_KEY))) {
+		return NULL;
+	}
+	tmp = item;
+	while ((tmp = tmp->next)) {
+		if (!strcasecmp(tmp->type, "configObject") && !strcasecmp(tmp->name, a->argv[4])) {
+			ast_cli(a->fd, "%s\n", tmp->name);
+			ast_cli(a->fd, "Synopsis: %s\n", AS_OR(tmp->synopsis, "n/a"));
+			ast_cli(a->fd, "Description:\n\t%s\n", AS_OR(tmp->description, "n/a"));
+			ast_cli(a->fd, "Syntax:\n\t%s\n", AS_OR(tmp->syntax, "n/a"));
+		}
+	}
+
+	return CLI_SUCCESS;
+}
+
+static char *cli_show_options(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a)
+{
+	RAII_VAR(struct ast_xml_doc_item *, item, NULL, ao2_cleanup);
+	struct ast_xml_doc_item *tmp;
+
+	switch (cmd) {
+	case CLI_INIT:
+		e->command = "config show options";
+		e->usage =
+			"Usage: config show options <module> [<type>]\n"
+			"   Lists a module's different config options.\n";
+		return NULL;
+	case CLI_GENERATE:
+		switch(a->pos) {
+		case 3: return complete_config_module(a->word, a->pos, a->n);
+		case 4: return complete_config_type(a->argv[3], a->word, a->pos, a->n);
+		default: return NULL;
+		}
+	}
+
+	if (a->argc < 4 || a->argc > 5) {
+		return CLI_SHOWUSAGE;
+	}
+
+	if (!(item = ao2_find(xmldocs, a->argv[3], OBJ_KEY))) {
+		return NULL;
+	}
+	tmp = item;
+	while ((tmp = tmp->next)) {
+		if (!strcasecmp(tmp->type, "configOption") && (a->argc == 4 || !strcasecmp(tmp->ref, a->argv[4]))) {
+			ast_cli(a->fd, "<%s> %s\n", tmp->ref, tmp->name);
+		}
+	}
+
+	return CLI_SUCCESS;
+}
+
+static char *cli_show_option(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a)
+{
+	RAII_VAR(struct ast_xml_doc_item *, item, NULL, ao2_cleanup);
+	struct ast_xml_doc_item *tmp;
+
+	switch (cmd) {
+	case CLI_INIT:
+		e->command = "config show option";
+		e->usage =
+			"Usage: config show option <module> <type> <name>\n"
+			"   Display detailed information about a config option.\n";
+		return NULL;
+	case CLI_GENERATE:
+		switch(a->pos) {
+		case 3: return complete_config_module(a->word, a->pos, a->n);
+		case 4: return complete_config_type(a->argv[3], a->word, a->pos, a->n);
+		case 5: return complete_config_option(a->argv[3], a->argv[4], a->word, a->pos, a->n);
+		default: return NULL;
+		}
+	}
+
+	if (a->argc != 6) {
+		return CLI_SHOWUSAGE;
+	}
+
+	if (!(item = ao2_find(xmldocs, a->argv[3], OBJ_KEY))) {
+		return NULL;
+	}
+	tmp = item;
+	while ((tmp = tmp->next)) {
+		if (!strcasecmp(tmp->type, "configOption") && !strcasecmp(tmp->ref, a->argv[4]) && !strcasecmp(tmp->name, a->argv[5])) {
+			ast_cli(a->fd, "<%s> %s\n", tmp->ref, tmp->name);
+			ast_cli(a->fd, "Default: %s\n", S_OR(tmp->default_value, "n/a"));
+			ast_cli(a->fd, "Synopsis: %s\n", AS_OR(tmp->synopsis, "n/a"));
+			ast_cli(a->fd, "Description:\n\t%s\n", AS_OR(tmp->description, "n/a"));
+			ast_cli(a->fd, "Syntax:\n\t%s\n", AS_OR(tmp->syntax, "n/a"));
+		}
+	}
+	return CLI_SUCCESS;
+}
+
+static struct ast_cli_entry cli_aco[] = {
+	AST_CLI_DEFINE(cli_show_types, "List a module's config object types"),
+	AST_CLI_DEFINE(cli_show_type, "Display detailed information about a config object type"),
+	AST_CLI_DEFINE(cli_show_options, "List a module's config options"),
+	AST_CLI_DEFINE(cli_show_option, "Display detailed information about a config option"),
+};
+
+static void aco_deinit(void)
+{
+	ast_cli_unregister(cli_aco);
+	ao2_cleanup(xmldocs);
+}
+#endif /* AST_XML_DOCS */
+
+int aco_init(void)
+{
+#ifdef AST_XML_DOCS
+	ast_register_atexit(aco_deinit);
+	if (!(xmldocs = ast_xmldoc_build_documentation("configInfo"))) {
+		ast_log(LOG_ERROR, "Couldn't build config documentation\n");
+		return -1;
+	}
+	ast_cli_register_multiple(cli_aco, ARRAY_LEN(cli_aco));
+#endif /* AST_XML_DOCS */
 	return 0;
 }
 
@@ -728,7 +1079,7 @@
 			ast_parse_arg(var->value, flags, field, (int) opt->args[1], (int) opt->args[2]);
 		if (res) {
 			if (opt->flags & PARSE_RANGE_DEFAULTS) {
-				ast_log(LOG_WARNING, "Failed to set %s=%s. Set to  %d instead due to range limit (%d, %d)\n", var->name, var->value, *field, (int) opt->args[1], (int) opt->args[2]);
+				ast_log(LOG_WARNING, "Failed to set %s=%s. Set to %d instead due to range limit (%d, %d)\n", var->name, var->value, *field, (int) opt->args[1], (int) opt->args[2]);
 				res = 0;
 			} else if (opt->flags & PARSE_DEFAULT) {
 				ast_log(LOG_WARNING, "Failed to set %s=%s, Set to default value %d instead.\n", var->name, var->value, *field);
@@ -758,7 +1109,7 @@
 			ast_parse_arg(var->value, flags, field, (unsigned int) opt->args[1], (unsigned int) opt->args[2]);
 		if (res) {
 			if (opt->flags & PARSE_RANGE_DEFAULTS) {
-				ast_log(LOG_WARNING, "Failed to set %s=%s. Set to  %d instead due to range limit (%d, %d)\n", var->name, var->value, *field, (int) opt->args[1], (int) opt->args[2]);
+				ast_log(LOG_WARNING, "Failed to set %s=%s. Set to %d instead due to range limit (%d, %d)\n", var->name, var->value, *field, (int) opt->args[1], (int) opt->args[2]);
 				res = 0;
 			} else if (opt->flags & PARSE_DEFAULT) {
 				ast_log(LOG_WARNING, "Failed to set %s=%s, Set to default value %d instead.\n", var->name, var->value, *field);

Modified: team/group/aco_xmldocs/main/xml.c
URL: http://svnview.digium.com/svn/asterisk/team/group/aco_xmldocs/main/xml.c?view=diff&rev=373739&r1=373738&r2=373739
==============================================================================
--- team/group/aco_xmldocs/main/xml.c (original)
+++ team/group/aco_xmldocs/main/xml.c Tue Sep 25 16:13:56 2012
@@ -28,6 +28,7 @@
 #include "asterisk.h"
 #include "asterisk/xml.h"
 #include "asterisk/logger.h"
+#include "asterisk/utils.h"
 
 ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
 
@@ -35,6 +36,7 @@
 #include <libxml/parser.h>
 #include <libxml/tree.h>
 #include <libxml/xinclude.h>
+#include <libxml/xpath.h>
 /* libxml2 ast_xml implementation. */
 
 
@@ -316,5 +318,42 @@
 	return (struct ast_xml_node *) ((xmlNode *) node)->parent;
 }
 
+struct ast_xml_node *ast_xml_xpath_get_first_result(struct ast_xml_xpath_results *results)
+{
+	return (struct ast_xml_node *) ((xmlXPathObjectPtr) results)->nodesetval->nodeTab[0];
+}
+
+void ast_xml_xpath_results_free(struct ast_xml_xpath_results *results)
+{
+	xmlXPathFreeObject((xmlXPathObjectPtr) results);
+}
+
+int ast_xml_xpath_num_results(struct ast_xml_xpath_results *results)
+{
+	return ((xmlXPathObjectPtr) results)->nodesetval->nodeNr;
+}
+
+struct ast_xml_xpath_results *ast_xml_query(struct ast_xml_doc *doc, const char *xpath_str)
+{
+	xmlXPathContextPtr context;
+	xmlXPathObjectPtr result;
+	if (!(context = xmlXPathNewContext((xmlDoc *) doc))) {
+		ast_log(LOG_ERROR, "Could not create XPath context!\n");
+		return NULL;
+	}
+	result = xmlXPathEvalExpression((xmlChar *) xpath_str, context);
+	xmlXPathFreeContext(context);
+	if (!result) {
+		ast_log(LOG_WARNING, "Error for query: %s\n", xpath_str);
+		return NULL;
+	}
+	if (xmlXPathNodeSetIsEmpty(result->nodesetval)) {
+		xmlXPathFreeObject(result);
+		ast_debug(5, "No results for query: %s\n", xpath_str);
+		return NULL;
+	}
+	return (struct ast_xml_xpath_results *) result;
+}
+
 #endif /* defined(HAVE_LIBXML2) */
 

Modified: team/group/aco_xmldocs/main/xmldoc.c
URL: http://svnview.digium.com/svn/asterisk/team/group/aco_xmldocs/main/xmldoc.c?view=diff&rev=373739&r1=373738&r2=373739
==============================================================================
--- team/group/aco_xmldocs/main/xmldoc.c (original)
+++ team/group/aco_xmldocs/main/xmldoc.c Tue Sep 25 16:13:56 2012
@@ -38,6 +38,7 @@
 #include "asterisk/term.h"
 #include "asterisk/astobj2.h"
 #include "asterisk/xmldoc.h"
+#include "asterisk/cli.h"
 
 #ifdef AST_XML_DOCS
 
@@ -1112,11 +1113,60 @@
 	return ret;
 }
 
+static char *xmldoc_get_syntax_config_object(struct ast_xml_node *fixnode, const char *name)
+{
+	struct ast_xml_node *matchinfo, *tmp;
+	int match;
+	const char *text;
+	RAII_VAR(struct ast_str *, syntax, ast_str_create(128), ast_free);
+
+	if (!syntax) {
+		return NULL;
+	}
+	if (!(matchinfo = ast_xml_find_element(ast_xml_node_get_children(fixnode), "matchInfo", NULL, NULL))) {
+		return NULL;
+	}
+	if (!(tmp  = ast_xml_find_element(ast_xml_node_get_children(matchinfo), "category", NULL, NULL))) {
+		return NULL;
+	}
+	match = ast_true(ast_xml_get_attribute(tmp, "match"));
+	text = ast_xml_get_text(tmp);
+	ast_str_set(&syntax, 0, "category %s /%s/\n", match ? "=~" : "!~", text);
+
+	if ((tmp = ast_xml_find_element(ast_xml_node_get_children(matchinfo), "field", NULL, NULL))) {
+		text = ast_xml_get_text(tmp);
+		ast_str_append(&syntax, 0, "matchfield: %s = %s\n", ast_xml_get_attribute(tmp, "name"), text);
+	}
+	return ast_strdup(ast_str_buffer(syntax));
+}
+
+static char *xmldoc_get_syntax_config_option(struct ast_xml_node *fixnode, const char *name)
+{
+	struct ast_xml_node *node;
+	const char *text;
+	RAII_VAR(struct ast_str *, syntax, ast_str_create(128), ast_free);
+
+	if (!syntax) {
+		return NULL;
+	}
+	if (!(node = ast_xml_find_element(ast_xml_node_get_children(fixnode), "dataType", NULL, NULL))) {
+		return NULL;
+	}
+	text = ast_xml_get_attribute(node, "type");
+	ast_str_set(&syntax, 0, "type: %s\n", text);
+	return ast_strdup(ast_str_buffer(syntax));
+
+	return NULL;
+}
+
 /*! \brief Types of syntax that we are able to generate. */
 enum syntaxtype {
 	FUNCTION_SYNTAX,
 	MANAGER_SYNTAX,
 	MANAGER_EVENT_SYNTAX,
+	CONFIG_INFO_SYNTAX,
+	CONFIG_OPTION_SYNTAX,
+	CONFIG_OBJECT_SYNTAX,
 	COMMAND_SYNTAX
 };
 
@@ -1125,11 +1175,14 @@
 	const char *type;
 	enum syntaxtype stxtype;
 } stxtype[] = {
-	{ "function",		FUNCTION_SYNTAX			},
-	{ "application",	FUNCTION_SYNTAX			},
-	{ "manager",		MANAGER_SYNTAX			},
-	{ "managerEvent",	MANAGER_EVENT_SYNTAX	},
-	{ "agi",			COMMAND_SYNTAX			}
+    { "function",     FUNCTION_SYNTAX      },
+    { "application",  FUNCTION_SYNTAX      },
+    { "manager",      MANAGER_SYNTAX       },
+    { "managerEvent", MANAGER_EVENT_SYNTAX },
+    { "configInfo",   CONFIG_INFO_SYNTAX   },
+    { "configOption", CONFIG_OPTION_SYNTAX },
+    { "configObject", CONFIG_OBJECT_SYNTAX },
+    { "agi",          COMMAND_SYNTAX       },
 };
 
 /*! \internal
@@ -1190,6 +1243,12 @@
 		break;
 	case MANAGER_EVENT_SYNTAX:
 		syntax = xmldoc_get_syntax_manager(node, name, "Event");
+		break;
+	case CONFIG_OPTION_SYNTAX:
+		syntax = xmldoc_get_syntax_config_option(node, name);
+		break;
+	case CONFIG_OBJECT_SYNTAX:
+		syntax = xmldoc_get_syntax_config_object(node, name);
 		break;
 	default:
 		syntax = xmldoc_get_syntax_fun(node, name, "parameter", 1, 1);
@@ -2222,6 +2281,127 @@
 	ast_free(description);
 
 	return item;
+}
+
+static struct ast_xml_xpath_results * __attribute__((format(printf, 1, 2))) query_xmldocs(const char *fmt, ...)
+{
+	struct ast_xml_xpath_results *results = NULL;
+	struct documentation_tree *doctree;
+	RAII_VAR(struct ast_str *, xpath_str, ast_str_create(128), ast_free);
+	va_list ap;
+
+	if (!xpath_str) {
+		return NULL;
+	}
+
+	va_start(ap, fmt);
+	ast_str_set_va(&xpath_str, 0, fmt, ap);
+	va_end(ap);
+
+	AST_RWLIST_RDLOCK(&xmldoc_tree);
+	AST_LIST_TRAVERSE(&xmldoc_tree, doctree, entry) {
+		if (!(results = ast_xml_query(doctree->doc, ast_str_buffer(xpath_str)))) {
+			continue;
+		}
+		break;
+	}
+	AST_RWLIST_UNLOCK(&xmldoc_tree);
+
+	return results;
+}
+
+#define XMLDOC_STRICT 0
+int ast_xmldoc_update_config_type(const char *module, const char *name, const char *category, const char *matchfield, const char *matchvalue, unsigned int matches)
+{
+	RAII_VAR(struct ast_xml_xpath_results *, results, NULL, ast_xml_xpath_results_free);
+	struct ast_xml_node *type, *syntax, *matchinfo, *tmp;
+
+	/* If we already have a syntax element, bail. This isn't an error, since we may unload a module which
+	 * has updated the docs and then load it again. */
+	if ((results = query_xmldocs("//configInfo[@name='%s']/*/configObject[@name='%s']/syntax", name, module))) {
+		return 0;
+	}
+
+	if (!(results = query_xmldocs("//configInfo[@name='%s']/*/configObject[@name='%s']", name, module))) {
+		ast_log(LOG_WARNING, "Cannot update type '%s' in module '%s' because it has no existing documentation!\n", name, module);
+		return XMLDOC_STRICT ? -1 : 0;
+	}
+
+	if (!(type = ast_xml_xpath_get_first_result(results))) {
+		ast_log(LOG_WARNING, "Could not retrieve documentation for type '%s' in module '%s'\n", name, module);
+		return XMLDOC_STRICT ? -1 : 0;
+	}
+
+	if (!(syntax = ast_xml_new_child(type, "syntax"))) {
+		ast_log(LOG_WARNING, "Could not create syntax node for type '%s' in module '%s'\n", name, module);
+		return -1;
+	}
+
+	if (!(matchinfo = ast_xml_new_child(syntax, "matchInfo"))) {
+		ast_log(LOG_WARNING, "Could not create matchInfo node for type '%s' in module '%s'\n", name, module);
+		return -1;
+	}
+
+	if (!(tmp = ast_xml_new_child(matchinfo, "category"))) {
+		ast_log(LOG_WARNING, "Could not create category node for type '%s' in module '%s'\n", name, module);
+		return -1;
+	}
+
+	ast_xml_set_text(tmp, category);
+	ast_xml_set_attribute(tmp, "match", matches ? "true" : "false");
+
+	if (!ast_strlen_zero(matchfield) && !(tmp = ast_xml_new_child(matchinfo, "field"))) {
+		ast_log(LOG_WARNING, "Could not add %s attribute for type '%s' in module '%s'\n", matchfield, name, module);
+		return -1;
+	}
+
+	ast_xml_set_attribute(tmp, "name", matchfield);
+	ast_xml_set_text(tmp, matchvalue);
+
+	return 0;
+}
+
+int ast_xmldoc_update_config_option(const char *module, const char *name, const char *object_name, const char *default_value, unsigned int regex)
+{
+	RAII_VAR(struct ast_xml_xpath_results *, results, NULL, ast_xml_xpath_results_free);
+	struct ast_xml_node *option;
+
+	if (!(results = query_xmldocs("//configInfo[@name='%s']/*/configObject[@name='%s']/configOption[@name='%s']", module, object_name, name))) {
+		ast_log(LOG_WARNING, "Could not find option '%s' with type '%s' in module '%s'\n", name, object_name, module);
+		return XMLDOC_STRICT ? -1 : 0;
+	}
+
+	if (!(option = ast_xml_xpath_get_first_result(results))) {
+		ast_log(LOG_WARNING, "Could objtain results for option '%s' with type '%s' in module '%s'\n", name, object_name, module);
+		return XMLDOC_STRICT ? -1 : 0;
+	}
+	ast_xml_set_attribute(option, "regex", regex ? "true" : "false");
+	ast_xml_set_attribute(option, "default", default_value);
+
+	return 0;
+}
+
+static void build_config_docs(struct ast_xml_node *cur, struct ast_xml_doc_item **tail)
+{
+	struct ast_xml_node *iter;
+	struct ast_xml_doc_item *item;
+
+	for (iter = ast_xml_node_get_children(cur); iter; iter = ast_xml_node_get_next(iter)) {
+		if (strncasecmp(ast_xml_node_get_name(iter), "config", 6)) {
+			continue;
+		}
+		/* Now add all of the child config-related items to the list */
+		if (!(item = xmldoc_build_documentation_item(iter, ast_xml_get_attribute(iter, "name"), ast_xml_node_get_name(iter)))) {
+			ast_log(LOG_ERROR, "Could not build documentation for '%s:%s'\n", ast_xml_node_get_name(iter), ast_xml_get_attribute(iter, "name"));
+			break;
+		}
+		if (!strcasecmp(ast_xml_node_get_name(iter), "configOption")) {
+			ast_string_field_set(item, ref, ast_xml_get_attribute(cur, "name"));
+		}
+		(*tail)->next = item;
+		*tail = (*tail)->next;
+		build_config_docs(iter, tail);
+	}
 }
 
 struct ao2_container *ast_xmldoc_build_documentation(const char *type)
@@ -2273,6 +2453,19 @@
 				}
 				item = root;
 				break;
+			case CONFIG_INFO_SYNTAX:
+			{
+				struct ast_xml_doc_item *tail;
+				if (item || !ast_xml_node_get_children(node) || strcasecmp(ast_xml_node_get_name(node), "configInfo")) {
+					break;
+				}
+				if (!(item = xmldoc_build_documentation_item(node, ast_xml_get_attribute(node, "name"), "configInfo"))) {
+					break;
+				}
+				tail = item;
+				build_config_docs(node, &tail);
+				break;
+			}
 			default:
 				item = xmldoc_build_documentation_item(node, name, type);
 			}
@@ -2331,10 +2524,47 @@
 }
 #endif
 
+
+static char *handle_dump_docs(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a)
+{
+	struct documentation_tree *doctree;
+	FILE *f;
+
+	switch (cmd) {
+	case CLI_INIT:
+		e->command = "xmldoc dump";
+		e->usage =
+			"Usage: xmldoc dump <filename>\n"
+			"  Dump XML documentation to a file\n";
+		return NULL;
+	case CLI_GENERATE:
+		return NULL;
+	}
+
+	if (a->argc != 3) {
+		return CLI_SHOWUSAGE;
+	}
+	if (!(f = fopen(a->argv[2], "w"))) {
+		ast_log(LOG_ERROR, "Could not open file '%s': %s\n", a->argv[2], strerror(errno));
+		return CLI_FAILURE;
+	}
+	AST_RWLIST_RDLOCK(&xmldoc_tree);
+	AST_LIST_TRAVERSE(&xmldoc_tree, doctree, entry) {
+		ast_xml_doc_dump_file(f, doctree->doc);
+	}
+	AST_RWLIST_UNLOCK(&xmldoc_tree);
+	fclose(f);
+	return CLI_SUCCESS;
+}
+
+static struct ast_cli_entry cli_dump_xmldocs = AST_CLI_DEFINE(handle_dump_docs, "Dump the XML docs to the specified file");
+
 /*! \brief Close and unload XML documentation. */
 static void xmldoc_unload_documentation(void)
 {
-        struct documentation_tree *doctree;
+	struct documentation_tree *doctree;
+
+	ast_cli_unregister(&cli_dump_xmldocs);
 
 	AST_RWLIST_WRLOCK(&xmldoc_tree);
 	while ((doctree = AST_RWLIST_REMOVE_HEAD(&xmldoc_tree, entry))) {
@@ -2378,6 +2608,7 @@
 	/* initialize the XML library. */
 	ast_xml_init();
 
+	ast_cli_register(&cli_dump_xmldocs);
 	/* register function to be run when asterisk finish. */
 	ast_register_atexit(xmldoc_unload_documentation);
 




More information about the asterisk-commits mailing list