source: tags/ms_r18q1/ARB_GDE/GDE_ParseMenu.cxx

Last change on this file was 16986, checked in by westram, 6 years ago

Update: continued by [17178]

  • Property svn:eol-style set to native
  • Property svn:keywords set to Author Date Id Revision
File size: 22.2 KB
Line 
1#include "GDE_proto.h"
2
3#include <aw_window.hxx>
4#include <MultiFileReader.h>
5#include <arb_file.h>
6
7#include <cctype>
8
9/*
10  Copyright (c) 1989, University of Illinois board of trustees.  All rights
11  reserved.  Written by Steven Smith at the Center for Prokaryote Genome
12  Analysis.  Design and implementation guidance by Dr. Gary Olsen and Dr.
13  Carl Woese.
14
15  Copyright (c) 1990,1991,1992 Steven Smith at the Harvard Genome Laboratory.
16  All rights reserved.
17
18  Changed to fit into ARB by ARB development team.
19*/
20
21
22inline bool only_whitespace(const char *line) {
23    size_t white = strspn(line, " \t");
24    return line[white] == 0; // only 0 after leading whitespace
25}
26
27static char *readableItemname(const GmenuItem& i) {
28    return GBS_global_string_copy("%s/%s", i.parent_menu->label, i.label);
29}
30
31inline __ATTR__NORETURN void throwError(const char *msg) {
32    throw string(msg);
33}
34
35static __ATTR__NORETURN void throwParseError(const char *msg, const LineReader& file) {
36    fprintf(stderr, "\n%s:%zu: %s\n", file.getFilename().c_str(), file.getLineNumber(), msg);
37    fflush(stderr);
38    throwError(msg);
39}
40
41static __ATTR__NORETURN void throwItemError(const GmenuItem& i, const char *error, const LineReader& file) {
42    char       *itemName = readableItemname(i);
43    const char *msg      = GBS_global_string("[Above this line] Invalid item '%s' defined: %s", itemName, error);
44    free(itemName);
45    throwParseError(msg, file);
46}
47
48static void CheckItemConsistency(const GmenuItem *item, const LineReader& file) {
49    // (incomplete) consistency check.
50    // bailing out with ItemError() here, will make unit-test and arb-startup fail!
51
52    if (item) {
53        const GmenuItem& I = *item;
54        if (I.seqtype != '-' && I.numinputs<1) {
55            // Such an item would create a window where alignment/species selection is present,
56            // but no sequence export will take place.
57            //
58            // Pressing 'GO' would result in failure or deadlock.
59            throwItemError(I, "item defines seqtype ('seqtype:' <> '-'), but is lacking input-specification ('in:')", file);
60        }
61        if (I.seqtype == '-' && I.numinputs>0) {
62            // Such an item would create a window where alignment/species selection has no GUI-elements,
63            // but sequences are exported (generating a corrupt sequence file)
64            //
65            // Pressing 'GO' would result in failure.
66            throwItemError(I, "item defines no seqtype ('seqtype:' = '-'), but defines input-specification ('in:')", file);
67        }
68    }
69}
70
71#define THROW_IF_NO(ptr,name) do { if (!ptr) throwParseError(GBS_global_string("'%s' used w/o '" name "'", head), in); } while(0)
72
73#define THROW_IF_NO_MENU()   THROW_IF_NO(thismenu, "menu")
74#define THROW_IF_NO_ITEM()   THROW_IF_NO(thisitem, "item")
75#define THROW_IF_NO_ARG()    THROW_IF_NO(thisarg, "arg")
76#define THROW_IF_NO_INPUT()  THROW_IF_NO(thisinput, "in")
77#define THROW_IF_NO_OUTPUT() THROW_IF_NO(thisoutput, "out")
78
79static const char *ignore_bufsiz(const char *str) { return str; }
80#define IGNORE_BUFSIZ(str) ignore_bufsiz(str)
81
82static void ParseMenus(LineReader& in) {
83    /*  parses menus via LineReader (contains ALL found menu-files) and
84     *  assemble an internal representation of the menu/menu-item hierarchy.
85     *
86     *  please document changes in ../HELP_SOURCE/oldhelp/gde_menus.hlp
87     */
88
89    memset((char*)&menu[0], 0, sizeof(Gmenu)*GDEMAXMENU);
90
91    int curarg    = 0;
92    int curinput  = 0;
93    int curoutput = 0;
94
95    Gmenu        *thismenu   = NULp;
96    GmenuItem    *thisitem   = NULp;
97    GmenuItemArg *thisarg    = NULp;
98    GfileFormat  *thisinput  = NULp;
99    GfileFormat  *thisoutput = NULp;
100
101    bool thismenu_firstOccurrence = true;
102
103    int  j;
104    char temp[GBUFSIZ];
105    char head[GBUFSIZ];
106    char tail[GBUFSIZ];
107
108    string lineStr;
109
110    while (in.getLine(lineStr)) {
111        gde_assert(lineStr.length()<GBUFSIZ); // otherwise buffer usage may fail
112
113        const char *in_line = lineStr.c_str();
114        if (in_line[0] == '#' || (in_line[0] && in_line[1] == '#')) {
115            ; // skip line
116        }
117        else if (only_whitespace(in_line)) {
118            ; // skip line
119        }
120        else {
121            splitEntry(in_line, head, temp);
122
123            // menu: chooses menu to use (may occur multiple times with same label!)
124            if (strcmp(head, "menu") == 0) {
125                int curmenu = -1;
126                for (j=0; j<num_menus && curmenu == -1; j++) {
127                    if (strcmp(temp, menu[j].label) == 0) curmenu=j;
128                }
129
130                thismenu_firstOccurrence = curmenu == -1;
131
132                // If menu not found, make a new one
133                if (thismenu_firstOccurrence) {
134                    curmenu               = num_menus++;
135                    thismenu              = &menu[curmenu];
136                    thismenu->label       = ARB_strdup(temp);
137                    thismenu->numitems    = 0;
138                    thismenu->active_mask = AWM_ALL;
139                }
140                else {
141                    thismenu = &menu[curmenu];
142                }
143
144                CheckItemConsistency(thisitem, in);
145                thisitem   = NULp;
146                thisarg    = NULp;
147                thisoutput = NULp;
148                thisinput  = NULp;
149            }
150            else if (strcmp(head, "menumask") == 0) {
151                THROW_IF_NO_MENU();
152                AW_active wanted_mask = strcmp("expert", temp) == 0 ? AWM_EXP : AWM_ALL;
153                if (!thismenu_firstOccurrence && thismenu->active_mask != wanted_mask) {
154                    throwParseError(GBS_global_string("menumask has inconsistent definitions (in different definitions of menu '%s')", thismenu->label), in);
155                }
156                thismenu->active_mask = wanted_mask;
157            }
158            else if (strcmp(head, "menumeta") == 0) {
159                THROW_IF_NO_MENU();
160                char wanted_meta = temp[0];
161                if (!thismenu_firstOccurrence && thismenu->meta != wanted_meta) {
162                    if (wanted_meta != 0) {
163                        if (thismenu->meta != 0) {
164                            throwParseError(GBS_global_string("menumeta has inconsistent definitions (in different definitions of menu '%s')", thismenu->label), in);
165                        }
166                        else {
167                            thismenu->meta = wanted_meta;
168                        }
169                    }
170                }
171                else {
172                    thismenu->meta = wanted_meta;
173                }
174            }
175            // item: chooses menu item to use
176            else if (strcmp(head, "item") == 0) {
177                CheckItemConsistency(thisitem, in);
178
179                THROW_IF_NO_MENU();
180
181                curarg    = -1;
182                curinput  = -1;
183                curoutput = -1;
184
185                int curitem = thismenu->numitems++;
186
187                // Resize the item list for this menu (add one item)
188                if (curitem == 0) {
189                    ARB_alloc(thismenu->item, 1);
190                }
191                else {
192                    ARB_realloc(thismenu->item, thismenu->numitems);
193                }
194
195                thisitem = &(thismenu->item[curitem]);
196
197                thisitem->numargs    = 0;
198                thisitem->numoutputs = 0;
199                thisitem->numinputs  = 0;
200                thisitem->label      = ARB_strdup(temp);
201                thisitem->method     = NULp;
202                thisitem->input      = NULp;
203                thisitem->output     = NULp;
204                thisitem->arg        = NULp;
205                thisitem->meta       = '\0';
206                thisitem->seqtype    = '-';   // no default sequence export
207                thisitem->aligned    = false;
208                thisitem->help       = NULp;
209
210                thisitem->parent_menu = thismenu;
211                thisitem->aws         = NULp; // no window opened yet
212                thisitem->active_mask = AWM_ALL;
213                thisitem->popup       = NULp;
214
215                for (int i = 0; i<curitem; ++i) {
216                    if (strcmp(thismenu->item[i].label, thisitem->label) == 0) {
217                        throwParseError(GBS_global_string("Duplicated item label '%s'", thisitem->label), in);
218                    }
219                }
220
221                thisarg = NULp;
222            }
223
224            // itemmethod: generic command line generated by this item
225            else if (strcmp(head, "itemmethod") == 0) {
226                THROW_IF_NO_ITEM();
227                ARB_calloc(thisitem->method, strlen(temp)+1);
228
229                {
230                    char *to = thisitem->method;
231                    char *from = temp;
232                    char last = 0;
233                    char c;
234
235                    do {
236                        c = *from++;
237                        if (c == '@' && last == '@') {
238                            // replace "@@" with "'"
239                            // [WHY_USE_DOUBLE_AT]
240                            // - cant use 1 single quote  ("'"). Things inside will not be preprocessed correctly.
241                            // - cant use 2 single quotes ("''") any longer. clang fails on OSX.
242                            to[-1] = '\'';
243                        }
244                        else {
245                            *to++ = c;
246                        }
247                        last = c;
248                    }
249                    while (c!=0);
250                }
251
252            }
253            // Help file
254            else if (strcmp(head, "itemhelp") == 0) {
255                THROW_IF_NO_ITEM();
256                thisitem->help = GBS_string_eval(temp, "*.help=agde_*1.hlp");
257            }
258            // Meta key equiv
259            else if (strcmp(head, "itemmeta") == 0) {
260                THROW_IF_NO_ITEM();
261                thisitem->meta = temp[0];
262            }
263            else if (strcmp(head, "itemmask") == 0) {
264                THROW_IF_NO_ITEM();
265                if (strcmp("expert", temp) == 0) thisitem->active_mask = AWM_EXP;
266            }
267            // Sequence type restriction
268            else if (strcmp(head, "seqtype") == 0) {
269                THROW_IF_NO_ITEM();
270                thisitem->seqtype = toupper(temp[0]);
271                /* 'A' -> amino acids,
272                 * 'N' -> nucleotides,
273                 * '-' -> don't select sequences,
274                 * otherwise any alignment
275                 */
276            }
277            /* arg: defines the symbol for a command line argument.
278             *      this is used for substitution into the itemmethod
279             *      definition.
280             */
281
282            else if (strcmp(head, "arg") == 0) {
283                THROW_IF_NO_ITEM();
284
285                curarg = thisitem->numargs++;
286                ARB_recalloc(thisitem->arg, curarg, thisitem->numargs);
287
288                thisarg = &(thisitem->arg[curarg]);
289
290                thisarg->symbol      = ARB_strdup(temp);
291                thisarg->type        = 0;
292                thisarg->min         = 0.0;
293                thisarg->max         = 0.0;
294                thisarg->numchoices  = 0;
295                thisarg->choice      = NULp;
296                thisarg->textvalue   = NULp;
297                thisarg->ivalue      = 0;
298                thisarg->fvalue      = 0.0;
299                thisarg->label       = NULp;
300                thisarg->active_mask = AWM_ALL;
301            }
302            // argtype: Defines the type of argument (menu,chooser, text, slider)
303            else if (strcmp(head, "argtype") == 0) {
304                THROW_IF_NO_ARG();
305                int arglen = -1;
306                if (strncmp(temp, "text", (arglen = 4)) == 0) {
307                    thisarg->type         = TEXTFIELD;
308                    freedup(thisarg->textvalue, "");
309
310                    if (temp[arglen] == 0) thisarg->textwidth = TEXTFIELDWIDTH; // only 'text'
311                    else {
312                        if (temp[arglen] != '(' || temp[strlen(temp)-1] != ')') {
313                            sprintf(head, "Unknown argtype '%s' -- syntax: text(width) e.g. text(20)", IGNORE_BUFSIZ(temp));
314                            throwParseError(head, in);
315                        }
316                        thisarg->textwidth = atoi(temp+arglen+1);
317                        if (thisarg->textwidth<1) {
318                            sprintf(head, "Illegal textwidth specified in '%s'", IGNORE_BUFSIZ(temp));
319                            throwParseError(head, in);
320                        }
321                    }
322                }
323                else if (strcmp(temp, "choice_list") == 0) thisarg->type = CHOICE_LIST;
324                else if (strcmp(temp, "choice_menu") == 0) thisarg->type = CHOICE_MENU;
325                else if (strcmp(temp, "chooser")     == 0) thisarg->type = CHOOSER;
326                else if (strcmp(temp, "filename")    == 0) {
327                    thisarg->type = FILE_SELECTOR;
328                    freedup(thisarg->textvalue, "");
329                }
330                else if (strcmp(temp, "sai")         == 0) thisarg->type = CHOICE_SAI;
331                else if (strcmp(temp, "slider")      == 0) thisarg->type = SLIDER;
332                else if (strcmp(temp, "tree")        == 0) thisarg->type = CHOICE_TREE;
333                else if (strcmp(temp, "weights")     == 0) thisarg->type = CHOICE_WEIGHTS;
334                else {
335                    sprintf(head, "Unknown argtype '%s'", IGNORE_BUFSIZ(temp));
336                    throwParseError(head, in);
337                }
338            }
339            /* argtext: The default text value of the symbol.
340             *          $argument is replaced by this value if it is not
341             *           changed in the dialog box by the user.
342             */
343            else if (strcmp(head, "argtext") == 0) {
344                THROW_IF_NO_ARG();
345                freedup(thisarg->textvalue, temp);
346            }
347            /* arglabel: Text label displayed in the dialog box for
348             *           this argument. It should be a descriptive label.
349             */
350            else if (strcmp(head, "arglabel") == 0) {
351                THROW_IF_NO_ARG();
352                thisarg->label = GBS_string_eval(temp, "\\\\n=\\n");
353            }
354            /* Argument choice values use the following notation:
355             *
356             * argchoice:Displayed value:Method
357             *
358             * Where "Displayed value" is the label displayed in the dialog box
359             * and "Method" is the value passed back on the command line.
360             */
361            else if (strcmp(head, "argchoice") == 0) {
362                THROW_IF_NO_ARG();
363                splitEntry(temp, head, tail);
364
365                int curchoice = thisarg->numchoices++;
366                ARB_recalloc(thisarg->choice, curchoice, thisarg->numchoices);
367
368                thisarg->choice[curchoice].label  = ARB_strdup(head);
369                thisarg->choice[curchoice].method = ARB_strdup(tail);
370            }
371            // argmin: Minimum value for a slider
372            else if (strcmp(head, "argmin") == 0) {
373                THROW_IF_NO_ARG();
374                (void)sscanf(temp, "%lf", &(thisarg->min));
375            }
376            // argmax: Maximum value for a slider
377            else if (strcmp(head, "argmax") == 0) {
378                THROW_IF_NO_ARG();
379                (void)sscanf(temp, "%lf", &(thisarg->max));
380            }
381            // argvalue: default value for a slider
382            else if (strcmp(head, "argvalue") == 0) {
383                THROW_IF_NO_ARG();
384                if (thisarg->type == TEXT) {
385                    freedup(thisarg->textvalue, temp);
386                }
387                else {
388                    (void)sscanf(temp, "%lf", &(thisarg->fvalue));
389                    thisarg->ivalue = (int) thisarg->fvalue;
390                }
391            }
392            else if (strcmp(head, "argmask") == 0) {
393                THROW_IF_NO_ARG();
394                if (strcmp("expert", temp) == 0) thisarg->active_mask = AWM_EXP;
395            }
396            // in: Input file description
397            else if (strcmp(head, "in") == 0) {
398                THROW_IF_NO_ITEM();
399
400                curinput = (thisitem->numinputs)++;
401                ARB_recalloc(thisitem->input, curinput, thisitem->numinputs);
402
403                thisinput = &(thisitem->input)[curinput];
404
405                thisinput->save     = false;
406                thisinput->format   = 0;
407                thisinput->symbol   = ARB_strdup(temp);
408                thisinput->name     = NULp;
409                thisinput->typeinfo = BASIC_TYPEINFO;
410            }
411            else if (strcmp(head, "informat") == 0) {
412                THROW_IF_NO_INPUT();
413                if (Find(temp, "genbank")) thisinput->format   = GENBANK;
414                else if (Find(temp, "flat")) thisinput->format = NA_FLAT;
415                else throwParseError(GBS_global_string("Unknown informat '%s' (allowed 'genbank' or 'flat')", temp), in);
416            }
417            else if (strcmp(head, "insave") == 0) {
418                THROW_IF_NO_INPUT();
419                thisinput->save = true;
420            }
421            else if (strcmp(head, "intyped") == 0) {
422                THROW_IF_NO_INPUT();
423                if (Find(temp, "detailed")) thisinput->typeinfo   = DETAILED_TYPEINFO;
424                else if (Find(temp, "basic")) thisinput->typeinfo = BASIC_TYPEINFO;
425                else throwParseError(GBS_global_string("Unknown value '%s' for 'intyped' (known: 'detailed', 'basic')", temp), in);
426            }
427            // out: Output file description
428            else if (strcmp(head, "out") == 0) {
429                THROW_IF_NO_ITEM();
430
431                curoutput = (thisitem->numoutputs)++;
432                ARB_recalloc(thisitem->output, curoutput, thisitem->numoutputs);
433
434                thisoutput = &(thisitem->output)[curoutput];
435
436                thisoutput->save   = false;
437                thisoutput->format = 0;
438                thisoutput->symbol = ARB_strdup(temp);
439                thisoutput->name   = NULp;
440            }
441            else if (strcmp(head, "outformat") == 0) {
442                THROW_IF_NO_OUTPUT();
443                if (Find(temp, "genbank")) thisoutput->format   = GENBANK;
444                else if (Find(temp, "gde")) thisoutput->format  = GDE;
445                else if (Find(temp, "flat")) thisoutput->format = NA_FLAT;
446                else throwParseError(GBS_global_string("Unknown outformat '%s' (allowed 'genbank', 'gde' or 'flat')", temp), in);
447            }
448            else if (strcmp(head, "outaligned") == 0) {
449                THROW_IF_NO_OUTPUT();
450                if (Find(temp, "yes")) thisitem->aligned = true;
451                else throwParseError(GBS_global_string("Unknown outaligned '%s' (allowed 'yes' or skip entry)", temp), in);
452            }
453            else if (strcmp(head, "outsave") == 0) {
454                THROW_IF_NO_OUTPUT();
455                thisoutput->save = true;
456            }
457            else {
458                throwParseError(GBS_global_string("No known GDE-menu-command found (line='%s')", in_line), in);
459            }
460        }
461    }
462
463    CheckItemConsistency(thisitem, in);
464
465    gde_assert(num_menus>0); // if this fails, the file arb.menu contained no menus (maybe file has zero size)
466}
467
468GB_ERROR LoadMenus() {
469    /*! Load menu config files
470     *
471     * loads all '*.menu' from "$ARBHOME/lib/gde" and "$ARB_PROP/gde"
472     */
473
474    GB_ERROR error = NULp;
475    StrArray files;
476    {
477        char *user_menu_dir = ARB_strdup(GB_path_in_arbprop("gde"));
478
479        if (!GB_is_directory(user_menu_dir)) {
480            error = GB_create_directory(user_menu_dir);
481        }
482        gde_assert(!GB_have_error());
483
484        if (!error) {
485            GBS_read_dir(files, user_menu_dir, "/\\.menu$/");
486            GBS_read_dir(files, GB_path_in_ARBLIB("gde"), "/\\.menu$/");
487            error = GB_incur_error();
488        }
489
490        free(user_menu_dir);
491    }
492
493    if (!error) {
494        MultiFileReader menus(files);
495        error = menus.get_error();
496        if (!error) {
497            try {
498                ParseMenus(menus);
499            }
500            catch (const string& err) {
501                error = GBS_static_string(err.c_str());
502            }
503        }
504    }
505
506    if (error) error = GBS_global_string("Error while loading menus: %s", error);
507    return error;
508}
509
510bool Find(const char *target, const char *key) {
511    // Search the target string for the given key
512    return strstr(target, key) ? true : false;
513}
514
515int Find2(const char *target, const char *key) {
516    /* Like Find(), but returns the index of the leftmost
517     * occurrence, and -1 if not found.
518     */
519    const char *found = strstr(target, key);
520    return found ? int(found-target) : -1;
521}
522
523// --------------------------------------------------------------------------------
524
525inline void trim(char *str) {
526    int s = 0;
527    int d = 0;
528
529    while (isspace(str[s])) ++s;
530    while (str[s]) str[d++] = str[s++];
531
532    str[d] = 0;
533    while (d>0 && isspace(str[d-1])) str[--d] = 0;
534}
535
536void splitEntry(const char *input, char *head, char *tail) {
537    /*! Split "this:that[:the_other]" into: "this" and "that[:the_other]"
538     */
539    const char *colon = strchr(input, ':');
540    if (colon) {
541        int len   = colon-input;
542        memcpy(head, input, len);
543        head[len] = 0;
544
545        strcpy(tail, colon+1);
546
547        trim(tail);
548    }
549    else {
550        strcpy(head, input);
551    }
552    trim(head);
553}
554
555// --------------------------------------------------------------------------------
556
557#ifdef UNIT_TESTS
558#ifndef TEST_UNIT_H
559#include <test_unit.h>
560#endif
561
562void TEST_load_menu() {
563    // very basic test: just detects failing assertions, crashes and errors
564
565    gb_getenv_hook old = GB_install_getenv_hook(arb_test::fakeenv);
566    {
567        // ../UNIT_TESTER/run/homefake/
568
569        TEST_EXPECT_NO_ERROR(LoadMenus());
570
571        // basic check of loaded data (needs to be adapted if menus change):
572        TEST_EXPECT_EQUAL(num_menus, 13);
573
574        string menus;
575        string menuitems;
576        for (int m = 0; m<num_menus; ++m) {
577            menus = menus + menu[m].label + ";";
578            menuitems += GBS_global_string("%i;", menu[m].numitems);
579        }
580
581        TEST_EXPECT_EQUAL(menus,
582                          "Import;Export;Print;Align;Network;SAI;Incremental phylogeny;Phylogeny Distance Matrix;"
583                          "Phylogeny max. parsimony;Phylogeny max. Likelihood EXP;Phylogeny max. Likelihood;Phylogeny (Other);User;");
584        TEST_EXPECT_EQUAL(menuitems, "3;1;2;10;1;1;1;3;2;1;8;5;0;");
585    }
586    TEST_EXPECT_EQUAL((void*)arb_test::fakeenv, (void*)GB_install_getenv_hook(old));
587}
588
589#endif // UNIT_TESTS
590
591// --------------------------------------------------------------------------------
592
Note: See TracBrowser for help on using the repository browser.