source: tags/ms_r16q2/ARB_GDE/GDE_ParseMenu.cxx

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