source: tags/ms_r16q3/ARB_GDE/GDE_ParseMenu.cxx

Last change on this file was 15279, checked in by westram, 8 years ago
  • Property svn:eol-style set to native
  • Property svn:keywords set to Author Date Id Revision
File size: 22.0 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    string lineStr;
106
107    while (in.getLine(lineStr)) {
108        gde_assert(lineStr.length()<GBUFSIZ); // otherwise buffer usage may fail
109
110        const char *in_line = lineStr.c_str();
111        if (in_line[0] == '#' || (in_line[0] && in_line[1] == '#')) {
112            ; // skip line
113        }
114        else if (only_whitespace(in_line)) {
115            ; // skip line
116        }
117        else {
118            splitEntry(in_line, head, temp);
119
120            // menu: chooses menu to use (may occur multiple times with same label!)
121            if (strcmp(head, "menu") == 0) {
122                int curmenu = -1;
123                for (j=0; j<num_menus && curmenu == -1; j++) {
124                    if (strcmp(temp, menu[j].label) == 0) curmenu=j;
125                }
126
127                thismenu_firstOccurance = curmenu == -1;
128
129                // If menu not found, make a new one
130                if (thismenu_firstOccurance) {
131                    curmenu               = num_menus++;
132                    thismenu              = &menu[curmenu];
133                    thismenu->label       = ARB_strdup(temp);
134                    thismenu->numitems    = 0;
135                    thismenu->active_mask = AWM_ALL;
136                }
137                else {
138                    thismenu = &menu[curmenu];
139                }
140
141                CheckItemConsistency(thisitem, in);
142                thisitem   = NULL;
143                thisarg    = NULL;
144                thisoutput = NULL;
145                thisinput  = NULL;
146            }
147            else if (strcmp(head, "menumask") == 0) {
148                THROW_IF_NO_MENU();
149                AW_active wanted_mask = strcmp("expert", temp) == 0 ? AWM_EXP : AWM_ALL;
150                if (!thismenu_firstOccurance && thismenu->active_mask != wanted_mask) {
151                    throwParseError(GBS_global_string("menumask has inconsistent definitions (in different definitions of menu '%s')", thismenu->label), in);
152                }
153                thismenu->active_mask = wanted_mask;
154            }
155            else if (strcmp(head, "menumeta") == 0) {
156                THROW_IF_NO_MENU();
157                char wanted_meta = temp[0];
158                if (!thismenu_firstOccurance && thismenu->meta != wanted_meta) {
159                    if (wanted_meta != 0) {
160                        if (thismenu->meta != 0) {
161                            throwParseError(GBS_global_string("menumeta has inconsistent definitions (in different definitions of menu '%s')", thismenu->label), in);
162                        }
163                        else {
164                            thismenu->meta = wanted_meta;
165                        }
166                    }
167                }
168                else {
169                    thismenu->meta = wanted_meta;
170                }
171            }
172            // item: chooses menu item to use
173            else if (strcmp(head, "item") == 0) {
174                CheckItemConsistency(thisitem, in);
175
176                THROW_IF_NO_MENU();
177
178                curarg    = -1;
179                curinput  = -1;
180                curoutput = -1;
181
182                int curitem = thismenu->numitems++;
183
184                // Resize the item list for this menu (add one item)
185                if (curitem == 0) {
186                    ARB_alloc(thismenu->item, 1);
187                }
188                else {
189                    ARB_realloc(thismenu->item, thismenu->numitems);
190                }
191
192                thisitem = &(thismenu->item[curitem]);
193
194                thisitem->numargs    = 0;
195                thisitem->numoutputs = 0;
196                thisitem->numinputs  = 0;
197                thisitem->label      = ARB_strdup(temp);
198                thisitem->method     = NULL;
199                thisitem->input      = NULL;
200                thisitem->output     = NULL;
201                thisitem->arg        = NULL;
202                thisitem->meta       = '\0';
203                thisitem->seqtype    = '-';   // no default sequence export
204                thisitem->aligned    = false;
205                thisitem->help       = NULL;
206
207                thisitem->parent_menu = thismenu;
208                thisitem->aws         = NULL; // no window opened yet
209                thisitem->active_mask = AWM_ALL;
210                thisitem->popup       = NULL;
211
212                for (int i = 0; i<curitem; ++i) {
213                    if (strcmp(thismenu->item[i].label, thisitem->label) == 0) {
214                        throwParseError(GBS_global_string("Duplicated item label '%s'", thisitem->label), in);
215                    }
216                }
217
218                thisarg = NULL;
219            }
220
221            // itemmethod: generic command line generated by this item
222            else if (strcmp(head, "itemmethod") == 0) {
223                THROW_IF_NO_ITEM();
224                ARB_calloc(thisitem->method, strlen(temp)+1);
225
226                {
227                    char *to = thisitem->method;
228                    char *from = temp;
229                    char last = 0;
230                    char c;
231
232                    do {
233                        c = *from++;
234                        if (c == '@' && last == '@') {
235                            // replace "@@" with "'"
236                            // [WHY_USE_DOUBLE_AT]
237                            // - cant use 1 single quote  ("'"). Things inside will not be preprocessed correctly.
238                            // - cant use 2 single quotes ("''") any longer. clang fails on OSX.
239                            to[-1] = '\'';
240                        }
241                        else {
242                            *to++ = c;
243                        }
244                        last = c;
245                    }
246                    while (c!=0);
247                }
248
249            }
250            // Help file
251            else if (strcmp(head, "itemhelp") == 0) {
252                THROW_IF_NO_ITEM();
253                thisitem->help = GBS_string_eval(temp, "*.help=agde_*1.hlp", 0);
254            }
255            // Meta key equiv
256            else if (strcmp(head, "itemmeta") == 0) {
257                THROW_IF_NO_ITEM();
258                thisitem->meta = temp[0];
259            }
260            else if (strcmp(head, "itemmask") == 0) {
261                THROW_IF_NO_ITEM();
262                if (strcmp("expert", temp) == 0) thisitem->active_mask = AWM_EXP;
263            }
264            // Sequence type restriction
265            else if (strcmp(head, "seqtype") == 0) {
266                THROW_IF_NO_ITEM();
267                thisitem->seqtype = toupper(temp[0]);
268                /* 'A' -> amino acids,
269                 * 'N' -> nucleotides,
270                 * '-' -> don't select sequences,
271                 * otherwise any alignment
272                 */
273            }
274            /* arg: defines the symbol for a command line argument.
275             *      this is used for substitution into the itemmethod
276             *      definition.
277             */
278
279            else if (strcmp(head, "arg") == 0) {
280                THROW_IF_NO_ITEM();
281
282                curarg = thisitem->numargs++;
283                ARB_recalloc(thisitem->arg, curarg, thisitem->numargs);
284
285                thisarg = &(thisitem->arg[curarg]);
286
287                thisarg->symbol      = ARB_strdup(temp);
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                ARB_recalloc(thisarg->choice, curchoice, thisarg->numchoices);
364
365                thisarg->choice[curchoice].label  = ARB_strdup(head);
366                thisarg->choice[curchoice].method = ARB_strdup(tail);
367            }
368            // argmin: Minimum value for a slider
369            else if (strcmp(head, "argmin") == 0) {
370                THROW_IF_NO_ARG();
371                (void)sscanf(temp, "%lf", &(thisarg->min));
372            }
373            // argmax: Maximum value for a slider
374            else if (strcmp(head, "argmax") == 0) {
375                THROW_IF_NO_ARG();
376                (void)sscanf(temp, "%lf", &(thisarg->max));
377            }
378            // argvalue: default value for a slider
379            else if (strcmp(head, "argvalue") == 0) {
380                THROW_IF_NO_ARG();
381                if (thisarg->type == TEXT) {
382                    freedup(thisarg->textvalue, temp);
383                }
384                else {
385                    (void)sscanf(temp, "%lf", &(thisarg->fvalue));
386                    thisarg->ivalue = (int) thisarg->fvalue;
387                }
388            }
389            else if (strcmp(head, "argmask") == 0) {
390                THROW_IF_NO_ARG();
391                if (strcmp("expert", temp) == 0) thisarg->active_mask = AWM_EXP;
392            }
393            // in: Input file description
394            else if (strcmp(head, "in") == 0) {
395                THROW_IF_NO_ITEM();
396
397                curinput = (thisitem->numinputs)++;
398                ARB_recalloc(thisitem->input, curinput, thisitem->numinputs);
399
400                thisinput = &(thisitem->input)[curinput];
401
402                thisinput->save     = false;
403                thisinput->format   = 0;
404                thisinput->symbol   = ARB_strdup(temp);
405                thisinput->name     = NULL;
406                thisinput->typeinfo = BASIC_TYPEINFO;
407            }
408            else if (strcmp(head, "informat") == 0) {
409                THROW_IF_NO_INPUT();
410                if (Find(temp, "genbank")) thisinput->format   = GENBANK;
411                else if (Find(temp, "flat")) thisinput->format = NA_FLAT;
412                else throwParseError(GBS_global_string("Unknown informat '%s' (allowed 'genbank' or 'flat')", temp), in);
413            }
414            else if (strcmp(head, "insave") == 0) {
415                THROW_IF_NO_INPUT();
416                thisinput->save = true;
417            }
418            else if (strcmp(head, "intyped") == 0) {
419                THROW_IF_NO_INPUT();
420                if (Find(temp, "detailed")) thisinput->typeinfo   = DETAILED_TYPEINFO;
421                else if (Find(temp, "basic")) thisinput->typeinfo = BASIC_TYPEINFO;
422                else throwParseError(GBS_global_string("Unknown value '%s' for 'intyped' (known: 'detailed', 'basic')", temp), in);
423            }
424            // out: Output file description
425            else if (strcmp(head, "out") == 0) {
426                THROW_IF_NO_ITEM();
427
428                curoutput = (thisitem->numoutputs)++;
429                ARB_recalloc(thisitem->output, curoutput, thisitem->numoutputs);
430
431                thisoutput = &(thisitem->output)[curoutput];
432
433                thisoutput->save   = false;
434                thisoutput->format = 0;
435                thisoutput->symbol = ARB_strdup(temp);
436                thisoutput->name   = NULL;
437            }
438            else if (strcmp(head, "outformat") == 0) {
439                THROW_IF_NO_OUTPUT();
440                if (Find(temp, "genbank")) thisoutput->format   = GENBANK;
441                else if (Find(temp, "gde")) thisoutput->format  = GDE;
442                else if (Find(temp, "flat")) thisoutput->format = NA_FLAT;
443                else throwParseError(GBS_global_string("Unknown outformat '%s' (allowed 'genbank', 'gde' or 'flat')", temp), in);
444            }
445            else if (strcmp(head, "outaligned") == 0) {
446                THROW_IF_NO_OUTPUT();
447                if (Find(temp, "yes")) thisitem->aligned = true;
448                else throwParseError(GBS_global_string("Unknown outaligned '%s' (allowed 'yes' or skip entry)", temp), in);
449            }
450            else if (strcmp(head, "outsave") == 0) {
451                THROW_IF_NO_OUTPUT();
452                thisoutput->save = true;
453            }
454            else {
455                throwParseError(GBS_global_string("No known GDE-menu-command found (line='%s')", in_line), in);
456            }
457        }
458    }
459
460    CheckItemConsistency(thisitem, in);
461
462    gde_assert(num_menus>0); // if this fails, the file arb.menu contained no menus (maybe file has zero size)
463}
464
465GB_ERROR LoadMenus() {
466    /*! Load menu config files
467     *
468     * loads all '*.menu' from "$ARBHOME/lib/gde" and "$ARB_PROP/gde"
469     */
470
471    GB_ERROR error = NULL;
472    StrArray files;
473    {
474        char *user_menu_dir = ARB_strdup(GB_path_in_arbprop("gde"));
475
476        if (!GB_is_directory(user_menu_dir)) {
477            error = GB_create_directory(user_menu_dir);
478        }
479        gde_assert(!GB_have_error());
480
481        if (!error) {
482            GBS_read_dir(files, user_menu_dir, "/\\.menu$/");
483            GBS_read_dir(files, GB_path_in_ARBLIB("gde"), "/\\.menu$/");
484            error = GB_incur_error();
485        }
486
487        free(user_menu_dir);
488    }
489
490    if (!error) {
491        MultiFileReader menus(files);
492        error = menus.get_error();
493        if (!error) {
494            try {
495                ParseMenus(menus);
496            }
497            catch (const string& err) {
498                error = GBS_static_string(err.c_str());
499            }
500        }
501    }
502
503    if (error) error = GBS_global_string("Error while loading menus: %s", error);
504    return error;
505}
506
507bool Find(const char *target, const char *key) {
508    // Search the target string for the given key
509    return strstr(target, key) ? true : false;
510}
511
512int Find2(const char *target, const char *key) {
513    /* Like Find(), but returns the index of the leftmost
514     * occurrence, and -1 if not found.
515     */
516    const char *found = strstr(target, key);
517    return found ? int(found-target) : -1;
518}
519
520// --------------------------------------------------------------------------------
521
522inline void trim(char *str) {
523    int s = 0;
524    int d = 0;
525
526    while (isspace(str[s])) ++s;
527    while (str[s]) str[d++] = str[s++];
528
529    str[d] = 0;
530    while (d>0 && isspace(str[d-1])) str[--d] = 0;
531}
532
533void splitEntry(const char *input, char *head, char *tail) {
534    /*! Split "this:that[:the_other]" into: "this" and "that[:the_other]"
535     */
536    const char *colon = strchr(input, ':');
537    if (colon) {
538        int len   = colon-input;
539        memcpy(head, input, len);
540        head[len] = 0;
541
542        strcpy(tail, colon+1);
543
544        trim(tail);
545    }
546    else {
547        strcpy(head, input);
548    }
549    trim(head);
550}
551
552// --------------------------------------------------------------------------------
553
554#ifdef UNIT_TESTS
555#ifndef TEST_UNIT_H
556#include <test_unit.h>
557#endif
558
559void TEST_load_menu() {
560    // very basic test: just detects failing assertions, crashes and errors
561
562    gb_getenv_hook old = GB_install_getenv_hook(arb_test::fakeenv);
563    {
564        // ../UNIT_TESTER/run/homefake/
565
566        TEST_EXPECT_NO_ERROR(LoadMenus());
567
568        // basic check of loaded data (needs to be adapted if menus change):
569        TEST_EXPECT_EQUAL(num_menus, 13);
570
571        string menus;
572        string menuitems;
573        for (int m = 0; m<num_menus; ++m) {
574            menus = menus + menu[m].label + ";";
575            menuitems += GBS_global_string("%i;", menu[m].numitems);
576        }
577
578        TEST_EXPECT_EQUAL(menus,
579                          "Import;Export;Print;Align;Network;SAI;Incremental phylogeny;Phylogeny Distance Matrix;"
580                          "Phylogeny max. parsimony;Phylogeny max. Likelihood EXP;Phylogeny max. Likelihood;Phylogeny (Other);User;");
581        TEST_EXPECT_EQUAL(menuitems, "3;1;2;10;1;1;1;3;2;1;8;5;0;");
582    }
583    TEST_EXPECT_EQUAL((void*)arb_test::fakeenv, (void*)GB_install_getenv_hook(old));
584}
585
586#endif // UNIT_TESTS
587
588// --------------------------------------------------------------------------------
589
Note: See TracBrowser for help on using the repository browser.