Revision: 65121
http://sourceforge.net/p/brlcad/code/65121
Author: starseeker
Date: 2015-05-31 16:19:26 +0000 (Sun, 31 May 2015)
Log Message:
-----------
Get dynamic option handling working, and add a demonstration in gcv
Modified Paths:
--------------
brlcad/trunk/include/bu/opt.h
brlcad/trunk/src/conv/gcv/gcv.cpp
brlcad/trunk/src/libbu/opt.c
Modified: brlcad/trunk/include/bu/opt.h
===================================================================
--- brlcad/trunk/include/bu/opt.h 2015-05-30 19:42:02 UTC (rev 65120)
+++ brlcad/trunk/include/bu/opt.h 2015-05-31 16:19:26 UTC (rev 65121)
@@ -51,6 +51,8 @@
*/
typedef int (*bu_opt_arg_process_t)(struct bu_vls *, struct bu_opt_data *);
+typedef struct bu_ptbl bu_opt_dtbl_t;
+
/**
* "Option description" structure
*/
@@ -67,20 +69,30 @@
};
#define BU_OPT_DESC_NULL {-1, 0, 0, NULL, NULL, NULL, NULL, NULL, NULL}
-/** Set the values in a struct bu_opt_desc */
-BU_EXPORT extern void bu_opt_desc_add(struct bu_ptbl *dtbl, int ind,
+/**
+ * Initialize a bu_opt_desc ptbl. If ds is not NULL, populate the table
+ * with the bu_opt_desc structs in the ds array. */
+BU_EXPORT extern void bu_opt_desc_init(bu_opt_dtbl_t **dtbl, struct
bu_opt_desc *ds);
+
+/**
+ * Add an option description to a bu_opt_desc ptbl */
+BU_EXPORT extern void bu_opt_desc_add(bu_opt_dtbl_t *dtbl, int ind,
size_t min, size_t max, const char *shortopt,
const char *longopt, bu_opt_arg_process_t arg_process,
const char *shortopt_doc, const char *longopt_doc, const char
*help_str);
-
-BU_EXPORT extern void bu_opt_desc_del(struct bu_ptbl *dtbl, int ind);
-BU_EXPORT extern void bu_opt_desc_del_name(struct bu_ptbl *dtbl, const char
*name);
/**
+ * Remove option descriptions with index matching key from a bu_opt_desc ptbl
*/
+BU_EXPORT extern void bu_opt_desc_del(bu_opt_dtbl_t *dtbl, int key);
+/**
+ * Remove option descriptions with either a shortopt or a longopt matching name
+ * from a bu_opt_desc ptbl */
+BU_EXPORT extern void bu_opt_desc_del_name(bu_opt_dtbl_t *dtbl, const char
*name);
+/**
* If bu_opt_desc instances aren't part of static arrays, they'll
* be inside a table. This function frees them, but does not free
* the bu_ptbl container.
*/
-BU_EXPORT extern void bu_opt_desc_free(struct bu_ptbl *tbl);
+BU_EXPORT extern void bu_opt_desc_free(bu_opt_dtbl_t *tbl);
/**
@@ -176,19 +188,19 @@
*
* enum d1_opt_ind {D1_HELP, D1_VERBOSITY};
* struct bu_ptbl dtbl;
- * BU_OPT_DESC_PTBL_INIT(4, &dtbl);
- * bu_opt_desc_set(BU_OPT_DESC_GET_PTBL(0), D1_HELP, 0, 0, "h", "help", NULL,
help_str);
- * bu_opt_desc_set(BU_OPT_DESC_GET_PTBL(1), D1_HELP, 0, 0, "?", "", NULL,
help_str);
- * bu_opt_desc_set(BU_OPT_DESC_GET_PTBL(2), D1_VERBOSITY, 0, 1, "v",
"verbosity", &(dtbl_verbosity), "Set verbosity");
+ * bu_opt_desc_init(&dtbl, NULL);
+ * bu_opt_desc_add(D1_HELP, 0, 0, "h", "help", NULL, "-h", "--help",
"Help");
+ * bu_opt_desc_add(D1_HELP, 0, 0, "?", "", NULL, "-?", "",
"");
+ * bu_opt_desc_add(D1_O1, 0, 1, "v", "verbose", &(dtbl_v), "-v",
"--verbose", "Set verbosity");
* bu_opt_parse_ptbl(argc, argv, dtbl);
*/
-BU_EXPORT extern int bu_opt_parse_dtbl(struct bu_ptbl **tbl, struct bu_vls
*msgs, int ac, const char **argv, struct bu_ptbl *dtbl);
+BU_EXPORT extern int bu_opt_parse_dtbl(struct bu_ptbl **tbl, struct bu_vls
*msgs, int ac, const char **argv, bu_opt_dtbl_t *dtbl);
/**
* Option parse an argv array defined as a space separated string. This
* is a convenience function that calls bu_opt_parse_dtbl and also handles
* breaking str down into a proper argv array. */
-BU_EXPORT extern int bu_opt_parse_str_dtbl(struct bu_ptbl **tbl, struct bu_vls
*msgs, const char *str, struct bu_ptbl *dtbl);
+BU_EXPORT extern int bu_opt_parse_str_dtbl(struct bu_ptbl **tbl, struct bu_vls
*msgs, const char *str, bu_opt_dtbl_t *dtbl);
/**
* In situations where multiple options are present, the general rule is that
@@ -292,7 +304,7 @@
};
BU_EXPORT extern const char *bu_opt_describe(struct bu_opt_desc *ds, struct
bu_opt_desc_opts *settings);
-BU_EXPORT extern const char *bu_opt_describe_tbl(struct bu_ptbl *dtbl, struct
bu_opt_desc_opts *settings);
+BU_EXPORT extern const char *bu_opt_describe_dtbl(bu_opt_dtbl_t *dtbl, struct
bu_opt_desc_opts *settings);
Modified: brlcad/trunk/src/conv/gcv/gcv.cpp
===================================================================
--- brlcad/trunk/src/conv/gcv/gcv.cpp 2015-05-30 19:42:02 UTC (rev 65120)
+++ brlcad/trunk/src/conv/gcv/gcv.cpp 2015-05-31 16:19:26 UTC (rev 65121)
@@ -266,7 +266,7 @@
#define gcv_help_str "Print help and exit. If a format is specified to
--help, print help specific to that format"
-enum gcv_opt_enums { GCV_HELP, IN_FILE, OUT_FILE, IN_FORMAT, OUT_FORMAT,
IN_OPTS, OUT_OPTS };
+enum gcv_opt_enums { GCV_HELP, IN_FILE, OUT_FILE, IN_FORMAT, OUT_FORMAT,
IN_OPTS, OUT_OPTS, GCV_OPTS_MAX };
struct bu_opt_desc gcv_opt_desc[9] = {
{GCV_HELP, 0, 1, "h", "help", NULL, "-h [format]",
"--help [format]", gcv_help_str},
{GCV_HELP, 0, 1, "?", "", NULL, "-? [format]",
"", ""},
@@ -286,6 +286,7 @@
size_t i;
int fmt = 0;
int ret = 0;
+ bu_opt_dtbl_t *top_opt_desc;
const char *in_fmt = NULL;
const char *out_fmt = NULL;
mime_model_t in_type = MIME_MODEL_UNKNOWN;
@@ -305,15 +306,20 @@
ac-=(ac>0); av+=(ac>0); // skip program name argv[0] if present
+ bu_opt_desc_init(&top_opt_desc, (struct bu_opt_desc *)&gcv_opt_desc);
+
if (ac == 0) {
- const char *help = bu_opt_describe(gcv_opt_desc, NULL);
+ const char *help = bu_opt_describe_dtbl(top_opt_desc, NULL);
bu_log("%s\n", help);
- bu_free((char *)help, "help str");
+ if (help) bu_free((char *)help, "help str");
// TODO - print some help
goto cleanup;
}
+ /*
(void)bu_opt_parse(&results, NULL, ac, (const char **)av, gcv_opt_desc);
+ */
+ (void)bu_opt_parse_dtbl(&results, NULL, ac, (const char **)av,
top_opt_desc);
bu_opt_compact(results);
/* First, see if help was supplied */
@@ -324,7 +330,49 @@
// TODO - generate some help based on format
} else {
// TODO - generate some generic gcv help
+ { /* Test static help print */
+ bu_log("Static help printing:\n");
+ const char *help = bu_opt_describe(gcv_opt_desc, NULL);
+ bu_log("%s\n", help);
+ if (help) bu_free((char *)help, "help str");
+ }
+ { /* Test help print before dynamic opts */
+ bu_log("Dynamic help printing:\n");
+ const char *help = bu_opt_describe_dtbl(top_opt_desc, NULL);
+ bu_log("%s\n", help);
+ if (help) bu_free((char *)help, "help str");
+ }
+
+ /* Simulate a plug-in adding a new option to the toplevel options */
+
+ bu_opt_desc_add(top_opt_desc, BU_PTBL_LEN(top_opt_desc) + 1, 0, 1,
"", "decimate", NULL, "", "--decimate [algorithm]",
+ "Decimate output triangles. If an algorithm is supplied
use it, otherwise use FOO");
+
+ int parallel_key = BU_PTBL_LEN(top_opt_desc) + 1;
+ bu_opt_desc_add(top_opt_desc, parallel_key, 0, 0, "p", "parallel",
NULL, "-p", "--parallel", "Enable parallel processing");
+ bu_opt_desc_add(top_opt_desc, parallel_key, 0, 0, "P", "", NULL,
"-P", "", "");
+
+ { /* Test help print with dynamic opts added */
+ bu_log("Dynamic help printing with added opts:\n");
+ const char *help = bu_opt_describe_dtbl(top_opt_desc, NULL);
+ bu_log("%s\n", help);
+ if (help) bu_free((char *)help, "help str");
+ }
+
+ bu_opt_desc_del(top_opt_desc, IN_OPTS);
+ bu_opt_desc_del(top_opt_desc, OUT_OPTS);
+ bu_opt_desc_del_name(top_opt_desc, "P");
+
+ { /* Test help print with dynamic opts removed */
+ bu_log("Dynamic help printing with removed opts:\n");
+ const char *help = bu_opt_describe_dtbl(top_opt_desc, NULL);
+ bu_log("%s\n", help);
+ if (help) bu_free((char *)help, "help str");
+ }
+
+#if 0
+
// TODO - figure out how to get this info from each plugin to
construct this table
// on the fly...
bu_log("\nSupported formats:\n");
@@ -340,7 +388,7 @@
bu_log(" | iges | Initial Graphics | Yes | No
|\n");
bu_log(" | | Exchange Specification | |
|\n");
bu_log("
|----------------------------------------------------------|\n");
-
+#endif
}
goto cleanup;
}
@@ -516,6 +564,7 @@
bu_vls_free(&input_opts);
bu_vls_free(&output_opts);
bu_opt_data_free_tbl(results);
+ if (top_opt_desc) bu_opt_desc_free(top_opt_desc);
return ret;
}
Modified: brlcad/trunk/src/libbu/opt.c
===================================================================
--- brlcad/trunk/src/libbu/opt.c 2015-05-30 19:42:02 UTC (rev 65120)
+++ brlcad/trunk/src/libbu/opt.c 2015-05-31 16:19:26 UTC (rev 65121)
@@ -483,8 +483,56 @@
}
void
-bu_opt_desc_init(struct bu_opt_desc *d)
+bu_opt_data_print(const char *title, struct bu_ptbl *data)
{
+ size_t i = 0;
+ size_t j = 0;
+ int offset_1 = 3;
+ struct bu_vls log = BU_VLS_INIT_ZERO;
+ if (!data || BU_PTBL_LEN(data) == 0) return;
+ if (title) {
+ bu_vls_sprintf(&log, "%s\n", title);
+ } else {
+ bu_vls_sprintf(&log, "Options:\n");
+ }
+ for (i = 0; i < BU_PTBL_LEN(data); i++) {
+ struct bu_opt_data *d = (struct bu_opt_data *)BU_PTBL_GET(data, i);
+ if (d->name) {
+ bu_vls_printf(&log, "%*s%s", offset_1, " ", d->name);
+ if (d->valid) {
+ bu_vls_printf(&log, "\t(valid)");
+ } else {
+ bu_vls_printf(&log, "\t(invalid)");
+ }
+ if (d->desc && d->desc->arg_cnt_max > 0) {
+ if (d->args && BU_PTBL_LEN(d->args) > 0) {
+ bu_vls_printf(&log, ": ");
+ for (j = 0; j < BU_PTBL_LEN(d->args) - 1; j++) {
+ bu_vls_printf(&log, "%s, ", bu_opt_data_arg(d, j));
+ }
+ bu_vls_printf(&log, "%s\n", bu_opt_data_arg(d,
BU_PTBL_LEN(d->args) - 1));
+ }
+ } else {
+ bu_vls_printf(&log, "\n");
+ }
+ } else {
+ bu_vls_printf(&log, "%*s(unknown): ", offset_1, " ", d->name);
+ if (d->args && BU_PTBL_LEN(d->args) > 0) {
+ for (j = 0; j < BU_PTBL_LEN(d->args) - 1; j++) {
+ bu_vls_printf(&log, "%s ", bu_opt_data_arg(d, j));
+ }
+ bu_vls_printf(&log, "%s\n", bu_opt_data_arg(d,
BU_PTBL_LEN(d->args) - 1));
+ }
+ }
+ }
+ bu_log("%s", bu_vls_addr(&log));
+ bu_vls_free(&log);
+}
+
+
+HIDDEN void
+bu_opt_desc_init_entry(struct bu_opt_desc *d)
+{
if (!d) return;
d->index = -1;
d->arg_cnt_min = 0;
@@ -497,7 +545,7 @@
d->help_string = NULL;
}
-void
+HIDDEN void
bu_opt_desc_set(struct bu_opt_desc *d, int ind,
size_t min, size_t max, const char *shortopt,
const char *longopt, bu_opt_arg_process_t arg_process,
@@ -507,12 +555,12 @@
d->index = ind;
d->arg_cnt_min = min;
d->arg_cnt_max = max;
- d->shortopt = shortopt;
- d->longopt = longopt;
+ d->shortopt = (shortopt) ? bu_strdup(shortopt) : NULL;
+ d->longopt = (longopt) ? bu_strdup(longopt) : NULL;
d->arg_process = arg_process;
- d->shortopt_doc= shortopt_doc;
- d->longopt_doc = longopt_doc;
- d->help_string = help_str;
+ d->shortopt_doc = (shortopt_doc) ? bu_strdup(shortopt_doc) : NULL;
+ d->longopt_doc = (longopt_doc) ? bu_strdup(longopt_doc) : NULL;
+ d->help_string = (help_str) ? bu_strdup(help_str) : NULL;;
}
HIDDEN void
@@ -525,10 +573,97 @@
if (d->longopt_doc) bu_free((char *)d->longopt_doc, "longopt_doc");
if (d->help_string) bu_free((char *)d->help_string, "help_string");
}
+void
+bu_opt_desc_init(bu_opt_dtbl_t **dtbl, struct bu_opt_desc *ds)
+{
+ size_t i;
+ size_t array_cnt = 0;
+ struct bu_opt_desc *cd;
+ struct bu_ptbl *tbl;
+ if (!dtbl) return;
+ BU_GET(tbl, struct bu_ptbl);
+ if (!(!ds || ds[0].index == -1)) {
+ while (ds[array_cnt].index != -1) array_cnt++;
+ bu_ptbl_init(tbl, array_cnt + 1, "new ptbl");
+ for (i = 0; i < array_cnt; i++) {
+ struct bu_opt_desc *d = &(ds[i]);
+ BU_GET(cd, struct bu_opt_desc);
+ bu_opt_desc_init_entry(cd);
+ bu_opt_desc_set(cd, d->index, d->arg_cnt_min, d->arg_cnt_max,
+ d->shortopt, d->longopt, d->arg_process,
+ d->shortopt_doc, d->longopt_doc,
+ d->help_string);
+ bu_ptbl_ins(tbl, (long *)cd);
+ }
+ } else {
+ bu_ptbl_init(tbl, 8, "new ptbl");
+ }
+ BU_GET(cd, struct bu_opt_desc);
+ bu_opt_desc_init_entry(cd);
+ bu_ptbl_ins(tbl, (long *)cd);
+ (*dtbl) = tbl;
+}
void
-bu_opt_desc_free(struct bu_ptbl *tbl)
+bu_opt_desc_add(bu_opt_dtbl_t *tbl, int ind, size_t min, size_t max, const
char *shortopt,
+ const char *longopt, bu_opt_arg_process_t arg_process,
+ const char *shortopt_doc, const char *longopt_doc, const char *help_str)
{
+ struct bu_opt_desc *d;
+ long *dn;
+ if (!tbl || (!shortopt && !longopt)) return;
+ dn = BU_PTBL_GET(tbl, BU_PTBL_LEN(tbl) - 1);
+ bu_ptbl_rm(tbl, dn);
+ BU_GET(d, struct bu_opt_desc);
+ bu_opt_desc_init_entry(d);
+ bu_opt_desc_set(d, ind, min, max, shortopt,
+ longopt, arg_process, shortopt_doc,
+ longopt_doc, help_str);
+ bu_ptbl_ins(tbl, (long *)d);
+ bu_ptbl_ins(tbl, dn);
+}
+
+void
+bu_opt_desc_del(bu_opt_dtbl_t *tbl, int key)
+{
+ size_t i = 0;
+ struct bu_ptbl tmp_tbl;
+ if (!tbl || key < 0) return;
+ bu_ptbl_init(&tmp_tbl, 64, "tmp tbl");
+ for (i = 0; i < BU_PTBL_LEN(tbl) - 1; i++) {
+ struct bu_opt_desc *d = (struct bu_opt_desc *)BU_PTBL_GET(tbl, i);
+ if (d->index == key) {
+ bu_ptbl_ins(&tmp_tbl, (long *)d);
+ }
+ }
+ for (i = 0; i < BU_PTBL_LEN(&tmp_tbl); i++) {
+ bu_ptbl_rm(tbl, BU_PTBL_GET(&tmp_tbl, i));
+ }
+ bu_ptbl_free(&tmp_tbl);
+}
+
+void
+bu_opt_desc_del_name(bu_opt_dtbl_t *tbl, const char *name)
+{
+ size_t i = 0;
+ struct bu_ptbl tmp_tbl;
+ if (!tbl || !name) return;
+ bu_ptbl_init(&tmp_tbl, 64, "tmp tbl");
+ for (i = 0; i < BU_PTBL_LEN(tbl) - 1; i++) {
+ struct bu_opt_desc *d = (struct bu_opt_desc *)BU_PTBL_GET(tbl, i);
+ if (BU_STR_EQUAL(d->shortopt, name) || BU_STR_EQUAL(d->longopt, name)) {
+ bu_ptbl_ins(&tmp_tbl, (long *)d);
+ }
+ }
+ for (i = 0; i < BU_PTBL_LEN(&tmp_tbl); i++) {
+ bu_ptbl_rm(tbl, BU_PTBL_GET(&tmp_tbl, i));
+ }
+ bu_ptbl_free(&tmp_tbl);
+}
+
+void
+bu_opt_desc_free(bu_opt_dtbl_t *tbl)
+{
size_t i;
if (!tbl) return;
for (i = 0; i < BU_PTBL_LEN(tbl); i++) {
@@ -573,7 +708,7 @@
}
HIDDEN const char *
-bu_opt_describe(struct bu_opt_desc *ds, struct bu_opt_desc_opts *settings)
+bu_opt_describe_internal_ascii(struct bu_opt_desc *ds, bu_opt_dtbl_t *tbl,
struct bu_opt_desc_opts *settings)
{
size_t i = 0;
size_t j = 0;
@@ -588,7 +723,7 @@
const char *finalized;
struct bu_vls description = BU_VLS_INIT_ZERO;
int *status;
- if (!ds || ds[0].index == -1) return NULL;
+ if (((!ds || ds[0].index == -1) && !tbl) || (ds && tbl)) return NULL;
if (settings) {
offset = settings->offset;
@@ -596,12 +731,20 @@
desc_cols = settings->description_columns;
}
- while (ds[i].index != -1) i++;
+ if (ds) {
+ while (ds[i].index != -1) i++;
+ } else {
+ int tbl_len = BU_PTBL_LEN(tbl) - 1;
+ i = (tbl_len < 0) ? 0 : tbl_len;
+ }
+ if (i == 0) return NULL;
opt_cnt = i;
status = (int *)bu_calloc(opt_cnt, sizeof(int), "opt status");
i = 0;
while (i < opt_cnt) {
- struct bu_opt_desc *curr = &(ds[i]);
+ struct bu_opt_desc *curr;
+ struct bu_opt_desc *d;
+ curr = (ds) ? &(ds[i]) : (struct bu_opt_desc *)BU_PTBL_GET(tbl, i) ;
if (!status[i]) {
struct bu_vls opts = BU_VLS_INIT_ZERO;
struct bu_vls help_str = BU_VLS_INIT_ZERO;
@@ -610,7 +753,7 @@
* pass, so set the status flags accordingly */
j = i;
while (j < opt_cnt) {
- struct bu_opt_desc *d = &(ds[j]);
+ d = (ds) ? &(ds[j]) : (struct bu_opt_desc *)BU_PTBL_GET(tbl, j);
if (d->index == curr->index) {
status[j] = 1;
}
@@ -621,7 +764,7 @@
* the same index defining aliases, so accumulate all of them. */
j = i;
while (j < opt_cnt) {
- struct bu_opt_desc *d = &(ds[j]);
+ d = (ds) ? &(ds[j]) : (struct bu_opt_desc *)BU_PTBL_GET(tbl, j);
if (d->index == curr->index) {
int new_len = strlen(d->shortopt_doc);
if (new_len > 0) {
@@ -642,7 +785,7 @@
/* Now do the long opts */
j = i;
while (j < opt_cnt) {
- struct bu_opt_desc *d = &(ds[j]);
+ d = (ds) ? &(ds[j]) : (struct bu_opt_desc *)BU_PTBL_GET(tbl, j);
if (d->index == curr->index) {
int new_len = strlen(d->longopt_doc);
if (new_len > 0) {
@@ -679,54 +822,24 @@
return finalized;
}
-void
-bu_opt_data_print(const char *title, struct bu_ptbl *data)
+const char *
+bu_opt_describe(struct bu_opt_desc *ds, struct bu_opt_desc_opts *settings)
{
- size_t i = 0;
- size_t j = 0;
- int offset_1 = 3;
- struct bu_vls log = BU_VLS_INIT_ZERO;
- if (!data || BU_PTBL_LEN(data) == 0) return;
- if (title) {
- bu_vls_sprintf(&log, "%s\n", title);
- } else {
- bu_vls_sprintf(&log, "Options:\n");
- }
- for (i = 0; i < BU_PTBL_LEN(data); i++) {
- struct bu_opt_data *d = (struct bu_opt_data *)BU_PTBL_GET(data, i);
- if (d->name) {
- bu_vls_printf(&log, "%*s%s", offset_1, " ", d->name);
- if (d->valid) {
- bu_vls_printf(&log, "\t(valid)");
- } else {
- bu_vls_printf(&log, "\t(invalid)");
- }
- if (d->desc && d->desc->arg_cnt_max > 0) {
- if (d->args && BU_PTBL_LEN(d->args) > 0) {
- bu_vls_printf(&log, ": ");
- for (j = 0; j < BU_PTBL_LEN(d->args) - 1; j++) {
- bu_vls_printf(&log, "%s, ", bu_opt_data_arg(d, j));
- }
- bu_vls_printf(&log, "%s\n", bu_opt_data_arg(d,
BU_PTBL_LEN(d->args) - 1));
- }
- } else {
- bu_vls_printf(&log, "\n");
- }
- } else {
- bu_vls_printf(&log, "%*s(unknown): ", offset_1, " ", d->name);
- if (d->args && BU_PTBL_LEN(d->args) > 0) {
- for (j = 0; j < BU_PTBL_LEN(d->args) - 1; j++) {
- bu_vls_printf(&log, "%s ", bu_opt_data_arg(d, j));
- }
- bu_vls_printf(&log, "%s\n", bu_opt_data_arg(d,
BU_PTBL_LEN(d->args) - 1));
- }
- }
- }
- bu_log("%s", bu_vls_addr(&log));
- bu_vls_free(&log);
+ if (!ds) return NULL;
+ if (!settings) return bu_opt_describe_internal_ascii(ds, NULL, NULL);
+ return NULL;
}
+const char *
+bu_opt_describe_dtbl(bu_opt_dtbl_t *dtbl, struct bu_opt_desc_opts *settings)
+{
+ if (!dtbl) return NULL;
+ if (!settings) return bu_opt_describe_internal_ascii(NULL, dtbl, NULL);
+ return NULL;
+}
+
+
/*
* Local Variables:
* mode: C
This was sent by the SourceForge.net collaborative development platform, the
world's largest Open Source development site.
------------------------------------------------------------------------------
_______________________________________________
BRL-CAD Source Commits mailing list
[email protected]
https://lists.sourceforge.net/lists/listinfo/brlcad-commits