source: branches/items/ARB_GDE/GDE_ParseMenu.cxx

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