source: tags/testbuild/ARB_GDE/GDE_ParseMenu.cxx

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